ui: echte Mobile-First-Variante — Recording-Hero, Job-Cards, Admin-Klappe
Mobile-Layout ab <700px komplett umgebaut, nicht mehr nur Desktop-Verkleinerung: - Header: nur Logo + User + Profil-Mini-Dropdown + Logout - Upload-Card: großer roter Aufnahme-Button als Hero-Element - Profil und Titel darunter, kompakt - Datei-Upload in <details>-Klappe (auf Desktop offen, Mobile zu) - Jobs: Card-Liste statt Tabelle (Tabelle nur Desktop, Cards nur Mobile) - Admin: H2 als Toggle, Body kollabiert (auf Desktop immer offen) - Inputs auf Mobile mit font-size:16px → kein iOS-Zoom-on-Focus - Recording-Pane: Timer 64px, Buttons full-width - safe-area-inset für iPhone-Notch + Bottom-Bar Ohne JS-Mode-Switch — alles via CSS Media Query + .desktop-only / .mobile-only Helfer-Klassen. JS rendert beide Job-Repräsentationen, CSS schaltet die richtige sichtbar.
This commit is contained in:
parent
e75bc05421
commit
24ac3ac058
@ -37,6 +37,10 @@ const defaultProfileName = document.getElementById("default-profile-name");
|
|||||||
const headerProfileWrap = document.getElementById("header-profile-wrap");
|
const headerProfileWrap = document.getElementById("header-profile-wrap");
|
||||||
const headerProfile = document.getElementById("header-profile");
|
const headerProfile = document.getElementById("header-profile");
|
||||||
|
|
||||||
|
const jobsCards = document.getElementById("jobs-cards");
|
||||||
|
const adminToggle = document.getElementById("admin-toggle");
|
||||||
|
const adminCollapsible = document.getElementById("admin-card");
|
||||||
|
|
||||||
const recordToggle = document.getElementById("record-toggle");
|
const recordToggle = document.getElementById("record-toggle");
|
||||||
const recordToggleLabel = document.getElementById("record-toggle-label");
|
const recordToggleLabel = document.getElementById("record-toggle-label");
|
||||||
const recordPane = document.getElementById("record-pane");
|
const recordPane = document.getElementById("record-pane");
|
||||||
@ -302,6 +306,43 @@ function row(job) {
|
|||||||
return tr;
|
return tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function card(job) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.className = "jc";
|
||||||
|
const title = job.title || job.original_name;
|
||||||
|
const showProfileTag = availableProfiles.length > 1 || (job.profile && job.profile !== "meeting");
|
||||||
|
const profileTag = showProfileTag
|
||||||
|
? `<span class="tag-profile">${escapeHtml(profileLabelOf(job.profile || "meeting"))}</span>`
|
||||||
|
: "";
|
||||||
|
const ownerLine = currentUser?.is_admin && job.owner_username
|
||||||
|
? `<div class="jc-sub">von ${escapeHtml(job.owner_username)}</div>` : "";
|
||||||
|
|
||||||
|
const dlButtons = DOWNLOADS.map(({ kind, label, title: tip }) => {
|
||||||
|
const dis = !job.has[kind];
|
||||||
|
const href = dis ? "" : `href="/api/jobs/${job.id}/download/${kind}"`;
|
||||||
|
return `<a class="btn-dl ${dis ? "disabled" : ""}" ${href} title="${escapeHtml(tip)}">${label}</a>`;
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
div.innerHTML = `
|
||||||
|
<div class="jc-head">
|
||||||
|
<div>
|
||||||
|
<div class="jc-title">${escapeHtml(title)} ${profileTag}</div>
|
||||||
|
<div class="jc-sub">${escapeHtml(job.original_name)} · <span class="jc-id">#${job.id}</span></div>
|
||||||
|
${ownerLine}
|
||||||
|
</div>
|
||||||
|
<span class="status-pill status-${job.status}">${escapeHtml(job.status)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="jc-status-row">
|
||||||
|
<div class="jc-progress"><div class="bar"><div class="bar-fill" style="width:${job.progress}%"></div></div></div>
|
||||||
|
<small class="muted">${job.progress}%</small>
|
||||||
|
</div>
|
||||||
|
${job.error ? `<div class="jc-error" title="${escapeHtml(job.error)}">${escapeHtml(job.error)}</div>` : ""}
|
||||||
|
<div class="jc-sub">aktualisiert: ${escapeHtml(fmtDate(job.updated_at))}</div>
|
||||||
|
<div class="jc-dl">${dlButtons}</div>
|
||||||
|
`;
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
|
||||||
async function loadJobs() {
|
async function loadJobs() {
|
||||||
if (!currentUser) return;
|
if (!currentUser) return;
|
||||||
try {
|
try {
|
||||||
@ -309,11 +350,16 @@ async function loadJobs() {
|
|||||||
if (r.status === 401) { showAuth("login"); return; }
|
if (r.status === 401) { showAuth("login"); return; }
|
||||||
const jobs = await r.json();
|
const jobs = await r.json();
|
||||||
jobsBody.innerHTML = "";
|
jobsBody.innerHTML = "";
|
||||||
|
jobsCards.innerHTML = "";
|
||||||
if (!jobs.length) {
|
if (!jobs.length) {
|
||||||
jobsBody.innerHTML = '<tr><td colspan="6" class="muted">Noch keine Jobs.</td></tr>';
|
jobsBody.innerHTML = '<tr><td colspan="6" class="muted">Noch keine Jobs.</td></tr>';
|
||||||
|
jobsCards.innerHTML = '<div class="muted">Noch keine Jobs.</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
jobs.forEach((j) => jobsBody.appendChild(row(j)));
|
jobs.forEach((j) => {
|
||||||
|
jobsBody.appendChild(row(j));
|
||||||
|
jobsCards.appendChild(card(j));
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
@ -613,6 +659,13 @@ recPauseBtn.addEventListener("click", pauseRecording);
|
|||||||
recStopBtn.addEventListener("click", stopRecording);
|
recStopBtn.addEventListener("click", stopRecording);
|
||||||
recordDiscardBtn.addEventListener("click", discardRecording);
|
recordDiscardBtn.addEventListener("click", discardRecording);
|
||||||
|
|
||||||
|
// Admin-Toggle (klappbar auf Mobile, immer offen auf Desktop)
|
||||||
|
adminToggle.addEventListener("click", () => {
|
||||||
|
if (window.matchMedia("(max-width: 700px)").matches) {
|
||||||
|
adminCollapsible.classList.toggle("open");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ─── Init ──────────────────────────────────────────────────────────────────
|
// ─── Init ──────────────────────────────────────────────────────────────────
|
||||||
bootstrap();
|
bootstrap();
|
||||||
checkMac();
|
checkMac();
|
||||||
|
|||||||
@ -19,17 +19,17 @@
|
|||||||
<div class="header-row">
|
<div class="header-row">
|
||||||
<div class="header-left">
|
<div class="header-left">
|
||||||
<h1>Voice-Agent</h1>
|
<h1>Voice-Agent</h1>
|
||||||
<p class="sub">Sitzungsaufnahmen automatisch transkribieren und protokollieren</p>
|
<p class="sub desktop-only">Sitzungsaufnahmen automatisch transkribieren und protokollieren</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div id="mac-status" class="badge">Mac-Backend: prüfe …</div>
|
<div id="mac-status" class="badge desktop-only">Mac-Backend: prüfe …</div>
|
||||||
<div id="user-bar" class="user-bar hidden">
|
<div id="user-bar" class="user-bar hidden">
|
||||||
<span class="muted">Angemeldet als</span>
|
<span class="muted desktop-only">Angemeldet als</span>
|
||||||
<strong id="user-name"></strong>
|
<strong id="user-name"></strong>
|
||||||
<span id="user-admin-badge" class="badge admin-badge hidden">Admin</span>
|
<span id="user-admin-badge" class="badge admin-badge hidden">Admin</span>
|
||||||
<span id="header-profile-wrap" class="header-profile-wrap hidden">
|
<span id="header-profile-wrap" class="header-profile-wrap hidden">
|
||||||
<span class="muted">·</span>
|
<span class="muted desktop-only">·</span>
|
||||||
<span class="muted">Profil:</span>
|
<span class="muted desktop-only">Profil:</span>
|
||||||
<select id="header-profile" class="header-profile" title="Standard-Profil — wird sofort gespeichert"></select>
|
<select id="header-profile" class="header-profile" title="Standard-Profil — wird sofort gespeichert"></select>
|
||||||
</span>
|
</span>
|
||||||
<button type="button" id="logout-btn" class="btn-link">Abmelden</button>
|
<button type="button" id="logout-btn" class="btn-link">Abmelden</button>
|
||||||
@ -59,30 +59,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main id="app" class="hidden">
|
<main id="app" class="hidden">
|
||||||
<section class="card">
|
<section class="card upload-card">
|
||||||
<h2>Neue Aufnahme hochladen</h2>
|
<h2 class="desktop-only">Neue Aufnahme hochladen</h2>
|
||||||
<form id="upload-form">
|
<form id="upload-form">
|
||||||
<label>
|
<!-- HERO: Aufnahme-Button (auf Mobile groß und zentriert) -->
|
||||||
Titel (optional)
|
|
||||||
<input type="text" name="title" id="title" placeholder="z. B. Monatsbesprechung" />
|
|
||||||
</label>
|
|
||||||
<label id="profile-label" class="hidden">
|
|
||||||
Verarbeitungs-Profil
|
|
||||||
<select id="profile-select"></select>
|
|
||||||
<small class="muted">Standard: <span id="default-profile-name">—</span> (in Einstellungen änderbar)</small>
|
|
||||||
</label>
|
|
||||||
<div class="audio-source">
|
|
||||||
<label class="audio-file-label">
|
|
||||||
<span>Audiodatei wählen <small class="muted">(MP3, WAV, M4A, MP4, OGG, FLAC, WebM)</small></span>
|
|
||||||
<input type="file" name="audio" id="audio" accept="audio/*,.mp3,.wav,.m4a,.mp4,.ogg,.flac,.webm" />
|
|
||||||
</label>
|
|
||||||
<div class="audio-or muted">— oder —</div>
|
|
||||||
<button type="button" id="record-toggle" class="btn-record">
|
<button type="button" id="record-toggle" class="btn-record">
|
||||||
<span class="rec-dot"></span>
|
<span class="rec-dot"></span>
|
||||||
<span id="record-toggle-label">Aufnahme starten</span>
|
<span id="record-toggle-label">Aufnahme starten</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<!-- Aufnahme-Bühne -->
|
||||||
<div id="record-pane" class="record-pane hidden">
|
<div id="record-pane" class="record-pane hidden">
|
||||||
<div class="record-timer" id="record-timer">00:00</div>
|
<div class="record-timer" id="record-timer">00:00</div>
|
||||||
<div class="record-status" id="record-status">Aufnahme läuft</div>
|
<div class="record-status" id="record-status">Aufnahme läuft</div>
|
||||||
@ -92,12 +78,35 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Aufnahme bereit -->
|
||||||
<div id="record-result" class="record-result hidden">
|
<div id="record-result" class="record-result hidden">
|
||||||
<span class="muted">Aufnahme bereit:</span>
|
<span class="muted">Aufnahme bereit:</span>
|
||||||
<span id="record-filename"></span>
|
<span id="record-filename"></span>
|
||||||
<button type="button" id="record-discard" class="btn-link btn-danger">Verwerfen</button>
|
<button type="button" id="record-discard" class="btn-link btn-danger">Verwerfen</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Profil + Titel -->
|
||||||
|
<div class="upload-meta">
|
||||||
|
<label id="profile-label" class="hidden">
|
||||||
|
Verarbeitungs-Profil
|
||||||
|
<select id="profile-select"></select>
|
||||||
|
<small class="muted desktop-only">Standard: <span id="default-profile-name">—</span></small>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Titel <span class="muted">(optional)</span>
|
||||||
|
<input type="text" name="title" id="title" placeholder="z. B. Monatsbesprechung" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Datei-Upload als Klappe (auf Mobile collapsed, auf Desktop offen) -->
|
||||||
|
<details class="audio-file-details" id="audio-file-details">
|
||||||
|
<summary>Lieber eine vorhandene Audio-Datei hochladen?</summary>
|
||||||
|
<label class="audio-file-label">
|
||||||
|
<span class="muted">MP3, WAV, M4A, MP4, OGG, FLAC, WebM</span>
|
||||||
|
<input type="file" name="audio" id="audio" accept="audio/*,.mp3,.wav,.m4a,.mp4,.ogg,.flac,.webm" />
|
||||||
|
</label>
|
||||||
|
</details>
|
||||||
|
|
||||||
<button type="submit" id="submit-btn">Hochladen & verarbeiten</button>
|
<button type="submit" id="submit-btn">Hochladen & verarbeiten</button>
|
||||||
</form>
|
</form>
|
||||||
<div id="upload-progress" class="hidden">
|
<div id="upload-progress" class="hidden">
|
||||||
@ -107,9 +116,10 @@
|
|||||||
<p id="upload-msg" class="msg"></p>
|
<p id="upload-msg" class="msg"></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="card">
|
<section class="card jobs-card">
|
||||||
<h2>Jobs</h2>
|
<h2>Jobs</h2>
|
||||||
<table id="jobs-table">
|
<!-- Desktop: Tabelle -->
|
||||||
|
<table id="jobs-table" class="desktop-only">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-id">#</th>
|
<th class="col-id">#</th>
|
||||||
@ -124,11 +134,16 @@
|
|||||||
<tr><td colspan="6" class="muted">Noch keine Jobs.</td></tr>
|
<tr><td colspan="6" class="muted">Noch keine Jobs.</td></tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<!-- Mobile: Card-Liste -->
|
||||||
|
<div id="jobs-cards" class="jobs-cards mobile-only">
|
||||||
|
<div class="muted">Noch keine Jobs.</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Nur sichtbar für Admins -->
|
<!-- Nur sichtbar für Admins -->
|
||||||
<section id="admin-card" class="card hidden">
|
<section id="admin-card" class="card hidden admin-collapsible">
|
||||||
<h2>Benutzer verwalten</h2>
|
<h2 class="admin-toggle" id="admin-toggle">Benutzer verwalten <span class="admin-toggle-icon">▾</span></h2>
|
||||||
|
<div class="admin-body">
|
||||||
<form id="user-form" class="user-form">
|
<form id="user-form" class="user-form">
|
||||||
<label>
|
<label>
|
||||||
Benutzername
|
Benutzername
|
||||||
@ -157,6 +172,7 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody id="users-body"></tbody>
|
<tbody id="users-body"></tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
|||||||
@ -281,24 +281,6 @@ select {
|
|||||||
}
|
}
|
||||||
.header-profile:disabled { opacity: .5; }
|
.header-profile:disabled { opacity: .5; }
|
||||||
|
|
||||||
/* ── Audio-Quelle: Datei-Upload vs. Aufnahme ───────────────────── */
|
|
||||||
.audio-source {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr auto 1fr;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
.audio-file-label {
|
|
||||||
display: grid;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
.audio-or {
|
|
||||||
font-size: 12px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: .5px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Aufnahme-Button (groß, rot, touch-tauglich) */
|
/* Aufnahme-Button (groß, rot, touch-tauglich) */
|
||||||
.btn-record {
|
.btn-record {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@ -378,36 +360,174 @@ select {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Mobile-Optimierung ────────────────────────────────────────── */
|
/* ── Sichtbarkeit-Helfer ───────────────────────────────────────── */
|
||||||
|
.mobile-only { display: none; }
|
||||||
|
|
||||||
|
/* Upload-Form Layout (Desktop) */
|
||||||
|
.upload-meta {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.audio-file-details summary {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 4px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 13px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.audio-file-details summary:hover { color: var(--text); }
|
||||||
|
.audio-file-details[open] summary { color: var(--text); margin-bottom: 8px; }
|
||||||
|
.audio-file-label {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Admin-Toggle (auf Desktop deaktiviert) */
|
||||||
|
.admin-toggle { cursor: default; }
|
||||||
|
.admin-toggle-icon { display: none; }
|
||||||
|
|
||||||
|
/* ── Mobile-Optimierung (echte Mobile-First-UI) ────────────────── */
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
header { padding: 16px 12px 12px; }
|
.desktop-only { display: none !important; }
|
||||||
header h1 { font-size: 22px; }
|
.mobile-only { display: block; }
|
||||||
.sub { font-size: 13px; }
|
|
||||||
main { padding: 0 10px; gap: 14px; }
|
|
||||||
.card { padding: 14px 16px; border-radius: 10px; }
|
|
||||||
.card h2 { font-size: 16px; }
|
|
||||||
|
|
||||||
.header-row { flex-direction: column; align-items: stretch; gap: 10px; }
|
body { font-size: 15px; }
|
||||||
.header-right { align-items: stretch; }
|
|
||||||
.user-bar { flex-wrap: wrap; justify-content: flex-start; gap: 6px; font-size: 12px; }
|
|
||||||
.header-profile { max-width: 140px; }
|
|
||||||
|
|
||||||
.audio-source { grid-template-columns: 1fr; gap: 8px; }
|
/* HEADER: nur Logo + User */
|
||||||
.audio-or { padding: 4px 0; }
|
header {
|
||||||
.btn-record { padding: 18px; font-size: 17px; }
|
padding: 10px 14px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
header h1 { font-size: 18px; margin: 0; }
|
||||||
|
.header-row {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.header-left, .header-right { align-items: center; }
|
||||||
|
.header-right { flex-direction: row; gap: 8px; flex: 1; justify-content: flex-end; }
|
||||||
|
.user-bar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
.user-bar strong { font-size: 14px; max-width: 90px; overflow: hidden; text-overflow: ellipsis; }
|
||||||
|
.header-profile { max-width: 130px; padding: 4px 6px; font-size: 12px; }
|
||||||
|
.btn-link { font-size: 12px; padding: 4px 6px; }
|
||||||
|
|
||||||
/* Tabelle horizontal scrollbar statt zerquetscht */
|
main { padding: 14px 12px 90px; gap: 14px; }
|
||||||
.card section, .card { overflow-x: auto; }
|
.card { padding: 16px; border-radius: 12px; }
|
||||||
table { min-width: 720px; font-size: 13px; }
|
.card h2 { font-size: 16px; margin-bottom: 10px; }
|
||||||
button[type="submit"] { padding: 14px 20px; font-size: 15px; min-height: 48px; }
|
|
||||||
|
|
||||||
.record-timer { font-size: 56px; }
|
/* UPLOAD-CARD: Recording-Hero-Style */
|
||||||
.btn-record-ctrl { padding: 16px 24px; font-size: 17px; }
|
.upload-card form { display: flex; flex-direction: column; gap: 14px; }
|
||||||
|
|
||||||
|
.btn-record {
|
||||||
|
/* HERO: groß und auffällig */
|
||||||
|
padding: 26px;
|
||||||
|
font-size: 19px;
|
||||||
|
min-height: 90px;
|
||||||
|
border-radius: 14px;
|
||||||
|
box-shadow: 0 4px 20px rgba(231,76,60,.15);
|
||||||
|
}
|
||||||
|
.btn-record .rec-dot { width: 16px; height: 16px; animation: pulse 1.5s ease-in-out infinite; }
|
||||||
|
|
||||||
|
.upload-meta { grid-template-columns: 1fr; gap: 10px; }
|
||||||
|
.upload-meta select,
|
||||||
|
.upload-meta input[type=text] {
|
||||||
|
padding: 14px 12px;
|
||||||
|
font-size: 16px; /* iOS: ≥16px verhindert Zoom-on-Focus */
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.audio-file-details summary { padding: 12px 4px; font-size: 14px; }
|
||||||
|
|
||||||
|
button[type="submit"] {
|
||||||
|
padding: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
min-height: 52px;
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Aufnahme-Bühne: noch größer auf Mobile */
|
||||||
|
.record-pane { padding: 24px 16px; }
|
||||||
|
.record-timer { font-size: 64px; line-height: 1; }
|
||||||
|
.record-controls { width: 100%; gap: 12px; }
|
||||||
|
.btn-record-ctrl {
|
||||||
|
flex: 1;
|
||||||
|
padding: 18px;
|
||||||
|
font-size: 17px;
|
||||||
|
min-height: 56px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* JOBS-CARDS statt Tabelle */
|
||||||
|
.jobs-cards { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.jc {
|
||||||
|
background: #0f1219;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
}
|
||||||
|
.jc-head {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.jc-id { color: var(--muted); font-size: 12px; }
|
||||||
|
.jc-title { font-weight: 600; font-size: 15px; line-height: 1.3; word-break: break-word; }
|
||||||
|
.jc-sub { color: var(--muted); font-size: 12px; margin-top: 2px; }
|
||||||
|
.jc-status-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 8px 0 6px;
|
||||||
|
}
|
||||||
|
.jc-progress { flex: 1; }
|
||||||
|
.jc-error {
|
||||||
|
color: var(--err);
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 4px 0;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.jc-dl {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.jc-dl .btn-dl { padding: 8px; font-size: 12px; min-height: 36px; }
|
||||||
|
|
||||||
|
/* Admin-Bereich: collapsible */
|
||||||
|
.admin-collapsible .admin-toggle {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.admin-toggle-icon { display: inline; transition: transform .2s; }
|
||||||
|
.admin-collapsible:not(.open) .admin-body { display: none; }
|
||||||
|
.admin-collapsible.open .admin-toggle-icon { transform: rotate(180deg); }
|
||||||
|
|
||||||
|
.user-form { grid-template-columns: 1fr; }
|
||||||
|
.user-form input { font-size: 16px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* iOS PWA: oberer Sicherheitsbereich (Notch) respektieren */
|
/* iOS PWA: oberer Sicherheitsbereich (Notch) respektieren */
|
||||||
@supports (padding: max(0px)) {
|
@supports (padding: max(0px)) {
|
||||||
header { padding-top: max(16px, env(safe-area-inset-top)); }
|
header { padding-top: max(10px, env(safe-area-inset-top)); }
|
||||||
|
main { padding-bottom: max(20px, env(safe-area-inset-bottom)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Profile-Tag in der Job-Zeile */
|
/* Profile-Tag in der Job-Zeile */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user