/* ZaehlWeb — appearance following the Ceyeborg CI.
 *
 * Colors and fonts come from ceyeb.org: Exo as the house typeface, Source Code
 * Pro for numbers, midnight blue #000A32 as the base tone, magenta #E40EAB as
 * the only accent. The status colors (good/warning/bad) also come from the
 * house palette, but are deliberately kept apart from the accent: the accent
 * leads, the status color judges.
 *
 * Both themes hang on attributes, never on fixed colors inside a block —
 * :root carries light completely, the two dark blocks only override tokens.
 * Whoever adds a color has to declare it as a token, otherwise it is missing
 * in the other theme.
 */

:root{
  --nacht:#000A32; --magenta:#E40EAB; --violett:#8539F1;

  --grund:#ffffff; --feld:#F5F5F5; --feld2:#ebebef;
  --schrift:#000A32; --schrift2:#444444; --schrift3:#888888;
  --linie:#e0e0e0; --linie2:#c9c9d2;
  --akzent:#E40EAB; --akzent-weich:#fdeaf7;
  --gut:#067a4e; --gut-weich:#e6f6ee;
  --warn:#8a5a10; --warn-weich:#fdf3e0;
  --schlecht:#cf2e2e; --schlecht-weich:#fdeaea;
  --schatten:0 1px 2px rgba(0,10,50,.06), 0 4px 14px rgba(0,10,50,.05);
  --rund:10px;

  /* Chart series. Computed, not guessed: checked for color vision deficiency
     against the respective background (worst pair dE 8.5 against a target of
     8, all pairs, not just adjacent ones). The order is fixed — the color
     belongs to the series, not to its rank. */
  --reihe-app:#E40EAB;
  --reihe-bild:#0693e3;
  --reihe-video:#00d084;
  --reihe-kunde:#8539F1;
  /* Fifth series, the hand count from the app. Orange because that hue was
     the one gap left between magenta, blue, green and violet; checked the
     same way as the other four, and it costs nothing -- the worst pair is
     still app/kunde under protanopia at dE00 9.98, exactly as before. */
  --reihe-soll:#fe7802;
  --gitterlinie:#ececf0;
  --bandfarbe:#f0eff4;
  /* Clusters of the dataset panel: eight categorical hues in fixed order
     (validated: CVD worst adjacent dE 9.1 on white, 8.4 on midnight blue).
     Below 3:1 on white for three of them, so every cluster also carries its
     number — on the map and in the table. */
  --gruppe-1:#2a78d6; --gruppe-2:#eb6834; --gruppe-3:#1baf7a; --gruppe-4:#eda100;
  --gruppe-5:#e87ba4; --gruppe-6:#008300; --gruppe-7:#6250d6; --gruppe-8:#e34948;
}
@media (prefers-color-scheme:dark){
  :root:not([data-thema="hell"]){
    --grund:#000A32; --feld:#081541; --feld2:#0d1c50;
    --schrift:#ffffff; --schrift2:#c2cbe6; --schrift3:#8892b4;
    --linie:#17255c; --linie2:#22316e;
    --akzent:#FF3FC0; --akzent-weich:#2a0b34;
    --gut-weich:#062a20;
    --warn:#fcb900; --warn-weich:#2e2308;
    --schlecht:#ff6b6b; --schlecht-weich:#340f12;
    --schatten:0 1px 2px rgba(0,0,0,.5), 0 4px 16px rgba(0,0,0,.35);
    /* Own steps, not an inversion of the light ones: on midnight blue the
       permissible lightness band is L 0.48-0.67, and the light tones fell
       outside it. Same hues, re-stepped, checked again. */
    --reihe-app:#ca0096;
    --reihe-bild:#097abe;
    --reihe-video:#0a8a57;
    --reihe-kunde:#8930fd;
    --reihe-soll:#b55508;
    --gitterlinie:#152352;
    --bandfarbe:#0c1a4a;
    --gruppe-1:#3987e5; --gruppe-2:#d95926; --gruppe-3:#199e70; --gruppe-4:#c98500;
    --gruppe-5:#d55181; --gruppe-6:#008300; --gruppe-7:#9085e9; --gruppe-8:#e66767;
  }
}
:root[data-thema="dunkel"]{
  --grund:#000A32; --feld:#081541; --feld2:#0d1c50;
  --schrift:#ffffff; --schrift2:#c2cbe6; --schrift3:#8892b4;
  --linie:#17255c; --linie2:#22316e;
  --akzent:#FF3FC0; --akzent-weich:#2a0b34;
  --gut:#00d084; --gut-weich:#062a20;
  --warn:#fcb900; --warn-weich:#2e2308;
  --schlecht:#ff6b6b; --schlecht-weich:#340f12;
  --schatten:0 1px 2px rgba(0,0,0,.5), 0 4px 16px rgba(0,0,0,.35);
  --reihe-app:#ca0096;
  --reihe-bild:#097abe;
  --reihe-video:#0a8a57;
  --reihe-kunde:#8930fd;
  --reihe-soll:#b55508;
  --gitterlinie:#152352;
  --bandfarbe:#0c1a4a;
    --gruppe-1:#3987e5; --gruppe-2:#d95926; --gruppe-3:#199e70; --gruppe-4:#c98500;
    --gruppe-5:#d55181; --gruppe-6:#008300; --gruppe-7:#9085e9; --gruppe-8:#e66767;
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--grund); color:var(--schrift);
  font:400 15px/1.6 Exo,"Segoe UI",-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
  -webkit-font-smoothing:antialiased;
}
.zahl,.z{font-family:"Source Code Pro",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-variant-numeric:tabular-nums}
a{color:var(--akzent); text-decoration:none}
a:hover{text-decoration:underline}
:focus-visible{outline:2px solid var(--akzent); outline-offset:2px; border-radius:4px}

/* ---------- Header ---------- */
.kopf{position:sticky; top:0; z-index:20; background:var(--grund);
  border-bottom:1px solid var(--linie); padding:10px 16px;
  display:flex; align-items:center; gap:14px; flex-wrap:wrap}
.kopf .marke{display:flex; align-items:center; gap:10px; min-width:0}
.kopf .marke .marke-logo{height:26px}
.kopf .wo{font-weight:600; letter-spacing:-.01em; white-space:nowrap}
.kopf .wo span{color:var(--schrift3); font-weight:400}
.kopf .luft{flex:1 1 auto}
.kopf .wer{color:var(--schrift3); font-size:13px; white-space:nowrap}
/* Theme, language, user and sign out. On a wide screen the wrapper does not
   exist for the layout; on a phone it folds behind the menu button
   (kopfmenue.js), so the header stays one line instead of three. */
.kopfmenue{display:contents}
/* .kopf in front: .knopf comes further down and would show it again. */
.kopf .menueknopf{display:none}
@media (max-width:700px){
  .kopf{padding:7px 12px; gap:10px}
  .kopf .wo{display:none}
  .kopf .marke .marke-logo{width:85px; height:22px}
  .kopf .reiter a{padding:5px 9px}
  .kopf.faltbar .menueknopf{display:inline-block; width:38px; padding:5px 0;
    text-align:center; font-size:15px; line-height:1.2}
  .kopf.faltbar .kopfmenue{display:none}
  .kopf.faltbar.offen .kopfmenue{display:flex; flex:1 0 100%; flex-wrap:wrap;
    align-items:center; justify-content:flex-end; gap:10px; padding:4px 0 2px}
}

/* ---------- Building blocks ---------- */
.huelle{padding:16px; max-width:1400px; margin:0 auto}
.knopf{display:inline-block; background:var(--feld); color:var(--schrift);
  border:1px solid var(--linie2); border-radius:99px; padding:5px 13px;
  font-size:13.5px; cursor:pointer; white-space:nowrap}
.knopf:hover{border-color:var(--schrift3); text-decoration:none}
.knopf[aria-pressed="true"]{background:var(--akzent); border-color:var(--akzent);
  color:#fff; font-weight:500}
.knopf.stark{background:var(--akzent); border-color:var(--akzent); color:#fff;
  font-weight:600; padding:9px 18px; font-size:15px}

.kacheln{display:grid; gap:10px; margin:0 0 16px;
  grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}
.kachel{background:var(--feld); border:1px solid var(--linie); border-radius:var(--rund);
  padding:11px 13px}
.kachel b{display:block; font:600 26px/1.15 "Source Code Pro",monospace;
  font-variant-numeric:tabular-nums; letter-spacing:-.02em}
.kachel span{display:block; color:var(--schrift3); font-size:11.5px;
  text-transform:uppercase; letter-spacing:.06em; margin-top:3px}
.kachel em{font-style:normal; color:var(--schrift3); font-size:11.5px}

.werkzeug{display:flex; gap:8px; flex-wrap:wrap; align-items:center; margin:0 0 14px}
.werkzeug input[type=search]{flex:1 1 190px; min-width:0; background:var(--feld);
  color:var(--schrift); border:1px solid var(--linie2); border-radius:99px;
  padding:7px 15px; font:inherit; font-size:14px}
.werkzeug .gruppe{display:flex; gap:6px; flex-wrap:wrap}

.perle{display:inline-block; border-radius:99px; padding:2px 9px; font-size:12px;
  font-weight:500; border:1px solid transparent; white-space:nowrap}
.perle.gut{background:var(--gut-weich); color:var(--gut); border-color:var(--gut)}
.perle.schlecht{background:var(--schlecht-weich); color:var(--schlecht); border-color:var(--schlecht)}
.perle.warn{background:var(--warn-weich); color:var(--warn); border-color:var(--warn)}
.perle.ruhig{background:var(--feld2); color:var(--schrift2); border-color:var(--linie2)}
/* The app version a trained model ships in: a small flag with a notched tail. */
.release-ribbon{display:inline-block; background:var(--akzent); color:#fff; font-size:12px;
  font-weight:600; line-height:1.5; padding:1px 14px 1px 8px; margin-left:4px; white-space:nowrap;
  text-transform:none; letter-spacing:normal;
  border-radius:3px 0 0 3px; clip-path:polygon(0 0, 100% 0, calc(100% - 7px) 50%, 100% 100%, 0 100%)}
.neu-export{display:flex; gap:10px; align-items:center; flex-wrap:wrap; margin:0 0 8px}
.neu-export .knopf:disabled{opacity:.45; cursor:default}
.abw.gut{color:var(--gut)} .abw.schlecht{color:var(--schlecht)}

/* ---------- List: cards (phone) ---------- */
.karten{display:grid; gap:10px}
.karte{display:block; background:var(--grund); border:1px solid var(--linie);
  border-left:3px solid var(--linie2); border-radius:var(--rund);
  padding:12px 14px; color:inherit; box-shadow:var(--schatten)}
.karte:hover{border-color:var(--schrift3); text-decoration:none}
.karte.raus{border-left-color:var(--schlecht)}
.karte.drin{border-left-color:var(--gut)}
.karte .oben{display:flex; justify-content:space-between; align-items:baseline; gap:10px}
.karte .titel{font-weight:600; font-size:14.5px; word-break:break-all}
.karte .unten{display:flex; gap:14px; flex-wrap:wrap; margin-top:9px;
  color:var(--schrift2); font-size:13px}
.karte .unten b{font-family:"Source Code Pro",monospace; font-variant-numeric:tabular-nums;
  color:var(--schrift); font-weight:600}
.karte .unten i{font-style:normal; color:var(--schrift3); font-size:11px;
  text-transform:uppercase; letter-spacing:.05em; display:block}

/* ---------- List: table (desktop) ---------- */
.rahmen{display:none}
table{border-collapse:collapse; width:100%; font-size:13.5px}
th,td{padding:7px 10px; text-align:left; border-bottom:1px solid var(--linie);
  white-space:nowrap}
thead th{position:sticky; top:0; background:var(--grund); z-index:5;
  font-size:11px; text-transform:uppercase; letter-spacing:.06em;
  color:var(--schrift3); font-weight:600}
td.n{text-align:right; font-family:"Source Code Pro",monospace;
  font-variant-numeric:tabular-nums}
tbody tr:hover{background:var(--feld)}

@media (min-width:860px){
  .karten{display:none}
  .rahmen{display:block; overflow-x:auto; border:1px solid var(--linie);
    border-radius:var(--rund); background:var(--grund)}
  .huelle{padding:20px 24px}
}

/* ---------- Capture ---------- */
.zurueck{display:inline-block; margin:0 0 12px; color:var(--schrift3); font-size:13.5px}
h1{font-size:21px; margin:0 0 4px; letter-spacing:-.02em; text-wrap:balance;
  word-break:break-all}
h2{font-size:12px; text-transform:uppercase; letter-spacing:.07em;
  color:var(--schrift3); margin:24px 0 9px; font-weight:600}
.unterzeile{color:var(--schrift3); font-size:13.5px; margin:0 0 6px}
.paare{display:grid; gap:1px; background:var(--linie); border:1px solid var(--linie);
  border-radius:var(--rund); overflow:hidden}
.paar{display:flex; justify-content:space-between; gap:16px; align-items:baseline;
  background:var(--grund); padding:9px 13px}
.paar dt{color:var(--schrift2); font-size:13.5px}
.paar dd{margin:0; font-family:"Source Code Pro",monospace;
  font-variant-numeric:tabular-nums; font-weight:600; text-align:right}
.spalten{display:grid; gap:18px}
@media (min-width:860px){.spalten{grid-template-columns:1fr 1fr}}

/* ---------- Login ---------- */
.tor{min-height:100dvh; display:grid; place-items:center; padding:24px}
.tor form{width:100%; max-width:340px; display:grid; gap:13px}
.tor .marke-logo{height:34px}
.tor h1{font-size:17px; text-align:center; margin:0 0 2px}
.tor p.sub{text-align:center; color:var(--schrift3); font-size:13.5px; margin:0 0 8px}
.tor label{display:grid; gap:5px; font-size:13px; color:var(--schrift2)}
.tor input{background:var(--feld); color:var(--schrift); border:1px solid var(--linie2);
  border-radius:8px; padding:10px 13px; font:inherit; width:100%}
.tor .fehler{background:var(--schlecht-weich); color:var(--schlecht);
  border:1px solid var(--schlecht); border-radius:8px; padding:9px 12px; font-size:13.5px}
.leer{color:var(--schrift3); padding:36px 12px; text-align:center}

@media (prefers-reduced-motion:reduce){*{animation:none!important; transition:none!important}}

/* Language switcher: two compact buttons that do not bulk up the header. */
/* Flex on its own, not inherited: .gruppe carries display:flex only below
   .werkzeug — which does not exist in the header or on the login page, so the
   switcher's two forms used to sit one below the other. */
.sprache{display:flex; gap:4px; flex-wrap:nowrap; align-items:center}
.knopf.mini{padding:3px 9px; font-size:12px; letter-spacing:.04em; font-weight:600}


/* ---------- Overview chart ---------- */
.uebersicht-rahmen{margin:0 0 16px; padding:12px 14px 6px; background:var(--grund);
  border:1px solid var(--linie); border-radius:var(--rund); box-shadow:var(--schatten)}
.uebersicht-rahmen figcaption{display:flex; flex-wrap:wrap; gap:4px 12px;
  align-items:baseline; margin:0 0 8px}
.uebersicht-rahmen figcaption b{font-size:14px; font-weight:600}
.uebersicht-rahmen figcaption span{color:var(--schrift3); font-size:12.5px}

.legende{display:flex; flex-wrap:wrap; gap:6px 16px; margin:0 0 8px; padding:0;
  list-style:none; font-size:12.5px; color:var(--schrift2)}
.legende li{display:flex; align-items:center; gap:6px}
.legende i{width:11px; height:11px; border-radius:3px; display:block; flex:none}
.legende i.trendmarke{width:18px; height:0; border-top:2px dashed var(--schrift3); border-radius:0}
.legende i.trendmarke.app_bild{border-top-color:var(--reihe-app)}
.legende i.trendmarke.video_bild{border-top:2px dotted var(--reihe-video)}

/* The chart is wider than a phone. It scrolls inside its own box so that the
   page itself never scrolls sideways. */
.rollfeld{overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch;
  padding-bottom:4px}
/* `.ans-ende` carries no style of its own — it tells the Javascript to put
   the box at its right-hand end, at the newest recording (diagramm.js).
   Doing it here instead, by laying the box out right to left, looked
   tempting and was wrong: a chart NARROWER than the box was then pushed
   against its right edge and left a gap on the left, and the writing
   direction also takes `-webkit-overflow-scrolling:touch` with it, which is
   where the scrolling fell apart. The layout stays left to right; only the
   scroll position moves. */
.rollfeld svg{display:block}
/* The y-axis stays in view while the chart scrolls: its labels are a strip of
   their own (diagramm.zeichnen) that sticks to the left edge of the box. It
   needs the background colour, otherwise the bars scroll through under the
   numbers. Sticky rather than outside the box, so that the box, its scroll
   position and its own scroll bar (diagramm.js) stay exactly as they were. */
.rollfeld .diagrammzeile{display:flex; width:max-content}
.rollfeld svg.achse-fest{flex:none; position:sticky; left:0; z-index:1;
  background:var(--grund)}
/* The chart carries its own scroll bar (diagramm.js). It has to: the bar is
   the one hint that there is more chart to the left, and now that the box
   starts at its right-hand end it is the hint that decides whether the older
   recordings are found at all. The system's bar cannot do that job — it
   fades out a couple of seconds after the page settles, on iOS it is an
   overlay that can neither be styled nor kept, and giving it an appearance
   of its own does not hold it in place in every engine either. A bar that
   is part of the page does not fade anywhere.
   `.hat-balken` is set by the Javascript, so the system's bar is hidden only
   once ours has actually been built — without Javascript the box keeps the
   ordinary one and scrolls as it always did. */
.rollfeld.hat-balken{scrollbar-width:none}
.rollfeld.hat-balken::-webkit-scrollbar{display:none}
/* Shown while the pointer is over the chart, and for a moment after a
   scroll. Its space is reserved the whole time — only the opacity moves —
   so the chart never jumps when it comes and goes. */
.rollbalken{position:relative; height:10px; margin:2px 0 6px; border-radius:5px;
  background:var(--feld); border:1px solid var(--linie); touch-action:none;
  opacity:0; transition:opacity .16s ease}
.rollbalken.sichtbar{opacity:1}
@media (prefers-reduced-motion:reduce){ .rollbalken{transition:none} }
.rollbalken .griff{position:absolute; top:0; bottom:0; left:0; border-radius:5px;
  background:var(--schrift3); cursor:grab}
.rollbalken .griff:hover{background:var(--schrift2)}
.rollbalken .griff.zieht{background:var(--schrift2); cursor:grabbing}

svg.uebersicht .gitter line{stroke:var(--gitterlinie); stroke-width:1}
svg.uebersicht .band{fill:var(--bandfarbe)}
svg.uebersicht .null{stroke:var(--linie2); stroke-width:1}
/* Where the lower axis changes gear. Dashed and a shade stronger than the
   grid: a line that says "the scale is different above me" must not look
   like one more reference line. */
svg.uebersicht .knick line{stroke:var(--linie2); stroke-width:1; stroke-dasharray:3 3}
svg.uebersicht .trend{stroke:var(--schrift3); stroke-width:2; stroke-dasharray:5 4}
/* Two trend lines, told apart by the colour of the count that is compared
   AND by the dash, so colour is never the only difference. */
svg.uebersicht .trend.app_bild{stroke:var(--reihe-app)}
svg.uebersicht .trend.video_bild{stroke:var(--reihe-video); stroke-dasharray:1.5 3.5;
  stroke-linecap:round}
svg.uebersicht .trend.ungesichert{opacity:.45}
/* Where a new app version begins. Ink, not a series colour: it is an event on
   the time axis, not a value, and must not read as a sixth series. The text
   gets a halo in the background colour so a bar behind it cannot swallow it. */
svg.uebersicht .version line{stroke:var(--schrift); stroke-width:1.5; stroke-dasharray:6 3}
svg.uebersicht .version text{fill:var(--schrift); font-size:11px; font-weight:600;
  paint-order:stroke; stroke:var(--grund); stroke-width:3px; stroke-linejoin:round}
svg.uebersicht .punkt{stroke:var(--grund); stroke-width:2}   /* 2px ring so that
   overlapping points stay apart */
svg.uebersicht .ueber{stroke:var(--grund); stroke-width:1.5}
svg.uebersicht .saeule:hover{filter:brightness(1.12)}
svg.uebersicht text.achse{fill:var(--schrift3); font-size:10.5px;
  font-family:"Source Code Pro",ui-monospace,monospace; font-variant-numeric:tabular-nums}
svg.uebersicht text.achse.schwach{opacity:.72; font-size:9.5px}
svg.uebersicht text.feldname{fill:var(--schrift2); font-size:11px; font-weight:500}

/* Hover tracks: invisible until the pointer rests on them. */
svg.uebersicht .spur{fill:transparent; cursor:crosshair}
svg.uebersicht .spur:hover,
svg.uebersicht .spur.aktiv{fill:color-mix(in srgb, var(--akzent) 8%, transparent)}
svg.uebersicht .spur:focus-visible{outline:2px solid var(--akzent); outline-offset:-2px}

/* Tooltip box — sits beside the hovered column, never over it, and stays
   inside the frame. Placed by diagramm.js. */
.uebersicht-rahmen{position:relative}
.hinweis{position:absolute; z-index:30; pointer-events:none;
  background:var(--grund); color:var(--schrift);
  border:1px solid var(--linie2); border-radius:9px; padding:9px 11px;
  box-shadow:0 4px 18px rgba(0,10,50,.18); font-size:12.5px; min-width:196px;
  max-width:280px}
.hinweis[hidden]{display:none}
.hinweis .ht{font-weight:600; font-size:13px; margin:0 0 1px; word-break:break-all}
.hinweis .hu{color:var(--schrift3); font-size:11.5px; margin:0 0 7px}
.hinweis .hs{color:var(--schrift3); font-size:10.5px; text-transform:uppercase;
  letter-spacing:.06em; margin:7px 0 3px; border-top:1px solid var(--linie);
  padding-top:5px}
.hinweis .hz{display:flex; align-items:center; gap:7px; padding:1.5px 0}
.hinweis .hz i{width:9px; height:9px; border-radius:2px; flex:none}
.hinweis .hz i.ohne{background:transparent}
.hinweis .hz span{color:var(--schrift2); flex:1 1 auto}
.hinweis .hz b{font-family:"Source Code Pro",ui-monospace,monospace;
  font-variant-numeric:tabular-nums; font-weight:600; white-space:nowrap}

/* Brief flash when new captures have arrived in the background. */
.uebersicht-rahmen.frisch{animation:frisch 1.2s ease-out}
@keyframes frisch{from{border-color:var(--akzent); box-shadow:0 0 0 3px var(--akzent-weich)}
  to{border-color:var(--linie); box-shadow:var(--schatten)}}
@media (prefers-reduced-motion:reduce){.uebersicht-rahmen.frisch{animation:none}}

/* Sequential number — the same one as on the chart's x axis. It does not
   refer to the capture itself but to its position in the current selection:
   filtering restarts the count, and the chart counts along. */
.nr{font-family:"Source Code Pro",ui-monospace,monospace; font-variant-numeric:tabular-nums;
  color:var(--schrift3); font-weight:600}
b.nr{display:inline-block; min-width:1.6em; padding:0 5px; margin-right:4px;
  background:var(--feld2); border-radius:5px; text-align:center; font-size:11.5px}
b.nr.gross{font-size:15px; padding:1px 8px; vertical-align:middle}
td.nr{color:var(--schrift3)}

/* ---------- Stage: panorama, overlay, video ---------- */
.buehne{margin:0 0 20px}
.reiter{display:flex; gap:6px; align-items:center; flex-wrap:wrap; margin:0 0 10px}
.reiter .luft{flex:1 1 auto}
.schalter{display:flex; align-items:center; gap:6px; font-size:13px;
  color:var(--schrift2); cursor:pointer}

/* The panorama is up to 31426 px tall — it scrolls inside its own box so
   that the page itself does not become endless. */
.tafel{max-height:70vh; overflow:auto; background:var(--feld);
  border:1px solid var(--linie); border-radius:var(--rund);
  -webkit-overflow-scrolling:touch}
/* The markers are positioned against the image, not the scrolling box. */
.tafel-innen{position:relative}
.tafel img{display:block; width:100%; height:auto}
.tafel .marker{position:absolute; inset:0; pointer-events:none}
.tafel .marker i{position:absolute; width:100%; left:0!important;
  border-top:1px solid var(--akzent); opacity:.75}
.tafel .marker i::after{content:""; position:absolute; right:4px; top:-3px;
  width:6px; height:6px; border-radius:50%; background:var(--akzent)}
.buehne .fach[hidden]{display:none}
.buehne video{width:100%; max-height:70vh; background:#000;
  border:1px solid var(--linie); border-radius:var(--rund); display:block}

/* ---------- Narrow form pages ---------- */
.huelle.schmal{max-width:440px}
.formular{display:grid; gap:13px; margin:14px 0 0}
.formular label{display:grid; gap:5px; font-size:13px; color:var(--schrift2)}
.formular input{background:var(--feld); color:var(--schrift);
  border:1px solid var(--linie2); border-radius:8px; padding:10px 13px;
  font:inherit; width:100%}
.formular .fehler{background:var(--schlecht-weich); color:var(--schlecht);
  border:1px solid var(--schlecht); border-radius:8px; padding:9px 12px; font-size:13.5px}
.hinweistext{color:var(--schrift3); font-size:12.5px; margin:0}
a.wer{color:var(--schrift3); text-decoration:none; border-bottom:1px dotted var(--linie2)}
a.wer:hover{color:var(--akzent); text-decoration:none}

/* Band per capture: just visible enough to hold the group together, and faint
   enough not to distort the series colors. */
svg.uebersicht .streifen rect{fill:var(--schrift); opacity:.035}

/* Language switcher on the login page: top right, as in the header of the
   signed-in pages — the same corner, so nobody has to look for it twice. It
   is layered over the login box rather than placed before it: the input is
   meant to stay centered. */
.tor{position:relative}
.tor-sprache{position:absolute; top:14px; right:16px; z-index:5}

/* ---------- Loading state of the stage ----------
   As long as image or video are not there, the box is kept empty and a word
   is shown. The markers stay invisible: over an empty box they would all
   stack on top of each other at the upper edge and look like a bug. */
/* While loading, the box stays flat. Otherwise the image reserves its full
   height — up to 31426 px — via width/height, and the centered loading word
   would sit 15000 px below the visible window. All one would see is an empty
   gray area, and the page would be taken for broken. */
.tafel-innen.laedt{min-height:260px; max-height:260px; overflow:hidden}
.videofeld.laedt{min-height:260px}
.tafel-innen.laedt img,
.videofeld.laedt video{opacity:0}
.tafel-innen.laedt .marker{visibility:hidden}
.tafel-innen .ladewort,
.videofeld .ladewort,
.tafel-innen .ladefehler,
.videofeld .ladefehler{position:absolute; inset:0; margin:0;
  display:flex; align-items:center; justify-content:center; gap:10px;
  color:var(--schrift3); font-size:13.5px; text-align:center; padding:20px}
.tafel-innen:not(.laedt) .ladewort,
.videofeld:not(.laedt) .ladewort{display:none}
.ladefehler[hidden]{display:none!important}
.ladefehler{color:var(--schlecht)}
.videofeld{position:relative; min-height:220px}

/* The dot left of the word spins so that it stays recognizable that
   something is happening — with a 25 MB panorama it takes a while. */
.ladewort::before{content:""; width:14px; height:14px; flex:none;
  border:2px solid var(--linie2); border-top-color:var(--akzent);
  border-radius:50%; animation:dreht .8s linear infinite}
@keyframes dreht{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion:reduce){
  .ladewort::before{animation:none; border-top-color:var(--linie2)}
}
/* Image and video fade in softly as soon as they are there. */
.tafel-innen img,.videofeld video{transition:opacity .25s ease}
@media (prefers-reduced-motion:reduce){
  .tafel-innen img,.videofeld video{transition:none}
}

/* Which collection is being looked at. It stands above everything it
   changes, and reads as a statement of place rather than as one more filter:
   the count belongs to the name, and a collection that cannot be written to
   says so once, here, instead of on every control it hides. */
.satzwahl{margin:0 0 14px}
.satzwahl .knopf b{margin-left:7px; color:var(--schrift3); font-weight:600}
.satzwahl .knopf[aria-pressed="true"] b{color:inherit; opacity:.75}
.nurlesend{display:inline-flex; align-items:center; gap:6px; margin:0;
  font-size:12px; color:var(--schrift3)}
.nurlesend::before{content:""; width:6px; height:6px; border-radius:50%;
  background:var(--schrift3); flex:none}

/* Taking a recording out of the view. Deliberately plain and set apart from
   the numbers above it: it is an administrative act, not part of reading the
   measurement. */
.verstecken{display:flex; flex-wrap:wrap; align-items:center; gap:8px;
  margin:18px 0 0; padding:12px 14px; border:1px dashed var(--linie);
  border-radius:var(--rund); background:var(--feld2)}
.verstecken label{font-size:12px; font-weight:600; letter-spacing:.04em;
  text-transform:uppercase; color:var(--schrift2)}
.verstecken input{flex:1 1 180px; min-width:0}
/* .hinweis is also the chart's floating tooltip; here it is plain text in
   the flow, not a box laid over the input field. */
.verstecken .hinweis{position:static; z-index:auto; pointer-events:auto;
  background:none; border:0; border-radius:0; padding:0; box-shadow:none;
  min-width:0; max-width:none; flex:1 1 100%; font-size:12px; color:var(--schrift3)}

/* The direction pair. The arrow carries the meaning, the word explains it —
   and the word steps aside on a narrow screen, where the toolbar has to fit
   a filter row, a view switch and the sorting keys as well. */
.richtungswahl .knopf{gap:5px}
@media (max-width:640px){
  .richtungswahl .wort{position:absolute; width:1px; height:1px; padding:0;
    margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap}
}

/* ---------- Tile view ---------- */
.kachelraster{display:grid; gap:14px; margin:0 0 18px;
  grid-template-columns:repeat(auto-fill,minmax(210px,1fr))}
.bildkachel{display:block; color:inherit; background:var(--grund);
  border:1px solid var(--linie); border-left:3px solid var(--linie2);
  border-radius:var(--rund); overflow:hidden; box-shadow:var(--schatten)}
.bildkachel:hover{border-color:var(--schrift3); text-decoration:none}
.bildkachel.raus{border-left-color:var(--schlecht)}
.bildkachel.drin{border-left-color:var(--gut)}
.bildfeld{position:relative; background:var(--feld2); aspect-ratio:3/2}
.bildfeld img{display:block; width:100%; height:100%; object-fit:cover}
/* A recording without a panorama keeps the shape of a tile — an empty
   frame reads as "nothing was stitched here", a broken image reads as a
   fault in the application. */
.ohnebild{position:absolute; inset:0; display:flex; align-items:center;
  justify-content:center; font-size:12px; color:var(--schrift3);
  background:repeating-linear-gradient(135deg,transparent,transparent 7px,
    var(--linie) 7px,var(--linie) 8px)}
/* Number and deviation sit on the image — they belong to the capture, not
   to the caption, and are readable without a change of gaze. */
.bildfeld .nr{position:absolute; top:7px; left:7px; margin:0;
  background:var(--grund); box-shadow:var(--schatten)}
/* Layer polygons of this recording are in CVAT. Violet: not a judgement
   (that is the status colours' job) and not the accent (that leads). */
.cvatmarke{display:inline-block; font-size:10.5px; font-weight:700; letter-spacing:.06em;
  padding:1px 6px; border-radius:99px; color:#fff; background:var(--violett);
  vertical-align:middle; line-height:1.5}
/* CVAT, pallet and distance in one row bottom left, up to the ribbon's
   corner; a long pallet name is cut rather than wrapped over the image. */
.bildfeld .aufbild-marken{position:absolute; bottom:7px; left:7px; right:52px;
  display:flex; gap:4px; align-items:center; min-width:0}
.angabemarke{font-size:10.5px; font-weight:600; padding:1px 6px; border-radius:99px;
  line-height:1.5; background:var(--grund); color:var(--schrift); box-shadow:var(--schatten);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0}
.angabemarke.korrekt{background:var(--gut-weich); color:var(--gut)}
/* A pallet in its own colour (models.Palette.farbe), so the tiles of one
   pallet are found at a glance. */
.angabemarke.farbe{color:#fff; text-shadow:0 0 2px rgba(0,0,0,.35)}
.angabemarke.farbe-1{background:var(--gruppe-1)} .angabemarke.farbe-2{background:var(--gruppe-2)}
.angabemarke.farbe-3{background:var(--gruppe-3)} .angabemarke.farbe-4{background:var(--gruppe-4)}
.angabemarke.farbe-5{background:var(--gruppe-5)} .angabemarke.farbe-6{background:var(--gruppe-6)}
.angabemarke.farbe-7{background:var(--gruppe-7)} .angabemarke.farbe-8{background:var(--gruppe-8)}
.angabemarke.weit{background:var(--schlecht-weich); color:var(--schlecht)}
.aufbild-marken .cvatmarke{flex:none}
.cvatmarke.gross{font-size:12.5px; padding:4px 11px; letter-spacing:.02em}
.cvatmarke.gross[href]:hover{text-decoration:none; filter:brightness(1.1)}
.cvatmarke.gross.laeuft{background:var(--warn-weich); color:var(--warn)}
.cvatmarke[hidden]{display:none}
.bildfeld .perle{position:absolute; top:7px; right:7px;
  background:var(--grund); box-shadow:var(--schatten)}
/* "Too far" from the app: a ribbon across the bottom right corner -- the
   one corner of the preview nothing else sits in. */
.bildfeld, .blick-vorschau{overflow:hidden}
.zuweit-band{position:absolute; bottom:16px; right:-30px; width:120px; z-index:1;
  transform:rotate(-45deg); text-align:center; font-size:10.5px; font-weight:700;
  letter-spacing:.06em; text-transform:uppercase; line-height:1.9;
  color:var(--grund); background:var(--schlecht); box-shadow:var(--schatten)}
.untertitel{padding:9px 11px 11px; display:grid; gap:3px}
.untertitel .titel{font-weight:600; font-size:13px; word-break:break-all}
/* Up to four numbers on a tile that is 150px wide on a phone. Flex with a
   wrap keeps them readable instead of running off the edge, and the row gap
   is tighter than the column gap so a wrapped line still reads as one
   group. */
.untertitel .zahlen{display:flex; flex-wrap:wrap; gap:1px 9px;
  color:var(--schrift3); font-size:12px; line-height:1.5}
.untertitel .zahlen b{font-family:"Source Code Pro",ui-monospace,monospace;
  font-variant-numeric:tabular-nums; color:var(--schrift); font-weight:600}
.werkzeug .luft{flex:1 1 auto}
@media (max-width:859px){ .kachelraster{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))} }

/* Tiles grouped by recording day. A day is a shift at the machine: what was
   recorded on one day shares its conditions, and in one uninterrupted grid
   the step from one day to the next passed unnoticed. The heading is a
   divider, not a decoration — it carries the date and how many recordings
   belong to it. */
.tage{display:grid; gap:24px}
.tagblock{display:grid; gap:10px}
.tagblock .kachelraster{margin:0}
/* Where a new app version begins: across the whole grid, the same dashed ink
   as the line in the chart, so the two read as the same mark. */
.versionslinie{grid-column:1/-1; display:flex; align-items:center; gap:10px;
  color:var(--schrift); font-size:12.5px; font-weight:600; margin:2px 0}
.versionslinie::before, .versionslinie::after{content:""; flex:1 1 auto;
  border-top:1.5px dashed var(--schrift)}
.versionslinie span{flex:none; white-space:nowrap}
.tagkopf{display:flex; align-items:baseline; gap:12px; margin:0;
  font-size:12px; font-weight:600; letter-spacing:.06em; text-transform:uppercase;
  color:var(--schrift2)}
/* The rule fills whatever the date and the count leave over. */
.tagkopf::after{content:""; flex:1 1 auto; height:1px; background:var(--linie)}
.tagkopf .datum{font-variant-numeric:tabular-nums}
.tagkopf .anzahl{order:3; font-weight:500; text-transform:none; letter-spacing:0;
  color:var(--schrift3); white-space:nowrap}

/* ---------- Tiles with a detail panel beside them ----------
   The panel only appears once a recording is picked, so the tiles get the
   full width until then. Below 1100 px there is no room for two columns:
   the panel then sits above the tiles as a block. */
.werkfeld{display:grid; gap:16px; align-items:start}
@media (min-width:1100px){
  .werkfeld:has(.seitenblick:not([hidden])){grid-template-columns:1fr 340px}
  .werkfeld .seitenblick{position:sticky; top:60px; max-height:calc(100vh - 76px);
    overflow:auto}
}
.seitenblick{position:relative; background:var(--grund); border:1px solid var(--linie);
  border-radius:var(--rund); padding:14px; box-shadow:var(--schatten)}
.seitenblick[hidden]{display:none}
.seitenblick .knopf.zu{position:absolute; top:8px; right:8px; z-index:2;
  width:26px; height:26px; padding:0; line-height:1; font-size:15px}
.blick-leer{color:var(--schrift3); text-align:center; padding:28px 8px; font-size:13.5px}
.blick-vorschau{display:block; position:relative; border-radius:8px; overflow:hidden; margin:0 0 10px;
  background:var(--feld2); aspect-ratio:3/2}
.blick-vorschau img{display:block; width:100%; height:100%; object-fit:cover}
.blick-kopf{display:flex; align-items:center; gap:2px; margin:0 0 2px}
.blick-name{font-weight:600; font-size:13.5px; word-break:break-all}
.blick-perlen{display:flex; gap:6px; flex-wrap:wrap; margin:8px 0 0}
.seitenblick h2{margin:16px 0 6px}
.seitenblick .paar{padding:6px 10px}
.seitenblick .knopf.stark{width:100%; text-align:center; margin-top:14px}
/* The second button in the panel — into the counting surface — sits
   under the first and is the same width, so the two read as one pair. */
.seitenblick .knopf{width:100%; text-align:center}
/* Without room beside the tiles the panel used to drop below the last of
   them: on a phone a tap on a tile seemed to do nothing, because what it
   opened was a few screens further down. Below 1100 px it is a sheet over
   the bottom of the screen instead, in sight the moment a tile is tapped.
   The close button stays at the top of it while the sheet scrolls. */
@media (max-width:1099px){
  .werkfeld .seitenblick{position:fixed; left:0; right:0; bottom:0; z-index:40;
    width:min(560px,100%); margin:0 auto; max-height:82dvh; overflow:auto;
    overscroll-behavior:contain; border-bottom:0;
    border-radius:var(--rund) var(--rund) 0 0;
    padding:14px 16px calc(16px + env(safe-area-inset-bottom));
    box-shadow:0 -12px 40px rgba(0,0,0,.28)}
  .werkfeld .seitenblick .knopf.zu{position:sticky; top:0; right:auto; float:right;
    margin:-4px -6px 0 8px; width:34px; height:34px; font-size:18px;
    background:var(--grund)}
}
/* The chosen tile keeps a visible ring, so it is clear what the panel shows. */
.bildkachel.gewaehlt{outline:2px solid var(--akzent); outline-offset:-1px}

/* Theme switch: one button, three states. The glyph is written by thema.js. */
.knopf.thema{width:30px; padding:3px 0; text-align:center; font-size:14px; line-height:1.3}
.knopf.thema[data-stand="hell"]{background:var(--warn-weich); border-color:var(--warn); color:var(--warn)}
.knopf.thema[data-stand="dunkel"]{background:var(--akzent-weich); border-color:var(--akzent); color:var(--akzent)}
.tor-sprache{gap:6px; align-items:center}

/* ---------- Counting surface ----------
 * The counting tool (Tool/zaehltool.py) is served in a frame under the
 * header. So the page must not scroll itself: the frame gets whatever height
 * is left over, and the scrolling happens inside it, where the image stage
 * expects it. A fixed calc(100vh - 47px) would be wrong the moment the header
 * wraps onto a second line, so this is a flex column instead — the header
 * keeps its natural height, the stage takes the rest.
 *
 * The header is static here rather than sticky: in a column that does not
 * scroll there is nothing for it to stick to, and position:sticky would only
 * cost it a stacking context.
 */
body.zaehlseite{display:flex; flex-direction:column; height:100dvh; overflow:hidden}
body.zaehlseite .kopf{position:static; flex:0 0 auto}
.zaehlleiste{flex:0 0 auto; display:flex; align-items:baseline; gap:12px;
  padding:6px 16px; border-bottom:1px solid var(--linie); min-width:0}
.zaehlleiste .zurueck{margin:0}
.zaehlmeldung{font-size:13.5px; font-weight:600; min-width:0}
.zaehlmeldung:empty{display:none}
.zaehlmeldung.gut{color:var(--gut)}
.zaehlmeldung.warten{color:var(--warn)}
.zaehlmeldung.schlecht{color:var(--schlecht)}
.zaehlname{color:var(--schrift3); font-size:13.5px; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap}
.zaehlbuehne{flex:1 1 auto; min-height:0}
.zaehlbuehne iframe{display:block; width:100%; height:100%; border:0}
/* On a phone every line above the frame is a line less of the stack: the
   back link and the name share one line and the name gives way first. */
@media (max-width:700px){
  .zaehlleiste{padding:5px 12px; gap:8px; font-size:13px}
  .zaehlleiste .zurueck, .zaehlname, .zaehlmeldung{font-size:12.5px}
  .zaehlleiste .zurueck{white-space:nowrap}
  .lagenleiste{flex-wrap:nowrap; overflow-x:auto; padding:5px 12px;
    scrollbar-width:none}
  .lagenleiste > *, .lagenleiste .gruppe{flex:none; flex-wrap:nowrap}
}
/* ---------- Layer bar ----------
   Above the counting frame, for recordings that carry the scanner's
   prelabels_cvat.xml. The polygons themselves are drawn inside the frame, on
   the counting tool's stage (lagen.js); this bar switches editing on and
   sends the result to CVAT. */
.lagenleiste{flex:0 0 auto; display:flex; flex-wrap:wrap; align-items:center;
  gap:6px 12px; padding:6px 16px; border-bottom:1px solid var(--linie);
  background:var(--feld)}
.lagenleiste[hidden]{display:none}
.lagentitel{font-weight:600; font-size:13.5px}
/* Lines mode (the video): the polygon tools have nothing to act on. */
.lagenleiste.linien .nurpolygone{display:none}
.lagenleiste .gruppe{display:flex; flex-wrap:wrap; align-items:center; gap:6px}
.lagenleiste .luft{flex:1 1 auto}
.lagenleiste .knopf.stark{padding:5px 14px; font-size:13.5px}
.lagenleiste .knopf:disabled{opacity:.45; cursor:default}
.lagenzahl{font-size:13px; color:var(--schrift3)}
.lagenzahl.offen{color:var(--warn)}
.lagenmeldung{font-size:13px; color:var(--schrift2)}
.lagenmeldung:empty{display:none}
.lagenmeldung.gut{color:var(--gut)}
.lagenmeldung.schlecht{color:var(--schlecht)}
.lagenmeldung.warten{color:var(--warn)}
.lagenmeldung a{color:inherit; font-weight:600; text-decoration:underline}

.fehlerfeld{background:var(--schlecht-weich); color:var(--schlecht);
  border:1px solid var(--schlecht); border-radius:var(--rund);
  padding:10px 14px; margin:0 0 10px; max-width:60em}

/* ---------- Logo ----------
   Three states, the same pattern as every colour token: the bare rule is
   light, the media query covers "system dark" unless light was chosen
   explicitly, and the explicit dark choice wins in the other direction.

   This has to follow data-thema, not just prefers-color-scheme. The negative
   version fills "CEYE" with #fff — on a white ground it simply disappears,
   which is what happened when the theme switch was set to light on a dark
   system. */
.marke-logo{display:block; width:105px; height:26px; flex:none;
  background:url("logo.6bf5f903bea4.svg") no-repeat left center; background-size:contain}
.marke-logo.gross{width:137px; height:34px; margin:0 auto 6px}
@media (prefers-color-scheme:dark){
  :root:not([data-thema="hell"]) .marke-logo{background-image:url("logo-negativ.915b47bc1a0b.svg")}
}
:root[data-thema="dunkel"] .marke-logo{background-image:url("logo-negativ.915b47bc1a0b.svg")}

/* ---------- Editable customer count ---------- */
.kundenzeile dd{display:flex; align-items:center; gap:6px}
.kundenform{display:flex; align-items:center; gap:5px; margin:0}
.kundenform input{width:5.5em; background:var(--feld); color:var(--schrift);
  border:1px solid var(--linie2); border-radius:6px; padding:3px 7px;
  font:inherit; font-family:"Source Code Pro",ui-monospace,monospace;
  font-variant-numeric:tabular-nums; text-align:right}
.kundenform input:focus{border-color:var(--akzent); outline:none}
/* The tick says the number was entered here rather than read from the file —
   otherwise there is no way to tell the two apart. */
.quelle{color:var(--gut); font-weight:700; cursor:help}
.quelle.masken{font-size:11px; font-weight:600; letter-spacing:.03em}
.kundenmeldung{margin:8px 0 0; padding:6px 9px; border-radius:6px; font-size:12.5px}
.kundenmeldung[hidden]{display:none}
.kundenmeldung.gut{background:var(--gut-weich); color:var(--gut)}
.kundenmeldung.schlecht{background:var(--schlecht-weich); color:var(--schlecht)}
/* Counts lead to the recording: a number that looks wrong is a reason to
   watch it, and that should be one click. */
a.zumvideo{color:inherit; text-decoration:none; border-bottom:1px dotted var(--linie2)}
a.zumvideo:hover{color:var(--akzent); border-bottom-color:var(--akzent); text-decoration:none}

/* The chart's tracks are links to the recording. */
svg.uebersicht a{cursor:pointer}
svg.uebersicht a:focus-visible .spur{fill:color-mix(in srgb, var(--akzent) 14%, transparent);
  outline:2px solid var(--akzent); outline-offset:-2px}
.hinweis .hklick{margin:7px 0 0; padding-top:6px; border-top:1px solid var(--linie);
  color:var(--akzent); font-size:11.5px}

/* ---------- Tabs in the header: recordings | training ---------- */
.kopf .reiter{display:flex; gap:4px}
.kopf .reiter a{color:var(--schrift2); font-size:13.5px; padding:5px 11px;
  border-radius:99px; border:1px solid transparent; white-space:nowrap}
.kopf .reiter a:hover{border-color:var(--linie2); text-decoration:none}
.kopf .reiter a[aria-current="page"]{background:var(--feld2); color:var(--schrift);
  font-weight:600; border-color:var(--linie2)}

/* ---------- Training tab ---------- */
.meldungen{list-style:none; padding:0; margin:0 0 14px; display:grid; gap:6px}
.meldungen li{background:var(--feld); border:1px solid var(--linie2);
  border-radius:8px; padding:8px 12px; font-size:13.5px}
.meldungen li.success{background:var(--gut-weich); border-color:var(--gut)}
.meldungen li.error{background:var(--schlecht-weich); border-color:var(--schlecht); color:var(--schlecht)}
.kachel.neu b{color:var(--akzent)}
.training-kurve{width:100%; height:auto; max-width:760px}
.legende{display:flex; flex-wrap:wrap; gap:4px 14px; list-style:none; padding:0;
  margin:6px 0 0; font-size:12.5px; color:var(--schrift2)}
.legende i{display:inline-block; width:14px; height:3px; border-radius:2px;
  vertical-align:middle; margin-right:6px}
.startform{display:grid; gap:12px; grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
  background:var(--feld); border:1px solid var(--linie); border-radius:var(--rund);
  padding:14px}
.startform label{display:grid; gap:5px; align-content:start; font-size:13px; color:var(--schrift2)}
.startform label.haken{display:flex; gap:8px; align-items:center}
.startform input:not([type=checkbox]), .startform select{background:var(--grund);
  color:var(--schrift); border:1px solid var(--linie2); border-radius:8px;
  padding:8px 11px; font:inherit; font-size:13.5px; width:100%; min-width:0}
.startform .hilfe{color:var(--schrift3); font-size:11.5px}
.startform .fehler{color:var(--schlecht); font-size:12px}
.startform .ganz{grid-column:1/-1}
.logfeld{background:var(--feld); border:1px solid var(--linie); border-radius:var(--rund);
  padding:10px 12px; font:12px/1.45 "Source Code Pro",monospace; color:var(--schrift2);
  max-height:460px; overflow:auto; white-space:pre-wrap; word-break:break-all; margin:0}
td.klein, span.klein{color:var(--schrift3); font-size:12px}
td.err-gross{color:var(--schlecht); font-weight:600}
/* The training tables have no card view for phones: they scroll sideways in
   their frame instead, on every width. */
.rahmen.immer{display:block; overflow-x:auto; border:1px solid var(--linie);
  border-radius:var(--rund); background:var(--grund)}
/* Metrics over the epochs: small multiples, one axis each, side by side. */
.kurven{display:grid; gap:10px; grid-template-columns:repeat(auto-fill,minmax(320px,1fr))}
.kurven .uebersicht-rahmen{margin:0}
.training-kurve.klein{max-width:none}
.training-kurve a, .legende a{color:inherit}
/* GPUs: free memory as a bar, the used part filled. */
.gpus{display:grid; gap:10px; grid-template-columns:repeat(auto-fill,minmax(260px,1fr)); margin:0 0 6px}
.gpu{background:var(--feld); border:1px solid var(--linie); border-radius:var(--rund); padding:11px 13px}
.gpu .oben{display:flex; gap:8px; align-items:baseline; flex-wrap:wrap}
.gpu .unten{display:flex; justify-content:space-between; gap:10px; font-size:12.5px;
  color:var(--schrift2); margin-top:6px}
.gpu .speicher{height:8px; border-radius:4px; background:var(--grund);
  border:1px solid var(--linie2); overflow:hidden; margin-top:8px}
.gpu .speicher i{display:block; height:100%; background:var(--schrift3)}
details summary{cursor:pointer; margin:4px 0}

/* ---------- Training charts: interaction (training_kurven.js) ---------- */
.kurvengruppe{margin:0 0 6px}
svg.training-kurve .spur-flaeche{fill:transparent; cursor:crosshair; touch-action:none}
svg.training-kurve:focus-visible{outline:2px solid var(--akzent); outline-offset:2px}
svg.training-kurve .fadenkreuz line{stroke:var(--schrift3); stroke-width:1; stroke-dasharray:3 3}
svg.training-kurve .markiert{stroke:var(--grund); stroke-width:2}
svg.training-kurve .auswahl{fill:color-mix(in srgb, var(--akzent) 14%, transparent);
  stroke:var(--akzent); stroke-width:1}
svg.training-kurve g.reihe.aus{display:none}
.hinweis .hz em{font-style:normal; color:var(--schrift3); font-size:11px}
/* Legend buttons switch a run off and on; off, the swatch is hollow. */
.legende.schalter{gap:6px; margin:0 0 10px}
.legende.schalter .knopf{display:inline-flex; align-items:center; gap:6px; font-size:12.5px}
.legende.schalter .knopf i{margin:0}
.legende.schalter .knopf[aria-pressed="true"]{background:var(--feld); color:var(--schrift);
  border-color:var(--linie2); font-weight:400}
.legende.schalter .knopf[aria-pressed="false"]{color:var(--schrift3); text-decoration:line-through}
.legende.schalter .knopf[aria-pressed="false"] i{background:transparent !important;
  outline:1px solid var(--schrift3)}

/* Sortable tables: the header is a button; the arrow shows the direction. */
th .sortknopf{all:unset; cursor:pointer; display:inline-flex; gap:4px; align-items:center}
th .sortknopf:focus-visible{outline:2px solid var(--akzent); outline-offset:2px}
th .sortknopf::after{content:"↕"; opacity:.35; font-size:10px}
th[aria-sort="ascending"] .sortknopf::after{content:"▲"; opacity:1}
th[aria-sort="descending"] .sortknopf::after{content:"▼"; opacity:1}
th.n .sortknopf{justify-content:flex-end; width:100%}
.startform label[hidden], .startform [hidden]{display:none}
.kurvenwahl[hidden], .kurven figure[hidden]{display:none}
/* The hint under the runs table flashes when a fifth run is ticked. */
.hinweistext.blinkt{animation:blinkt 1.2s ease-out}
@keyframes blinkt{from{color:var(--schlecht)} to{color:var(--schrift3)}}
@media (prefers-reduced-motion:reduce){.hinweistext.blinkt{animation:none; color:var(--schlecht)}}

/* ---------- Dataset panel (training_dataset.html) ---------- */
.legende i.quadrat{width:11px; height:11px; border-radius:3px}
.empfehlungen{margin:0 0 18px; padding-left:20px; display:grid; gap:6px; font-size:14px}
.pca-flaeche svg{display:block; width:100%; height:auto; max-height:520px}
.karte-pca .punkt[data-href]{cursor:pointer}
.karte-pca .punkt:hover > :first-of-type{stroke:var(--schrift); stroke-width:2}
.karte-pca .gruppennr{font:700 15px "Source Code Pro",monospace; fill:var(--schrift);
  paint-order:stroke; stroke:var(--grund); stroke-width:4px; text-anchor:middle;
  dominant-baseline:middle; pointer-events:none}
.gruppenchip{display:inline-grid; place-items:center; min-width:24px; height:24px;
  padding:0 6px; border-radius:6px; background:var(--farbe); color:#fff;
  font:700 13px "Source Code Pro",monospace; margin-right:4px}
.gruppen td.beispiele{display:flex; gap:3px}
.gruppen td.beispiele img, img.mini-bild{display:block; border-radius:3px; object-fit:cover;
  background:var(--feld2)}
.abdeckung{display:inline-block; width:56px; height:6px; margin-right:6px; vertical-align:middle;
  background:var(--feld2); border-radius:3px; position:relative; overflow:hidden}
.abdeckung::after{content:""; position:absolute; left:50%; top:0; bottom:0; width:1px;
  background:var(--schrift3)}
.abdeckung i{display:block; height:100%; background:var(--schrift2); border-radius:3px}
.abdeckung.duenn i{background:var(--warn)}
.verteilungen{display:grid; gap:14px; grid-template-columns:repeat(auto-fit,minmax(300px,1fr));
  margin:0 0 18px}
.verteilung{margin:0; padding:10px 12px; border:1px solid var(--linie); border-radius:var(--rund)}
.verteilung figcaption{display:flex; gap:8px; align-items:baseline; margin:0 0 6px}
.verteilung table{font-size:12.5px}
.verteilung th, .verteilung td{padding:3px 6px; border:0}
.verteilung th{width:70px; color:var(--schrift2); font-weight:400; text-transform:none;
  letter-spacing:0; font-size:12px; position:static}
.verteilung td:nth-child(2){width:100%}
.verteilung .balken{display:block; height:5px; border-radius:0 3px 3px 0; margin:1px 0;
  min-width:1px}
.verteilung .balken.training{background:var(--reihe-bild)}
.verteilung .balken.feld{background:var(--reihe-soll)}
.verteilung tr.luecke th{color:var(--schrift); font-weight:600}
.typ-neu input[type=text]{flex:1 1 200px; min-width:0; background:var(--feld); color:var(--schrift);
  border:1px solid var(--linie2); border-radius:8px; padding:7px 10px; font:inherit}
.stapelkarten{display:grid; gap:10px; grid-template-columns:repeat(auto-fill,minmax(320px,1fr));
  margin:0 0 18px}
.stapelkarte{display:flex; gap:10px; padding:10px; border:1px solid var(--linie);
  border-radius:var(--rund); align-items:flex-start; min-width:0}
.stapelkarte > a img{display:block; border-radius:4px; object-fit:cover; background:var(--feld2)}
.stapelinfo{display:grid; gap:6px; min-width:0; flex:1 1 auto}
.stapelinfo .klein{word-break:break-all}
.stapelinfo select, .kachel-einzeln select{max-width:100%; background:var(--feld); color:var(--schrift);
  border:1px solid var(--linie2); border-radius:6px; padding:3px 6px; font:inherit; font-size:12px}
.stapelinfo details summary{cursor:pointer}
.kacheln-einzeln{display:grid; gap:8px; grid-template-columns:repeat(auto-fill,minmax(130px,1fr));
  margin-top:6px}
.kachel-einzeln{display:grid; gap:3px; padding:3px; border:2px solid transparent; border-radius:6px}
.kachel-einzeln img{width:100%; height:auto; border-radius:4px; display:block}
.kachel-einzeln.markiert{border-color:var(--akzent)}
.gespeichert select{outline:2px solid var(--gut); outline-offset:1px}
.neu-laden[hidden]{display:none}
/* An opened tile strip takes the whole row, so the tiles are big enough to see a glint. */
.stapelkarte:has(details[open]){grid-column:1/-1}
.kachel-einzeln select{width:100%}
.kachel-einzeln .klein{min-height:1.3em}
.kachel-einzeln.aus img{opacity:.4}

/* ---------- Pallets and distance (training_pallets.html) ---------- */
.palettenliste{display:flex; flex-wrap:wrap; gap:6px; align-items:center; margin:0 0 12px}
.palettenliste button{cursor:pointer; font:inherit; font-size:12px}
.palettengruppe{border:1px solid var(--linie); border-radius:var(--rund); padding:10px 12px;
  margin:0 0 12px}
.palettengruppe header{display:flex; flex-wrap:wrap; gap:6px 10px; align-items:center; margin:0 0 8px}
.palettengruppe header .haken{display:flex; align-items:center; gap:8px; cursor:pointer}
.palettenkarten{display:grid; gap:8px; grid-template-columns:repeat(auto-fill,minmax(112px,1fr))}
.palettenkarte{display:grid; gap:3px; justify-items:start; padding:6px; cursor:pointer;
  border:2px solid var(--linie); border-radius:8px; position:relative; min-width:0}
.palettenkarte input{position:absolute; top:8px; left:8px; width:18px; height:18px}
.palettenkarte img{width:100%; height:auto; aspect-ratio:2/3; object-fit:cover; border-radius:4px;
  background:var(--feld2)}
.palettenkarte.gewaehlt{border-color:var(--akzent); background:var(--akzent-weich)}
.palettenkarte .angaben{display:flex; flex-wrap:wrap; gap:3px; min-height:22px}
.palettenkarte .perle{font-size:11px; padding:1px 7px; max-width:100%; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap}
.palettenkarte .leer-angabe{font-style:italic}
.zuweisleiste{position:sticky; bottom:0; z-index:10; display:flex; flex-wrap:wrap; gap:8px 14px;
  align-items:center; padding:10px 14px; margin:12px -4px 0; background:var(--grund);
  border:1px solid var(--linie2); border-radius:var(--rund); box-shadow:var(--schatten)}
.zuweisleiste label{display:flex; gap:6px; align-items:center; font-size:13px; color:var(--schrift2)}
.zuweisleiste select, .zuweisleiste input[type=text]{background:var(--feld); color:var(--schrift);
  border:1px solid var(--linie2); border-radius:6px; padding:6px 8px; font:inherit; font-size:13px}
.zuweisleiste input[hidden]{display:none}
.zuweisleiste .fehlt{color:var(--schlecht); font-weight:600}

/* Pallet and distance of a recording (_angabe.html). */
.angabeform{margin:8px 0 0}
.angabeform select, .angabeform input[type=text]{background:var(--feld); color:var(--schrift);
  border:1px solid var(--linie2); border-radius:6px; padding:4px 7px; font:inherit; font-size:13px;
  max-width:100%}
.angabeform input[hidden]{display:none}
.angabeform dd{display:flex; flex-wrap:wrap; gap:5px; align-items:center; justify-content:flex-end}
.wahlchips label{cursor:pointer}
.wahlchips input[type=radio]{position:absolute; opacity:0; pointer-events:none}
.wahlchips label span{display:inline-block; padding:3px 9px; border:1px solid var(--linie2);
  border-radius:99px; font-size:12px; color:var(--schrift2)}
.wahlchips input:checked + span{background:var(--akzent); border-color:var(--akzent); color:#fff}
.wahlchips input:focus-visible + span{outline:2px solid var(--akzent); outline-offset:2px}
.wahlchips label:hover span{border-color:var(--schrift3)}
/* The pallets wrap under their label; a long list scrolls. */
.paar.angabe-palette{flex-wrap:wrap}
.palettenwahl{flex:1 1 100%; max-height:9.5em; overflow-y:auto}
.hinweis-palette{margin:4px 0 0; color:var(--schrift3)}
/* Tiles of a pallet, shown on hover (views.palette_vorschau): big enough to
   compare with the tiles of the recording being assigned. */
.palettenblick-huelle{position:fixed; z-index:60; pointer-events:none; width:min(640px, 94vw);
  background:var(--grund); border:1px solid var(--linie2); border-radius:var(--rund);
  box-shadow:var(--schatten); padding:10px 12px}
.palettenblick-huelle[hidden]{display:none}
.palettenblick .kopfzeile{display:flex; gap:8px; align-items:baseline; margin:0 0 8px}
.palettenblick .blickraster{display:grid; grid-template-columns:repeat(3,1fr); gap:6px}
.palettenblick figure{margin:0}
.palettenblick img{display:block; width:100%; height:auto; aspect-ratio:1; object-fit:cover;
  border-radius:4px; background:var(--feld2)}
.angabemeldung{margin:6px 0 0; padding:5px 9px; border-radius:6px; font-size:12.5px}
.angabemeldung[hidden]{display:none}
.angabemeldung.gut{background:var(--gut-weich); color:var(--gut)}
.angabemeldung.schlecht{background:var(--schlecht-weich); color:var(--schlecht)}
.mittekacheln{display:grid; grid-template-columns:repeat(3,1fr); gap:4px; margin:8px 0 0}
.mittekacheln img{display:block; width:100%; height:auto; aspect-ratio:1; object-fit:cover;
  border-radius:4px; background:var(--feld2)}
.mittekacheln figcaption{grid-column:1/-1; margin-top:2px}
.mittekacheln figcaption{font-size:11.5px; color:var(--schrift3)}

/* The training image with its labels (trainingsbild.html). The checkbox is
   the switch; its label is the button. */
.trainingsschalter{position:absolute; opacity:0; pointer-events:none}
.trainingsschalter:checked + label{background:var(--akzent); border-color:var(--akzent); color:#fff}
.trainingsschalter:focus-visible + label{outline:2px solid var(--akzent); outline-offset:2px}
.trainingsbild{position:relative; max-width:900px; margin:12px 0}
.trainingsbild img{display:block; width:100%; height:auto}
.trainingsbild svg{position:absolute; inset:0; width:100%; height:100%}
.trainingsbild polygon{fill:var(--farbe); fill-opacity:.3; stroke:var(--farbe);
  stroke-width:2; vector-effect:non-scaling-stroke}
.trainingsschalter:not(:checked) ~ .trainingsbild svg{display:none}
