:root{
  --bg:#232322; --bg2:#2c2c2a; --bg3:#3a3937;
  --line:#444441; --line2:#5f5e5a;
  --ink:#f1efe8; --ink2:#b4b2a9;
  --ok:#97c459; --ok-ink:#173404;
  --warn-bg:#3a3020; --warn:#fac775;
  --info-bg:#1e2b3d; --info:#85b7eb;
  --danger:#f09595;
  --radius:12px; --radius-lg:16px;
  --font:system-ui,-apple-system,"Segoe UI",Roboto,"Noto Sans",sans-serif;
  --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Noto Sans Mono",monospace;
}
*{box-sizing:border-box}
/* Author `display` declarations below beat the UA's [hidden] rule, so every
   hidden() toggle in app.js needs this to actually hide anything. */
[hidden]{display:none!important}
html,body{margin:0;height:100%}
body{background:var(--bg);color:var(--ink);font-family:var(--font);font-size:16px;line-height:1.5;-webkit-font-smoothing:antialiased;overscroll-behavior:none}
.mono{font-family:var(--mono)}
.dim{color:var(--ink2)}
.tiny{font-size:12px}
/* Overrides .dim: a blocked-storage diagnostic must not be the quietest line on
   the screen. Same weight as the surrounding text, just no longer greyed out. */
.warntext{color:var(--danger);font-weight:500}
h1{font-size:24px;font-weight:500;line-height:1.35;margin:1.4rem 0 0}
h2{font-size:19px;font-weight:500;margin:0}
p{margin:0}
button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer;-webkit-tap-highlight-color:transparent}
input[type=text]{font:inherit;width:100%;background:transparent;border:0.5px solid var(--line2);border-radius:var(--radius);padding:12px 14px;color:var(--ink)}
input[type=text]::placeholder{color:var(--ink2)}
:focus-visible{outline:2px solid var(--ink);outline-offset:2px;border-radius:6px}

.screen{display:none;min-height:100dvh;flex-direction:column;max-width:520px;margin:0 auto}
.screen.active{display:flex}
.pad{padding:20px;display:flex;flex-direction:column;gap:16px}
.eyebrow{font-size:12px;color:var(--ink2);letter-spacing:.04em}
.brand{font-size:12px;color:var(--ink2);letter-spacing:.14em}
.sub{color:var(--ink2);font-size:14px;margin-top:6px}

.topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 18px;min-height:48px}
.linklike{color:var(--ink);font-size:14px;padding:8px;margin:-8px}
.anon{font-size:12px;color:var(--ink2)}

/* buttons */
.btn{display:block;width:100%;text-align:center;border-radius:var(--radius);padding:14px;font-size:16px;min-height:48px}
.btn.big{padding:15px}
.btn.primary{background:var(--ink);color:var(--bg)}
.btn.primary:active{transform:scale(.985)}
.btn.outline{border:0.5px solid var(--line2);color:var(--ink)}
.btn.ghost{color:var(--ink2)}
.btn.share{background:var(--ok);color:var(--ok-ink)}
.btn[disabled]{opacity:.45}

/* chips */
.chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px}
.chip{font-size:15px;padding:10px 18px;border-radius:20px;border:0.5px solid var(--line2);color:var(--ink);min-height:44px}
.chip[aria-pressed="true"],.chip.on{background:var(--ink);color:var(--bg);border-color:var(--ink)}

.promise{border-top:0.5px solid var(--line);padding-top:16px;margin-top:8px}
.promise-main{font-size:16px}
.promise-sub{font-size:13px;color:var(--ink2);margin-top:4px}

/* coach */

.hint{position:absolute;left:50%;transform:translateX(-50%);background:var(--ink);color:var(--bg);border-radius:10px;padding:8px 13px;font-size:13px;white-space:nowrap}
.hint-right{left:auto;right:14px;transform:none}



/* capture */
.viewfinder{position:relative;background:var(--bg3);aspect-ratio:3/4;overflow:hidden;flex:1;min-height:0}
#cam{width:100%;height:100%;object-fit:cover;display:block;background:var(--bg3)}
.cam-fallback{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:24px;text-align:center;background:var(--bg3)}
.cam-fallback .btn{width:auto;padding:12px 22px}
.wm{position:absolute;left:12px;bottom:12px;background:rgba(20,20,19,.45);border-radius:8px;padding:6px 9px}
.wm p{font-size:12px;color:var(--ink);line-height:1.5}
.wm .weak{color:var(--warn)}

/* review: the sealed frame replaces the viewfinder until you accept or retake */
.preview{position:absolute;inset:0;background:#101010}
.preview img,.preview video{width:100%;height:100%;object-fit:contain;display:block}
.vid-badge{position:absolute;top:12px;right:12px;display:flex;align-items:center;gap:6px;background:rgba(20,20,19,.62);border-radius:20px;padding:6px 11px;font-size:13px}
/* While reviewing a capture there is nothing to capture, so the shutter, the
   mode switch and the hint all go — the only decisions left are Retake/Use it. */
#s-capture.previewing .wm,
#s-capture.previewing #btnShutter,
#s-capture.previewing #modeSwitch,
#s-capture.previewing #tapHoldHint{display:none}
.review-actions{display:flex;gap:10px}
.review-actions .btn{flex:1}
.rec-badge{position:absolute;top:12px;left:12px;display:flex;align-items:center;gap:7px;background:rgba(20,20,19,.55);border-radius:20px;padding:6px 12px;font-size:13px}
.rec-dot{width:9px;height:9px;border-radius:50%;background:var(--danger)}
@media (prefers-reduced-motion:no-preference){.rec-dot{animation:blink 1.1s ease-in-out infinite}}
@keyframes blink{50%{opacity:.35}}

.capture-panel{padding:14px 18px 22px;display:flex;flex-direction:column;gap:12px}
.cat-line{text-align:center;font-size:16px;min-height:44px;display:flex;align-items:center;justify-content:center;gap:7px;cursor:pointer}
/* Choosing a category is required — it is what routes the report to a
   department — so an unset one is styled as the outstanding action, not as
   ordinary grey text nobody notices until they are refused. */
.cat-line.unset{color:var(--warn)}
.cat-line.unset .dim{color:var(--warn);opacity:.75}
#catIcon{display:inline-flex;align-items:center}
/* Chips carry a glyph plus their words. */
.chip{display:inline-flex;align-items:center;gap:7px}
.cat-line .dim{font-size:13px}

/* Icons are stroked, never filled, so one glyph works on any background and
   inherits the colour of whatever it sits in. */
.svg-sprite{position:absolute;width:0;height:0;overflow:hidden}
.ic{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.9;
    stroke-linecap:round;stroke-linejoin:round;flex:0 0 auto;pointer-events:none}
.ic.sm{width:18px;height:18px}
.ic.lg{width:28px;height:28px}

/* Photo | Video. A visible mode beats a hidden gesture: the old build hid video
   behind a long-press on an unlabelled circle. */
.modeswitch{display:flex;justify-content:center;gap:4px;margin-bottom:12px}
.mode{display:flex;align-items:center;gap:6px;font:inherit;font-size:13px;
      padding:7px 14px;min-height:38px;border-radius:19px;border:0.5px solid transparent;
      background:none;color:var(--ink2);cursor:pointer}
.mode.on{background:var(--bg3);border-color:var(--line2);color:var(--ink)}
.mode[hidden]{display:none}

.controls-row{display:flex;align-items:flex-start;justify-content:center;gap:26px}
/* The side controls are icon + word. An icon alone has to be guessed; the word
   removes the guess without costing a tap. */
.side-btn{display:flex;flex-direction:column;align-items:center;gap:5px;
          background:none;border:0;padding:0;font:inherit;cursor:pointer;flex:0 0 auto;
          width:64px;color:var(--ink)}
.side-circle{width:48px;height:48px;border-radius:50%;background:var(--bg3);
             display:flex;align-items:center;justify-content:center;flex:0 0 auto;
             transition:background .12s ease,transform .12s ease}
.side-btn:active .side-circle{transform:scale(.93)}
.side-label{font-size:12px;color:var(--ink2);line-height:1.1;text-align:center}
/* Recording: the whole control goes red and the label says how to stop. */
.side-btn.recording .side-circle{background:var(--danger);color:var(--bg)}
.side-btn.recording .side-label{color:var(--danger)}
.shutter{position:relative;width:76px;height:76px;border-radius:50%;border:2px solid var(--ink2);display:flex;align-items:center;justify-content:center;flex:0 0 auto}
.shutter-core{width:60px;height:60px;border-radius:50%;background:var(--ink);transition:transform .12s ease,background .12s ease}
.shutter:active .shutter-core{transform:scale(.92)}
.shutter.recording .shutter-core{background:var(--danger);border-radius:16px;transform:scale(.55)}
/* Armed for video but not yet rolling: a red dot, the universal "will record". */
.shutter.videomode .shutter-core{background:var(--danger)}
.shutter.videomode.recording .shutter-core{border-radius:16px;transform:scale(.55)}
.hold-ring{position:absolute;inset:-2px;transform:rotate(-90deg);pointer-events:none}
.hold-ring circle{fill:none;stroke:var(--danger);stroke-width:3;stroke-linecap:round;stroke-dasharray:220;stroke-dashoffset:220}
.shutter.recording .hold-ring circle{transition:stroke-dashoffset 10s linear;stroke-dashoffset:0}
.hint-line{text-align:center;font-size:13px;color:var(--ink2);min-height:18px}

.voice-chip{display:flex;align-items:center;gap:11px;border:0.5px solid var(--line2);border-radius:14px;padding:10px 13px}
.vc-text{flex:1;min-width:0;font-size:14px}
.x{font-size:18px;color:var(--ink2);padding:6px 10px;margin:-6px -6px -6px 0}

/* sheet */
.sheet-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9}
.sheet{position:fixed;left:0;right:0;bottom:0;z-index:10;background:var(--bg);border-radius:16px 16px 0 0;padding:14px 18px calc(20px + env(safe-area-inset-bottom));max-width:520px;margin:0 auto;max-height:80dvh;overflow:auto}
.grab{width:34px;height:4px;border-radius:2px;background:var(--line2);margin:0 auto 14px}
.sheet-label{font-size:13px;color:var(--ink2)}
.sub-block{margin-top:16px;padding-top:14px;border-top:0.5px solid var(--line)}
.sheet .btn{margin-top:18px}

/* confirm */
.mapbox{flex:1;min-height:0;background:var(--bg3)}
#map{z-index:1}
.map-fallback{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:20px;text-align:center}
.map-fallback svg{width:min(320px,80%);}
.map-fallback line{stroke:var(--line2);stroke-width:11}
.map-fallback .acc{fill:var(--ink2);fill-opacity:.13;stroke:var(--ink2);stroke-width:.5}
.map-fallback .pin{fill:var(--ink)}
.map-fallback .mono{font-size:13px}
.map-fallback .dim{font-size:12px}

/* receipt */
.sealed-row{display:flex;align-items:center;gap:9px}
.sealed-row h2{flex:0 0 auto}
.sealed-row .mono{margin-left:auto;font-size:12px}
.dot{width:11px;height:11px;border-radius:50%;flex:0 0 auto}
.dot.ok{background:var(--ok)}
.banner{border-radius:10px;padding:11px 13px;font-size:13px;line-height:1.55}
.banner.warn{background:var(--warn-bg);color:var(--warn)}
.banner.info{background:var(--info-bg);color:var(--info)}
/* A bare .banner (no .warn/.info) is the neutral one — used where the fact is
   important but is neither a warning nor a tip, e.g. "no notified service". */
.banner:not(.warn):not(.info){background:var(--bg3);color:var(--ink)}
/* The label line inside a banner. Uppercase and small so a banner can carry a
   heading without a real <h*> competing with the screen's own hierarchy. */
.banner .lbl{font-size:11px;letter-spacing:.06em;text-transform:uppercase;opacity:.8;margin:0 0 3px}
.banner p{margin:0}
.banner p + p{margin-top:5px}

/* ADVISE MODE. The receipt used to be the busiest screen in the app and the
   reporter said it was cramped; this one carries strictly more, so the blocks are
   separated by real space and a rule rather than being stacked tighter. */
.advblock{display:flex;flex-direction:column;gap:9px;margin:26px 0 0;
          padding-top:20px;border-top:0.5px solid var(--line2)}
.advblock h3{font-size:15px;font-weight:500;margin:0}
#adviceRouteSlot{margin-top:18px}
#adviceDraft{font:inherit;font-size:14px;line-height:1.6;width:100%;min-height:190px;
             background:var(--bg2);border:0.5px solid var(--line2);border-radius:var(--radius);
             padding:13px 14px;color:var(--ink);resize:vertical}
#s-advice h2{margin:14px 0 0}

/* nearby + reports */
.case-card{border:0.5px solid var(--line);border-radius:14px;padding:14px;margin-top:12px}
.case-card .title-row{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
.case-card .title-row .mono{font-size:12px;color:var(--ink2)}
.yn{display:flex;gap:8px;margin-top:12px}
.yn button{flex:1;border-radius:11px;padding:12px 0;font-size:15px;min-height:48px}
.yn .yes{background:var(--ink);color:var(--bg)}
.yn .no{border:0.5px solid var(--line2);color:var(--ink)}
.yn button.picked{outline:2px solid var(--ok)}

.timeline{display:flex;align-items:center;gap:5px;margin-top:12px}
.timeline .td{width:9px;height:9px;border-radius:50%;flex:0 0 auto}
.timeline .td.full{background:var(--ink)}
.timeline .td.empty{border:1.5px solid var(--line2)}
.timeline .td.done{background:var(--ok)}
.timeline .tl{flex:1;height:2px;background:var(--line2)}
.timeline .tl.full{background:var(--ink)}
.tlabels{display:flex;justify-content:space-between;margin-top:5px;font-size:12px}
.tlabels .on{color:var(--ink)}
.tlabels .off{color:var(--ink2)}
.tlabels .fx{color:var(--ok)}

.toast{position:fixed;left:50%;bottom:26px;transform:translateX(-50%);background:var(--ink);color:var(--bg);border-radius:20px;padding:10px 18px;font-size:14px;z-index:20;max-width:88%;text-align:center}

.empty{color:var(--ink2);font-size:14px;text-align:center;padding:32px 0}

/* confirm: coordinates, accuracy and the landmark the crew actually navigates by */
.loc-line{display:flex;align-items:baseline;justify-content:center;gap:8px;font-size:13px;flex-wrap:wrap}
.loc-line .mono{font-size:13px}
.btn.danger{border:0.5px solid var(--danger);color:var(--danger)}

/* report cards: evidence thumbnail + when + status, tappable through to detail */
button.case-card{display:block;width:100%;text-align:left}
.card-head{display:flex;gap:12px;align-items:flex-start}
.card-head .grow{flex:1;min-width:0}
.thumb{width:56px;height:56px;border-radius:10px;object-fit:cover;display:block;flex:0 0 auto;background:var(--bg3)}
.card-title{font-size:15px;line-height:1.4}
.card-note{font-size:13px;color:var(--ink2);margin-top:2px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.card-meta{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-top:5px;font-size:12px;color:var(--ink2)}
.pill{font-size:11px;border-radius:20px;padding:2px 8px;border:0.5px solid var(--line2);color:var(--ink2);white-space:nowrap}
.pill.sent{border-color:var(--ok);color:var(--ok)}
.pill.queued{border-color:var(--warn);color:var(--warn)}

/* detail */
.detail-media{border-radius:var(--radius);overflow:hidden;background:var(--bg3)}
.detail-media img,.detail-media video{width:100%;display:block}
.kv{display:flex;flex-direction:column;gap:10px;border-top:0.5px solid var(--line);padding-top:14px}
.kv-row{display:flex;gap:12px;font-size:14px;align-items:baseline}
.kv-row .k{color:var(--ink2);flex:0 0 36%}
.kv-row .v{flex:1;min-width:0;overflow-wrap:anywhere}

.attrib{margin-top:6px;border-top:0.5px solid var(--line);padding-top:12px}
.attrib summary{font-size:12px;color:var(--ink2);cursor:pointer;list-style:none}
.attrib summary::-webkit-details-marker{display:none}
.attrib summary::before{content:"› ";}
.attrib[open] summary::before{content:"⌄ ";}
.attrib p{font-size:11px;color:var(--ink2);line-height:1.5;margin-top:8px}

.testbanner{position:sticky;top:0;z-index:99;background:#8a1c1c;color:#fff;padding:7px 12px;text-align:center}
.warnbanner{background:var(--danger);color:#2a1414}
.testbanner p{margin:0;font-size:12px;font-weight:600;line-height:1.35}

/* official route — shown on the receipt, after the report is sealed */
.route{border-top:0.5px solid var(--line);margin-top:18px;padding-top:14px;display:flex;flex-direction:column;gap:10px}
.route h3{font-size:15px;font-weight:600;margin:0}
.route-dl{display:flex;flex-direction:column;gap:8px;margin:0}
.route-dl dt{font-size:12px;color:var(--ink2);margin:0}
.route-dl dd{font-size:14px;margin:0 0 2px;overflow-wrap:anywhere}
.route-dl dd.dim{color:var(--ink2)}
.ladder{border:0.5px solid var(--line2);border-radius:12px;padding:11px 13px;margin-top:4px}
.ladder>summary{cursor:pointer;font-size:14px;list-style:none}
.ladder>summary::-webkit-details-marker{display:none}
.ladder>summary::after{content:" ▾";color:var(--ink2)}
.ladder[open]>summary::after{content:" ▴"}
.ladder ol{margin:10px 0 0;padding-left:20px;display:flex;flex-direction:column;gap:9px}
.ladder li{font-size:14px;line-height:1.45}
.ladder .who{font-weight:500}
.ladder .when{color:var(--ink2);font-size:13px;display:block}
.route-tip{font-size:13px;line-height:1.5;background:var(--bg3);border-radius:10px;padding:9px 11px;margin:0}
.route-actions{display:flex;flex-wrap:wrap;gap:8px}
.route-actions .btn{flex:0 1 auto;font-size:13px;padding:9px 12px;text-decoration:none;text-align:center}

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

/* ---- first-run coach tip (inline, on the capture screen) ----
   Replaces the old s-coach interstitial. Sits just above the real controls it
   describes, so the hints point at the actual buttons. Gentle slide+fade in —
   never a flash: repeated flashing is a seizure risk (WCAG 2.3.1) and reads as
   an error, and this is a hint. Honors prefers-reduced-motion. */
.coachtip{position:relative;margin:0 12px 8px;padding:10px 34px 10px 13px;
  background:var(--ink);color:var(--bg);border-radius:12px;
  box-shadow:0 6px 20px rgba(0,0,0,.28);
  /* Visible BY DEFAULT. The entrance is a CSS animation that runs when the
     element is shown, not a class JS must add: an earlier version waited on
     requestAnimationFrame, and where rAF does not fire (a background tab, some
     WebViews) the tip stayed at opacity 0 — present, sized, and invisible.
     No `both` fill either: with the base at opacity 1, an animation that never
     runs leaves the tip VISIBLE rather than stuck on its first keyframe. */
  opacity:1;animation:coachIn .24s ease}
@keyframes coachIn{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
.coachtip-row{margin:0 0 4px;font-size:13px;line-height:1.45}
.coachtip-row b{font-weight:650}
.coachtip-x{position:absolute;top:4px;right:6px;background:none;border:0;
  color:var(--bg);opacity:.75;font-size:20px;line-height:1;padding:4px 6px;cursor:pointer}
.coachtip-x:hover{opacity:1}
.coachtip-ok{margin-top:6px;padding:6px 14px;font-size:13px;min-height:0;
  border-color:rgba(255,255,255,.5);color:var(--bg)}
/* Reduced motion: appear, do not animate. Still visible. */
@media (prefers-reduced-motion:reduce){
  .coachtip{animation:none}
}

/* ---- MOBILE TOUCH: make the hold controls behave like buttons ----
   On iOS a long-press on any element raises the text-selection MAGNIFIER and
   the callout menu, and the browser may claim the gesture for panning. The
   shutter and mic are HOLD controls, so every one of those fired instead of the
   recording: the reporter saw their button magnify and nothing record. iOS also
   needs `user-select:none` — preventDefault() on pointerdown does not stop the
   callout on its own. touch-action:none keeps the gesture ours for the whole
   press rather than losing it to a scroll. */
.shutter,.side-btn,.mode,.coachtip-x,.coachtip-ok{
  -webkit-touch-callout:none;
  -webkit-user-select:none;-moz-user-select:none;user-select:none;
  touch-action:none;
  -webkit-tap-highlight-color:transparent;
}
/* Everything else that is tappable still gets the fast-tap treatment, but may
   scroll normally. */
button,a.btn,.chip,.cat-line{touch-action:manipulation;-webkit-tap-highlight-color:transparent}
/* The video element must never be selectable/callout-able either — a long-press
   over the viewfinder was raising "Save Image". */
#cam,#previewImg,#previewVid,.viewfinder{
  -webkit-touch-callout:none;-webkit-user-select:none;user-select:none;
}

/* ---- MAP PIN ----
   Leaflet's default marker is a PNG from unpkg, which our CSP img-src does not
   allow (self/data/blob/tile.openstreetmap.org only) — so it rendered as an
   empty shaded box. A CSS circle needs no image at all, works offline, and is
   the clear blue dot people expect to drag. */
.pin-dot{
  width:26px;height:26px;border-radius:50%;
  background:#1e6fe8;border:3px solid #fff;
  box-shadow:0 0 0 2px rgba(30,111,232,.35),0 3px 10px rgba(0,0,0,.4);
  cursor:grab;
}
.pin-dot:active{cursor:grabbing;transform:scale(1.08)}
.leaflet-marker-draggable{cursor:grab}

/* ---- readable, copyable portal address ---- */
.url-line{margin:-4px 0 8px;word-break:break-all;font-family:var(--mono,ui-monospace,monospace);
  font-size:11.5px;line-height:1.4;-webkit-user-select:text;user-select:text}

/* ---- RECEIPT BREATHING ROOM ----
   The post-submission screen carries the most information in the app: case id,
   seal, outcome, statutory deadline, the official route, contacts and the
   escalation ladder. Stacked at uniform density it read as one wall. Group it:
   clear space between sections, a rule before each heading, and generous line
   height on the parts people actually have to READ and act on. */
#s-receipt .pad{padding-bottom:40px}
#s-receipt h3{margin-top:22px;padding-top:14px;border-top:1px solid var(--line,#e6e3da);
  font-size:14px;letter-spacing:.01em}
#s-receipt h3:first-of-type{border-top:0;padding-top:0}
#s-receipt .banner{margin:12px 0;line-height:1.5}
#s-receipt .actions{display:flex;flex-direction:column;gap:8px;margin:10px 0 4px}
#s-receipt .actions .btn{width:100%;text-align:center}
.sealed-row{gap:10px;margin-bottom:2px}
#hashLine{margin-bottom:14px;word-break:break-all;line-height:1.45}

/* ---- scannable location on the receipt ---- */
.qrbox{display:flex;gap:14px;align-items:center;margin:14px 0;padding:12px;
  background:var(--card,#fff);border:1px solid var(--line,#e6e3da);border-radius:12px}
.qrbox canvas{width:120px;height:120px;flex:0 0 120px;image-rendering:pixelated;
  background:#fff;border-radius:6px}
.qrbox-text{min-width:0}
.qrbox-text p{margin:0 0 4px;line-height:1.45;word-break:break-word}
@media (max-width:360px){.qrbox{flex-direction:column;align-items:flex-start}}

/* ---- PWA install / update bars ----
   Above every screen, dismissible, and never over the shutter: an install nag
   that blocks a capture would cost the very report it is trying to make easier. */
.pwabar{position:sticky;top:0;z-index:40;display:flex;flex-wrap:wrap;gap:8px 12px;
  align-items:center;justify-content:space-between;
  padding:10px 14px;background:var(--ink);color:var(--bg);
  box-shadow:0 2px 10px rgba(0,0,0,.2)}
.pwabar.update{background:#1e6fe8}
.pwabar p{margin:0;font-size:13px;line-height:1.4;flex:1 1 200px}
.pwabar .tiny{flex-basis:100%;opacity:.85;font-size:12px}
.pwabar-actions{display:flex;gap:8px;align-items:center;flex:0 0 auto}
.pwabar .btn.tiny{padding:5px 12px;font-size:12.5px;min-height:0;
  border-color:rgba(255,255,255,.55);color:var(--bg)}
.pwabar .linklike.tiny{font-size:12.5px;color:var(--bg);opacity:.8}
.pwabar .linklike.tiny:hover{opacity:1}


/* ---- PUBLIC WORK CONNECTED TO THIS COMPLAINT ----
   Layer 1 is ONE LINE with a [Show]. That is a layout constraint carrying a product
   rule: a procurement list on the first screen would bury the thing the citizen
   actually came for (who has to fix it, and by when) under records that may not even
   be about their spot. The 44 px minimum on the toggle is the same tap-target floor
   every other control here meets. */
.pw{border-top:0.5px solid var(--line);margin-top:18px;padding-top:14px}
.pw-line{display:flex;flex-wrap:wrap;gap:6px 12px;align-items:baseline;margin:0;
  font-size:14px;line-height:1.5}
.pw-line #pwHeadline{flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
.pw-line .linklike{flex:0 0 auto;min-height:44px}
.pw-state{font-size:14px;line-height:1.5;margin:12px 0 4px}
.pw-warn{color:var(--warn);font-size:13px;line-height:1.5;margin:6px 0}
/* Each bucket is a heading, so "cancelled or re-tendered" can never be read as a
   subheading of "could be the contract covering your problem". */
.pw-bucket{font-size:13px;font-weight:600;color:var(--ink2);margin:16px 0 6px;
  text-transform:none}
.pw-card{background:var(--bg2);border:0.5px solid var(--line);border-radius:var(--radius);
  padding:12px 13px;margin:0 0 10px}
.pw-work{font-size:14px;line-height:1.5;margin:0 0 10px;overflow-wrap:anywhere}
.pw-dl{display:grid;grid-template-columns:minmax(7.5rem,auto) 1fr;gap:4px 12px;margin:0}
.pw-dl dt{font-size:12px;color:var(--ink2);margin:0}
.pw-dl dd{font-size:13.5px;margin:0;overflow-wrap:anywhere}
/* Under ~360 px the two-column list stops being a list and starts being two words
   per line, so it stacks. */
@media (max-width:360px){
  .pw-dl{grid-template-columns:1fr;gap:1px 0}
  .pw-dl dd{margin-bottom:7px}
}
.pw-why{margin-top:10px}
.pw-why summary{font-size:12.5px;color:var(--ink2);min-height:44px;
  display:flex;align-items:center;cursor:pointer}
.pw-why p{margin:2px 0}
.pw-full{display:inline-block;margin-top:10px;font-size:13px;color:var(--info);
  min-height:44px;line-height:44px}
.pw-disc{border-top:0.5px solid var(--line);margin-top:14px;padding-top:10px}
.pw-disc .lbl{font-size:13px;color:var(--ink2);margin:0 0 6px}
.pw-disc p{margin:0 0 6px;line-height:1.5}

/* ==========================================================================
   THE ANSWER FLOW — what is it / where is it / the answer
   ==========================================================================

   ── THE MEASUREMENT THIS SECTION EXISTS TO FIX ────────────────────────────────
   At 390 x 844 (an iPhone 13/14/15 at dpr 3) the advice screen measured 8,673 px:
   10.3 phone screens, in one uniform column, with 270 text nodes at 12 px or below
   and a tender record 780 px tall carrying 22 labelled fields. Nothing on it was
   wrong; all of it was unreadable, because there was no order of importance
   expressed in the layout at all.

   Three rules follow, and every rule below is one of them:
     1. NOTHING SMALLER THAN 13 px on a screen a person reads outdoors. 12 px was
        being used as "less important", and importance is not a font size.
     2. EVERY CONTROL 44 px. Not most of them.
     3. THE CLOSED STATE MUST STILL ANSWER. A collapsed card whose summary says only
        "Who has to fix this" has hidden the answer; one that says "HSVP · 10 days"
        has summarised it. */

/* ---- 01 · the broad-group picker ---- */
.grouplist{display:flex;flex-direction:column;gap:10px}
.group{display:block;width:100%;text-align:left;border:0.5px solid var(--line2);
  border-radius:var(--radius-lg);padding:14px 15px;background:var(--bg2);
  min-height:44px}
.group[aria-expanded="true"]{border-color:var(--ink);background:var(--bg3)}
/* A single-category group IS the choice, so it reads as selected rather than open. */
.group[aria-pressed="true"],.group.on{border-color:var(--ink);background:var(--ink);color:var(--bg)}
.group.on .group-sub{color:var(--bg);opacity:.72}
.group.on .group-noclock{color:#7a4a00;opacity:1;font-weight:600}
.group-top{display:flex;align-items:center;gap:11px}
.group-name{font-size:17px;line-height:1.3;flex:1;min-width:0}
.group-chev{color:var(--ink2);font-size:15px;flex:0 0 auto}
/* The bodies this group actually routes to, and whether a clock exists. Derived
   from routing.js at runtime — see renderGroups — never written in the markup. */
.group-sub{font-size:13px;color:var(--ink2);line-height:1.45;margin-top:5px}
.group-noclock{color:var(--warn)}
.group-body{margin-top:12px;padding-top:12px;border-top:0.5px solid var(--line)}
.group-body .chips{margin-top:0}

/* ---- 02 · the place picker ---- */
.orline{display:flex;align-items:center;gap:10px;font-size:13px;color:var(--ink2);
  margin:6px 0 0}
.orline::before,.orline::after{content:"";flex:1;height:0.5px;background:var(--line)}
.chips.seg{margin-top:0;gap:6px}
#whereFilter{font:inherit;font-size:16px;width:100%;background:transparent;
  border:0.5px solid var(--line2);border-radius:var(--radius);padding:12px 14px;
  color:var(--ink);min-height:48px;-webkit-appearance:none;appearance:none}
#whereFilter::placeholder{color:var(--ink2)}
/* Capped in height and scrolled INSIDE itself: 207 villages as a page-length list
   would push the Continue button off the bottom of a 5-inch screen, which is how a
   picker becomes a dead end. */
.picklist{display:flex;flex-direction:column;gap:2px;max-height:46dvh;overflow-y:auto;
  border:0.5px solid var(--line);border-radius:var(--radius);
  -webkit-overflow-scrolling:touch;overscroll-behavior:contain}
.picklist:empty{display:none}
.pick{display:flex;align-items:center;gap:10px;width:100%;text-align:left;
  padding:12px 14px;min-height:48px;font-size:16px;border-bottom:0.5px solid var(--line)}
.pick:last-child{border-bottom:0}
.pick[aria-selected="true"]{background:var(--ink);color:var(--bg)}
.pick .pick-sub{font-size:13px;color:var(--ink2);margin-left:auto;flex:0 0 auto}
.pick[aria-selected="true"] .pick-sub{color:var(--bg);opacity:.7}
.pick-empty{padding:16px 14px;font-size:14px;color:var(--ink2)}
/* The primary action stays reachable without hunting for it. Not `position:fixed`:
   the answer flow is a normal document and a fixed bar would cover the last list row
   on a short screen — the very row the citizen is trying to tap. */
.sticky-cta{margin-top:4px}

/* ---- 03 · the answer, as four cards ---- */
/* A TIGHTER RHYTHM ON THIS SCREEN ONLY. .pad's 16px gap is right for screens with a
   handful of elements; here there are eleven, and at 390x844 it put the Continue button
   4px past the fold — close enough that both actions read as reachable and one is not.
   13px across eight gaps buys back the 24px, and the cards carry their own internal
   padding so the grouping does not weaken. */
#s-advice .pad{gap:13px}
/* The place the whole answer is conditional on. Sits with its heading, so a wrong
   sector is visible at a glance rather than three screens down in the drafted letter. */
.advice-where{font-size:14px;color:var(--ink2);line-height:1.45;margin:-8px 0 0;
  overflow-wrap:anywhere}
.acard{border:0.5px solid var(--line2);border-radius:var(--radius-lg);
  background:var(--bg2);overflow:hidden}
.acard + .acard{margin-top:-6px}          /* .pad's gap already separates them */
.acard > summary{display:flex;align-items:center;gap:10px;padding:15px 16px;
  min-height:56px;cursor:pointer;list-style:none;
  -webkit-tap-highlight-color:transparent}
.acard > summary::-webkit-details-marker{display:none}
.acard-h{font-size:16px;font-weight:500;line-height:1.35;flex:0 1 auto}
/* THE ANSWER, IN THE CLOSED STATE. Right-aligned, never dimmed, and never smaller
   than the body text: this is the line most citizens will read INSTEAD of opening
   the card, so styling it as a caption would be styling the answer as an aside. */
.acard-sum{margin-left:auto;font-size:15px;color:var(--ink);text-align:right;
  flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
.acard-sum.none{color:var(--warn)}
.acard-sum.ok{color:var(--ok)}
.acard-sum.quiet{color:var(--ink2)}
/* The affordance. A chevron, because a citizen who cannot see that a card opens will
   never open it, and the summary alone does not say "there is more". */
.acard > summary::after{content:"▾";color:var(--ink2);flex:0 0 auto;font-size:15px}
.acard[open] > summary::after{content:"▴"}
.acard[open] > summary{border-bottom:0.5px solid var(--line)}
.acard-body{padding:14px 16px 16px;display:flex;flex-direction:column;gap:12px}
/* Inside a card the route block is already inside a bordered container, so its own
   top rule and margin are doubled framing. */
.acard-body .route,.acard-body .pw{border-top:0;margin-top:0;padding-top:0}
.acard-body .subblock{border-top:0.5px solid var(--line);padding-top:14px;
  display:flex;flex-direction:column;gap:9px}
.acard-body .subblock h4{font-size:15px;font-weight:500;margin:0}

/* ---- 04 · the tender card, four lines ----
   WHAT THIS REPLACES: a 780 px card with a 22-row definition list on its face —
   tender id, department, published location, estimate, TCV, ABL, contractor, award,
   status, dates, schedule, DLP — every one of them true, none of them the thing a
   citizen came for. Sixteen of those stacked in one column was the "scamped up" wall.

   The face now carries four lines, in the order a person actually asks them in:
     WHEN was this bought · WHAT was bought · HOW MUCH for · WHO got it
   The full field list is unchanged and one tap away, because removing a published
   fact to tidy a layout would be the one thing this feature must not do. */
.tcard{background:var(--bg2);border:0.5px solid var(--line);
  border-radius:var(--radius);padding:13px 14px;margin:0}
.tcard + .tcard{margin-top:9px}
.tc-top{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;
  font-size:13px;color:var(--ink2)}
.tc-year{font-variant-numeric:tabular-nums;font-weight:600;color:var(--ink)}
.tc-where{overflow-wrap:anywhere}
/* Two lines, then an ellipsis. These titles run to 300 characters in the corpus and
   the full text is in the details block below, verbatim. */
.tc-what{font-size:15px;line-height:1.45;margin:6px 0 0;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.tc-money{font-size:16px;line-height:1.4;margin:8px 0 0;
  font-variant-numeric:tabular-nums}
.tc-money .tc-lbl{font-size:13px;color:var(--ink2);font-variant-numeric:normal}
.tc-who{font-size:14px;line-height:1.45;margin:4px 0 0;color:var(--ink);
  overflow-wrap:anywhere}
.tc-who.absent{color:var(--ink2)}
/* A cancelled or re-tendered record is history, never a live obligation
   (ASSET_CONTRACT.md §8.5), so it says so on the FACE and not inside a detail
   nobody opens. */
.tc-flag{font-size:13px;line-height:1.45;margin:8px 0 0;color:var(--warn)}
.tc-acts{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
/* Declared AFTER .pw-full so it wins at equal specificity: the layer-3 link keeps its
   `pw-full` class as a stable hook, and that rule's block layout (line-height:44px for
   a centred one-liner) must not leak into a flex row of buttons. */
.tc-acts > *{flex:0 1 auto;font-size:14px;padding:10px 13px;min-height:44px;
  display:inline-flex;align-items:center;line-height:1.3;border-radius:var(--radius);
  border:0.5px solid var(--line2);color:var(--ink);text-decoration:none;
  background:none;cursor:pointer;margin-top:0}
.tc-acts a{color:var(--info);border-color:var(--info)}
.tc-more{margin-top:12px;padding-top:12px;border-top:0.5px solid var(--line)}
/* THE FIELD LIST STACKS INSIDE A CARD, AT EVERY WIDTH.
   .pw-dl is a two-column grid whose label track is minmax(7.5rem, auto). `auto`
   resolves to max-content, and the longest label here is "Total Contract Value (as
   published)" — about 250 px. Inside a tender card the row has roughly 322 px to work
   with at a 390 px viewport (page padding 40 + card padding 28), so the label took
   nearly all of it and every value rendered one word per line: "The / government /
   expected / this / to / cost / ₹1,68,45…". Captured on the deployed build.
   There is a 360 px media query that already stacks this, which is exactly why the
   fault was invisible — it only appears on the phones ABOVE the breakpoint. Stacking
   unconditionally inside a card is the fix; the label is short, the value is a
   sentence, and putting a sentence in a side column was the mistake. */
.tc-more .pw-dl{grid-template-columns:1fr;gap:1px 0}
.tc-more .pw-dl dt{margin-top:8px}
.tc-more .pw-dl dt:first-child{margin-top:0}
.tc-more .pw-dl dd{margin-bottom:0}

/* ---- the bands inside a bucket ----
   A tender that names the citizen's SECTOR and one that only names their WARD were
   being shown as one flat list of fifteen. They are not equally relevant, and the
   difference is checkable rather than a matter of taste: the band heading states
   which area level the join was actually made on, and carries its own count, so the
   number a citizen reads always matches the rows they can see. */
.pw-band{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  margin:14px 0 8px;padding:10px 0;min-height:44px;font-size:14px;font-weight:600;
  color:var(--ink)}
.pw-band .pw-band-n{margin-left:auto;font-size:13px;font-weight:400;color:var(--ink2);
  font-variant-numeric:tabular-nums;flex:0 0 auto}
.pw-band[aria-expanded]::after{content:"▾";color:var(--ink2);font-weight:400;
  flex:0 0 auto}
.pw-band[aria-expanded="true"]::after{content:"▴"}
.pw-band-lvl{font-size:13px;font-weight:400;color:var(--ink2)}
/* States which rows are visible out of how many. The headline used to read "15
   records" above four visible cards, and a count that does not match what is on
   screen reads as either a bug or a boast. */
.pw-count{font-size:13px;color:var(--ink2);margin:2px 0 0}

/* ==========================================================================
   FLOORS. Applied to the whole answer flow, last, so nothing above can undercut
   them and nothing added later inherits a 12 px default by accident.
   ========================================================================== */
#s-what,#s-where,#s-advice{--min-text:13px}
/* .tiny is 12 px everywhere else in the app and is used heavily here for the
   disclaimers — which are the sentences that keep this feature honest, so they are
   exactly the wrong thing to render at the smallest size on the screen. */
#s-what .tiny,#s-where .tiny,#s-advice .tiny{font-size:13.5px;line-height:1.55}
#s-what .eyebrow,#s-where .eyebrow,#s-advice .eyebrow,
#s-advice .banner .lbl,#s-advice .route-dl dt,#s-advice .pw-dl dt,
#s-advice .anon,#s-where .anon,#s-what .anon{font-size:13px}
#s-advice .banner{font-size:14px}
#s-advice .route-dl dd,#s-advice .pw-dl dd,#s-advice .ladder li{font-size:15px}
#s-advice .pw-why summary,#s-advice .pw-full{font-size:14px}
#s-advice h2{font-size:20px;line-height:1.3}
/* Every interactive thing in the flow clears 44 px. The `summary` elements are
   included because a <details> summary IS the control that opens it. */
#s-what button,#s-where button,#s-advice button,
#s-what summary,#s-where summary,#s-advice summary,
#s-advice a.btn,#s-advice .pw-full{min-height:44px}
/* …except the ones that are genuinely inline text inside a sentence, where a 44 px
   box would break the line box it sits in. These keep their touch target through
   padding instead. */
#s-advice .linklike{min-height:44px;display:inline-flex;align-items:center}

/* A 5-inch phone (360 px) and a 4-inch one (320 px) still may not scroll sideways. */
@media (max-width:360px){
  .acard > summary{flex-wrap:wrap;gap:4px 10px}
  .acard-sum{margin-left:0;text-align:left;flex-basis:100%}
  .tc-acts > *{flex:1 1 100%;justify-content:center}
}
