/* Rajdhani — self-hosted (SIL OFL), no external CDN */
@font-face { font-family: 'Rajdhani'; font-style: normal; font-weight: 400; font-display: swap; src: url('fonts/rajdhani-400.woff2') format('woff2'); }
@font-face { font-family: 'Rajdhani'; font-style: normal; font-weight: 500; font-display: swap; src: url('fonts/rajdhani-500.woff2') format('woff2'); }
@font-face { font-family: 'Rajdhani'; font-style: normal; font-weight: 600; font-display: swap; src: url('fonts/rajdhani-600.woff2') format('woff2'); }
@font-face { font-family: 'Rajdhani'; font-style: normal; font-weight: 700; font-display: swap; src: url('fonts/rajdhani-700.woff2') format('woff2'); }
/* Nunito Sans — self-hosted (SIL OFL), no external CDN. Same duplication as
   Rajdhani above (tenant pages don't load app.css) — see findd-public.css for
   why one variable woff2 covers all four @font-face weights below. */
@font-face { font-family: 'Nunito Sans'; font-style: normal; font-weight: 400; font-display: swap; src: url('fonts/nunito-sans.woff2') format('woff2'); }
@font-face { font-family: 'Nunito Sans'; font-style: normal; font-weight: 500; font-display: swap; src: url('fonts/nunito-sans.woff2') format('woff2'); }
@font-face { font-family: 'Nunito Sans'; font-style: normal; font-weight: 600; font-display: swap; src: url('fonts/nunito-sans.woff2') format('woff2'); }
@font-face { font-family: 'Nunito Sans'; font-style: normal; font-weight: 700; font-display: swap; src: url('fonts/nunito-sans.woff2') format('woff2'); }
/* Lato — self-hosted (SIL OFL), no external CDN. Same duplication as above —
   see findd-public.css for the cut selection. */
@font-face { font-family: 'Lato'; font-style: normal; font-weight: 400; font-display: swap; src: url('fonts/lato-400.woff2') format('woff2'); }
@font-face { font-family: 'Lato'; font-style: italic; font-weight: 400; font-display: swap; src: url('fonts/lato-400-italic.woff2') format('woff2'); }
@font-face { font-family: 'Lato'; font-style: normal; font-weight: 700; font-display: swap; src: url('fonts/lato-700.woff2') format('woff2'); }
@font-face { font-family: 'Lato'; font-style: italic; font-weight: 700; font-display: swap; src: url('fonts/lato-700-italic.woff2') format('woff2'); }

:root {
  --bg: #0a0e12;
  --glass: rgba(255, 255, 255, 0.045);
  --glass-strong: rgba(255, 255, 255, 0.07);
  --glass-border: rgba(255, 255, 255, 0.11);
  --glass-border-bright: rgba(255, 255, 255, 0.22);
  --text: #f3f6f8;
  --muted: #9aa3ad;
  --muted2: #646c75;
  --accent: #71bfbc;
  --accent2: #3c8eb5;
  --accent-grad: linear-gradient(135deg, #3c8eb5, #71bfbc);
  --rose: #f5657f;
  --amber: #f5994a;
  --emerald: #71bfbc;
  --radius: 6px;
  --rail-w: 240px; /* left rail width — shared by every full-bleed admin page (catalog, media, settings) */
  --font: 'Rajdhani', system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* THE chevron. One geometry for the whole product, carried as a mask so the exact
   same shape works in markup (.chev) and in ::before/::after, always following
   currentColor. Its SVG twin is icon('chevron') in helpers.php — keep them equal.
   Before this, the product drew chevrons in eight different ways, five of them as
   text characters (▸ › ‹ ▾) whose shape depended on whichever font happened to
   cover them: Rajdhani in the backend, the tenant's own font on public pages.
   That, not the line weight, was why they looked weaker than a real icon set.
   Points right; rotate for the rest. Also in findd-public.css — tenant pages do
   not load app.css (same duplication as the Rajdhani @font-face). */
:root {
  --chev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m8 3 9 9-9 9'/%3E%3C/svg%3E");
  --chev-mask: var(--chev) center / contain no-repeat;
}
.chev {
  display: inline-block; flex: none; width: 1em; height: 1em; background: currentColor;
  -webkit-mask: var(--chev-mask); mask: var(--chev-mask);
}
.chev-down { transform: rotate(90deg); }
.chev-left { transform: rotate(180deg); }
.chev-up   { transform: rotate(-90deg); }

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
/* The hidden attribute always wins, even over a class that sets display (e.g. flex/grid). */
[hidden] { display: none !important; }
html, body { height: 100%; margin: 0; }
/* Always reserve the scrollbar so centered content keeps the same width
   across pages (short pages were otherwise slightly wider). */
html { scrollbar-gutter: stable; }

body {
  background-color: var(--bg);
  background-image:
    radial-gradient(60% 50% at 18% 12%, rgba(113, 191, 188, 0.13), transparent 60%),
    radial-gradient(55% 45% at 85% 60%, rgba(60, 142, 181, 0.12), transparent 60%),
    radial-gradient(70% 60% at 50% 110%, rgba(113, 191, 188, 0.07), transparent 60%);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.5;
  letter-spacing: .2px;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; }
.link { color: var(--accent); text-decoration: none; font-weight: 600; }
.link:hover { text-decoration: underline; }
.muted { color: var(--muted); }
.small { font-size: 13px; }
.mono { font-family: ui-monospace, "SF Mono", Menlo, monospace; letter-spacing: 0; }
.mb { margin-bottom: 16px; }
.mt { margin-top: 16px; }

.wrap { max-width: 1200px; margin: 0 auto; padding: 0 18px; }
/* Dashboard and settings share the full-bleed chrome (logo, top menu, sidebar
   at the edges). On settings only the content column is capped + centered. */
body.view-dash .wrap, body.view-dash .topbar-inner,
body.view-admin .wrap, body.view-admin .topbar-inner { max-width: none; }
.main { padding-top: 17px; padding-bottom: 48px; }

/* Glass surface mixin via .card.
   No backdrop-filter: it interacted with the toast's transform-driven slide-in
   to leave Chromium re-tiling seams along card edges (a faint white-to-transparent
   band) that only cleared on the next repaint. Border + the radial body glows
   still carry the HUD feel without the GPU compositing cost. */
.card {
  background: var(--glass);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  padding: 24px;
}

/* Page-management accordion: each page is a collapsible card — summary row
   (title + status badges + slug) toggles the edit form body. */
.page-acc { padding: 0; }
/* Saving jumps back to #seite-<id>. The adminbar is sticky at 54px, so without this
   the card lands underneath it and looks like the wrong one opened. */
.page-acc[id] { scroll-margin-top: 70px; }
.page-acc-sum { display: flex; align-items: center; gap: 12px; padding: 16px 24px; cursor: pointer; list-style: none; user-select: none; }
.page-acc-sum::-webkit-details-marker { display: none; }
/* Was a literal '▸' — a character Rajdhani does not carry, so it fell back to
   whatever system font did, in a different weight than everything around it. */
.page-acc-sum::before {
  content: ''; width: .72em; height: .72em; background: var(--muted); transition: transform .12s;
  -webkit-mask: var(--chev-mask); mask: var(--chev-mask);
}
.page-acc[open] > .page-acc-sum::before { transform: rotate(90deg); }
.page-acc-title { font-weight: 600; }

/* Pages screen: a pure structure tree (left) + the selected page's options
   (right) — the tree is the menu, so there is no second "order" box listing the
   same pages again, and options only ever show for the page just clicked
   (tabs.js — data-tabs on .pages-rail, data-tab-panel in .pages-detail). Mirrors
   the shared rail/body split (.cat-page/.settings-hub), just not full-bleed:
   this page keeps its .wrap, so the rail scrolls with the page instead of
   docking full-height. */
/* Wider than the shared --rail-w (240px) on purpose, not a copy-paste miss:
   this rail carries a whole tree (nested dropdown children, longer titles) where
   every other --rail-w consumer carries a flat list of short labels (folder/
   category names). 320px = --rail-w + a third, on request. */
.pages-page { display: grid; grid-template-columns: 320px 1fr; gap: 20px; align-items: start; }
.pages-rail {
  display: flex; flex-direction: column; gap: 4px;
  border: 1px solid var(--glass-border); border-radius: var(--radius);
  background: var(--glass); padding: 10px;
}
.pages-detail { min-width: 0; }
/* The trash card follows the grid directly; only space it when it's there, so the
   embedded lightbox (which zeroes the last child's margin) keeps its measured height. */
.pages-page:not(:last-child) { margin-bottom: 28px; }
@media (max-width: 900px) {
  .pages-page { grid-template-columns: 1fr; }
}

/* Deliberately NOT display:flex like .posadm-hint / .cat-hint. Those carry one
   plain text run; this one has a <strong> in it, and in a flex container every
   inline run becomes its own flex item — the sentence then sits in columns
   instead of flowing. The icon rides along inline instead. */
.pages-hint { margin: 0 0 12px; max-width: 68ch; }
.pages-new { margin: 0 0 18px; }
.pages-new select.input { flex: 0 1 220px; }
.pages-new .btn { flex: none; }
/* Inside the editor's page lightbox (html.is-embedded, set in layout_builder.php
   when the page is framed): no admin bar, no dunning banner, no second heading
   — the lightbox has its own — and the page fills the frame. */
html.is-embedded .adminbar,
html.is-embedded body > div[style]:has(~ main),
html.is-embedded .pages-wrap > .page-head { display: none; }
/* The lightbox takes its height from this document's body (builder.js measures
   it), so the body must be as tall as its content and no taller: no 100% /
   100vh floor, no reserved scrollbar gutter, and padding instead of a margin on
   the wrap — a margin collapses through <main> and <body> and sticks out of the
   measured box, which is exactly the few pixels that produced a scrollbar on a
   page that fitted. */
html.is-embedded, html.is-embedded body { height: auto; min-height: 0; }
html.is-embedded { scrollbar-gutter: auto; }
html.is-embedded .admin-main .wrap.pages-wrap { max-width: none; margin-block: 0; padding: 16px 18px; }
html.is-embedded .pages-wrap > :last-child { margin-bottom: 0; }
.pages-hint svg { width: 14px; height: 14px; vertical-align: -2px; margin-right: 4px; }

/* One page's row in the tree: grip + title button (the tabs.js trigger) + the
   show-in-menu eye, all siblings so dragging or toggling never fires the tab
   click. Drafts and hidden pages are NOT dimmed — a greyed-out row reads as
   "broken" or "not yours to touch", and both are perfectly ordinary pages that
   still want to be clicked; the badge and the struck-through eye say the same
   thing without pushing the row away. */
.pages-node { display: flex; align-items: center; gap: 6px; border-radius: 6px; }
.pages-node.is-dragging { opacity: .4; }
/* Depth 1: a page's children sit indented in their own block, never further
   nested (this feature caps at one level — there is nowhere to indent a
   grandchild to). */
.pages-node-children { display: flex; flex-direction: column; gap: 4px; margin: 2px 0 2px 26px; }
.pages-node-grip { color: var(--muted2); cursor: grab; display: flex; flex: none; padding: 6px 2px; }
.pages-node-grip svg { width: 16px; height: 16px; }
.pages-node-btn {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px;
  padding: 8px 10px; border: 0; border-radius: 6px; background: none; color: var(--text);
  font: inherit; text-align: left; cursor: pointer;
}
.pages-node-btn:hover { background: var(--glass-strong); }
/* The title is the tabs.js button: is-active = this page's options are showing. */
.pages-node-btn.is-active { background: var(--glass-strong); }
.pages-node-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Show-in-menu toggle. Always visible: a control that only appears on hover
   is not discoverable (and does not exist on touch). The exception still
   stands out — the struck-through eye is amber, the open one muted. */
.pages-node-eye {
  display: flex; flex: none; padding: 4px; margin-right: 4px; border: 0; border-radius: 6px;
  background: none; color: var(--muted2); cursor: pointer;
  transition: background .12s, color .12s;
}
.pages-node-eye:hover { background: var(--glass-strong); color: var(--text); }
.pages-node-eye.is-hidden { color: var(--amber); }
.pages-node-eye svg { width: 15px; height: 15px; }
/* One of the two glyphs, chosen by state — never built in JS. */
.pages-node-eye .pages-node-eye-off,
.pages-node-eye.is-hidden .pages-node-eye-on { display: none; }
.pages-node-eye.is-hidden .pages-node-eye-off { display: flex; }

/* Anchor row: a fixed marker, never draggable, never a drop target — its
   position belongs to its section on the home page, edited in the builder. */
.pages-anchor {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px; margin-left: 26px;
  border: 1px dashed var(--glass-border-bright); border-radius: 6px;
}
.pages-anchor-pin { color: var(--muted2); display: flex; flex: none; }
.pages-anchor-pin svg { width: 16px; height: 16px; }
.pages-anchor-label { font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Drop indicators painted by pages-tree.js while dragging: a hairline above/below
   a row for "reorder as sibling here", a highlighted row for "nest as its child". */
.pages-node.is-drop-before { box-shadow: inset 0 2px 0 var(--accent); }
.pages-node.is-drop-after  { box-shadow: inset 0 -2px 0 var(--accent); }
.pages-node.is-drop-nest   { background: var(--glass-strong); outline: 1px dashed var(--accent); outline-offset: -1px; }

.pages-slughint { margin: 6px 0 0; }

/* SEO check on the pages screen. Three levels, and the colours are the ones the
   admin already uses for state (rose = wrong, amber = worth a look, accent = fine)
   — a fourth palette would just be another thing to learn. */
/* Tables in the tenant admin. There were none until the recycle bin, the version
   history and the redirect list arrived — those four views first borrowed
   `.ctl-table`, which is the OPERATOR PANEL's class and lives in the <style> of
   layout_panel.php. The tenant admin never loads that, so the tables rendered as
   bare browser defaults on a dark background. Own namespace, admin tokens.
   Sorting works here too (sorttable.js matches both classes). */
.adm-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.adm-table th, .adm-table td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--glass-border); }
.adm-table th { color: var(--muted); font-weight: 600; font-size: 12px; text-transform: uppercase; letter-spacing: .5px; }
.adm-table tr:last-child td { border-bottom: 0; }
.adm-table td .row-actions { margin: 0; }
.adm-table form { margin: 0; }
/* A wide table scrolls inside its card instead of pushing the page sideways. */
.adm-tablewrap { overflow-x: auto; }
.adm-table th.is-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.adm-table th.is-sortable:hover { color: var(--text); }
.adm-table th.is-sortable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Same chevron as everywhere else (the mask), not a CSS border triangle. */
.adm-table th.is-sortable::after { content: ''; display: inline-block; width: .62em; height: .62em; margin-left: 6px;
  vertical-align: -.05em; opacity: 0; background: currentColor; -webkit-mask: var(--chev-mask); mask: var(--chev-mask); transform: rotate(90deg); }
.adm-table th.is-sortable:hover::after { opacity: .35; }
.adm-table th[aria-sort=ascending]::after, .adm-table th[aria-sort=descending]::after { opacity: 1; background: var(--accent); }
.adm-table th[aria-sort=ascending]::after { transform: rotate(-90deg); }
.adm-table th[aria-sort=ascending], .adm-table th[aria-sort=descending] { color: var(--text); }

/* Dashboard. The layout follows what every competitor's start screen does, and
   for the same reasons: a preview of the actual site answers "is my website
   alright?" before any number does; the numbers carry a comparison, because a bare
   "0 Besucher" reads as a broken screen; and the checklist keeps its DONE items so
   there is progress to see, not just a list of complaints that shrinks to nothing. */
/* 2FA nudge for the signed-in user — same amber as .alert.warn and as the panel's
   .pnl-mfa-warn. */
.dash-mfa-warn { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 0 0 18px; padding: 12px 16px;
  border: 1px solid rgba(245,153,74,.35); border-radius: 6px; background: rgba(245,153,74,.1); color: #fcc995; font-size: 14px; }
.dash-mfa-warn .ico { width: 18px; height: 18px; flex: 0 0 18px; }
.dash-mfa-warn span { flex: 1; min-width: 220px; }
/* The tiles are NOT align-items:start — they stretch to the row, and the row is
   sized by the preview. That is what keeps the 2x2 block flush with the preview
   instead of ending in mid-air above it. */
.dash-top { display: grid; grid-template-columns: 440px 1fr; gap: 18px; margin-bottom: 18px; }
@media (max-width: 1100px) {
  .dash-top { grid-template-columns: 1fr; }
  /* Stacked, the tile would be full width while the scaled page inside stays
     440px — the rest of the frame is bare white. Cap it at the frame's real
     content width instead. */
  .dash-preview { max-width: 440px; }
}

/* --- live site preview --------------------------------------------------- */
.dash-preview { display: block; border: 1px solid var(--glass-border); border-radius: var(--radius);
  background: var(--glass); overflow: hidden; text-decoration: none; color: var(--text); transition: border-color .15s; }
.dash-preview:hover { border-color: var(--accent); }
/* Fixed box, desktop-width page scaled into it: the thumbnail has to show the
   DESKTOP layout, or it is a picture of a different website than the one being
   edited. The iframe is inert — the whole tile is one link.
   Height and scale are the SAME number: the frame shows a whole 1280x800
   viewport, so scale = column width / 1280 (440/1280 = .34375) and height =
   800 * scale. Widening .dash-top without both is a crop, not a bigger preview. */
.dash-preview-frame { position: relative; height: 275px; overflow: hidden; background: #fff; }
.dash-preview-frame iframe { position: absolute; top: 0; left: 0; width: 1280px; height: 800px;
  border: 0; transform: scale(.34375); transform-origin: 0 0; pointer-events: none; }
.dash-preview-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 9px 12px; border-top: 1px solid var(--glass-border); font-size: 13px; }
.dash-preview-url { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-live { display: inline-flex; align-items: center; gap: 6px; color: var(--emerald); font-weight: 600; font-size: 12px; }
.dash-live-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--emerald); }

/* --- number tiles -------------------------------------------------------- */
/* Four tiles beside the preview: 2x2 fills the row, and grid-auto-rows: 1fr
   splits the stretched track evenly so the block ends level with the preview
   rather than floating above it. auto-fit would put three in a row and leave the
   fourth stranded with a hole next to it. Below the breakpoint the preview
   stacks, the rows go back to auto and the tiles flow. */
.dash-tiles { display: grid; gap: 14px; grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: 1fr; }
@media (max-width: 1100px) { .dash-tiles { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); grid-auto-rows: auto; } }
.dash-tile { display: flex; flex-direction: column; justify-content: center; gap: 4px; padding: 16px 18px; border: 1px solid var(--glass-border);
  border-radius: var(--radius); background: var(--glass); color: var(--text); text-decoration: none;
  transition: border-color .15s, transform .15s; }
.dash-tile:hover { border-color: var(--accent); transform: translateY(-1px); }
/* Unread enquiries are the one number worth interrupting for. */
.dash-tile.is-hot { border-color: var(--accent); background: rgba(113,191,188,.10); }
.dash-num { font-size: 30px; font-weight: 700; line-height: 1; color: var(--text); }
.dash-tile.is-hot .dash-num { color: var(--accent); }
.dash-lbl { font-size: 13px; color: var(--muted); }
.dash-trend { font-size: 12px; color: var(--muted2); }
.dash-trend.is-up { color: var(--emerald); }
.dash-trend.is-down { color: var(--amber); }
/* Sparkline: the same bars as the statistics page, just small. A zero day keeps a
   2% stub so the week reads as seven days, not as a gap. */
.dash-spark-tile { justify-content: space-between; }
.dash-spark { display: flex; align-items: flex-end; gap: 3px; flex: 1 1 42px; min-height: 42px; margin-top: 8px; }
.dash-spark > span { flex: 1 1 0; background: var(--accent-grad); border-radius: 2px 2px 0 0; min-height: 2px; }

/* --- columns ------------------------------------------------------------- */
.dash-cols { display: grid; gap: 18px; grid-template-columns: 1.55fr 1fr; align-items: start; }
@media (max-width: 1100px) { .dash-cols { grid-template-columns: 1fr; } }
.dash-side { display: grid; gap: 18px; }

/* --- the checklist ------------------------------------------------------- */
.dash-todo-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.dash-todo-head h3 { margin-bottom: 2px; }
.dash-todo-head p { margin: 0; }
/* Progress ring: one circle, dash-offset carries the percentage — no library. */
.dash-ring { width: 60px; height: 60px; flex: none; transform: rotate(-90deg); }
.dash-ring circle { fill: none; stroke-width: 6; }
.dash-ring-bg { stroke: var(--glass-border); }
.dash-ring-fg { stroke: var(--accent); stroke-linecap: round; transition: stroke-dashoffset .4s ease; }
.dash-ring text { transform: rotate(90deg); transform-origin: 30px 30px; fill: var(--text);
  font-size: 15px; font-weight: 700; stroke: none; }

.dash-todos { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 8px; }
/* A row, not a bullet: marker, text, action. The box and the hover are what make
   it read as a task you can tick off rather than a line of prose. */
.dash-todo { display: flex; gap: 12px; align-items: center; line-height: 1.4;
  padding: 11px 13px; border: 1px solid var(--glass-border); border-radius: var(--radius); background: var(--glass); }
.dash-todo:hover { border-color: var(--glass-border-bright); }
.dash-todo-text { flex: 1; min-width: 0; }
/* The action sits ON the item: a checklist that makes you go looking for the
   screen that fixes it is a list of complaints. */
.dash-todo .btn { flex: none; }
/* The empty checkbox. Its colour carries the severity, so the list needs no
   second signal — and it is the shape that says "this can be ticked off". */
.dash-check { flex: none; width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--muted2);
  display: inline-flex; align-items: center; justify-content: center; }
.dash-todo--warn .dash-check { border-color: var(--rose); }
.dash-todo--hint .dash-check { border-color: var(--amber); }
.dash-check.is-checked { border-color: var(--emerald); background: var(--emerald); }
.dash-check .ico { width: 12px; height: 12px; color: var(--bg); }
.dash-todo.is-done { background: transparent; border-color: transparent; padding-left: 0; padding-right: 0; }
.dash-todo.is-done .dash-todo-text { color: var(--muted); }
.dash-done { margin-top: 14px; }
.dash-done > summary { cursor: pointer; color: var(--muted); font-size: 13px; list-style: none; }
.dash-done > summary::-webkit-details-marker { display: none; }
.dash-done > summary::before { content: ''; display: inline-block; width: .6em; height: .6em; margin-right: 8px;
  background: currentColor; -webkit-mask: var(--chev-mask); mask: var(--chev-mask); transition: transform .15s; }
.dash-done[open] > summary::before { transform: rotate(90deg); }
.dash-done > summary:hover { color: var(--text); }

/* --- recent enquiries ---------------------------------------------------- */
.dash-feed { list-style: none; margin: 10px 0; padding: 0; display: grid; gap: 2px; }
.dash-feed a { display: flex; justify-content: space-between; gap: 10px; align-items: baseline;
  padding: 8px 10px; border-radius: var(--radius); color: var(--text); text-decoration: none; }
.dash-feed a:hover { background: var(--glass); }
.dash-feed-name { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Unread carries the same accent dot the adminbar badge uses. */
.dash-feed li.is-unread .dash-feed-name::before { content: ''; display: inline-block; width: 7px; height: 7px;
  border-radius: 50%; background: var(--accent); margin-right: 8px; vertical-align: middle; }

/* --- quick links --------------------------------------------------------- */
.dash-links { display: grid; gap: 8px; }
.dash-link { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: var(--radius);
  color: var(--text); text-decoration: none; border: 1px solid transparent; }
.dash-link:hover { background: var(--glass); border-color: var(--glass-border); }
.dash-link .ico { width: 17px; height: 17px; flex: none; }

.seo-chip { padding: 4px 9px 2px; border-radius: 999px; font-size: 11px; font-weight: 600; letter-spacing: .5px; text-transform: uppercase; border: 1px solid; white-space: nowrap; }
.seo-chip.seo-ok   { color: var(--emerald); border-color: rgba(113,191,188,.45); background: rgba(113,191,188,.10); }
.seo-chip.seo-hint { color: var(--amber);   border-color: rgba(245,153,74,.45);  background: rgba(245,153,74,.10); }
.seo-chip.seo-warn { color: var(--rose);    border-color: rgba(245,101,127,.45); background: rgba(245,101,127,.10); }

.seo-check { border: 1px solid var(--glass-border); border-radius: var(--radius); padding: 14px 16px; background: var(--glass); }
.seo-check-head { font-weight: 600; margin-bottom: 8px; }
.seo-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 7px; }
.seo-list li { display: flex; gap: 9px; align-items: flex-start; line-height: 1.45; font-size: .94rem; color: var(--muted); }
/* The dot sits on the text's first line rather than the top of the box, so a
   two-line finding still reads as one item. */
.seo-dot { flex: 0 0 8px; width: 8px; height: 8px; border-radius: 50%; margin-top: .45em; background: var(--muted2); }
.seo-list li.seo-ok   { color: var(--muted); }
.seo-list li.seo-ok   .seo-dot { background: var(--emerald); }
.seo-list li.seo-hint { color: var(--text); }
.seo-list li.seo-hint .seo-dot { background: var(--amber); }
.seo-list li.seo-warn { color: var(--text); }
.seo-list li.seo-warn .seo-dot { background: var(--rose); }
.page-acc-meta { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.page-acc[open] > .page-acc-sum { border-bottom: 1px solid var(--glass-border); }
.page-acc-body { padding: 20px 24px 24px; }

/* Headings — Rajdhani, slightly condensed, uppercase flavor for the HUD look */
h1 { font-size: 27px; font-weight: 700; margin: 0 0 6px; letter-spacing: .5px; }
h2 { font-size: 19px; font-weight: 600; margin: 0 0 10px; letter-spacing: .5px; text-transform: uppercase; }

/* Topbar */
.topbar {
  position: sticky; top: 0; z-index: 10;
  background: rgba(10, 14, 18, 0.55);
  backdrop-filter: blur(20px) saturate(130%);
  -webkit-backdrop-filter: blur(20px) saturate(130%);
  border-bottom: 1px solid var(--glass-border);
}
.brand { font-weight: 700; font-size: 21px; color: var(--text); text-decoration: none; letter-spacing: 1.5px; }
.nav { display: flex; gap: 4px; }
.nav a { padding: 8px 14px; border-radius: 6px; font-size: 14px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); text-decoration: none; transition: color .15s, background .15s; }
.nav a:hover { color: var(--text); }
.nav a.active { color: var(--accent); background: var(--glass-strong); }
/* Auf Dashboard/Termine/Archiv (view-dash) übernimmt die Bereichs-Navigation in der
   page-head die Dashboard-Navigation — die Topbar-Dashboard-Pill wäre dort auf Desktop
   ein Duplikat. Desktop ausblenden, auf Mobile (kompaktes Icon) als schnellen Rückweg
   behalten. Einstellungen (view-admin, ohne Bereichsnav) bleiben unberührt. */

/* Avatar dropdown menu (details/summary) */
.menu { position: relative; }
/* Dropdown carets: the chevron mask, pointing down (markup no longer carries a '▾'). */
.caret, .acct-caret, .nav-caret {
  display: inline-block; width: .62em; height: .62em; background: currentColor;
  -webkit-mask: var(--chev-mask); mask: var(--chev-mask); transform: rotate(90deg);
}
.caret { transition: transform .15s; }
.menu[open] .caret { transform: rotate(-90deg); }

/* Push-Toggle in Mein Konto: .btn-secondary wechselt im subscribed-State
   auf einen klar erkennbaren Teal-Ring (Outline-Look). */
.btn[data-push-state="subscribed"] {
  border-color: var(--accent);
  color: var(--accent);
}

/* Horizontal sub-nav at the top of a content area (e.g. inside /system).
   Underline-on-active tabs that match the dark theme. */
.subnav { display: flex; gap: 4px; border-bottom: 1px solid var(--glass-border); margin-bottom: 22px; flex-wrap: wrap; }
.subnav a { padding: 10px 14px; font-size: 13px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); text-decoration: none; border-bottom: 2px solid transparent; margin-bottom: -1px; transition: color .15s, border-color .15s; }
.subnav a:hover { color: var(--text); }
.subnav a.active { color: var(--accent); border-color: var(--accent); }
/* The grid item fills its track (auto margins on a grid item would shrink it
   to content). Cap + center the content on the normal block children instead,
   so every settings page is the same width. */
.admin-content { min-width: 0; }

/* Auth pages */
.center-page { display: grid; place-items: center; min-height: 100%; }
.auth-wrap { width: 100%; max-width: 410px; padding: 24px; }

/* Form */
.label { display: block; font-size: 12px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase; color: var(--muted); margin: 16px 0 7px; }
.card > form > .label:first-of-type:not(.mt), .label:first-child:not(.mt) { margin-top: 0; }
/* Help line between a field's label and its control. The label's own 7px bottom
   margin is the gap to the control, so the hint takes it over and keeps a tighter
   one to the label it belongs to. */
.field-hint { margin: -3px 0 7px; line-height: 1.45; }
/* Character budget under a field (charcount.js). Right-aligned, so it reads as
   the control's own footnote instead of competing with the next label; the top
   margin is small because the label below already brings 16px of its own. */
.charcount { font-size: 12px; letter-spacing: .3px; text-align: right; color: var(--muted); margin: 5px 2px 0; }
.charcount.is-near { color: var(--amber); }
.charcount.is-over { color: var(--rose); }
.input {
  width: 100%; border: 1px solid var(--glass-border); border-radius: 6px;
  background: rgba(0, 0, 0, 0.25); padding: 12px 14px; color: var(--text);
  font-size: 16px; font-family: var(--font); letter-spacing: .3px;
}
.input::placeholder { color: var(--muted2); }
.input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 0 18px rgba(113, 191, 188,.25); }
textarea.input { resize: vertical; }
/* Password field with a show/hide eye (pwtoggle.js). The eye sits inside the
   field's right padding; open eye = "show", struck-through = "hide again". */
.pw-field { position: relative; display: block; }
.pw-field > .input { padding-right: 46px; }
.pw-eye {
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  display: flex; padding: 8px; border: 0; border-radius: 6px;
  background: none; color: var(--muted2); cursor: pointer;
}
.pw-eye:hover, .pw-eye.is-shown { color: var(--text); }
.pw-eye:hover { background: var(--glass-strong); }
.pw-eye svg { width: 16px; height: 16px; }
.pw-eye > span { display: flex; }
.pw-eye .pw-eye-off, .pw-eye.is-shown .pw-eye-on { display: none; }
.pw-eye.is-shown .pw-eye-off { display: flex; }
/* Date/time fields: the whole field opens the native picker (datefields.js) and
   the calendar/clock glyph is inverted so it's visible on the dark theme. */
/* Colour fields — colorpicker.js replaces the OS dialog with a swatch button and a
   popover (area + hue + hex). These rules keep the *native* field usable without JS
   and are what the swatch button reuses; .cpick-* below is the enhanced form. */
.input[type="color"] { height: 46px; padding: 5px; cursor: pointer; }
.input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.input[type="color"]::-webkit-color-swatch { border: 1px solid var(--glass-border); border-radius: 4px; }
.input[type="color"]::-moz-color-swatch { border: 1px solid var(--glass-border); border-radius: 4px; }
/* Only time fields open something on a click anywhere (our own list, see
   datefields.js) — hence the pointer. Date/datetime fields are typed into, so
   they keep the text caret: clicking a segment is how you correct just the year. */
.input[type="time"] { cursor: pointer; }
/* The glyph is the picker's affordance now that a field click no longer opens it,
   so give it a hit area worth aiming at (the bare icon is ~14px). The negative
   margin keeps the visual position unchanged. */
.input::-webkit-calendar-picker-indicator { filter: invert(1); opacity: .65; cursor: pointer; padding: 8px; margin: -8px -4px -8px 0; }
.input::-webkit-calendar-picker-indicator:hover { opacity: 1; }
/* Time fields use our own list (datefields.js) — hide the native glyph so a click
   on it can't open the browser popup on top of it. Date fields keep theirs. */
.input[type="time"]::-webkit-calendar-picker-indicator { display: none; }
/* Scrollbars — ONE rule for the whole admin document instead of a list of
   containers that has to be kept in mind. The list approach was tried twice and
   drifted both times: the media dialog, all four docked rails and the panel's log
   boxes still showed the white system bar.
   app.css is loaded by exactly the dark-themed shells — admin, operator panel,
   login and the findd.de marketing page — so "everything in this document" is
   precisely the set that wants a dark bar. Tenant sites load findd-public.css and
   never see these rules.
   Deliberately weak (`:where()` keeps the selector at specificity 0), because two
   things must still win: the white editor canvas with its light thumb, and the
   public carousels rendered inside it that hide their bar on purpose.
   NEW SCROLLABLE CONTAINER = NOTHING TO DO. */
/* scrollbar-color inherits, scrollbar-width does NOT — so the width has to be said
   for every element, or Firefox goes back to system-wide bars that are merely
   recoloured. Both stay at specificity 0 and are therefore overridable. */
html { scrollbar-color: var(--glass-border-bright) transparent; }
html, :where(html) * { scrollbar-width: thin; }
:where(html)::-webkit-scrollbar, :where(html) *::-webkit-scrollbar { width: 10px; height: 10px; }
:where(html)::-webkit-scrollbar-track, :where(html) *::-webkit-scrollbar-track { background: transparent; }
:where(html)::-webkit-scrollbar-thumb, :where(html) *::-webkit-scrollbar-thumb {
  background: var(--glass-border-bright); border-radius: 6px; border: 2px solid transparent; background-clip: padding-box; }
:where(html)::-webkit-scrollbar-thumb:hover, :where(html) *::-webkit-scrollbar-thumb:hover {
  background: var(--muted); background-clip: padding-box; }
/* The native resize grip renders as a light triangle that clashes — blend it. */
textarea.input::-webkit-resizer { background: transparent; }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
/* A wide control plus a narrow companion (font + its weight): equal halves would
   give a three-word select the same room as a font list. Stacks when there is no
   room for two, so the font name never gets clipped. */
.grid2--main { grid-template-columns: 2fr 1fr; }
/* Three equal controls in a row (the colour pickers). Drops to two before it
   drops to one — three colour fields side by side stop being comparable long
   before they stop fitting. */
.grid3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
@media (max-width: 900px) { .grid3 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 620px) { .grid2, .grid2--main, .grid3 { grid-template-columns: 1fr; } }

/* Catalog list layout (Jobs/Events/News/Produkte): full-bleed like the editor —
   a docked category/filter rail flush to the left edge + the tile grid using the
   rest of the viewport (no .wrap max-width cap, so many products fit per row).
   Mirrors the settings hub rail. Stacks on narrow screens. */
.cat-page { display: grid; grid-template-columns: var(--rail-w) 1fr; align-items: start; }
.cat-rail {
  position: sticky; top: 54px; align-self: start;
  height: calc(100vh - 54px); overflow-y: auto;
  padding: 22px 16px; border-right: 1px solid var(--glass-border); background: var(--glass);
}
.cat-rail-head {
  font-size: 12px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text); padding: 0 4px 14px;
}
.cat-body { min-width: 0; padding: 22px 24px; }
@media (max-width: 900px) {
  .cat-page { grid-template-columns: 1fr; }
  .cat-rail {
    position: static; height: auto; overflow: visible;
    border-right: 0; border-bottom: 1px solid var(--glass-border);
  }
  .cat-body { padding: 18px 16px; }
}
/* Direkt aufeinander folgende grid2-Blöcke brauchen vertikalen Abstand
   (innerhalb eines Blocks deckt das grid-gap das ab — zwischen zwei Blöcken
   greift es nicht). Sonst kleben die Labels der zweiten Reihe am Input darüber. */
.grid2 + .grid2 { margin-top: 14px; }
/* .subhead und .label setzen ihre eigene margin-shorthand (zero-top) und
   überschreiben damit das .mt-Utility. Mit höherer Spezifität wieder Luft
   schaffen, sonst kleben Unter-Überschrift und Folge-Label am Input drüber. */
.subhead.mt, .label.mt { margin-top: 22px; }

/* Buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border-radius: 6px; padding: 12px 22px; font-weight: 600; font-size: 14px;
  letter-spacing: 1.5px; text-transform: uppercase;
  /* Default (non-primary) buttons get a dark glass surface so a classless .btn
     never falls back to the white browser default on the dark theme. */
  background: var(--glass-strong); color: var(--text);
  border: 1px solid var(--glass-border-bright);
  cursor: pointer; font-family: var(--font); text-decoration: none;
  transition: transform .1s, box-shadow .15s, background .15s, border-color .15s;
}
.btn:hover:not(:disabled) { background: rgba(255, 255, 255, .12); }
.btn:hover, .btn:focus, .btn:active { text-decoration: none; }
/* Compact size for inline/table actions. */
.btn-sm { padding: 7px 13px; font-size: 12px; letter-spacing: 1px; border-radius: 6px; }
.btn:disabled { opacity: .4; cursor: not-allowed; }
.btn:active:not(:disabled) { transform: scale(.98); }
/* Suppress the default browser focus outline on mouse click (looks like a white
   ring on the dark theme); keep a clear accent ring for keyboard users. */
.btn:focus { outline: none; }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn-primary { background: var(--accent-grad); color: #04201c; box-shadow: 0 0 22px rgba(113, 191, 188,.4); }
.btn-primary:hover:not(:disabled) { background: var(--accent-grad); box-shadow: 0 0 30px rgba(113, 191, 188,.6); }
.btn-secondary { background: var(--glass-strong); color: var(--text); border-color: var(--glass-border-bright); }
.btn-secondary:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
.full { width: 100%; }
.small-btn { padding: 8px 15px; font-size: 12px; }
/* Icon-only action button — square, so a row of them fits side by side on a
   narrow card instead of wrapping one per line. The `gap: 0` matters: .btn sets
   a gap for the label that is no longer there, and it would sit as dead space
   next to the glyph. Whoever uses this MUST carry the label in title +
   aria-label; the text is moved, not dropped. */
.btn.icon-btn { padding: 8px; gap: 0; }
.btn.icon-btn .ico { width: 16px; height: 16px; }

.divider { height: 1px; background: var(--glass-border); margin: 22px 0 16px; border: none; }

/* Structured weekly opening-hours editor (Stammdaten tab). Times grey out when
   the day is marked closed — pure CSS via :has(), no JS needed. */
/* One shared 4-column grid so day · geöffnet · times · 24 Stunden line up across
   every row regardless of how long the weekday name is. Rows use display:contents
   so their children land directly in the shared tracks. */
.oh-editor { display: grid; grid-template-columns: auto auto auto auto; align-items: center; gap: 10px 14px; margin-top: 6px; justify-content: start; }
.oh-row { display: contents; }
.oh-day { font-weight: 600; }
.oh-flag { font-size: 13px; color: var(--muted); white-space: nowrap; }
.oh-flag .switch-track { flex-shrink: 0; }
.oh-times { display: inline-flex; align-items: center; gap: 6px; min-width: 0; }
.oh-times .input { width: auto; min-width: 0; padding: 6px 8px; }
/* Times are live only while the day's switch is on. :not(:has()) rather than a
   "closed" flag — the stored shape is "time ranges present = open" (see the view). */
.oh-row:not(:has(input[data-oh-open]:checked)) .oh-times { opacity: .35; pointer-events: none; }
/* Around the clock: the times below it mean nothing while it is on, and the switch
   itself means nothing on a closed day — both are dimmed by the state that makes
   them irrelevant, so the row never shows two controls that contradict each other. */
.oh-row:has(input[data-oh-allday]:checked) .oh-times,
.oh-row:not(:has(input[data-oh-open]:checked)) .oh-allday { opacity: .35; pointer-events: none; }
/* The label mirrors the switch: "geöffnet" / "geschlossen". Both words sit in ONE
   grid cell, so the box is always as wide as the longer one — toggling a single day
   must not shift the time columns of the whole week. visibility, not display, for
   the same reason (and it keeps the hidden word out of the accessibility tree). */
.oh-state { display: inline-grid; }
.oh-state > span { grid-area: 1 / 1; }
.oh-flag input:checked ~ .oh-state .oh-state-off,
.oh-flag input:not(:checked) ~ .oh-state .oh-state-on { visibility: hidden; }
@media (max-width: 760px) {
  .oh-editor { display: block; }
  .oh-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 10px; margin-bottom: 10px; }
}

.alert { margin-top: 14px; padding: 12px 15px; border-radius: 6px; font-size: 14px; border: 1px solid; }
.alert.error { background: rgba(245,101,127,.1); border-color: rgba(245,101,127,.35); color: #fca5b4; }
.alert.warn { background: rgba(245,153,74,.1); border-color: rgba(245,153,74,.35); color: #fcc995; }
.alert.ok-msg, .ok-msg { background: rgba(113, 191, 188,.1); border-color: rgba(113, 191, 188,.35); color: var(--accent); }
.ok { color: var(--accent); }
.row-between { display: flex; justify-content: space-between; align-items: center; }
@keyframes ping { 75%, 100% { transform: scale(1.4); opacity: 0; } }
@keyframes mic-pulse { 50% { opacity: .5; } }

/* Native select dropdowns: dark option list (default is light grey on some OSes) */
select option { background: #0f172a; color: var(--text); }
/* Drop the native control chrome and draw our own chevron, so .input selects
   match the rest of the dark theme instead of the OS style. */
select.input {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  cursor: pointer; padding-right: 40px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239aa3ad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 18px;
}

/* Colour picker (colorpicker.js): swatch button + popover with a saturation/
   brightness area, a hue strip and a hex field — the two things a customer needs,
   instead of the OS dialog's eyedropper, spinners and colour spaces. The real
   input[type=color] stays as .cpick-native (value source, still submits). */
.cpick { position: relative; }
.cpick-native { position: absolute; width: 0; height: 0; padding: 0; border: 0; opacity: 0; pointer-events: none; }
.cpick-btn {
  display: flex; align-items: center; gap: 10px; width: 100%; height: 46px; padding: 5px;
  border: 1px solid var(--glass-border); border-radius: 6px; background: rgba(0, 0, 0, 0.25);
  color: var(--text); font: inherit; cursor: pointer; text-align: left;
}
.cpick-btn:hover { border-color: var(--glass-border-bright); }
.cpick.open .cpick-btn { border-color: var(--accent); }
.cpick-sw { flex: none; width: 34px; height: 34px; border-radius: 4px; border: 1px solid var(--glass-border); }
.cpick-val { font-family: ui-monospace, "SF Mono", Menlo, monospace; font-size: 13px; letter-spacing: 0; color: var(--muted); }
.cpick-panel {
  position: fixed; z-index: 60; width: 236px; padding: 12px;
  background: rgba(16, 22, 28, 0.92);
  backdrop-filter: blur(22px) saturate(130%); -webkit-backdrop-filter: blur(22px) saturate(130%);
  border: 1px solid var(--glass-border); border-radius: 6px; box-shadow: 0 12px 30px rgba(0, 0, 0, .45);
}
/* Two stacked gradients over the pure hue: white → transparent left to right
   (saturation), black → transparent bottom to top (brightness). */
.cpick-area {
  position: relative; height: 140px; border-radius: 6px; cursor: crosshair; touch-action: none;
  background-image: linear-gradient(to top, #000, rgba(0, 0, 0, 0)), linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
}
.cpick-hue {
  position: relative; height: 12px; margin-top: 12px; border-radius: 999px; cursor: ew-resize; touch-action: none;
  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
}
.cpick-dot {
  position: absolute; width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: 50%;
  border: 2px solid #fff; box-shadow: 0 0 0 1px rgba(0, 0, 0, .55), 0 1px 3px rgba(0, 0, 0, .5);
  pointer-events: none;
}
.cpick-hue .cpick-dot { top: 50%; }
.cpick-foot { margin-top: 12px; }
.cpick-hex { text-transform: uppercase; font-family: ui-monospace, "SF Mono", Menlo, monospace; letter-spacing: 0; font-size: 14px; }

/* Custom dropdown (app.js enhanceSelect): a fully themed replacement for the
   native select popup. The real <select> stays as .fselect-native (value source). */
.fselect { position: relative; }
.fselect-native { display: none; }
.fselect-btn {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  border: 1px solid var(--glass-border); border-radius: 6px;
  background: rgba(0, 0, 0, 0.25); padding: 12px 14px; color: var(--text);
  font-size: 16px; font-family: var(--font); letter-spacing: .3px; text-align: left; cursor: pointer;
  transition: border-color .15s, box-shadow .15s;
}
.fselect-btn:hover { border-color: var(--glass-border-bright); }
.fselect.open .fselect-btn, .fselect-btn:focus-visible {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), 0 0 18px rgba(113, 191, 188,.25);
}
.fselect-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fselect-label.is-placeholder { color: var(--muted2); }
/* Size and rotation live here, the shape comes from .chev (mask). Deliberately NOT
   .chev-down: the open state has to rotate from the down position, and a second
   transform on the same element would just overwrite the first. */
.fselect-caret { display: inline-block; flex-shrink: 0; width: 16px; height: 16px; color: var(--muted); transform: rotate(90deg); transition: transform .2s; }
.fselect.open .fselect-caret { transform: rotate(-90deg); }
.fselect-panel {
  /* Flush against the button (no margin/gap) so moving the pointer into the list
     never crosses a dead zone that would fire mouseleave and close it.
     Alpha bewusst niedrig (0.7), damit der backdrop-filter-Blur sichtbar wird
     und die Glas-Optik trägt — bei höherer Deckkraft (≥0.85) übertönt der
     dunkle Layer den Blur und das Panel sieht solide aus. Compositing-Seam-
     Risiko entfällt, seit .card keinen eigenen Blur mehr hat (commit 28bb019). */
  position: absolute; left: 0; right: 0; top: 100%; z-index: 40;
  margin: 0; padding: 6px; list-style: none; max-height: 260px; overflow-y: auto;
  background: rgba(16, 22, 28, 0.7);
  backdrop-filter: blur(22px) saturate(130%); -webkit-backdrop-filter: blur(22px) saturate(130%);
  border: 1px solid var(--glass-border); border-radius: 6px; box-shadow: 0 12px 30px rgba(0,0,0,.45);
}
.fselect-option {
  padding: 10px 12px; border-radius: 6px; font-size: 15px; color: var(--text); cursor: pointer;
  transition: background .12s, color .12s;
}
/* Builder selects: panel is positioned fixed by select.js (left/top/width inline)
   so it escapes the inspector/toolbar overflow clipping. */
.fselect--fixed .fselect-panel { position: fixed; left: auto; right: auto; top: auto; z-index: 1000; }
/* Group heading inside the panel (select.js mirrors <optgroup>). Not an option:
   no hover, no pointer, and it must never look clickable. */
.fselect-group {
  padding: 10px 12px 4px; font-size: 12px; font-weight: 700; letter-spacing: .04em;
  /* --muted, not --muted2: measured 7.25:1 vs 3.48:1 on the panel ground
     (#0e1419 after the 0.7 alpha) — a 12px uppercase heading below 4.5:1 is
     decoration, not a label. */
  text-transform: uppercase; color: var(--muted); cursor: default; pointer-events: none;
}
.fselect-group:not(:first-child) { margin-top: 4px; border-top: 1px solid var(--glass-border); padding-top: 10px; }
.fselect-option:hover { background: var(--glass-strong); }
.fselect-option.is-active { color: var(--accent); background: var(--glass-strong); }
.fselect-option.is-placeholder { color: var(--muted2); }

/* Time list (datefields.js) — same glass panel as .fselect, but anchored to the
   input by fixed coordinates so no scrolling ancestor can clip it. Full hours
   are drawn brighter: they are what the eye scans for while scrolling. */
.tpick-panel {
  position: fixed; z-index: 1000; margin: 0; padding: 6px; list-style: none;
  max-height: 260px; overflow-y: auto; overscroll-behavior: contain;
  background: rgba(16, 22, 28, 0.7);
  backdrop-filter: blur(22px) saturate(130%); -webkit-backdrop-filter: blur(22px) saturate(130%);
  border: 1px solid var(--glass-border); border-radius: 6px; box-shadow: 0 12px 30px rgba(0,0,0,.45);
}
.tpick-panel[hidden] { display: none; }
.tpick-option {
  padding: 7px 12px; border-radius: 6px; font-size: 15px; color: var(--muted); cursor: pointer;
  font-variant-numeric: tabular-nums; transition: background .12s, color .12s;
}
.tpick-option.is-hour { color: var(--text); font-weight: 600; }
.tpick-option:hover { background: var(--glass-strong); color: var(--text); }
.tpick-option.is-active { color: var(--accent); background: var(--glass-strong); }

/* Page head: filters left, actions right; flex-wrap catches narrow viewports. */
.page-head { display: flex; align-items: center; margin-bottom: 22px; gap: 10px; flex-wrap: wrap; }

/* One central size for every switch in the app. */
.switch { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch-track { position: relative; width: 34px; height: 19px; border-radius: 999px; background: #e6e9ec; transition: background .2s; flex-shrink: 0; }
.switch-track::after { content: ""; position: absolute; top: 3px; left: 3px; width: 13px; height: 13px; border-radius: 50%; background: #0a0e12; transition: transform .2s; }
.switch input:checked + .switch-track { background: var(--accent-grad); }
.switch input:checked + .switch-track::after { transform: translateX(15px); background: #fff; }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--accent); outline-offset: 2px; }
.reports { display: flex; flex-direction: column; gap: 16px; }
.ico { width: 1em; height: 1em; display: inline-block; vertical-align: -0.15em; flex-shrink: 0; }
.badge { padding: 5px 11px 3px; border-radius: 6px; font-size: 11px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase; border: 1px solid; }
@keyframes review-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(167,139,250,0); }
  50% { box-shadow: 0 0 12px 1px rgba(167,139,250,.7); border-color: #b9a7ff; }
}

@keyframes read-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(113, 191, 188,0); }
  50% { box-shadow: 0 0 12px 1px rgba(113, 191, 188,.6); border-color: var(--accent); }
}

.badge-draft { background: rgba(255,255,255,.06); color: var(--muted2); border-color: var(--glass-border-bright); }

/* Phone: kompaktere Zellen; Chips zeigen nur noch den farbigen Punkt + gekürzten Text. */

.star { background: none; border: none; font-size: 21px; line-height: 1; color: var(--muted2); cursor: pointer; padding: 0; transition: color .1s; }
.star:hover { color: var(--muted); }
.star.on { color: var(--amber); }
/* Phone: Labels weg, Icon-only (kompakt wie der Viewswitcher). */

/* (Dropped: a soft destructive style for #archive-purge / .user-delete — both
   managementai leftovers that no view renders any more. The live destructive
   button is .btn-danger in builder.css, the discreet one .btn.is-danger.) */

.qr-box { background: #fff; padding: 16px; border-radius: 6px; display: inline-block; }
.qr-box svg { display: block; width: 220px; height: 220px; }
#ping-result { margin-top: 14px; }

.log { border-radius: 6px; background: rgba(0,0,0,.3); border: 1px solid var(--glass-border); padding: 12px; font-size: 12px; white-space: pre-wrap; word-break: break-word; margin-top: 10px; color: #c2c8d0; font-family: ui-monospace, Menlo, monospace; }
/* Embed-Variante von layout_employee (?embed=1): kein Topbar/Segnav.
   Body bleibt halbtransparent (var(--bg) mit ~85% Alpha) — der Overlay-
   Blur schimmert dezent durch, ohne dass der Content unleserlich wird.
   Horizontal-Padding der .wrap bleibt erhalten, nur das Top-Padding
   fällt weg (keine Topbar darüber). */
/* --- POS admin ------------------------------------------------------------ */
/* Namespaced .posadm-* so nothing collides with the kiosk output's .pos-*
   classes (pos.css) — the two never share a document, but the names would. */
.pos-badge { display: inline-block; font-size: 12px; padding: 2px 8px; border-radius: 999px; background: #1f7a34; color: #fff; }
.pos-badge.is-off { background: #555; }
.pos-checklist { display: flex; flex-direction: column; gap: 4px; max-height: 220px; overflow-y: auto; border: 1px solid var(--glass-border, #2a3540); border-radius: 6px; padding: 8px; }
.pos-check { display: flex; align-items: center; gap: 8px; font-size: 14px; }

/* screens overview — tile grid like the catalog list. At most THREE per row
   (02.10.2026): at 280px the grid fit four, and a card that narrow (311px
   measured) can't hold its three buttons — Löschen stuck out 45px over the
   edge into the next card. The column is a third of the row but never under
   380px (what the buttons need), so narrower windows drop to two or one;
   min(100%, …) keeps a phone from overflowing. */
.posadm-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, max(380px, calc((100% - 28px) / 3))), 1fr));
}
.posadm-screen .row-actions { flex-wrap: wrap; }
.posadm-screen {
  border: 1px solid var(--glass-border); border-radius: var(--radius); background: var(--glass);
  padding: 16px; display: flex; flex-direction: column; gap: 10px;
}
.posadm-screen.is-off { opacity: .62; }
.posadm-screen-head { display: flex; align-items: center; gap: 12px; }
.posadm-screen-icon {
  flex: 0 0 40px; height: 40px; border-radius: var(--radius); display: grid; place-items: center;
  background: var(--accent-grad); color: #08131a;
}
.posadm-screen-icon svg { width: 20px; height: 20px; }
.posadm-screen-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; min-width: 0; }
.posadm-screen-meta { display: flex; gap: 12px; flex-wrap: wrap; }

/* screen detail — docked rail + slide list (same pattern as .cat-page) */
.posadm-page { display: grid; grid-template-columns: var(--rail-w) 1fr; align-items: start; }
.posadm-rail {
  position: sticky; top: 54px; align-self: start;
  height: calc(100vh - 54px); overflow-y: auto;
  padding: 22px 16px; border-right: 1px solid var(--glass-border); background: var(--glass);
}
.posadm-rail-head {
  font-size: 12px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: var(--text); padding: 8px 4px 14px; word-break: break-word;
}
.posadm-rail-label {
  font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--muted); opacity: .65; padding: 20px 4px 6px;
}
.posadm-rail-danger { margin-top: 22px; padding-top: 14px; border-top: 1px solid var(--glass-border); }
.posadm-body { min-width: 0; padding: 22px 24px; }
.posadm-add { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.posadm-hint { display: flex; align-items: center; gap: 6px; margin: 0 0 10px; }
.posadm-hint svg { width: 14px; height: 14px; }

.posadm-slides { display: flex; flex-direction: column; gap: 8px; }
.posadm-slide {
  position: relative; border: 1px solid var(--glass-border); border-radius: var(--radius);
  background: var(--glass);
}
.posadm-slide.is-off { opacity: .55; }
.posadm-slide.is-dragging { opacity: .4; }
.posadm-slide > details > summary {
  list-style: none; cursor: pointer; padding: 10px 148px 10px 10px;
  display: flex; align-items: center; gap: 12px;
}
.posadm-slide > details > summary::-webkit-details-marker { display: none; }
.posadm-slide > details[open] > summary { border-bottom: 1px solid var(--glass-border); }
.posadm-grip { color: var(--muted2); cursor: grab; display: flex; }
.posadm-grip svg { width: 18px; height: 18px; }
.posadm-num { color: var(--muted2); font-weight: 700; font-size: 13px; min-width: 16px; text-align: right; }
.posadm-tile {
  flex: 0 0 52px; height: 40px; border-radius: var(--radius); overflow: hidden;
  background: var(--glass-strong); display: grid; place-items: center; color: var(--accent);
}
.posadm-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.posadm-tile svg { width: 18px; height: 18px; }
.posadm-slide-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.posadm-slide-txt .small { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.posadm-warn {
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  font-size: 12px; color: var(--amber); border: 1px solid var(--amber);
  border-radius: 999px; padding: 2px 9px;
}
.posadm-warn svg { width: 13px; height: 13px; }
.posadm-slide-body { padding: 14px; }
.posadm-slide-ctl {
  position: absolute; top: 12px; right: 12px;
  display: flex; align-items: center; gap: 8px;
}
.posadm-slide-ctl form { display: inline; }
/* open/closed affordance for the summary row */
.posadm-chev { margin-left: auto; color: var(--muted2); display: flex; transition: transform .15s ease; }
.posadm-chev svg { width: 16px; height: 16px; }
.posadm-slide > details[open] > summary .posadm-chev { transform: rotate(180deg); }

@media (max-width: 900px) {
  .posadm-page { grid-template-columns: 1fr; }
  .posadm-rail {
    position: static; height: auto; overflow: visible;
    border-right: 0; border-bottom: 1px solid var(--glass-border);
  }
  .posadm-body { padding: 18px 16px; }
}
/* Phones: the row can't hold text and controls side by side — drop the controls
   into their own line at the top instead of squeezing the digest. */
@media (max-width: 560px) {
  .posadm-slide > details > summary { flex-wrap: wrap; padding: 44px 10px 10px; }
  .posadm-slide-ctl { top: 10px; }
}

/* --- Social Media --------------------------------------------------------- */
/* Same full-bleed rail + body skeleton as the catalog/media/POS pages. The rail
   width is the shared --rail-w token — never hardcode a width here. */
.soc-page,
.net-page { display: grid; grid-template-columns: var(--rail-w) 1fr; align-items: start; }
.soc-rail,
.net-rail {
  position: sticky; top: 54px; align-self: start;
  height: calc(100vh - 54px); overflow-y: auto;
  padding: 22px 16px; border-right: 1px solid var(--glass-border); background: var(--glass);
}
.soc-body,
.net-body { min-width: 0; padding: 22px 24px; }

.soc-channels { display: flex; flex-direction: column; gap: 8px; margin-bottom: 18px; }
.soc-chan { border: 1px solid var(--glass-border); border-radius: 6px; background: var(--glass); }
.soc-chan > summary {
  display: flex; align-items: center; gap: 9px; padding: 9px 10px;
  cursor: pointer; list-style: none; font-size: 14px;
}
.soc-chan > summary::-webkit-details-marker { display: none; }
.soc-chan-icon { display: flex; color: var(--muted); }
.soc-chan-icon svg { width: 17px; height: 17px; }
.soc-chan-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.soc-chan-form { padding: 0 10px 10px; }
.soc-chan-actions { margin-top: 10px; }
.soc-chan > form:last-child { padding: 0 10px 10px; }
.soc-add { display: flex; flex-direction: column; gap: 8px; }

.soc-sec { margin-bottom: 30px; }
.soc-sec-head { margin: 0 0 10px; font-size: 15px; }
.soc-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 14px; }
.soc-card {
  display: flex; flex-direction: column;
  background: var(--glass); border: 1px solid var(--glass-border); border-radius: 6px; overflow: hidden;
}
.soc-card.is-done { opacity: .62; }
.soc-card-img { position: relative; aspect-ratio: 16 / 9; overflow: hidden; background: var(--glass-strong); }
.soc-card-img img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }
.soc-card-body { display: flex; flex-direction: column; gap: 7px; padding: 11px 12px; flex: 1; }
.soc-card-top { display: flex; align-items: center; gap: 8px; }
.soc-card-net { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--muted); }
.soc-card-net svg { width: 16px; height: 16px; }
.soc-card-text { margin: 0; font-size: 14px; line-height: 1.45; white-space: pre-line; }
.soc-card-when { margin-top: auto; }
.soc-st-scheduled { color: var(--amber); }
.soc-st-published { color: var(--emerald); }
.soc-st-failed { color: var(--rose); }

/* Status chips over the plan. Own class, not a generic .chip — the codebase has
   no chip component, and inventing a global one for three buttons would be worse. */
.soc-filter { display: flex; flex-wrap: wrap; gap: 7px; margin-bottom: 12px; }
.soc-chip {
  padding: 5px 12px; border-radius: 999px; font: inherit; font-size: 13px; cursor: pointer;
  color: var(--muted); background: transparent; border: 1px solid var(--glass-border);
}
.soc-chip:hover { color: var(--text); border-color: var(--glass-border-bright); }
.soc-chip.is-active { color: var(--bg); background: var(--accent); border-color: var(--accent); }

.soc-sugg { display: flex; flex-direction: column; gap: 8px; }
.soc-sugg-item {
  display: flex; align-items: center; gap: 12px; padding: 9px 11px;
  border: 1px solid var(--glass-border); border-radius: 6px; background: var(--glass);
  color: var(--text); text-decoration: none;
}
.soc-sugg-item:hover { border-color: var(--glass-border-bright); background: var(--glass-strong); }
.soc-sugg-img { flex: 0 0 auto; width: 52px; height: 34px; border-radius: 4px; overflow: hidden; }
.soc-sugg-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.soc-sugg-text { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.soc-sugg-cta { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--accent); }
.soc-sugg-cta svg { width: 15px; height: 15px; }

/* Composer */
.soc-src { display: flex; align-items: center; gap: 14px; }
.soc-src-img { flex: 0 0 auto; width: 84px; height: 56px; border-radius: 4px; overflow: hidden; }
.soc-src-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.soc-text { min-height: 160px; resize: vertical; font-family: inherit; line-height: 1.5; }
.soc-meta-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 6px 0 4px; }
.soc-meta-row .is-over { color: var(--rose); }
.soc-comp-foot { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: 14px; }
.soc-comp-actions { display: flex; align-items: center; gap: 10px; }
/* The tab buttons carry a network icon. */
.soc-comp-form .tab-btn { display: inline-flex; align-items: center; gap: 7px; }
.soc-comp-form .tab-btn svg { width: 15px; height: 15px; }

@media (max-width: 900px) {
  .soc-page,
  .net-page { grid-template-columns: 1fr; }
  .soc-rail,
  .net-rail {
    position: static; height: auto; overflow: visible;
    border-right: 0; border-bottom: 1px solid var(--glass-border);
  }
  .soc-body,
  .net-body { padding: 18px 16px; }
}

/* --- findd-Netzwerk ------------------------------------------------------- */
/* Rail + Body erben oben vom .soc-*-Skelett (gemeinsame Regel, kein sechster
   Nachbau) — hier steht nur, was diese Seite eigenes hat. */
.net-switch { display: flex; align-items: center; gap: 9px; margin-bottom: 6px; }
.net-hint { margin: 0 0 16px; }
.net-facts { margin: 0 0 16px; }
.net-facts dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; margin: 0 0 6px; font-size: 13px; }
.net-facts dt { color: var(--muted); }
.net-facts dd { margin: 0; }
.net-radio { display: flex; align-items: center; gap: 8px; font-size: 14px; margin-bottom: 6px; }
.net-picklist { display: flex; flex-direction: column; gap: 5px; margin: 8px 0 16px; }
.net-pick { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.net-save { margin-top: 6px; width: 100%; }

.net-card + .net-card { margin-top: 18px; }
.net-card h2 { margin: 0 0 6px; font-size: 16px; }
.net-alert { margin-top: 14px; }

.net-offers { display: flex; flex-direction: column; gap: 6px; margin-top: 14px; }
.net-offer {
  display: flex; align-items: center; gap: 11px; padding: 9px 11px;
  border: 1px solid var(--glass-border); border-radius: 6px; background: var(--glass);
}
/* Angebotene Zeilen heben sich ab, damit "was ist gerade draußen?" ohne Lesen
   erkennbar ist — dieselbe Frage, die die Seite beantworten soll. */
.net-offer.is-offered { border-color: var(--accent); }
.net-offer-txt { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.net-offer-txt strong { font-weight: 600; }
.net-chip {
  margin-left: auto; display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; border-radius: 999px; font-size: 12px; white-space: nowrap;
}
.net-chip svg { width: 14px; height: 14px; }
.net-chip-warn { background: rgba(245,153,74,.1); color: #fcc995; }
.net-table td.num, .net-table th.num { text-align: right; }

/* --- Media library -------------------------------------------------------- */
/* Storage quota meter (media library). Neutral until it matters: the accent bar
   turns amber at StorageQuota::WARN_PERCENT and red when full, so the page does
   not shout at a tenant using 12% of their package. */
.quota-box { border: 1px solid var(--glass-border); border-radius: 6px; background: var(--glass); padding: 12px 14px; }
.quota-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.quota-bar { height: 7px; border-radius: 999px; background: var(--glass-strong); overflow: hidden; margin-top: 9px; }
.quota-bar > span { display: block; height: 100%; background: var(--accent); border-radius: inherit; }
.quota-box.is-warn { border-color: rgba(245, 153, 74, .45); }
.quota-box.is-warn .quota-bar > span { background: var(--amber); }
.quota-box.is-full { border-color: rgba(245, 101, 127, .45); }
.quota-box.is-full .quota-bar > span { background: var(--rose); }
.quota-box p { margin: 8px 0 0; }

.media-section-title { margin: 22px 0 10px; font-size: 15px; }
.media-section-title:first-of-type { margin-top: 4px; }
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 14px; }
.media-item { border: 1px solid var(--glass-border, #2a3540); border-radius: 6px; overflow: hidden; background: var(--glass, #141a20); display: flex; flex-direction: column; }
.media-thumb { display: block; aspect-ratio: 4 / 3; background: #0d1217; display: flex; align-items: center; justify-content: center; overflow: hidden; text-decoration: none; }
.media-thumb img { width: 100%; height: 100%; object-fit: cover; }
.media-doc { font-weight: 800; letter-spacing: 1px; color: #8aa; font-size: 20px; }
.media-meta { padding: 8px 10px; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.media-name { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.media-size { font-size: 11px; opacity: .6; }

/* Media library with folders: sticky folder rail (left) + grid (right). Drag a
   media item onto a folder to sort it; multi-select drives the bulk-move bar. */
/* Docked full-height folder rail — same pattern as .cat-rail / .settings-rail so
   the media page is consistent with the other full-bleed admin pages. */
.media-page { display: grid; grid-template-columns: var(--rail-w) 1fr; align-items: start; }
.media-page--flat { display: block; } /* no folders yet (pre-migration) → full-width grid */
.media-body { min-width: 0; padding: 22px 24px; }
.media-rail {
  position: sticky; top: 54px; align-self: start;
  height: calc(100vh - 54px); overflow-y: auto;
  display: flex; flex-direction: column; gap: 4px;
  padding: 22px 16px; border-right: 1px solid var(--glass-border); background: var(--glass);
}
/* Matches .cat-rail-head / .settings-rail-head so all three rail headers read alike. */
.media-rail-title { font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .12em; color: var(--text); padding: 0 10px 14px; }
.media-rail > * { flex: 0 0 auto; } /* the rail is a tall flex column — don't let items grow to fill it */
.media-folder-row { display: flex; align-items: center; gap: 2px; }
.media-folder-row .media-folder { flex: 1; } /* fill the row width next to the edit/delete tools */
.media-folder { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border: 1px solid transparent; border-radius: 6px; background: none; color: inherit; cursor: pointer; text-align: left; font: inherit; min-width: 0; }
.media-folder:hover { background: rgba(255,255,255,.05); }
.media-folder.is-active { background: rgba(255,255,255,.07); border-color: var(--glass-border-bright); }
.media-folder.is-drop { border-color: var(--accent); background: rgba(113,191,188,.14); }
.media-folder-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.media-folder-cnt { font-size: 12px; color: var(--muted); }
.media-folder-tools { display: flex; align-items: center; }
.media-folder-row:hover .media-folder-tools { opacity: 1; }
.media-folder-tools { opacity: 0; transition: opacity .12s; }
.media-folder-edit, .media-folder-del { border: 0; background: none; color: var(--muted); cursor: pointer; padding: 4px; font-size: 13px; line-height: 1; display: flex; }
.media-folder-edit:hover { color: var(--accent); }
.media-folder-del:hover { color: var(--rose); }
.media-folder-del .ico { width: 14px; height: 14px; }
.media-folder-new { display: flex; gap: 6px; margin-top: 8px; }
.media-folder-new .input { padding: 6px 8px; }

/* item selection + drag */
.media-item { position: relative; }
.media-item[draggable="true"] .media-thumb { cursor: grab; }
.media-item.is-dragging { opacity: .4; }
.media-item.is-sel { outline: 2px solid var(--accent); outline-offset: -2px; }
.media-check { position: absolute; top: 6px; left: 6px; z-index: 2; width: 22px; height: 22px; cursor: pointer; }
.media-check input { position: absolute; opacity: 0; }
.media-check span { display: block; width: 22px; height: 22px; border-radius: 6px; background: rgba(10,14,18,.55); border: 1px solid rgba(255,255,255,.4); }
.media-check input:checked + span { background: var(--accent); border-color: var(--accent); }
.media-check input:checked + span::after { content: ''; position: absolute; left: 7px; top: 3px; width: 5px; height: 10px; border: solid #fff; border-width: 0 2px 2px 0; transform: rotate(45deg); }

.media-bulkbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 14px; margin-bottom: 14px; background: var(--glass); border: 1px solid var(--glass-border-bright); border-radius: var(--radius); }
.media-bulkbar .label { margin: 0; }
.media-bulkbar .input { width: auto; min-width: 160px; }
.media-selectall { display: flex; align-items: center; gap: 6px; cursor: pointer; font-weight: 600; }
/* The move controls sit in the SAME bar as "Alle auswählen" (always visible),
   so they need their own flex row with the bar's gap — a plain <span> would
   bunch count/select/buttons together with no space between them. */
.media-bulk-sel { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Falle 2: an author `display` beats the UA [hidden]{display:none} regardless
   of specificity (different cascade origin) — toggling .hidden via JS needs
   this explicit rule or the row stays visible, empty-looking, while "hidden". */
.media-bulk-sel[hidden] { display: none; }

@media (max-width: 900px) {
  .media-page { grid-template-columns: 1fr; }
  .media-rail { position: static; height: auto; overflow: visible; border-right: 0; border-bottom: 1px solid var(--glass-border); }
  .media-body { padding: 18px 16px; }
}
/* Catalog list grid (Jobs/Events/Produkte/News): draggable cards, image on top,
   headline + cropped teaser + meta, small action buttons below. Drag to sort,
   the ↑ button pins a card to the front (persists via sorting). */
.cat-hint { margin: 0 0 12px; display: flex; align-items: center; gap: 6px; }
.cat-hint .ico { width: 15px; height: 15px; }
.cat-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 16px; }
.cat-card { display: flex; flex-direction: column; background: var(--glass); border: 1px solid var(--glass-border); border-radius: 6px; overflow: hidden; }
.cat-card.is-dragging { opacity: .4; }
.cat-card.is-drop-before { box-shadow: -3px 0 0 var(--accent); }
.cat-card-img { position: relative; aspect-ratio: 16 / 10; overflow: hidden; background: var(--glass-strong); }
/* Absolutely fill the box so a tall/panorama source can't stretch the card — the
   container's aspect-ratio stays authoritative regardless of the image's format. */
.cat-card-img img { position: absolute; inset: 0; display: block; width: 100%; height: 100%; object-fit: cover; }
.cat-card-img.is-empty { border-bottom: 1px dashed var(--glass-border-bright); }
.cat-card-grip { position: absolute; top: 6px; left: 6px; display: flex; padding: 3px; border-radius: 6px; background: rgba(10,14,18,.55); color: #fff; cursor: grab; }
.cat-card-grip .ico { width: 16px; height: 16px; }
.cat-card-top { position: absolute; top: 6px; right: 6px; display: flex; padding: 4px; border-radius: 6px; border: 0; background: rgba(10,14,18,.55); color: #fff; cursor: pointer; }
.cat-card-top:hover { background: var(--accent); }
.cat-card-top .ico { width: 16px; height: 16px; }
.cat-card-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; padding: 12px 14px 14px; }
.cat-card-title { font-size: 15px; line-height: 1.3; }
.cat-card-meta { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.cat-card-teaser {
  margin: 0; color: var(--muted); font-size: 13px; line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.catmulti { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.cat-card-switches { display: flex; flex-wrap: wrap; gap: 6px 18px; }
.cat-card-switches .switch { font-size: 13px; }
.cat-card.is-hidden { opacity: .6; }
.cat-card.is-hidden .cat-card-img img { filter: grayscale(.7); }
.cat-card .row-actions { margin-top: auto; padding-top: 3px; flex-wrap: wrap; }
/* Visibility state of a job ad (Catalog::jobState) — the card's last line. Same
   colour language as .seo-list: emerald = fine, amber = act soon, rose = act now,
   and the text goes from --muted to --text at the point where it needs reading.
   Wraps on a narrow card, so the "+30 Tage" button drops to its own line instead
   of squeezing the sentence.
   Deliberately NO dimming of an expired card the way .is-hidden dims one — the
   card that needs an action must not be the quietest one in the grid. */
.cat-card-state { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 6px 8px; font-size: 12px; line-height: 1.45; color: var(--muted); }
/* The dot is a pseudo-element ON the text, not a flex sibling of it. As a sibling
   it ended up alone on the first line whenever the sentence was longer than the
   card: a wrapping flex container assigns items to lines by their hypothetical
   size FIRST and only shrinks within a line afterwards, so `min-width: 0` on the
   text does not save it (measured at ~234px card width, the width the grid
   actually produces at 1280px). Inside the line box it cannot separate. */
.cat-card-state-txt { flex: 0 1 auto; min-width: 0; }
.cat-card-state-txt::before {
  content: ""; display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 7px; vertical-align: middle; background: var(--muted2);
}
.cat-card-state.is-ok .cat-card-state-txt::before { background: var(--emerald); }
.cat-card-state.is-soon { color: var(--text); }
.cat-card-state.is-soon .cat-card-state-txt::before { background: var(--amber); }
.cat-card-state.is-expired { color: var(--text); }
.cat-card-state.is-expired .cat-card-state-txt::before { background: var(--rose); }
/* Never shrink the button — a squeezed "+30 Tage" would wrap inside itself. */
.cat-card-state .btn { flex: none; }
.cat-empty-filter { padding: 24px 0; }
/* Head tools: search next to the "+ …" button. The icon sits inside the field,
   so the input carries matching left padding. */
.cat-head-tools { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.cat-search { position: relative; display: flex; align-items: center; }
.cat-search .ico {
  position: absolute; left: 10px; width: 16px; height: 16px;
  color: var(--muted); pointer-events: none;
}
.cat-search .input { width: 230px; padding-left: 34px; }
.cat-search .input::-webkit-search-cancel-button { filter: invert(1); opacity: .6; cursor: pointer; }
@media (max-width: 700px) {
  .cat-head-tools { width: 100%; margin-left: 0; }
  .cat-search { flex: 1; }
  .cat-search .input { width: 100%; }
}
/* Category filter buttons in the sidebar. */
/* Category rail: one list that filters AND manages. The active row is marked by
   a bar on its left edge — drawn as an inset shadow, not a border, so switching
   rows cannot shift the text by three pixels. */
.cat-filter { display: flex; flex-direction: column; margin-bottom: 14px; }
.cat-cat { display: flex; align-items: center; border-radius: 6px; }
.cat-cat + .cat-cat { margin-top: 1px; }
.cat-cat-btn {
  flex: 1; min-width: 0; text-align: left; font: inherit; font-size: 14px;
  background: none; border: 0; color: var(--text); cursor: pointer;
  padding: 7px 6px 7px 11px; border-radius: 6px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cat-cat:hover { background: var(--glass); }
.cat-cat.is-active { background: var(--glass); box-shadow: inset 3px 0 0 var(--accent); }
.cat-cat.is-active .cat-cat-btn { color: var(--accent); font-weight: 600; }
/* The delete is dimmed, not hidden: filtering is what this list is mostly for, so
   a column of full-strength ×s would read as its purpose — but hiding them until
   hover costs discoverability on a destructive action and needs a (hover: none)
   branch for touch, where there is no hover at all. Dimmed is one rule, always
   reachable, and behaves the same on every device. */
/* A card is hovering over this row and will be moved into it on release. */
.cat-cat.is-drop { background: var(--glass-strong); box-shadow: inset 0 0 0 1px var(--accent); }
.cat-cat.is-drop .cat-cat-btn { color: var(--accent); }
.cat-cat-del { opacity: .45; transition: opacity .12s; }
.cat-cat:hover .cat-cat-del, .cat-cat-del:hover, .cat-cat-del:focus-visible { opacity: 1; }

.media-item form { padding: 0 10px 10px; }
.media-item .btn { width: 100%; }
/* "In use" badge shown instead of the delete button for referenced media. */
.media-inuse { display: flex; align-items: center; justify-content: center; gap: 6px; margin: 0 10px 10px; font-size: 12px; color: var(--text-soft, #8b95a1); }
.media-inuse .ico { width: 14px; height: 14px; }

/* Alt-text state per file. Amber while missing — this is the only screen that can
   say WHICH picture has no description; the SEO check only knows how many. Muted
   once set, so a tidy library is quiet and the gaps are what catches the eye. */
.media-alt-flag {
  align-self: flex-start; margin: 0 10px 10px; padding: 2px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 600; letter-spacing: .3px; cursor: pointer;
  border: 1px solid var(--amber); color: var(--amber); background: rgba(245, 153, 74, .10);
  font-family: inherit;
}
.media-alt-flag.is-set { border-color: var(--glass-border); color: var(--muted); background: none; }
.media-alt-flag:hover { filter: brightness(1.15); }

/* Lightbox: one dialog card — picture on the left, its metadata and the alt field
   on the right — floating over a dimmed library. NOT a full-bleed viewer: the first
   version let the image grow to the viewport and that is exactly what the old
   new-tab behaviour did wrong. A lightbox shows the picture in CONTEXT; you look at
   it, describe it, and step to the next one.
   Fixed positioning rather than <dialog>: the admin has no other dialog element and
   the backdrop wants the same glass treatment as the rest of the shell. */
.media-lb {
  /* ONE number for the dialog's height, the picture's cap and the sidebar. Written
     as a variable because a percentage inside a flex item with an indefinite height
     is not reliably resolvable — this way the cap is stated, not derived. */
  --lb-h: min(72vh, 560px);
  position: fixed; inset: 0; z-index: 200; display: flex; padding: 24px;
  align-items: center; justify-content: center; background: rgba(4, 8, 12, .82);
  backdrop-filter: blur(3px);
}
.media-lb[hidden] { display: none; }  /* an author display beats [hidden] — see CLAUDE.md, Falle 2 */
body.media-lb-open { overflow: hidden; }
.media-lb-dialog {
  display: flex; width: min(940px, 92vw); max-height: var(--lb-h); overflow: hidden;
  background: var(--bg); border: 1px solid var(--glass-border); border-radius: 10px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .5);  /* same dialog surface as .crop-dialog */
}
.media-lb-stage { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; justify-content: center; padding: 14px; background: #0b0e12; }
/* Fitted, never upscaled: a 300px logo blown up would look broken and say nothing
   true about the file. object-fit is not enough on its own — the element still has
   to be capped, or a 4000px photo lays out at 4000px and the card scrolls. */
.media-lb-img { max-width: 100%; max-height: calc(var(--lb-h) - 28px); width: auto; height: auto; object-fit: contain; border-radius: 4px; }
.media-lb-side {
  flex: 0 0 300px; max-width: 300px; overflow-y: auto;
  position: relative; padding: 16px 18px; border-left: 1px solid var(--glass-border);
}
.media-lb-close {
  position: absolute; top: 4px; right: 8px; border: 0; background: none; cursor: pointer;
  color: var(--muted); font-size: 24px; line-height: 1; padding: 2px 6px; font-family: inherit;
}
.media-lb-close:hover { color: var(--text); }
.media-lb-cap { font-size: 12px; color: var(--muted); line-height: 1.6; margin: 2px 24px 12px 0; word-break: break-word; }
.media-lb-acts { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.media-lb-note { min-height: 1em; }
/* Stacked on a narrow screen — 300px of sidebar next to a picture is nothing. */
@media (max-width: 760px) {
  .media-lb { --lb-h: 88vh; padding: 12px; }
  .media-lb-dialog { flex-direction: column; width: 100%; }
  .media-lb-stage { flex: 0 0 auto; padding: 10px; }
  .media-lb-img { max-height: 38vh; }
  .media-lb-side { flex: 1 1 auto; max-width: none; border-left: 0; border-top: 1px solid var(--glass-border); }
}

/* Native custom-arrow select styling lives above (select.input); the themed
   popup (.fselect, select.js) replaces it on all admin selects except builder
   chrome, which falls back to this native style. */

/* --- in-page tabs (tabs.js): split long admin forms like Design ------------ */
.tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 20px; border-bottom: 1px solid var(--glass-border); }
.tab-btn {
  background: transparent; border: 0; border-bottom: 2px solid transparent; margin-bottom: -1px;
  color: var(--muted); font: inherit; font-weight: 600; font-size: 14px; padding: 10px 15px;
  cursor: pointer; border-radius: 6px 6px 0 0; transition: color .15s, border-color .15s, background .15s;
}
.tab-btn:hover { color: var(--text); background: var(--glass); }
.tab-btn.is-active { color: var(--accent); border-bottom-color: var(--accent); }

/* --- account (avatar) menu ------------------------------------------------ */
.acct { position: relative; }
.acct-btn { display: inline-flex; align-items: center; gap: 8px; background: transparent; border: 0; cursor: pointer; color: var(--muted); font: inherit; padding: 4px 6px; border-radius: 6px; }
.acct-btn:hover { color: var(--text); }
.acct-avatar { width: 30px; height: 30px; border-radius: 50%; background: var(--accent-grad); color: #06121a; font-weight: 800; font-size: 12px; display: inline-flex; align-items: center; justify-content: center; letter-spacing: .5px; }
.acct-name { font-size: 14px; font-weight: 600; }
.acct-caret { opacity: .7; }
.acct-menu { position: absolute; top: calc(100% + 8px); right: 0; min-width: 200px; background: var(--bg); border: 1px solid var(--glass-border); border-radius: 6px; box-shadow: 0 18px 50px rgba(0,0,0,.5); padding: 6px; z-index: 60; }
.acct-menu[hidden] { display: none; }
/* "Abmelden" is a POST form (a GET logout is CSRF-able), so its button has to carry
   the same look as the links beside it — otherwise the browser default pill breaks
   the menu. Selector lists both so the entry stays one row in the same list. */
.acct-menu a,
.acct-menu .acct-logout button { display: block; width: 100%; text-align: left; padding: 9px 12px; border-radius: 6px; color: var(--muted); text-decoration: none; font-size: 14px; background: none; border: 0; font-family: inherit; cursor: pointer; }
.acct-menu a:hover,
.acct-menu .acct-logout button:hover { background: var(--glass); color: var(--text); }
.acct-menu .acct-logout { margin: 0; }
.acct-sep { height: 1px; background: var(--glass-border); margin: 6px 4px; }

/* --- admin flash messages ------------------------------------------------- */
.admin-flash { padding: 12px 16px; border-radius: 6px; margin-bottom: 16px; font-weight: 600; font-size: 14px; }
.admin-flash.is-ok { background: rgba(46,204,113,.12); border: 1px solid rgba(46,204,113,.5); color: #2ecc71; }
.admin-flash.is-err { background: rgba(240,90,90,.12); border: 1px solid rgba(240,90,90,.5); color: #f05a5a; }

/* Unread-enquiry badge in the admin bar. An enquiry nobody has looked at is the
   only thing up there that costs money while it waits, so it gets the one number. */
.adminbar-nav .nav-badge {
  display: inline-block; min-width: 18px; padding: 0 5px; margin-left: 6px;
  border-radius: 999px; background: var(--accent); color: #04202a;
  font-size: 11px; font-weight: 700; line-height: 18px; text-align: center;
  vertical-align: 1px;
}

/* --- enquiry inbox (/admin/anfragen) -------------------------------------- */
.sub-list { display: flex; flex-direction: column; gap: 10px; }
.sub-item { padding: 0; }
.sub-item > summary {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 14px 16px; cursor: pointer; list-style: none;
}
.sub-item > summary::-webkit-details-marker { display: none; }
.sub-item > summary::before {
  content: ''; width: 8px; height: 8px; border-radius: 50%;
  background: var(--line); flex: none;
}
/* Unread is the state that matters: an enquiry nobody has looked at. */
.sub-item.is-unread > summary::before { background: var(--accent); }
.sub-item.is-unread > summary .sub-subject { font-weight: 700; }
.sub-when { color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
.sub-subject { flex: 1; min-width: 160px; }
/* Stored but never mailed — the case the inbox exists for. */
.sub-flag {
  font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 999px;
  background: rgba(240, 173, 78, .16); color: #f0ad4e; border: 1px solid rgba(240, 173, 78, .4);
}
.sub-fields { width: 100%; border-collapse: collapse; margin: 0 16px 12px; width: calc(100% - 32px); }
.sub-fields th, .sub-fields td { text-align: left; padding: 7px 0; vertical-align: top; border-top: 1px solid var(--line); }
.sub-fields th { width: 180px; color: var(--muted); font-weight: 600; padding-right: 16px; }
.sub-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 16px 14px; }
.sub-who { color: var(--muted); font-weight: 400; }

/* Einstellungen → Schriften (settings_fonts.php). Classes only — app.css shares a
   document with the builder canvas (Falle 13). */
.font-card-head { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.font-card-head h3 { margin: 0; }
.font-sample { font-size: 20px; line-height: 1.3; color: var(--text); }
.font-license { padding: 12px 14px; border: 1px solid var(--glass-border); border-radius: 6px; background: var(--glass); }
.font-license .switch { align-items: flex-start; }

/* --- Termine (Booking admin) ---------------------------------------------- */
.btn.is-current { color: var(--accent); border-color: var(--accent); }
.bk-new > summary { cursor: pointer; }
/* Extra form fields of a calendar: label · kind · required, one row each. */
.bk-xf { display: grid; grid-template-columns: minmax(0, 1fr) 140px 130px; gap: 12px; align-items: center; }
.bk-xf + .bk-xf { margin-top: 10px; }
@media (max-width: 620px) { .bk-xf { grid-template-columns: 1fr; } }
.bk-filter { flex-wrap: wrap; gap: 10px; }
/* A day: a quiet heading, then one row card per appointment. Each card is a
   grid — time · who · what (with its state) · actions. Every row is its OWN
   grid, so the tracks must not depend on content: the action column is a fixed
   width (an `auto` track grew with two buttons and shifted that row's columns
   against its neighbours — measured). */
.bk-day { margin-bottom: 26px; }
.bk-day-head { display: flex; align-items: center; gap: 10px; margin: 0 0 10px; font-size: 15px; }
.bk-day-count { margin-left: auto; font-size: 12px; font-weight: 600; color: var(--muted); }
.bk-rows { display: grid; gap: 8px; }
.bk-row {
  display: grid; grid-template-columns: 84px minmax(0, 1.4fr) minmax(0, 1fr) 224px;
  align-items: center; gap: 6px 20px; padding: 14px 18px 14px 16px;
  background: var(--glass); border: 1px solid var(--glass-border); border-left: 3px solid var(--accent);
  border-radius: 6px;
}
.bk-row.is-pending { border-left-color: #f5994a; }
.bk-row.is-off { border-left-color: var(--glass-border); }
.bk-row.is-off > * { opacity: .55; }
.bk-row.is-off .bk-time strong { text-decoration: line-through; }
.bk-time { display: flex; flex-direction: column; line-height: 1.25; font-variant-numeric: tabular-nums; }
.bk-time strong { font-size: 20px; }
.bk-time span { font-size: 12px; color: var(--muted); }
.bk-who, .bk-what { min-width: 0; }
.bk-name { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; font-weight: 700; }
.bk-contact { display: flex; flex-wrap: wrap; gap: 2px 14px; margin-top: 2px; font-size: 13px; }
.bk-contact a { color: var(--muted); text-decoration: none; overflow-wrap: anywhere; }
.bk-contact a:hover { color: var(--accent); }
.bk-note { margin-top: 6px; padding: 6px 10px; font-size: 13px; color: var(--muted); background: var(--glass-strong); border-radius: 6px; }
.bk-service { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 10px; font-weight: 600; }
.bk-meta { margin-top: 2px; font-size: 13px; color: var(--muted); }
/* State as a tinted pill with a dot — not the outlined uppercase badge, which
   read like a second button next to "Absagen". */
.bk-pill {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600; white-space: nowrap;
  color: var(--accent); background: rgba(113, 191, 188, .13);
}
.bk-pill::before { content: ''; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.bk-pill.is-pending { color: #fcc995; background: rgba(245, 153, 74, .14); }
.bk-pill.is-cancelled, .bk-pill.is-declined { color: var(--muted); background: var(--glass-strong); }
.bk-pill.is-new { color: #0a0e12; background: var(--accent); }
.bk-pill.is-new::before { display: none; }
.bk-actions { display: flex; justify-content: flex-end; gap: 8px; }
.bk-actions form { margin: 0; }
/* Reason dialog for "Absagen"/"Ablehnen" — same fixed overlay and dialog surface
   as the media lightbox. */
.bk-dlg {
  position: fixed; inset: 0; z-index: 200; display: flex; padding: 24px;
  align-items: center; justify-content: center; background: rgba(4, 8, 12, .82); backdrop-filter: blur(3px);
}
.bk-dlg[hidden] { display: none; }  /* an author display beats [hidden], Falle 2 */
.bk-dlg-card {
  width: min(480px, 100%); padding: 22px 24px; background: var(--bg);
  border: 1px solid var(--glass-border); border-radius: 10px; box-shadow: 0 24px 60px rgba(0, 0, 0, .5);
}
.bk-dlg-card h3 { margin: 0 0 4px; }
.bk-dlg-card .row-actions { justify-content: flex-end; }
.bk-delete .input { max-width: 220px; }
@media (max-width: 900px) {
  .bk-row { grid-template-columns: 84px minmax(0, 1fr); align-items: start; }
  .bk-what, .bk-actions { grid-column: 2; }
  .bk-actions { justify-content: flex-start; }
  .bk-actions:empty { display: none; }
}
