
/* ============================================================ fonts */
@font-face{font-family:Inter;font-style:normal;font-weight:400 700;font-display:swap;
  src:url(/static/fonts/Inter-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,
    U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:Inter;font-style:normal;font-weight:400 700;font-display:swap;
  src:url(/static/fonts/Inter-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:Newsreader;font-style:normal;font-weight:400 600;font-display:swap;
  src:url(/static/fonts/Newsreader-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,
    U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:Newsreader;font-style:normal;font-weight:400 600;font-display:swap;
  src:url(/static/fonts/Newsreader-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400;font-display:swap;
  src:url(/static/fonts/JetBrainsMono-latin.woff2) format('woff2');
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,
    U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400;font-display:swap;
  src:url(/static/fonts/JetBrainsMono-latin-ext.woff2) format('woff2');
  unicode-range:U+0100-024F,U+0259,U+1E00-1EFF,U+2020,U+20A0-20AB,U+20AD-20CF,U+2113,U+2C60-2C7F,U+A720-A7FF}

/* ============================================================ tokens (generated: theme.LIGHT / DARK) */
:root{color-scheme:light;--bg:#f4f2ee;--surface:#ffffff;--surface-2:#faf9f6;--sunken:#eeece7;--ink:#15191c;--ink-2:#39424a;--muted:#606971;--faint:#828a90;--line:#e2ded5;--line-2:#cfcabd;--control-line:#8a867c;--accent:#0d5049;--accent-2:#0a3f39;--accent-fg:#ffffff;--accent-soft:#e4efec;--ok:#0d6b48;--ok-soft:#e2f0e9;--warn:#8a5512;--warn-soft:#f7ecdc;--bad:#9c2b26;--bad-soft:#f7e6e4;--focus:#0d5049;--sh:0 1px 1px rgba(20,25,30,.04),0 2px 6px -2px rgba(20,25,30,.08);--sh-lg:0 1px 2px rgba(20,25,30,.04),0 18px 40px -22px rgba(20,25,30,.35);--sans:Inter,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;--serif:Newsreader,Georgia,"Times New Roman",serif;--mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;--r:10px;--r-lg:14px;}
:root[data-theme="dark"]{color-scheme:dark;--bg:#0d1013;--surface:#14181b;--surface-2:#1a1f23;--sunken:#0a0d0f;--ink:#e9ebe7;--ink-2:#c2c8cc;--muted:#98a1a8;--faint:#727c84;--line:#252b30;--line-2:#333b42;--control-line:#6b757e;--accent:#5cc3ae;--accent-2:#8ad9c8;--accent-fg:#07110f;--accent-soft:#12312c;--ok:#61cf9c;--ok-soft:#12291f;--warn:#e0ab5e;--warn-soft:#2b2213;--bad:#f0928c;--bad-soft:#2e1917;--focus:#5cc3ae;--sh:0 1px 1px rgba(0,0,0,.3);--sh-lg:0 20px 44px -24px #000;}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){color-scheme:dark;--bg:#0d1013;--surface:#14181b;--surface-2:#1a1f23;--sunken:#0a0d0f;--ink:#e9ebe7;--ink-2:#c2c8cc;--muted:#98a1a8;--faint:#727c84;--line:#252b30;--line-2:#333b42;--control-line:#6b757e;--accent:#5cc3ae;--accent-2:#8ad9c8;--accent-fg:#07110f;--accent-soft:#12312c;--ok:#61cf9c;--ok-soft:#12291f;--warn:#e0ab5e;--warn-soft:#2b2213;--bad:#f0928c;--bad-soft:#2e1917;--focus:#5cc3ae;--sh:0 1px 1px rgba(0,0,0,.3);--sh-lg:0 20px 44px -24px #000;}}


*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--bg);color:var(--ink);
  font:400 15px/1.6 var(--sans);font-feature-settings:"cv05","ss01";
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
h1,h2,h3,h4{font-family:var(--serif);font-weight:560;letter-spacing:-.005em;line-height:1.18;
  margin:0 0 .6rem;text-wrap:balance}
a{color:var(--accent);text-decoration:none;border-bottom:1px solid color-mix(in srgb,var(--accent) 32%,transparent)}
a:hover{border-bottom-color:var(--accent)}
:focus-visible{outline:2px solid var(--focus);outline-offset:2px;border-radius:4px}
code,kbd,samp,.mono{font-family:var(--mono);font-size:.86em;font-variant-ligatures:none}
.num{font-variant-numeric:tabular-nums}
hr{border:0;border-top:1px solid var(--line);margin:2rem 0}
::selection{background:var(--accent-soft);color:var(--ink)}
.skip{position:absolute;left:-999px}
.skip:focus{left:.6rem;top:.6rem;background:var(--surface);padding:.5rem .8rem;border-radius:8px;z-index:99;
  box-shadow:var(--sh-lg)}
.visually-hidden{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* ============================================================ the mark */
.mark{display:inline-flex;align-items:center;gap:.5rem;color:var(--ink);border:0;font-family:var(--serif);
  font-size:1.22rem;font-weight:600;letter-spacing:-.015em}
.mark svg{flex:none;color:var(--accent)}
.mark:hover{border:0}

/* ============================================================ operator shell
   On a wide screen the header becomes a LEFT RAIL — same markup, CSS only. An evidence tool is
   used by someone moving between five places all day; a rail keeps them all visible and gives the
   register the full width of the screen, which is what the data actually needs. */
/* `minmax(0,1fr)`, never a bare `1fr`: a grid item defaults to `min-width:auto`, so a bare
   `1fr` main column CANNOT shrink below its widest child's min-content width. One unwrapped
   register was enough to drag the whole document to 855 px at a 390 px viewport (measured
   2026-09-09) — banner, nav and all — because the column grew instead of the table scrolling.
   The two-column rule below always had `minmax(0,1fr)`, which is why only phones were wrong. */
.app{display:grid;grid-template-columns:minmax(0,1fr);min-height:100vh}
/* A22 (2026-09-14): same glass sticky bar as `.site .top` so `/` → `/ui` is one product, not a
   marketing theme handing off to an admin skin. Desktop rail keeps the blur; solid `--surface`
   under it would still read as a second brand. */
.app>header{display:flex;gap:1rem;align-items:center;flex-wrap:wrap;padding:.7rem 1.1rem;
  background:color-mix(in srgb,var(--bg) 88%,transparent);backdrop-filter:saturate(180%) blur(12px);
  border-bottom:1px solid var(--line);position:sticky;top:0;z-index:5}
.app>header>nav{display:flex;gap:.15rem;flex-wrap:wrap}
.app>header>nav a{color:var(--ink-2);border:0;padding:.34rem .6rem;border-radius:8px;font-size:.92rem;
  font-weight:500}
.app>header>nav a:hover{background:var(--sunken);color:var(--ink)}
.app>header>nav a[aria-current=page]{background:var(--accent-soft);color:var(--accent);font-weight:600}
.app>header .who{margin-left:auto;color:var(--muted);font-size:.84rem;display:flex;align-items:center;
  gap:.5rem;flex-wrap:wrap}
/* Content column matches `.site .wrap` (64rem) — was 78rem, so the operator view felt wider and
   looser than the shop window it sits inside. */
.app>main{padding:1.5rem 1.4rem 4rem;max-width:64rem;width:100%}
@media (min-width:64rem){
  .app{grid-template-columns:15.5rem minmax(0,1fr)}
  /* `nowrap` + scroll (2026-10-01): the phone bar's `flex-wrap:wrap` still applied here, so when the nav and the
     account block were taller than the window (the admin nav at 900 px) the account block wrapped into a SECOND
     column, past the rail's edge, with "Deutsch" on a line of its own. Measured over CDP: who at x=171 in a 248-px
     rail. A tall rail now scrolls inside itself. */
  .app>header{position:sticky;top:0;height:100vh;flex-direction:column;align-items:stretch;gap:1.2rem;
    flex-wrap:nowrap;overflow-y:auto;
    padding:1.4rem 1rem;border-bottom:0;border-right:1px solid var(--line);
    background:color-mix(in srgb,var(--bg) 92%,var(--surface))}
  .app>header>nav{flex-direction:column;gap:.1rem}
  .app>header>nav a{padding:.45rem .65rem}
  .app>header .who{margin-left:0;margin-top:auto;flex-direction:column;align-items:flex-start;gap:.4rem}
  .app>main{padding:2.2rem 1.8rem 5rem}
  .app>.banner{grid-column:1/-1;order:-1}
}
/* UI review 2026-09-28. The nav groups the daily pages above a "Setup" label; on a phone it is ONE row that
   scrolls sideways instead of three wrapped rows pushing the page below the fold. */
.app>header>nav .nav-sec{font:600 .68rem/1.3 var(--sans);letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);padding:.34rem .6rem;align-self:center}
/* The account block (owner, 2026-10-02): who you are on one line, your two actions alike on the next. */
.app>header .who .me{display:flex;flex-wrap:wrap;align-items:center;gap:.4rem;min-width:0}
.app>header .who .me .email{overflow-wrap:anywhere}
.app>header .who .acts{display:flex;flex-wrap:wrap;gap:.4rem;align-items:center}
.app>header .who .acts a[aria-current]{background:var(--accent-soft);color:var(--accent);border-color:transparent}
/* A row's own label in a table body reads as the row's first cell, not as a second header column. */
tbody th[scope=row]{background:transparent;font:500 .9rem/1.45 var(--sans);letter-spacing:0;text-transform:none;
  color:var(--ink);position:static;white-space:normal}
tbody tr:hover th[scope=row]{background:var(--surface-2)}
/* Row actions: one wrapping row with even gaps (the platform's tenant list put "Cancel" alone on a second line). */
.row-acts{display:flex;flex-wrap:wrap;gap:.35rem;align-items:center}
@media (min-width:64rem){.app>header>nav .nav-sec{padding:1rem .65rem .3rem;align-self:auto}}
@media (max-width:63.99rem){
  .app>header>nav{flex-wrap:nowrap;overflow-x:auto;max-width:100%;scrollbar-width:thin;
    -webkit-mask-image:linear-gradient(90deg,#000 88%,transparent);mask-image:linear-gradient(90deg,#000 88%,transparent)}
  .app>header>nav a,.app>header>nav .nav-sec{white-space:nowrap;flex:none}
}
.nowrap{white-space:nowrap}
/* A register on a phone: each row becomes a card, each cell carries its own column name. A table cut at the
   right edge hid licence, device and queue on the fleet page and class and evidence on the incident list. */
@media (max-width:44rem){
  table.cards,table.cards tbody,table.cards tr,table.cards td{display:block;width:100%}
  table.cards thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
  table.cards tr{padding:.55rem .2rem;border-bottom:1px solid var(--line)}
  table.cards tr:last-child{border-bottom:0}
  table.cards td{border:0;padding:.3rem .8rem .3rem 7.6rem;position:relative;min-height:1.6rem}
  table.cards td::before{content:attr(data-label);position:absolute;left:.8rem;top:.42rem;width:6.4rem;
    font:600 .66rem/1.5 var(--sans);letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
  table.cards td:not([data-label]){padding-left:.8rem}
  table.cards td:not([data-label])::before{content:none}
  table.cards td.actions{display:flex;gap:.8rem;align-items:center;flex-wrap:wrap;padding-top:.5rem}
  table.cards td.actions br{display:none}
  tbody tr:hover td{background:transparent}
  /* A long register (incidents) stays dense: no column names, time first, the rest flowing on two lines. */
  table.cards.compact tr{padding:.5rem .1rem}
  table.cards.compact td{display:inline-block;width:auto;padding:.1rem .45rem .1rem .8rem;min-height:0;
    vertical-align:middle}
  table.cards.compact td::before{content:none}
  table.cards.compact td:first-child{display:block;font-weight:600}
  table.cards.compact td br{display:none}
  table.cards.compact td .muted{margin-left:.3rem}
}
/* Onboarding: one line (progress + next step) that opens to the full checklist. */
/* A form card is as wide as its fields, not the page: three 20rem inputs in a 64rem card read as a half-empty box. */
.app>main form.card:not(.form-row){max-width:40rem}
.app>main form.card:not(.form-row) :is(input:not([type=checkbox]):not([type=radio]):not([type=hidden]),select){width:100%;max-width:none}
.form-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(10.5rem,1fr));gap:.4rem 1rem;align-items:end}
.form-row>div>:last-child{margin-bottom:0}
.form-row .go button{width:100%}
.onboard-card details>summary{display:flex;flex-wrap:wrap;align-items:baseline;gap:.4rem 1rem;cursor:pointer;
  list-style:none}
.onboard-card details>summary::-webkit-details-marker{display:none}
.onboard-card details>summary h2{margin:0}
.onboard-card details>summary::after{content:"show steps";margin-left:auto;font-size:.8rem;color:var(--accent)}
.onboard-card details[open]>summary::after{content:"hide"}
.onboard-card details[open]>summary{margin-bottom:.8rem}
.onboard-card .progress{color:var(--muted);font-size:.9rem}
.onboard-card .progress strong{color:var(--ink)}
.onboard-line{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem 1rem}
.onboard-line h2{margin:0}
.onboard-line .btn{margin-left:auto}
/* Get started (/ui/start): the four steps as a line each, the step in focus, and Prepare beside it. */
.stepper{list-style:none;margin:0 0 1rem;padding:0;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:.5rem}
.stepper a{display:grid;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:.15rem .55rem;height:100%;
  box-sizing:border-box;
  padding:.6rem .8rem;border:1px solid var(--line);border-radius:var(--r);background:var(--surface);color:var(--ink);
  text-decoration:none}
.stepper a:hover{border-color:var(--control-line)}
.stepper .step-mark{display:inline-grid;place-items:center;width:1.5rem;height:1.5rem;border-radius:50%;flex:none;
  border:1px solid var(--control-line);font:600 .78rem/1 var(--sans);color:var(--muted)}
.stepper .done .step-mark{background:var(--ok-soft);color:var(--ok);border-color:transparent}
.stepper .here a{border-color:var(--accent);background:var(--accent-soft)}
.stepper .here .step-mark{background:var(--accent);color:var(--accent-fg);border-color:var(--accent)}
.stepper .step-title{font-weight:600;font-size:.9rem}
.stepper .step-note{grid-column:2;color:var(--muted);font-size:.78rem}
/* Letter 6, P1: the letter that opens a consultation, offered on the confirmation step before its box. */
.callout{background:var(--accent-soft);border:1px solid color-mix(in srgb,var(--accent) 25%,transparent);
  border-radius:9px;padding:.7rem .85rem;font-size:.9rem;line-height:1.5}
.start{display:grid;grid-template-columns:minmax(0,1fr) minmax(16rem,21rem);gap:1rem;align-items:start}
.start>.card{margin:0}
.step-focus h2{margin-top:.1rem}
.step-focus h3{font-size:.98rem;margin:1.3rem 0 .5rem}
.step-focus .release code{overflow-wrap:anywhere}
.code-card h3{margin-top:0}
.prepare h2,.demo-first h2{margin-top:.1rem}
.prepare-list{list-style:none;margin:.9rem 0;padding:0;display:grid;gap:.8rem}
.prepare-list li{display:grid;gap:.15rem;padding-left:1.7rem;position:relative}
.prepare-list li::before{content:"";position:absolute;left:0;top:.15rem;width:1.05rem;height:1.05rem;box-sizing:border-box;
  border-radius:50%;border:1px solid var(--control-line)}
.prepare-list li.done::before{content:"¹3";display:grid;place-items:center;font:700 .7rem/1 var(--sans);
  color:var(--ok);background:var(--ok-soft);border-color:transparent}
.prepare-list li>a{font-weight:600}
.prepare-list li .tag{justify-self:start}
.prepare-list li .muted{font-size:.82rem}
.copy-pair{display:grid;gap:.7rem;margin:.2rem 0 .7rem}
.copy-label{display:block;font:600 .72rem/1.3 var(--sans);letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);margin-bottom:.25rem}
code.copy{display:block;padding:.55rem .7rem;border:1px dashed var(--control-line);border-radius:var(--r);
  background:var(--surface-2);user-select:all;-webkit-user-select:all;overflow-wrap:anywhere;font-size:.92rem}
p code.copy{display:inline;padding:.1rem .35rem}
.wait{padding:.75rem .9rem;border:1px solid var(--line);border-radius:var(--r);background:var(--surface-2);
  margin:.4rem 0 .8rem;overflow-wrap:anywhere}
.wait.ok{border-color:color-mix(in srgb,var(--ok) 40%,var(--line))}
table.checks{width:auto;margin:.4rem 0 .8rem}
table.checks th{text-align:left;font-weight:600;padding-right:1.2rem;white-space:nowrap;vertical-align:top}
.site-pick{max-width:28rem;margin:0 0 .8rem}
.demo-first{border-color:color-mix(in srgb,var(--accent) 40%,var(--line))}
.stack-form{max-width:28rem}
/* The path's language (decision 4): four names, the current one marked, above the steps. */
.lang-switch{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.25rem 1rem;margin:-.7rem 0 .7rem;font-size:.85rem}
.lang-switch a{color:var(--muted)}
.lang-switch a[aria-current]{color:var(--ink);font-weight:600;text-decoration:none}
/* The console's own switcher sits in the account block (console_i18n.switcher). It is a <nav>, so the rail's rules for
   the page navigation (a column, padded links, a sideways scroll on a phone) reached it too and stacked it past the
   rail's edge on the first render; here it is one compact wrapping row. */
.app>header .who .console-lang{flex-direction:row;flex-wrap:wrap;justify-content:flex-start;gap:.1rem .7rem;margin:0;
  overflow:visible;max-width:100%;font-size:.8rem}
.app>header .who .console-lang a{padding:0;background:none;border-radius:0;border:0;color:var(--ink-2);font-size:.92rem;
  font-weight:500}
.app>header .who .console-lang a[aria-current]{color:var(--ink);font-weight:600}
.auth-theme .console-lang{margin:0 0 0 .9rem}
.whole-view-card{display:grid;grid-template-columns:minmax(9rem,13rem) minmax(0,1fr);gap:1.1rem;align-items:start}
.whole-view-card h2{margin-top:0}
svg.whole-view{width:100%;height:auto;background:var(--surface-2);border-radius:var(--r)}
svg.whole-view rect{fill:var(--accent-soft);stroke:var(--accent);stroke-width:1.5;stroke-dasharray:5 3}
svg.whole-view text{fill:var(--accent);font:600 11px var(--sans);text-anchor:middle}
.step-focus .whole-view-card{margin:.6rem 0 0;padding:0;border:0;box-shadow:none}
@media (max-width:40rem){.whole-view-card{grid-template-columns:minmax(0,1fr)}svg.whole-view{max-width:14rem}}
@media (max-width:56rem){.start{grid-template-columns:minmax(0,1fr)}.stepper{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:30rem){.onboard-line .btn{margin-left:0;width:100%;justify-content:center}}
.page-head{display:flex;gap:1rem;align-items:flex-end;flex-wrap:wrap;margin:0 0 1.4rem}
.page-head h1{font-size:clamp(1.55rem,2.4vw,1.85rem);margin:0;letter-spacing:-.018em}
.page-head .sub{color:var(--muted);font-size:.9rem;margin:0}

/* ============================================================ surfaces */
.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);padding:1.15rem 1.25rem;
  box-shadow:var(--sh);margin:1rem 0}
.card>:first-child{margin-top:0}.card>:last-child{margin-bottom:0}
h2{font-size:1.18rem;margin:2rem 0 .7rem}
h3{font-size:1rem;font-family:var(--sans);font-weight:640;letter-spacing:0}
.eyebrow{font:600 .72rem/1.3 var(--sans);letter-spacing:.1em;text-transform:uppercase;color:var(--muted);
  margin:0 0 .5rem}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(13.5rem,1fr));gap:.85rem}
/* A22, the operator half. Measured at 390 px on 2026-09-10: `minmax(13.5rem,…)` cannot fit two
   columns into the ~21.9rem of usable width, so four stat tiles stacked into ~960 px of a 1560 px
   page — 62 % of a phone screen spent on four numbers, before the register the operator actually
   came for. Two columns and a smaller numeral give the same four facts in a quarter of the room.
   1.5rem bold is still WCAG large text, so `--faint` keeps its 3:1 allowance on it (the rule
   test_a16_ui.py pins). */
@media (max-width:32rem){
  .grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:.6rem}
  .grid .card{padding:.8rem .85rem}
  .grid .card strong{font-size:1.5rem}
}
.grid .card{margin:0}
.grid.pair{align-items:start}
/* Stat tiles. A count of what is WRONG is not the same kind of fact as a count of what exists, so it
   does not carry the same weight — the day the nodes page made "4 without a valid licence" read
   exactly like "4 cameras" is why this rule exists. */
.grid .card strong{display:block;font:600 1.85rem/1.15 var(--serif);letter-spacing:-.02em;
  font-variant-numeric:tabular-nums}
.card.alert{border-color:color-mix(in srgb,var(--bad) 38%,var(--line))}
.card.alert strong{color:var(--bad)}
.card.quiet strong{color:var(--faint)}
.stat strong{display:block;font:600 1.85rem/1.15 var(--serif);letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.stat span{color:var(--muted);font-size:.83rem}
.stat.alert{border-color:color-mix(in srgb,var(--bad) 38%,var(--line))}
.stat.alert strong{color:var(--bad)}
.stat.quiet strong{color:var(--faint)}

/* ============================================================ data tables */
/* Text utilities: the operator view marks a fact as quiet or alarming inline, and these classes are
   load-bearing in `ui.py` — part of the vocabulary, not decoration. */
.muted{color:var(--muted)}.faint{color:var(--faint)}
.ok{color:var(--ok)}.warn{color:var(--warn)}.bad{color:var(--bad)}
.tablewrap{overflow-x:auto;border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);
  box-shadow:var(--sh)}
table{width:100%;border-collapse:separate;border-spacing:0;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;box-shadow:var(--sh)}
.tablewrap table{border:0;border-radius:0;box-shadow:none}
th,td{text-align:left;padding:.62rem .8rem;border-bottom:1px solid var(--line);font-size:.9rem;
  vertical-align:top}
th{background:var(--surface-2);font:600 .72rem/1.4 var(--sans);letter-spacing:.08em;text-transform:uppercase;
  color:var(--muted);position:sticky;top:0;z-index:1;white-space:nowrap}
tbody tr:hover td{background:var(--surface-2)}
tr:last-child td,tr:last-child th{border-bottom:0}
td.num,th.num{text-align:right;font-variant-numeric:tabular-nums;font-family:var(--mono);font-size:.83rem}
td .id{font-family:var(--mono);font-size:.8rem;color:var(--muted)}
caption{caption-side:top;text-align:left;color:var(--muted);font-size:.85rem;padding:0 0 .5rem}
.stack{display:flex;flex-direction:column;gap:.15rem}
.stack .k{color:var(--muted);font-size:.76rem;letter-spacing:.02em}

/* ============================================================ state */
.tag{display:inline-flex;align-items:center;gap:.34rem;white-space:nowrap;padding:.12rem .5rem .12rem .42rem;
  border-radius:999px;font:600 .76rem/1.5 var(--sans);letter-spacing:.01em;
  background:var(--surface-2);border:1px solid var(--line);color:var(--ink-2)}
.tag::before{content:"";width:.42rem;height:.42rem;border-radius:50%;background:currentColor;flex:none}
.tag.ok{color:var(--ok);background:var(--ok-soft);border-color:color-mix(in srgb,var(--ok) 25%,transparent)}
.tag.warn{color:var(--warn);background:var(--warn-soft);border-color:color-mix(in srgb,var(--warn) 25%,transparent)}
.tag.bad{color:var(--bad);background:var(--bad-soft);border-color:color-mix(in srgb,var(--bad) 28%,transparent)}
.tag.muted{color:var(--muted)}
.tag.plain::before{display:none}
.msg{padding:.8rem 1rem;margin:1rem 0;border-radius:var(--r);border:1px solid currentColor;
  background:color-mix(in srgb,currentColor 7%,var(--surface));font-size:.92rem}
.msg.ok{color:var(--ok)}.msg.bad{color:var(--bad)}.msg.warn{color:var(--warn)}
.banner{padding:.45rem 1.2rem;font-size:.8rem;display:flex;gap:.45rem;align-items:center;
  background:var(--warn-soft);color:var(--warn);border-bottom:1px solid color-mix(in srgb,var(--warn) 22%,transparent)}

/* ============================================================ ADR-011: demo, sample, "try to change it" */
.banner.demo{background:var(--accent-soft);color:var(--accent);
  border-bottom-color:color-mix(in srgb,var(--accent) 22%,transparent)}
.banner.demo strong{font-weight:700}
/* Demo terms 0.5 §8.1: the notice that a demo with no camera ends — the warn colour of a plain banner, with its own
   small form (the reason and "Ask for more time") that wraps under the sentence on a narrow screen. */
.banner.notice{flex-wrap:wrap;line-height:1.45}
.banner.notice form{display:flex;flex-wrap:wrap;gap:.4rem;align-items:center;margin:0}
.banner.notice input{min-width:0;width:16rem;max-width:100%;font-size:.8rem;padding:.2rem .45rem}
.sample-note{display:flex;gap:.5rem;align-items:baseline;font-size:.86rem;color:var(--ink-2);
  padding:.55rem .8rem;border:1px dashed color-mix(in srgb,var(--accent) 40%,var(--line));border-radius:var(--r);
  background:color-mix(in srgb,var(--accent-soft) 45%,var(--surface))}
.demo-title{font-size:1.45rem;line-height:1.25;margin:.6rem 0 .35rem;letter-spacing:-.01em}
.demo-points{margin:.9rem 0 1.1rem;padding:0;list-style:none;display:grid;gap:.5rem}
.demo-points li{position:relative;padding-left:1.55rem;font-size:.93rem;color:var(--ink-2)}
.demo-points li::before{content:"";position:absolute;left:0;top:.42em;width:.8rem;height:.45rem;
  border-left:2px solid var(--accent);border-bottom:2px solid var(--accent);transform:rotate(-45deg)}
.langs{text-align:center;font-size:.84rem;margin:.8rem 0}
.langs a[aria-current=true]{font-weight:700;border-bottom-color:var(--accent)}
/* The honeypot: off-screen and unreachable, never display:none (some bots skip hidden fields). */
.hp{position:absolute;left:-10000px;top:auto;width:1px;height:1px;overflow:hidden}
.tamper-verdict{padding:.95rem 1.1rem;border-radius:var(--r);border:1px solid currentColor;font-size:.95rem;margin:.6rem 0 1rem;
  background:color-mix(in srgb,currentColor 8%,var(--surface))}
.tamper-verdict.ok{color:var(--ok)}.tamper-verdict.bad{color:var(--bad)}.tamper-verdict.neutral{color:var(--ink-2)}
.tamper-verdict strong{display:block;font-size:1.08rem;margin-bottom:.2rem}
tr.sample-row td{background:color-mix(in srgb,var(--accent-soft) 40%,transparent);font-size:.88rem;color:var(--ink-2)}
.tamper-table th,.tamper-table td{vertical-align:middle}
.tamper-table td input{width:100%;min-width:9rem;font-family:var(--mono);font-size:.86rem}
.tamper-table tr.changed{background:var(--bad-soft)}
.tamper-table tr.changed th,.tamper-table tr.changed input{color:var(--bad);font-weight:600}
.tamper-table tr.changed input{border-color:var(--bad)}

.install-steps{margin:0;padding-left:1.3rem;display:grid;gap:.9rem}
.install-steps h3{font-size:.98rem;margin:0 0 .2rem}
.install-steps p{margin:0;color:var(--ink-2);font-size:.9rem}
code.hash{word-break:break-all;font-size:.8rem}

/* On a phone the three columns do not fit and the one that matters — the inputs — scrolled off-screen (render 390 px,
   2026-09-29). Each field becomes a small card: its name, the signed value, then the box to change it. */
@media (max-width:40rem){
  .tamper-table thead{display:none}
  .tamper-table,.tamper-table tbody,.tamper-table tr,.tamper-table th,.tamper-table td{display:block;width:100%}
  .tamper-table{min-width:0}
  .tamper-table tr{padding:.6rem .75rem;border-bottom:1px solid var(--line)}
  .tamper-table th,.tamper-table td{padding:.15rem 0;border:0;background:none}
  .tamper-table td:nth-child(2)::before{content:"signed: ";color:var(--muted);font-size:.78rem}
  .tamper-table td input{min-width:0}
  .banner.demo{display:block;line-height:1.45}
}

/* Site confirmation (owner 2026-09-29): the country as three choices, and only that country's questions shown. */
.country-pick{display:flex;flex-wrap:wrap;gap:.5rem;border:0;padding:0;margin:.9rem 0 .4rem}
.country-pick legend{font:600 .82rem/1.4 var(--sans);color:var(--ink-2);margin-bottom:.35rem;padding:0}
.country-pick .pick{display:flex;align-items:center;gap:.45rem;margin:0;padding:.55rem .9rem;border:1px solid var(--control-line);
  border-radius:9px;background:var(--surface);cursor:pointer;font:500 .92rem/1.3 var(--sans);color:var(--ink)}
.country-pick .pick:has(input:checked){border-color:var(--accent);background:var(--accent-soft);color:var(--accent)}
/* U-6 (bug hunt 2026-09-30): the phone rule that widens every input to 100% stretched this radio to 69 px and wrapped
   "Another country" over two lines; three selectors beat its two, wherever it sits in the file. */
.country-pick .pick input[type=radio]{width:auto;flex:none;margin:0}
@supports selector(:has(a)){
  form .site-q{display:none}
  form:has(input[name=country][value=SI]:checked) .q-si,
  form:has(input[name=country][value=DE]:checked) .q-de,
  form:has(input[name=country][value=RS]:checked) .q-rs,
  form:has(input[name=country][value=OTHER]:checked) .q-other{display:block}
}

/* The controller's DPIA answers (2026-09-29): one page, the document's order, and three kinds of field told apart. */
.doc .answer,.doc-legend .answer{background:color-mix(in srgb,var(--accent) 16%,var(--surface));border-radius:3px;
  padding:0 .2em;overflow-wrap:anywhere}
.dpia-progress{display:flex;flex-wrap:wrap;align-items:center;gap:.6rem 1rem}
.dpia-answers .dpia-section{border:1px solid var(--line);border-radius:var(--r-lg);padding:.4rem 1.1rem 1rem;margin:0 0 1.1rem;
  background:var(--surface)}
.dpia-answers legend{font:600 1rem/1.3 var(--serif, inherit);padding:0 .4rem}
.dpia-answers textarea,.dpia-answers input:not([type=date]),.dpia-answers select{width:100%}

/* ============================================================ forms */
label{display:block;font:600 .82rem/1.4 var(--sans);margin:.9rem 0 .3rem;color:var(--ink-2)}
input,select,textarea{font:400 .95rem/1.5 var(--sans);padding:.5rem .65rem;background:var(--surface);
  color:var(--ink);border:1px solid var(--control-line);border-radius:8px;max-width:100%}
input:hover,select:hover,textarea:hover{border-color:color-mix(in srgb,var(--accent) 50%,var(--control-line))}
textarea{width:100%;min-height:15rem;font-family:var(--mono);font-size:.83rem;line-height:1.6}
/* Prose (a reason, a note) is written in the text face at a sensible height; the mono face is for JSON. */
textarea.prose{min-height:6rem;font:400 .95rem/1.5 var(--sans)}
/* `rows` is the author saying how big this one is: a 2-row reason box stood 15 rem tall on the retention
   screen and swallowed the page. The 15 rem floor stays for the textarea that declares no rows — the
   legal-document editor, which wants all the room it can get. */
textarea[rows]{min-height:0}
/* An input that is a DIRECT child of its label used to sit on the same line as the label text, with the
   closing bracket of "Keep images for (hours)" touching the box. Checkboxes and radios still want to be
   beside their words. */
label>input:not([type=checkbox]):not([type=radio]){display:block;margin-top:.3rem}
/* A text control keeps the browser's ~20-character default width, which reads as a finished
   field on a wide screen and as an unfinished one inside a full-width card on a phone — and
   is a smaller tap target than the card it sits in. Below the breakpoint every text control
   takes the width it already has. `submit` and `hidden` are excluded: a full-width button is
   a different decision, and there is nothing to widen on a hidden field. Measured at 390 px
   on /ui/licences, where the issue form's fields stopped at 72 % of the card. */
@media (max-width:48rem){
  input:not([type=submit]):not([type=hidden]),select{width:100%}
  .filters{display:grid;grid-template-columns:minmax(0,1fr);gap:.55rem}
}
button,.btn{display:inline-flex;align-items:center;gap:.45rem;font:600 .93rem/1.4 var(--sans);
  padding:.52rem 1rem;border:1px solid transparent;border-radius:9px;background:var(--accent);
  color:var(--accent-fg);cursor:pointer;text-decoration:none}
.btn{border-bottom:1px solid transparent}
button:hover,.btn:hover{background:var(--accent-2);border-bottom-color:transparent}
/* `.btn.secondary` was used by nine link-buttons (the platform's Cancel among them) and styled by nothing, so each
   rendered as the primary, green action. UI review 2026-09-28. */
button.secondary,.btn.ghost{background:var(--surface);color:var(--ink);border-color:var(--control-line)}
button.secondary:hover,.btn.ghost:hover{background:var(--surface-2)}
.btn.secondary{background:var(--surface);color:var(--ink);border-color:var(--control-line)}
.btn.secondary:hover{background:var(--surface-2)}
button.tiny,.btn.tiny{padding:.18rem .55rem;font-size:.78rem}
/* A link to a step that ends something (it opens a confirmation page) reads as a quiet red action, not a primary one. */
.btn.secondary.ends{color:var(--bad);border-color:color-mix(in srgb,var(--bad) 35%,var(--control-line))}
/* A button that destroys something must not look like the button that saves something. The only one
   today is the retention confirmation, where the word on it is "Delete N images". */
button.destructive{background:var(--bad);color:var(--accent-fg);border-color:transparent}
button.destructive:hover{background:color-mix(in srgb,var(--bad) 82%,#000)}
form.inline{display:inline}
.filters{display:flex;gap:.75rem;flex-wrap:wrap;align-items:flex-end;margin:0 0 1.1rem}
.filters label{margin:0 0 .25rem}
/* Example questions on the Ask page: chips, so a reader sees the SHAPES available at a glance
   rather than one prose line they have to parse. */
.examples{display:flex;flex-wrap:wrap;gap:.4rem;margin:0 0 1.2rem}
.examples a{border:1px solid var(--line-2);border-radius:999px;padding:.25rem .7rem;font-size:.84rem;
  color:var(--ink-2);background:var(--surface)}
.examples a:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-soft)}

/* ============================================================ a legal document read in the app (D69, /ui/dpia)
   Rendered at 390 and 1440 px, light and dark, before it shipped (2026-09-23). What the render showed, and the
   rule for each: a 64-character hash widened the phone's layout viewport to 534 px, cutting every table at the
   screen edge (`code.hash`); a heading sat on the bottom border of the table above it (`.doc .tablewrap` margin);
   the numbered sections were set smaller than their sub-sections (`.doc h3` / `h4`); a fact read from the account
   looked exactly like a field still to fill (`.filled` / `.ph`); the downloads were 20-px inline links
   (`.doc-actions`, buttons). On a phone a key/value table stacks key over value (`table.kv`) and a table of three or
   more columns stacks each row into a labelled card (`table.stack`), instead of scrolling sideways. */
code.hash{word-break:break-all;overflow-wrap:anywhere}
.doc h2{font-size:1.4rem;margin:0 0 1rem}
.doc h3{font:560 1.2rem/1.25 var(--serif);letter-spacing:-.005em;margin:2.4rem 0 .75rem;padding-top:1.1rem;
  border-top:1px solid var(--line)}
.doc h4{font:640 .98rem/1.35 var(--sans);letter-spacing:0;margin:1.7rem 0 .55rem}
.doc .tablewrap{margin:.85rem 0 1.3rem}
.doc th{white-space:normal}
.doc hr+h3{border-top:0;padding-top:0}
.doc blockquote{margin:1rem 0;padding:.8rem 1rem;border-left:3px solid var(--accent);background:var(--sunken);
  border-radius:0 var(--r) var(--r) 0}
.doc blockquote p{margin:0}
.doc li{margin:.2rem 0}
.doc td,.doc li,.doc p{overflow-wrap:break-word}
.filled{background:var(--accent-soft);color:var(--ink);border-radius:4px;padding:.05em .3em;
  box-decoration-break:clone;-webkit-box-decoration-break:clone;overflow-wrap:anywhere}
.ph{font:500 .9em/1.5 var(--sans);color:var(--warn);background:var(--warn-soft);border-radius:5px;
  border:1px dashed color-mix(in srgb,var(--warn) 55%,transparent);padding:0 .3em;overflow-wrap:anywhere;
  box-decoration-break:clone;-webkit-box-decoration-break:clone}
.doc-legend{margin:.6rem 0}
.row-action{margin:.55rem 0 0}
.row-action .btn{padding:.34rem .8rem;font-size:.86rem;min-height:2rem}
.doc-actions{display:flex;flex-wrap:wrap;gap:.5rem;margin:1rem 0}
.doc-actions .btn{min-height:2.5rem}
@media (max-width:40rem){
  .doc{padding:1rem .9rem}
  .doc table.kv,.doc table.kv tbody,.doc table.kv tr,.doc table.kv td{display:block;width:auto}
  .doc table.kv td:first-child{padding-bottom:0;border-bottom:0;font:600 .74rem/1.4 var(--sans);
    letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
  .doc table.kv td:last-child{padding-top:.2rem}
  .doc table.stack thead{display:none}
  .doc table.stack,.doc table.stack tbody,.doc table.stack tr,.doc table.stack td{display:block;width:auto}
  .doc table.stack tr{border-bottom:1px solid var(--line);padding:.45rem 0}
  .doc table.stack tr:last-child{border-bottom:0}
  .doc table.stack td{border:0;padding:.2rem .8rem}
  .doc table.stack td:first-child{font-weight:600}
  .doc table.stack td::before{content:attr(data-label);display:block;font:600 .7rem/1.4 var(--sans);
    letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}
  .doc-actions .btn{flex:1 1 100%;justify-content:center}
}

/* ============================================================ theme switch (A24)
   Three plain submit buttons in one form — System, Light, Dark — because the product ships no
   JavaScript. The pressed one says so with `aria-pressed`, a tinted background and the accent colour, not
   by colour alone. Each button is at least 24 x 24 CSS px (WCAG 2.2 target size). */
form.theme-switch{display:inline-flex;gap:2px;padding:2px;margin:0;border:1px solid var(--control-line);
  border-radius:999px;background:var(--surface)}
.theme-switch button{display:inline-grid;place-items:center;width:1.9rem;height:1.6rem;padding:0;border:0;
  border-radius:999px;background:transparent;color:var(--muted)}
.theme-switch button:hover{background:var(--surface-2);color:var(--ink)}
.theme-switch button[aria-pressed=true]{background:var(--accent-soft);color:var(--accent)}
.theme-switch svg{width:.95rem;height:.95rem;flex:none}
.auth .auth-theme{display:flex;justify-content:center;margin:1rem 0 0}

/* ============================================================ record view */
dl.record{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:.45rem 1.1rem;margin:0}
dl.record dt{color:var(--muted);font-size:.82rem;font-weight:600}
dl.record dd{margin:0;font-size:.9rem}
dl.record dd.hash{font-family:var(--mono);font-size:.8rem;word-break:break-all;color:var(--ink-2)}
img.snap{max-width:100%;border:1px solid var(--line);border-radius:var(--r);background:#000}

/* ============================================================ zone editor (A25)
   The picture is an <input type=image>: a click posts its x and y, so drawing needs no JavaScript. Those
   are CSS pixels of the DISPLAYED image (measured 2026-09-15 in Chromium and Firefox), so the copy on
   screen must not be scaled, padded or bordered, and the form rules above do all three to every input.
   With padding the engines even disagree: Chromium counts from the padding edge, Firefox from the content
   edge. Each copy's size is also set inline by ui.py, which outranks the phone rule that widens every
   input to 100 %. Three copies, one shown per width; the server learns which size was clicked from the
   name of the copy that sent the click. */
.zone-editor fieldset{border:1px solid var(--line);border-radius:var(--r-lg);padding:.3rem 1rem 1rem;margin:0 0 1rem}
.zone-editor legend{font:600 .82rem/1.4 var(--sans);color:var(--ink-2);padding:0 .3rem}
.zone-editor .fields{display:grid;grid-template-columns:repeat(auto-fit,minmax(10.5rem,1fr));gap:0 .9rem}
.zone-editor .fields input,.zone-editor .fields select{width:100%}
.zone-editor .actions{display:flex;flex-wrap:wrap;gap:.5rem;margin:.9rem 0 0}
.zone-editor .typed{display:flex;flex-wrap:wrap;gap:.5rem .6rem;align-items:flex-end}
.zone-editor .typed>div{flex:1 1 14rem;max-width:22rem}
.zone-editor .typed label{margin-top:0}
.zone-editor .typed input{width:100%}
.zone-editor h3{margin:1.8rem 0 .6rem}
.zone-editor .still-wrap{overflow-x:auto;max-width:100%;margin:.5rem 0 1rem}
.zone-editor .still{position:relative;display:inline-block;vertical-align:top;line-height:0;box-shadow:var(--sh)}
.zone-editor .still input[type=image]{display:none;margin:0;padding:0;border:0;border-radius:0;max-width:none;
  background:var(--sunken);cursor:crosshair}
.zone-editor .still input.still-s{display:block}
@media (min-width:44rem){
  .zone-editor .still input.still-s{display:none}
  .zone-editor .still input.still-m{display:block}
}
@media (min-width:80rem){
  .zone-editor .still input.still-m{display:none}
  .zone-editor .still input.still-l{display:block}
}
.zone-editor .still-overlay{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.still-overlay .z{fill:color-mix(in srgb,var(--accent) 26%,transparent);stroke:var(--accent)}
.still-overlay .ln{fill:none;stroke:var(--warn)}
.still-overlay .cur{fill:none;stroke:#fff;stroke-dasharray:6 4}
.still-overlay .pt{fill:var(--accent);stroke:#fff}
.still-overlay *{stroke-width:2.5;vector-effect:non-scaling-stroke}

/* sign in — the first screen anyone sees of Prova, including the auditor it is built for: its own
   page, one column, centred, and the unencrypted-connection notice as part of the card rather than
   a full-bleed strip that makes a working product look broken. */
body.auth-page{min-height:100vh;display:grid;place-items:center;padding:1.5rem;
  background:radial-gradient(120% 90% at 50% -10%,color-mix(in srgb,var(--accent) 8%,var(--bg)),var(--bg))}
.auth{width:100%;max-width:24.5rem}
.auth .card{padding:2rem 1.9rem 1.7rem;margin:0;border-radius:16px;box-shadow:var(--sh-lg)}
.auth .mark{margin:0 0 1rem;font-size:1.35rem}
.auth .tagline{color:var(--muted);font-size:.9rem;margin:0 0 1.5rem;line-height:1.5;text-wrap:balance}
.auth label{margin:0 0 .3rem}
.auth .field{margin:0 0 .9rem}
.auth .forgot{display:block;width:fit-content;margin:.4rem 0 0 auto;font-size:.79rem}
.auth input{width:100%;padding:.62rem .7rem;font-size:.97rem}
.auth button{width:100%;justify-content:center;padding:.68rem;margin-top:.5rem;font-size:.97rem}
.auth .theme-switch button{width:1.9rem;height:1.6rem;padding:0;margin:0;font-size:inherit}
.auth .notice{display:flex;gap:.55rem;align-items:flex-start;margin:1.3rem 0 0;padding:.65rem .75rem;
  border-radius:9px;font-size:.8rem;line-height:1.45;background:var(--warn-soft);color:var(--warn)}
.auth .foot{color:var(--muted);font-size:.79rem;margin:1.1rem 0 0;text-align:center;line-height:1.6}
.auth .msg{margin:0 0 1rem}
/* B13 sign-up: the same card, wide enough to read a contract in. Each text scrolls in its own box, and each
   confirmation is a whole clickable row, because what it says is what the company is bound by. */
.auth.wide{max-width:46rem}
.auth h2{font-size:.98rem;margin:1.4rem 0 .6rem}
.auth .hint{color:var(--muted);font-size:.84rem;margin:0 0 .8rem;line-height:1.5}
.auth select{width:100%;padding:.62rem .7rem;font-size:.97rem}
.auth details.legal-doc{border:1px solid var(--line);border-radius:10px;margin:0 0 .6rem;background:var(--surface)}
.auth details.legal-doc>summary{cursor:pointer;padding:.7rem .85rem;font-weight:600;font-size:.92rem}
.auth .legal{max-height:24rem;overflow:auto;padding:.2rem 1rem 1rem;border-top:1px solid var(--line);font-size:.86rem;
  line-height:1.6}
.auth .legal h2,.auth .legal h3,.auth .legal h4,.auth .legal h5{font-size:.93rem;margin:1.1rem 0 .4rem}
.auth .legal table{font-size:.8rem;box-shadow:none;margin:.5rem 0}
.auth .doc-meta{margin:0;padding:.5rem .85rem;font-size:.78rem;color:var(--muted);border-top:1px solid var(--line)}
.auth .confirm{display:flex;gap:.65rem;align-items:flex-start;margin:0 0 .8rem;padding:.75rem .85rem;
  border:1px solid var(--line);border-radius:10px;font-size:.9rem;line-height:1.55;cursor:pointer}
.auth .confirm input{width:auto;flex:none;margin:.2rem 0 0}
/* A legal statement set in bold for ten lines is hard to read, and reading it is the point (flow review 2026-09-29);
   the ticked box says so with the accent instead. */
.auth .confirm,.app .confirm{font-weight:400;color:var(--ink-2)}
.auth .confirm:has(input:checked),.app .confirm:has(input:checked){border-color:var(--accent);background:var(--accent-soft);
  color:var(--ink)}
.auth dl.review{display:grid;grid-template-columns:max-content 1fr;gap:.35rem 1rem;margin:0 0 .8rem;font-size:.9rem}
.auth dl.review dt{color:var(--muted)}
.auth dl.review dd{margin:0;overflow-wrap:anywhere}
.auth form.back button{width:auto;margin:0 0 .4rem}
/* D69: the site confirmations use the sign-up's confirmation box inside the app shell, and group each question. */
.app .confirm{display:flex;gap:.65rem;align-items:flex-start;margin:0 0 .8rem;padding:.75rem .85rem;
  border:1px solid var(--line);border-radius:10px;font-size:.9rem;line-height:1.55;cursor:pointer}
.app .confirm input{width:auto;flex:none;margin:.2rem 0 0}
.app fieldset.q{border:1px solid var(--line);border-radius:10px;padding:.3rem 1rem .8rem;margin:0 0 1rem}
/* A question is a sentence, and a wrapped <legend> straddles the fieldset's border on a phone (measured at 390 px):
   floated full width, it sits inside the box instead. */
.app fieldset.q legend{float:left;width:100%;font-weight:600;font-size:.9rem;padding:.6rem 0 .4rem;line-height:1.4}
.app fieldset.q legend+*{clear:both}
.app fieldset.q label.opt{display:inline-flex;gap:.4rem;align-items:center;margin:0 1.2rem .3rem 0;font-weight:400}
.app fieldset.q label.opt input{width:auto;margin:0}
@media (max-width:40rem){.auth dl.review{grid-template-columns:1fr}.auth dl.review dd{margin-bottom:.4rem}}
/* The console's DPA page (/ui/dpa, counsel 2026-09-30) shows the same documents and review as the sign-up; without these
   the DPA rendered open at full length and the page was 24 000 px tall (measured on the first render). */
.app details.legal-doc{border:1px solid var(--line);border-radius:10px;margin:0 0 .6rem;background:var(--surface)}
.app details.legal-doc>summary{cursor:pointer;padding:.7rem .85rem;font-weight:600;font-size:.92rem}
.app .legal{max-height:24rem;overflow:auto;padding:.2rem 1rem 1rem;border-top:1px solid var(--line);font-size:.86rem;
  line-height:1.6}
.app .legal h2,.app .legal h3,.app .legal h4,.app .legal h5{font-size:.93rem;margin:1.1rem 0 .4rem}
.app .legal table{font-size:.8rem;box-shadow:none;margin:.5rem 0}
.app .legal pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit;margin:.6rem 0 0}
.app .doc-meta{margin:0;padding:.5rem .85rem;font-size:.78rem;color:var(--muted);border-top:1px solid var(--line)}
.app dl.review{display:grid;grid-template-columns:max-content 1fr;gap:.35rem 1rem;margin:0 0 .8rem;font-size:.9rem}
.app dl.review dt{color:var(--muted)}
.app dl.review dd{margin:0;overflow-wrap:anywhere}
@media (max-width:40rem){.app dl.review{grid-template-columns:1fr}.app dl.review dd{margin-bottom:.4rem}}

@media (max-width:40rem){
  th,td{padding:.5rem .6rem;font-size:.86rem}
  /* A record is two columns on a desktop and ONE on a phone: at 390 px the value column is ten characters wide, so
     `dd.hash`'s break-all split a camera serial into pairs and a sha256 into a 30-line strip — measured 2026-09-16,
     the page went from 2 050 px to 6 132 px tall. The label goes above its value here, as `.auth dl.review` does. */
  dl.record{grid-template-columns:minmax(0,1fr);gap:.1rem}
  dl.record dt{margin-top:.5rem}
  dl.record dd{margin:0 0 .35rem}
  dl.record dd.hash{word-break:normal;overflow-wrap:anywhere}
  /* And the pair stacks: at 390 px `minmax(13.5rem,1fr)` still fitted two columns, so the picture was squeezed to
     ~150 px and the 240 px position schematic was clipped by its own card. Measured 2026-09-16. */
  .grid.pair{grid-template-columns:minmax(0,1fr)}
  .app>main{padding:1.1rem .9rem 3rem}
}

/* Landing-page layer over the product's design system (`theme.py`). Same tokens, same fonts, same
   accent — the person who clicks "Sign in" has to land somewhere that is visibly the same product. */
body.site{background:var(--bg)}
.site .wrap{max-width:64rem;margin:0 auto;padding:0 1.4rem}
.site section{padding:4rem 0;border-top:1px solid var(--line)}
.site section:first-of-type{border-top:0}
.site h2{font-size:clamp(1.5rem,2.6vw,2rem);margin:0 0 .8rem}
.site p{margin:0 0 1.1rem;max-width:42rem;text-wrap:pretty;color:var(--ink-2)}
.site .lede{font-size:1.08rem}

/* top bar */
.site .top{position:sticky;top:0;z-index:9;background:color-mix(in srgb,var(--bg) 88%,transparent);
  backdrop-filter:saturate(180%) blur(12px);border-bottom:1px solid var(--line)}
.site .top .wrap{display:flex;align-items:center;gap:1rem;padding-top:.75rem;padding-bottom:.75rem}
.site .top nav{display:flex;gap:.1rem;align-items:center;margin-left:auto;flex-wrap:wrap}
.site .secnav{display:flex;gap:.1rem}
@media (max-width:48rem){.site .secnav{display:none}}
.site .top nav a{color:var(--ink-2);border:0;font-size:.92rem;padding:.35rem .65rem;border-radius:8px}
.site .top nav a:hover{background:var(--surface);color:var(--accent)}
.site .top .lang{display:flex;gap:.1rem;margin-left:.4rem;padding-left:.6rem;border-left:1px solid var(--line)}
.site .top .lang a{font-size:.8rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;padding:.3rem .45rem}
.site .top .lang a[aria-current]{color:var(--accent);background:var(--accent-soft)}
.site footer .theme{display:flex}
.site .top .cta{background:var(--accent);color:var(--accent-fg);font-weight:600;margin-left:.75rem}
.site .top .cta:hover{background:var(--accent-2);color:var(--accent-fg)}
/* Phone header: the bar is ONE row in every language, and German is what proved it is not free.
   Measured at 390 px (the pinned e2e viewport): logo 84 + gap 16 + language switcher 143 + 12 +
   "Anmelden" 94 = 349 px into 345 px of content width — over by FOUR, so the sign-in button dropped
   to a second row and the German header stood 91 px tall against 60 px in the other three languages.
   That is 31 px of every phone screen spent on a wrap, and A22's budget is counted in screens.
   The fix is room, not a shorter German word: a label is one translation away from doing this again,
   and the next language we add has not been measured at all. Targets stay above the 24 px WCAG 2.2
   minimum (the switcher's links go 36 -> 31 px wide, height untouched) and the gutter stays at 16 px. */
@media (max-width:30rem){
  .site .top .wrap{padding-left:1rem;padding-right:1rem;gap:.6rem}
  .site .top .lang{margin-left:.2rem;padding-left:.4rem}
  .site .top .lang a{padding:.3rem .3rem}
  .site .top .cta{margin-left:.4rem}
}

/* hero */
.site .hero{padding:4.5rem 0 3.5rem}
.site .hero-in{display:grid;grid-template-columns:minmax(0,1fr);gap:2.6rem;align-items:center}
@media (min-width:62rem){.site .hero-in{grid-template-columns:minmax(0,1.25fr) minmax(0,1fr)}}
.site .hero h1{font-size:clamp(2.1rem,5vw,3.15rem);line-height:1.08;letter-spacing:-.022em;font-weight:560;
  margin:0 0 1.2rem;max-width:16ch}
.site .hero .tagline{font-size:1.25rem;font-weight:600;color:var(--accent);margin:-.4rem 0 .9rem}
.site .hero .lede{font-size:1.13rem;max-width:34rem;color:var(--ink-2)}
.site .actions{display:flex;gap:.7rem;flex-wrap:wrap;margin:1.8rem 0 0}
.site .hero .price-hint{font-size:.93rem;color:var(--ink-2);margin:.8rem 0 0}
.site .hero .sub a+a{margin-left:.5rem}
.site .hero .sub{font-size:.87rem;color:var(--muted);margin-top:1.2rem;max-width:32rem}

/* the specimen record */
.site .specimen{margin:0;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:1.15rem 1.3rem 1.3rem;box-shadow:var(--sh-lg)}
.site .specimen figcaption{font:600 .7rem/1.4 var(--sans);letter-spacing:.1em;text-transform:uppercase;
  color:var(--muted);margin-bottom:.9rem;display:flex;align-items:center;gap:.5rem}
.site .specimen figcaption::after{content:"";flex:1;height:1px;background:var(--line)}
.site .specimen dl{display:grid;grid-template-columns:5.4rem minmax(0,1fr);gap:.6rem 1rem;margin:0}
.site .specimen dt{color:var(--muted);font-size:.78rem;font-weight:600;padding-top:.1rem}
.site .specimen dd{margin:0;font-size:.88rem;line-height:1.45;color:var(--ink)}
.site .specimen .v{display:inline-block;font:600 .71rem/1.6 var(--mono);color:var(--accent);
  background:var(--accent-soft);border-radius:5px;padding:0 .35rem;margin-left:.25rem;white-space:nowrap}
.site .specimen .hash{font-family:var(--mono);font-size:.76rem;color:var(--muted)}
.site .specimen .verdict{display:flex;align-items:center;gap:.4rem;color:var(--ok);font-weight:600;font-size:.85rem;margin:1rem 0 0;padding-top:.85rem;border-top:1px solid var(--line)}

/* the chain */
.site .flow{display:flex;gap:.55rem;align-items:stretch;flex-wrap:wrap;margin:1.8rem 0 .8rem}
.site .step{flex:1 1 12rem;background:var(--surface);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:1rem 1.05rem;box-shadow:var(--sh)}
.site .step .n{display:block;font:600 .69rem/1.4 var(--mono);letter-spacing:.06em;color:var(--accent);
  text-transform:uppercase;margin-bottom:.45rem}
.site .step h3{font-size:.97rem;margin:0 0 .35rem}
.site .step p{font-size:.86rem;color:var(--muted);margin:0}
.site .arrow{align-self:center;color:var(--line-2);flex:0 0 auto;font-size:1.15rem}
.site .arrow::before{content:"\2192"}
@media (max-width:52rem){.site .flow{flex-direction:column}.site .step{flex:0 0 auto}.site .arrow{align-self:start;margin-left:1.3rem}
  .site .arrow::before{content:"\2193"}}

/* record cards, lists, pricing */
.site .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(16.5rem,1fr));gap:.9rem;margin:1.6rem 0 0}
.site .cards .card{margin:0}
.site .cards h3{font-size:.97rem;margin:0 0 .4rem}
.site .cards p{font-size:.88rem;color:var(--muted);margin:0}
.site .cards p+p{margin-top:.55rem}
.site ul.plain{list-style:none;padding:0;margin:1.3rem 0 0;max-width:44rem}
.site ul.plain li{padding:.7rem 0 .7rem 1.8rem;border-bottom:1px solid var(--line);font-size:.93rem;
  position:relative;color:var(--ink-2)}
.site ul.plain li:last-child{border-bottom:0}
.site ul.plain li::before{position:absolute;left:0;top:.72rem;font-weight:700;font-size:.85rem}
/* A22: the ✗ list used to be the ONLY colour on the page, which made "what Prova does not do" the
   most visually prominent claim on a page meant to sell what it DOES. The mark stays (the list is
   deliberately blunt and must not be hidden) but it is now neutral, and the accent is spent on the
   positives instead — see ul.yes below. Colour on this page always means something; here it means
   "this is a thing you get", not "this is a thing you don't". */
.site ul.no li::before{content:"\2715";color:var(--muted)}
.site ul.yes li::before{content:"\2713";color:var(--accent)}
.site ul.plain b{font-weight:640;color:var(--ink)}
.site .punch{font:560 1.2rem/1.35 var(--serif);letter-spacing:-.015em;color:var(--ink);
  margin:0 0 1.15rem;max-width:36rem}
.site h3.compat-h{font-size:1.15rem;margin:2.2rem 0 .7rem}
.site .hero .sub a{color:var(--ink-2);border:0;text-decoration:underline;text-underline-offset:.15em}
.site .hero .sub a:hover{color:var(--ink)}
.site #alongside .vs{max-width:44rem;color:var(--ink-2);font-size:1.02rem;line-height:1.55}
/* A22 design pass (2026-09-15): the positioning line was plain body text in a section like every other, so the
   one sentence that says what Prova is did not stand out. A tinted band and a larger heading carry it; ink,
   ink-2 and muted all clear AA on --accent-soft in both palettes (test_a22_design_pass.py computes it). */
.site section#alongside{background:var(--accent-soft);border-top:0}
.site #alongside h2{font-size:clamp(1.8rem,3.6vw,2.6rem);letter-spacing:-.02em;max-width:22ch}
.site .legal .lede-legal{font:560 1.15rem/1.4 var(--serif);color:var(--ink);margin:0 0 1.5rem}
.site .compat-matrix{width:100%;max-width:40rem;border-collapse:collapse;margin:1rem 0 1.5rem;
  font-size:.9rem}
.site .compat-matrix th,.site .compat-matrix td{border:1px solid var(--line);padding:.55rem .7rem;
  text-align:left;vertical-align:top}
.site .compat-matrix thead th{background:var(--surface-2);font-weight:600}
.site .compat-matrix tbody th{background:var(--surface-2);font-weight:560;width:8.5rem}
.site .partner-pdf{margin:1.5rem 0 0}

.site #start .flow{margin-top:1.4rem}
.site #start .actions,.site #start>div.wrap>p:last-of-type{margin-top:1.6rem}
.site #start .btn+.btn{margin-left:.55rem}
.site .two{display:grid;grid-template-columns:repeat(auto-fit,minmax(19rem,1fr));gap:2.2rem}
/* Pricing (owner 2026-09-15): one row of three EQUAL cards. Each reads like the old priced diagram (which part, what
   it costs, how often) and then gives the detail, so every amount is shown once. No card is marked over the
   others (owner 2026-09-10). HTML+CSS only: the selling page ships under `default-src 'none'`. */
.site ol.price{list-style:none;padding:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(14rem,1fr));
  gap:.9rem;margin:1.6rem 0 1rem}
.site ol.price>li.card{margin:0;display:flex;flex-direction:column;gap:.3rem}
.site .price .n{font:600 .69rem/1.4 var(--mono);letter-spacing:.06em;color:var(--accent);text-transform:uppercase}
.site .price .where{font-weight:640;font-size:.95rem;color:var(--ink)}
.site .price .amount{font:560 2rem/1.15 var(--serif);letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.site .price .unit{color:var(--muted);font-size:.82rem;margin:0 0 .6rem}
.site .price p{font-size:.87rem;color:var(--muted);margin:0}
/* C2: the worked example — a buyer should not have to add three prices up to learn what a site pays */
.site .price-example{font-size:.95rem;color:var(--ink-2);margin:0 0 .6rem;max-width:46rem}
.site .price-example b{color:var(--ink);font-weight:640}
.site .note{background:var(--warn-soft);border:1px solid color-mix(in srgb,var(--warn) 22%,transparent);
  border-radius:var(--r-lg);padding:1rem 1.15rem;font-size:.9rem;color:var(--ink-2)}
.site .note b{color:var(--warn)}
.site .note p:last-child{margin-bottom:0}

/* A22 — progressive disclosure, with no JavaScript and no exception to `default-src 'none'`.
   `<details>`/`<summary>` is the only disclosure primitive that works under this CSP, and it is the
   same one VAP's objections list uses (read 2026-09-10). What is collapsed is REFERENCE DETAIL whose
   heading already carries the claim — never the selling narrative: hero, the three-step flow and
   pricing stay open at every width, because a buyer must not have to click to learn what this is.
   Collapsed content is still in the DOM, so it is searchable, printable and reachable without JS. */
.site details.more{border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);
  margin:1.3rem 0 0;overflow:hidden}
.site details.more>summary{cursor:pointer;list-style:none;padding:.95rem 1.15rem;font-weight:600;
  font-size:.94rem;color:var(--ink);display:flex;align-items:center;gap:.6rem}
.site details.more>summary::-webkit-details-marker{display:none}
.site details.more>summary::after{content:"+";margin-left:auto;color:var(--accent);font-weight:700;
  font-size:1.1rem;line-height:1;font-family:var(--mono)}
.site details.more[open]>summary::after{content:"\2212"}
.site details.more[open]>summary{border-bottom:1px solid var(--line)}
.site details.more>summary:hover{background:var(--surface-2)}
.site details.more>summary:focus-visible{outline:2px solid var(--focus);outline-offset:-2px}
.site details.more .in{padding:1.1rem 1.15rem 1.2rem}
.site details.more .in>:last-child{margin-bottom:0}
.site details.more ul.plain{margin:0}
.site .reclist{max-width:44rem;margin:1.6rem 0 0}
.site .reclist details.more{margin:0 0 .55rem}
.site .reclist details.more:last-child{margin-bottom:0}

/* shot of the real product */
.site figure.shot{margin:1.8rem 0 0}
.site figure.shot picture{display:block}
.site figure.shot img{width:100%;height:auto;border:1px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--sh-lg);
  display:block}
.site figure.shot figcaption{color:var(--muted);font-size:.83rem;margin-top:.7rem;max-width:40rem}
/* A22 — at 390 px a 1440-wide dashboard scaled to fit is 210 px tall and unreadable, and it is the
   only evidence of the real product on the page. Below 52rem it renders at a legible size inside its
   own horizontal scroller instead of being shrunk to a smear; the page body still never scrolls
   sideways. `touch-action` keeps the pan from fighting the page's vertical scroll. */
@media (max-width:52rem){
  .site figure.shot{overflow-x:auto;-webkit-overflow-scrolling:touch;touch-action:pan-x pan-y;
    border-radius:var(--r-lg)}
  .site figure.shot img{width:auto;max-width:none;height:26rem}
  .site figure.shot figcaption{position:sticky;left:0}
}

/* A22 — seven sections at 4rem of vertical padding is most of the phone page's height and none of
   its content. Tightened below 52rem only; the desktop rhythm is unchanged.
   Second cut, 2026-09-15, measured on the pilot at 390 px: 2.6rem was still 42 px above and below every
   section (658 px of a 8 169 px page), and the arrows between the STACKED step cards were 29 px each plus
   their flex gaps (~190 px) between cards already numbered "1 · check", "2 · install". On a phone the
   number carries the order; the desktop row keeps its arrows. */
@media (max-width:52rem){
  .site section{padding:1.9rem 0}
  .site .hero{padding:1.9rem 0 1.6rem}
  .site .flow .arrow{display:none}
  .site .two{gap:.9rem}  /* stacked, the desktop's 2.2rem column gap read as a missing paragraph */
}

.site footer{border-top:1px solid var(--line);padding:2rem 0 3.5rem;color:var(--muted);font-size:.85rem;
  background:var(--surface-2)}
.site footer .wrap{display:flex;gap:1.2rem;flex-wrap:wrap;align-items:baseline}
.site footer a{color:var(--muted);border-bottom-color:var(--line-2)}
.site footer .end{margin-left:auto}
.site .legal{max-width:44rem;padding-block:3rem 4rem}
.site .legal h2{font-size:1.15rem;margin:2rem 0 .6rem}
.site .legal p,.site .legal li{color:var(--ink-2);font-size:.94rem}
.site .legal ul{padding-left:1.1rem}
/* ADR-011 — the demo's door on the partners page, the one call to action there that needs no e-mail to us. */
.site .demo-cta{margin:2rem 0 2.4rem;padding:1.4rem 1.5rem;border-radius:var(--r-lg);
  border:1px solid color-mix(in srgb,var(--accent) 30%,var(--line));
  background:linear-gradient(135deg,var(--accent-soft),color-mix(in srgb,var(--accent-soft) 30%,var(--surface)))}
.site .legal .demo-cta h2{margin:0 0 .4rem}
.site .legal .demo-cta p{color:var(--ink)}
