/* --- generated from app/theme.py — do not edit by hand; run scripts/gen_theme.py --- */
:root{
  --teal:#1f6f78;            /* brand accent: top bar, buttons, links */
  --teal-dark:#17565d;       /* accent hover, section headings */
  --ink:#222;                /* body text */
  --muted:#667;              /* secondary text, table headers, labels */
  --line:#dde3e6;            /* borders, rules */
  --bg:#f4f6f7;              /* page background */
  --surface:#fff;            /* cards, tables, secondary buttons */
  --on-accent:#fff;          /* text on any filled accent or band colour */
  --on-accent-soft:#dde9eb;  /* de-emphasised text on the accent bar */
  --accent-raised:#357d86;   /* raised on the accent bar: language select, active nav tab */
  --accent-line:#c7dbdd;     /* borders of controls on the accent bar */
  --marker:#111;             /* needle on the z-score / score bars */
  --green:#27ae60;           /* band: normal */
  --green-dark:#1e864a;      /* band normal, under white text (chips, delta) */
  --yellow:#f1c40f;          /* band: borderline — print darkens it, the PDF sets white text on it */
  --yellow-dark:#8e7200;     /* band borderline, under white text (chips) */
  --orange:#e67e22;          /* band: elevated */
  --orange-dark:#b35f14;     /* band elevated, under white text (chips) */
  --red:#c0392b;             /* band: strongly elevated — dark enough for white text as is */
  --err-bg:#fdecea;          /* error banner background (text uses --red) */
  --notice-bg:#e8f5ec;       /* success banner background */
  --notice-ink:#1e7a43;      /* success banner text */
  --status-bg:#eef6f7;       /* neutral status banner background */
}
/* --- end generated --- */
*{box-sizing:border-box}
body{margin:0;font:15px/1.45 system-ui,Segoe UI,Roboto,sans-serif;color:var(--ink);background:var(--bg)}
a{color:var(--teal);text-decoration:none}
a:hover{text-decoration:underline}
.content{max-width:1100px;margin:1.2rem auto;padding:0 1rem}

/* Top bar. The controls here used to be white-alpha overlays and opacity-tinted
   text. Both are gone: an alpha overlay's contrast depends on what it sits on,
   and a nested opacity multiplies with its parent's, so neither is checkable
   from the palette. They are explicit tones now (--accent-raised,
   --accent-line, --on-accent-soft) and covered by CONTRAST_PAIRS.
   The one literal left is the native select popup, which the OS draws on its
   own white background — #000 there is not a palette colour. */
.topbar{display:flex;align-items:center;gap:1rem;background:var(--teal);color:var(--on-accent);padding:.6rem 1rem}
.topbar a{color:var(--on-accent)}
.brand{font-weight:700;font-size:1.15rem}
/* Nav tabs: full-tone text (no opacity, see above), the current page filled
   with the same raised tone the controls on the bar use. */
.topbar nav{display:flex;gap:.3rem}
.topbar nav a{padding:.35rem .7rem;border-radius:5px;font-weight:600;font-size:.95rem;white-space:nowrap}
.topbar nav a:hover{background:var(--accent-raised);text-decoration:none}
.topbar nav a.active{background:var(--accent-raised)}
.spacer{flex:1}
.whoami{font-size:.85rem}
.logout{margin-left:1rem;border:1px solid var(--accent-line);padding:.2rem .6rem;border-radius:4px}
.langsel{background:var(--accent-raised);color:var(--on-accent);border:1px solid var(--accent-line);border-radius:4px;padding:.15rem .3rem;font-size:.82rem;width:auto;text-align:center}
.langsel option{color:#000}
/* The width select shares the language select's chrome but shows words, not a
   two-letter code, so it is not centred. */
.widthsel{text-align:left}

/* cards / tables */
.card{background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:1rem 1.2rem;margin-bottom:1.2rem}
h1{font-size:1.4rem;margin:.2rem 0 1rem}
h2{font-size:1.1rem;color:var(--teal-dark);border-bottom:2px solid var(--line);padding-bottom:.3rem;margin:1.4rem 0 .7rem}
table{width:100%;border-collapse:collapse}
th,td{text-align:left;padding:.4rem .5rem;border-bottom:1px solid var(--line);font-size:.9rem}
th{color:var(--muted);font-weight:600}
.btn{display:inline-block;background:var(--teal);color:var(--on-accent);border:0;border-radius:5px;padding:.5rem .9rem;cursor:pointer;font-size:.9rem}
.btn.secondary{background:var(--surface);color:var(--teal);border:1px solid var(--teal)}
/* A button the page turned off during a running device call. */
.btn:disabled{opacity:.5;cursor:default}
.btn:hover{background:var(--teal-dark)}
label{display:block;font-size:.82rem;color:var(--muted);margin:.6rem 0 .15rem}
input,select,textarea{width:100%;padding:.45rem;border:1px solid var(--line);border-radius:5px;font:inherit}
textarea{min-height:5rem}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:.2rem 1rem}
.row{display:flex;gap:.8rem;align-items:center}
.muted{color:var(--muted)}

/* login */
.login-wrap{max-width:360px;margin:6vh auto}
.login-wrap .brand-big{font-size:1.8rem;font-weight:700;color:var(--teal);text-align:center;margin-bottom:1rem}
/* "Angemeldet bleiben" — a label that sits on one line with its checkbox,
   against the block-level default the form labels above it use. */
.checkline{display:flex;align-items:center;gap:.4rem;margin-top:.9rem;color:var(--ink);font-size:.9rem;cursor:pointer}
.checkline input{width:auto;margin:0}
.err{background:var(--err-bg);color:var(--red);padding:.5rem .7rem;border-radius:5px;margin-bottom:.7rem}
.notice{background:var(--notice-bg);color:var(--notice-ink);padding:.5rem .7rem;border-radius:5px;margin-bottom:.7rem}

/* The five bars below blend instead of stepping: every colour holds a plateau
   and hands over across a 6-point window centred on its threshold, e.g.
   ".. var(--green) 33% 67%, var(--yellow) 73% .." puts the green/yellow line at
   70%. Hard stops read as a stack of tiles at this height; the reading is the
   marker position, and a scale that shades makes that easier, not harder. The
   percentages below are still the classifier's thresholds — they are the one
   copy that cannot be generated, so they move with it. */

/* z-score band bar (elements + ratios) */
/* z ∈ [-5, +5] proportional: the classifier's zones are unequal — green is the
   whole reference band (|z| <= 2), red starts a half band-width outside it
   (|z| >= 4), and the two steps between are 1 z wide. The template's marker
   position ((z+5)/10) shares this scale. Thresholds at 10/20/30/70/80/90 %. */
.zbar{position:relative;height:16px;border-radius:3px;overflow:hidden;
  background:linear-gradient(90deg,
    var(--red) 0 7%, var(--orange) 13% 17%, var(--yellow) 23% 27%,
    var(--green) 33% 67%, var(--yellow) 73% 77%,
    var(--orange) 83% 87%, var(--red) 93% 100%);min-width:120px}

/* Functional-index bar: signed distance from the middle of the reference band,
   aggregated denominator-free, 0..INDEX_MAX. evaluation.INDEX_EDGES
   (0.86/1.23/1.51) over 1.8 → 47.8/68.3/83.9 %, minus the 3 % blend either side.
   scripts/calibrate_indexes.py prints these; it is the only copy of the edges
   that is not generated. */
.indexbar{position:relative;height:16px;border-radius:3px;overflow:hidden;
  background:linear-gradient(90deg,var(--green) 0 44.78%,var(--yellow) 50.78% 65.33%,
    var(--orange) 71.33% 80.89%,var(--red) 86.89% 100%);min-width:120px}

/* Impact bar (toxic Impact-Index 0..>1.8). evaluation.IMPACT_EDGES over
   IMPACT_MAX — 0.6/1.2/1.8 of 2.0, i.e. 30/60/90 %. */
.scorebar{position:relative;height:16px;border-radius:3px;overflow:hidden;
  background:linear-gradient(90deg,var(--green) 0 27%,var(--yellow) 33% 57%,
    var(--orange) 63% 87%,var(--red) 93% 100%);min-width:120px}

/* Toxic load bar (plain "Toxische Metalle" table). Its own gradient, because
   its axis is a multiple of the element's limit, not the 0..2 impact score:
   0..2·L, so evaluation.py's four zones — green below 0.8·L, yellow to 1.2·L,
   orange to 1.6·L, red above — land at 40/60/80 %. It used to borrow .scorebar,
   whose stops belong to the impact axis and meant nothing here.
   There used to be a white hairline at 50 % marking the limit. It is gone: the
   limit is 1.0·L, which falls inside the yellow plateau, so the line marked a
   place where nothing changes. The zone edges carry the reading instead. */
.loadbar{position:relative;height:16px;border-radius:3px;overflow:hidden;
  background:linear-gradient(90deg,var(--green) 0 37%,var(--yellow) 43% 57%,
    var(--orange) 63% 77%,var(--red) 83% 100%);min-width:120px}

/* Oxidative load bar. The axis is the percentage itself, so the two edges from
   evaluation.OXIDATIVE_EDGES — 25 and 40 — sit at 25 % and 40 % of the bar,
   minus the 3 % blend either side. Three zones, not four: the reading has no
   fourth step to make. */
.oxbar{position:relative;height:16px;border-radius:3px;overflow:hidden;
  background:linear-gradient(90deg,var(--green) 0 22%,var(--yellow) 28% 37%,
    var(--red) 43% 100%);min-width:120px}

/* The marker on all five bars: a needle with a dot at each end. The dots are
   what the eye finds down a column of rows, the needle is what it reads the
   position off.
   Each bar keeps overflow:hidden, so the dots are cut off flush with its top
   and bottom edge rather than standing clear of it. That is deliberate: a dot
   that overhangs reads as a separate object floating over the scale, while a
   cut one reads as part of the bar — and it keeps the marker inside the row's
   own height. The geometry below puts 6 of each dot's 10 px inside the bar. */
.zbar .marker,.indexbar .marker,.scorebar .marker,.loadbar .marker,.oxbar .marker{
  position:absolute;top:-2px;width:2px;height:20px;background:var(--marker)}
.zbar .marker::before,.zbar .marker::after,
.indexbar .marker::before,.indexbar .marker::after,
.scorebar .marker::before,.scorebar .marker::after,
.loadbar .marker::before,.loadbar .marker::after,
.oxbar .marker::before,.oxbar .marker::after{content:"";position:absolute;
  left:-4px;width:10px;height:10px;border-radius:50%;background:var(--marker)}
.zbar .marker::after,.indexbar .marker::after,
.scorebar .marker::after,.loadbar .marker::after,.oxbar .marker::after{top:-2px}
.zbar .marker::before,.indexbar .marker::before,
.scorebar .marker::before,.loadbar .marker::before,.oxbar .marker::before{bottom:-2px}
/* The prior measurement in the comparison table, on the same bar as the
   current one: a hollow dot, so which of the two is "now" needs no legend. It
   sits inside the bar rather than spanning it — only the dumbbell is a reading
   to take, the ring is where that reading came from. */
.zbar .prev,.loadbar .prev{position:absolute;top:2px;margin-left:-6px;
  width:12px;height:12px;border-radius:50%;background:var(--surface);border:2px solid var(--marker)}

/* --- measured values in the tables ------------------------------------- */
/* A column of values is read down, not across. Fixed decimals come from
   i18n.format_number; these two lines are what makes the separators line up:
   right alignment, and tabular figures so a 1 takes the same width as a 0.
   The header goes with its column. */
th.num,td.num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}

/* The two laid-out cells below put their grid/flex on an inner div, never on
   the <td> itself: display:grid or :flex on a cell takes it out of the table's
   own layout, and the row rules then step up and down from column to column
   instead of running straight across. */

/* A reference or ideal range is two number columns with a rule between them,
   so the cell is a grid centred on the dash rather than on the whole string.
   The 1fr either side is what pushes the low value right and the high left. */
td.refrange>div{display:grid;grid-template-columns:1fr auto 1fr;
  font-variant-numeric:tabular-nums;white-space:nowrap}
td.refrange .hi{text-align:left}
td.refrange .lo{text-align:right}

/* The classification chip's position across its column encodes the direction of
   the deviation: left for a deficiency, centred inside the reference band,
   right for an excess (the template's band_pos picks p1..p5).
   The two pseudo-elements share out only the space the chip does not need, in
   the ratio below — so the chip can never be pushed out of its own cell, and a
   narrow column simply travels less than a wide one. No breakpoint needed. */
td.cls>div{display:flex;align-items:center;white-space:nowrap}
td.cls>div::before,td.cls>div::after{content:"";flex-basis:0}
td.cls.p1>div::before{flex-grow:0} td.cls.p1>div::after{flex-grow:4}
td.cls.p2>div::before{flex-grow:1} td.cls.p2>div::after{flex-grow:3}
td.cls.p3>div::before{flex-grow:2} td.cls.p3>div::after{flex-grow:2}
td.cls.p4>div::before{flex-grow:3} td.cls.p4>div::after{flex-grow:1}
td.cls.p5>div::before{flex-grow:4} td.cls.p5>div::after{flex-grow:0}

/* Headers of the two columns that are not read left-to-right: the reference is
   centred on its dash, the classification spreads across its cell, so a
   left-aligned label sits over neither. */
th.mid{text-align:center}

/* Explanatory layer: band legend, per-row info toggle, expanded text row.
   Texts come from app/explain.py, the band edges from evaluation.score_bands. */
.legend{display:flex;flex-wrap:wrap;gap:.15rem 1rem;font-size:.78rem;color:var(--ink);margin:.1rem 0 .8rem}
.legend .sw{display:inline-block;width:10px;height:10px;border-radius:2px;margin-right:.3rem;vertical-align:-1px}
.legend .range{color:var(--muted);margin-left:.25rem}
.sw-green{background:var(--green)} .sw-yellow{background:var(--yellow)}
.sw-orange{background:var(--orange)} .sw-red{background:var(--red)}
.subtitle{color:var(--muted);font-size:.86rem;margin:.1rem 0 .5rem}
/* The patient's buffer state, stated once above the Impact table instead of
   repeated on all 14 metal rows (evaluation.buffer_status). */
.bufferline{border-left:3px solid var(--teal);padding:.4rem .6rem;margin:.2rem 0 .7rem;
  background:var(--status-bg);border-radius:0 3px 3px 0;line-height:1.5}
.small{font-size:.82rem}
.info{border:1px solid var(--teal);background:transparent;color:var(--teal);border-radius:50%;
  width:1.15rem;height:1.15rem;font-size:.72rem;font-weight:700;line-height:1;padding:0;
  margin-left:.35rem;cursor:pointer;vertical-align:1px}
.info:hover{background:var(--teal);color:var(--on-accent)}
tr.explain-row>td{background:var(--status-bg);font-size:.85rem;padding-top:.5rem;padding-bottom:.6rem}
tr.explain-row p{margin:0 0 .3rem}
details.explain{margin-bottom:.7rem}
details.explain>summary{cursor:pointer;color:var(--teal);font-size:.86rem}
details.explain>p{font-size:.86rem;margin:.4rem 0 0;max-width:70ch}

/* band chips */
.chip{display:inline-block;padding:.05rem .45rem;border-radius:10px;font-size:.78rem;color:var(--on-accent)}
.b-green{background:var(--green-dark)} .b-yellow{background:var(--yellow-dark)} .b-orange{background:var(--orange-dark)} .b-red{background:var(--red)}
.delta-pos{color:var(--green-dark)} .delta-neg{color:var(--red)}
.toolbar{display:flex;gap:.6rem;align-items:center;flex-wrap:wrap;margin-bottom:1rem}
.toolbar label{display:inline-flex;gap:.25rem;align-items:center;margin:0;font-size:.85rem;color:var(--ink)}
/* Selects in a toolbar sit next to their label, so they take their own width
   rather than the full-row width the form default gives them. */
.toolbar select{width:auto}
/* The form default gives every input width:100%, a padding and a border. On a
   checkbox inside a flex label that means a full-width, shrinkable box: once
   the toolbar had enough entries to wrap, the last one was squeezed to nothing
   and its label stood there on its own. Same exception .checkline already
   makes for the login page; flex:none is the half that stops the shrinking. */
.toolbar input[type=checkbox]{width:auto;flex:none;padding:0;border:0;margin:0}
.status{background:var(--status-bg);border:1px solid var(--line);padding:.5rem .7rem;border-radius:5px;margin:.5rem 0}
/* The same status block, but for a number the reader has to act on: the
   prepaid balance running low. Border and text carry it — a full red fill at
   this size reads as an error that already happened. */
.status.warn{background:var(--err-bg);border-color:var(--red);color:var(--red)}
.status.warn .muted{color:var(--red)}

/* Desktop client vs. browser (app/clientkind.py). The body carries
   data-client="app"|"browser"; until the bridge has answered it is unset and
   both variants stay hidden, so nothing flickers into view and out again. */
.app-only,.browser-only{display:none}
body[data-client="app"] .app-only{display:inline}
body[data-client="browser"] .browser-only{display:inline}
body[data-client="app"] div.app-only,body[data-client="browser"] div.browser-only{display:block}
body[data-client="app"] .card.app-only,body[data-client="browser"] .card.browser-only{display:block}
.clientchip{font-size:.75rem;color:var(--on-accent-soft);margin-left:.4rem}
