/* p.stonn app stylesheet. Served from /static with a content-hashed ?v= and an
   immutable cache; every full page links it, so it survives boosted swaps. */
@font-face {
  font-family:'Space Grotesk'; font-style:normal; font-weight:600; font-display:swap;
  src:url('spacegrotesk-wordmark.woff2?v=27978720b4') format('woff2'); /* 27978720b4 is stamped at startup (templates.go) so a replaced font busts the immutable cache */
  /* Subset covers only the wordmark glyphs (p . s t o n); scope the font to them
     so it is never partially applied to arbitrary headings. */
  unicode-range:U+002E, U+006E, U+006F, U+0070, U+0073, U+0074;
}
:root {
  color-scheme: light dark;
  --primary:#0d9488; --primary-2:#0f766e; --primary-soft:#d3f3ee; --on-primary:#fff;
  --accent:#d97706; --accent-soft:#fdf1dc; --accent-fill:#b45309; --on-accent:#fff;
  --bg:#eceff5; --surface:#ffffff; --surface-2:#f7f9fc;
  --ink:#0f1729; --muted:#5b6675; --line:#e4e8f0;
  --ok:#0f766e; --ok-soft:#d3f3ee; --warn:#b45309; --warn-soft:#fdf1dc; --danger:#dc2626; --danger-soft:#fdeaea; --info:#1d4ed8; --info-soft:#dbe7fe;
  /* Section palette: one hue per page, worn by its nav tab, page titles, and
     any hint that points there. Spaced ≥40° apart so no two tabs blur:
     schedule teal · vehicles blue · guests rose · activity violet · settings
     slate. Keep clear of the semantic colours (ok/warn/danger/info). */
  --sec-schedule:#0d9488; --sec-vehicles:#2563eb; --sec-guests:#db2777; --sec-activity:#7c3aed; --sec-settings:#475569;
  --radius:16px; --radius-sm:11px;
  --shadow:0 1px 2px rgba(16,24,40,.05), 0 1px 3px rgba(16,24,40,.04);
  --shadow-lg:0 12px 32px rgba(16,24,40,.13);
  --pattern:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Ccircle cx='2.4' cy='2.4' r='2.4' fill='%230f1729' fill-opacity='0.05'/%3E%3C/svg%3E");
}
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"] {
  color-scheme: dark;
  --primary:#2dd4bf; --primary-2:#14b8a6; --primary-soft:#123f3a; --on-primary:#04201c;
  --accent:#fbbf24; --accent-soft:#3a2f14; --accent-fill:#fbbf24; --on-accent:#2b1c02;
  --bg:#0b1120; --surface:#141d2e; --surface-2:#1a2436;
  --ink:#e6eaf2; --muted:#94a2b8; --line:#26324a;
  --ok:#2dd4bf; --ok-soft:#123f3a; --warn:#fbbf24; --warn-soft:#3a2f14; --danger:#f87171; --danger-soft:#3a1a1a; --info:#7cb3ff; --info-soft:#17294a;
  --sec-schedule:#2dd4bf; --sec-vehicles:#60a5fa; --sec-guests:#f472b6; --sec-activity:#a78bfa; --sec-settings:#94a3b8;
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 1px 3px rgba(0,0,0,.3);
  --shadow-lg:0 14px 36px rgba(0,0,0,.55);
  --pattern:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Ccircle cx='2.4' cy='2.4' r='2.4' fill='%23ffffff' fill-opacity='0.06'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme:dark) {
  :root:not([data-theme="light"]) {
    --primary:#2dd4bf; --primary-2:#14b8a6; --primary-soft:#123f3a; --on-primary:#04201c;
    --accent:#fbbf24; --accent-soft:#3a2f14; --accent-fill:#fbbf24; --on-accent:#2b1c02;
    --bg:#0b1120; --surface:#141d2e; --surface-2:#1a2436;
    --ink:#e6eaf2; --muted:#94a2b8; --line:#26324a;
    --ok:#2dd4bf; --ok-soft:#123f3a; --warn:#fbbf24; --warn-soft:#3a2f14; --danger:#f87171; --danger-soft:#3a1a1a; --info:#7cb3ff; --info-soft:#17294a;
    --sec-schedule:#2dd4bf; --sec-vehicles:#60a5fa; --sec-guests:#f472b6; --sec-activity:#a78bfa; --sec-settings:#94a3b8;
    --shadow:0 1px 2px rgba(0,0,0,.4), 0 1px 3px rgba(0,0,0,.3);
    --shadow-lg:0 14px 36px rgba(0,0,0,.55);
    --pattern:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Ccircle cx='2.4' cy='2.4' r='2.4' fill='%23ffffff' fill-opacity='0.06'/%3E%3C/svg%3E");
  }
}
* { box-sizing:border-box; }
[x-cloak] { display:none !important; }
/* Skip link: off-screen until focused, then pinned top-left so a keyboard user
   can jump past the app bar and nav straight to the page content. */
.skip-link { position:fixed; top:-60px; left:8px; z-index:200; padding:9px 14px; border-radius:10px; background:var(--primary); color:var(--on-primary); font-weight:600; font-size:14px; box-shadow:var(--shadow-lg); transition:top .15s; }
.skip-link:focus { top:8px; outline:2px solid var(--on-primary); outline-offset:-4px; }
main:focus { outline:none; }
html { scroll-behavior:smooth; overflow-x:hidden; }
body { margin:0; font:15px/1.55 system-ui,-apple-system,Segoe UI,Roboto,sans-serif; background:var(--bg); background-image:var(--pattern); color:var(--ink); -webkit-font-smoothing:antialiased; overflow-x:hidden; }
a { color:var(--primary); text-decoration:none; }
svg { width:18px; height:18px; stroke:currentColor; fill:none; stroke-width:1.7; stroke-linecap:round; stroke-linejoin:round; display:block; }
@keyframes rise { from { opacity:0; transform:translateY(6px); } to { opacity:1; transform:none; } }
/* htmx loading bar */
#htmx-bar { position:fixed; top:0; left:0; right:0; height:3px; z-index:100; overflow:hidden; pointer-events:none; opacity:0; transition:opacity .2s; }
#htmx-bar.on { opacity:1; }
#htmx-bar::before { content:""; position:absolute; height:100%; width:35%; left:-35%; border-radius:0 3px 3px 0; background:linear-gradient(90deg,transparent,var(--primary)); }
#htmx-bar.on::before { animation:htmxslide 1.1s cubic-bezier(.4,0,.2,1) infinite; }
/* Toast for server-side validation errors (surfaced from a 422 response). */
#toast { position:fixed; left:50%; bottom:24px; transform:translate(-50%,140%); z-index:120; max-width:min(92vw,420px); padding:12px 16px; border-radius:12px; background:var(--danger-soft); color:var(--danger); border:1px solid var(--danger); box-shadow:var(--shadow-lg); font-size:14px; line-height:1.45; opacity:0; transition:transform .25s cubic-bezier(.4,0,.2,1),opacity .25s; pointer-events:none; }
#toast.on { transform:translate(-50%,0); opacity:1; }
/* Sticky error panel: stays until dismissed, and the text can be selected
     and copied — these are the long "what happened and what to do" messages. */
#toast.sticky { pointer-events:auto; user-select:text; -webkit-user-select:text; padding-right:38px; }
.toast-x { position:absolute; top:6px; right:8px; border:0; background:none; color:inherit; font-size:18px; line-height:1; padding:4px; cursor:pointer; opacity:.7; }
.toast-x:hover { opacity:1; }
@keyframes htmxslide { 0%{left:-35%} 100%{left:100%} }

/* ---- app bar ---- */
.appbar { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:11px 18px; background:var(--surface); border-bottom:1px solid var(--line); position:sticky; top:0; z-index:20; }
.brand { display:flex; align-items:center; gap:10px; font-weight:650; font-size:15.5px; letter-spacing:-.01em; }
.brand .mark { width:32px; height:32px; border-radius:9px; background:linear-gradient(135deg,var(--primary),var(--primary-2)); color:var(--on-primary); display:grid; place-items:center; box-shadow:var(--shadow); }
.brand .mark svg { width:19px; height:19px; stroke-width:1.9; }
.appbar-r { display:flex; align-items:center; gap:8px; color:var(--muted); font-size:13px; }
/* The feature-tour link beside the profile button: quiet like .link-quiet, an
   icon plus a word that drops away on narrow screens (the aria-label carries it). */
.howlink { display:inline-flex; align-items:center; gap:6px; color:var(--muted); font-size:13px; font-weight:500; text-decoration:none; padding:8px 9px; border-radius:10px; transition:background .15s,color .15s; }
.howlink svg { width:17px; height:17px; fill:none; stroke:currentColor; stroke-width:2; stroke-linecap:round; stroke-linejoin:round; flex:0 0 auto; }
.howlink:hover { color:var(--primary); background:var(--surface-2); }
.howlink.active { color:var(--ink); }
@media (max-width: 560px) { .howlink-txt { display:none; } }
.icon-btn { background:transparent; border:0; color:var(--muted); padding:8px; border-radius:10px; cursor:pointer; display:inline-flex; transition:background .15s,color .15s; }
.icon-btn:hover { background:var(--surface-2); color:var(--ink); }
.usermenu { position:relative; display:inline-flex; }
.menu { position:absolute; top:calc(100% + 8px); right:0; min-width:220px; background:var(--surface); border:1px solid var(--line); border-radius:13px; box-shadow:var(--shadow-lg); padding:7px; z-index:30; }
.menu-email { font-size:12.5px; color:var(--muted); padding:8px 10px 9px; border-bottom:1px solid var(--line); margin-bottom:5px; word-break:break-all; }
.menu-item { display:flex; align-items:center; gap:9px; padding:9px 10px; border-radius:9px; font-size:14px; color:var(--ink); }
.menu-item:hover { background:var(--surface-2); }
/* a <button> menu item must not inherit the filled primary-button look */
button.menu-item { width:100%; background:none; border:0; box-shadow:none; font:inherit; font-size:14px; font-weight:inherit; color:var(--ink); text-align:left; justify-content:flex-start; cursor:pointer; }
button.menu-item:hover { background:var(--surface-2); filter:none; box-shadow:none; }
.menu-note { display:block; padding:6px 10px; font-size:12.5px; color:var(--muted); }
:focus-visible { outline:2px solid var(--primary); outline-offset:2px; }
@media (prefers-reduced-motion:reduce) { *, *::before, *::after { animation-duration:.001ms !important; transition-duration:.001ms !important; scroll-behavior:auto !important; } }
.th-sun { display:none; }
:root[data-theme="dark"] .th-moon { display:none; }
:root[data-theme="dark"] .th-sun { display:inline-flex; }
@media (prefers-color-scheme:dark) {
  :root:not([data-theme="light"]) .th-moon { display:none; }
  :root:not([data-theme="light"]) .th-sun { display:inline-flex; }
}

/* No flex gap: word↔tag spacing is an explicit margin so the area separator can
   own equal spacing on BOTH sides (a shared gap would only pad its left). */
.brand-text { display:inline-flex; align-items:baseline; }
.brand .word { font-family:'Space Grotesk',system-ui,sans-serif; font-weight:600; font-size:20px; letter-spacing:-.02em; line-height:1; }
.brand .word .dot { color:var(--primary); }
.brand .brand-tag { font-family:system-ui,sans-serif; font-weight:500; font-size:12.5px; color:var(--muted); letter-spacing:0; margin-left:10px; }
@media (max-width:560px){ .brand .brand-tag { display:none; } }
.brand .area-name { color:var(--muted); font-size:12.5px; font-weight:500; letter-spacing:0; white-space:nowrap; }
/* The middot sits equidistant from the text on either side (symmetric margins,
   no leading flex gap). */
.brand .area-name::before { content:"·"; margin:0 .5em; }

/* ---- modal ---- */
.modal-wrap { position:fixed; inset:0; z-index:40; display:grid; place-items:center; padding:16px; }
.scrim { position:absolute; inset:0; background:rgba(8,12,22,.62); }
/* Height is the small (toolbar-shown) phone viewport where dvh exists: vh is
   the toolbar-hidden height on iOS, so 88vh put the bottom of a tall modal —
   the row with the Book button — behind Safari's toolbar. */
.modal { position:relative; z-index:1; width:min(620px,100%); max-height:88vh; max-height:min(88vh, calc(100dvh - 32px)); overflow:auto; background:var(--surface); border:1px solid var(--line); border-radius:20px; box-shadow:var(--shadow-lg); padding:22px; }
.modal-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:16px; }
.modal-head strong { font-size:16px; letter-spacing:-.01em; }
/* Pinned to the bottom of the modal's own scroll: on a short phone screen the
   booking form is taller than the viewport, and a submit row that only appears
   after scrolling inside the modal reads as a cut-off dialog. The negative
   margins let the row own the modal's bottom padding; the gradient fades content
   out beneath it while scrolling and is invisible once the row sits in place.
   bottom:-22px because the sticky rectangle is the scroller's CONTENT box, so
   bottom:0 would float the row 22px above the modal's edge with the next field
   peeking out beneath it; the 22px the offset reaches into is the row's own
   bottom padding, so an engine that measures the padding box instead hides
   nothing but padding. */
.modal-actions { position:sticky; bottom:-22px; display:flex; justify-content:flex-end; gap:10px; margin:6px -22px -22px; padding:14px 22px 22px; background:linear-gradient(to bottom, transparent, var(--surface) 14px); }
/* Shared confirm dialog: native <dialog> for the focus trap / Esc / top-layer
   guarantees, styled with the app's own surface instead of browser chrome. */
dialog.confirm { border:0; padding:0; border-radius:16px; background:var(--surface); color:var(--ink); box-shadow:var(--shadow-lg); width:min(92vw,360px); }
dialog.confirm::backdrop { background:rgba(8,12,22,.55); }
dialog.confirm[open] { animation:rise .18s ease both; }
.dlg-box { padding:20px; display:flex; flex-direction:column; gap:16px; }
.dlg-msg { margin:0; font-size:14.5px; line-height:1.55; overflow-wrap:anywhere; }
.dlg-actions { display:flex; justify-content:flex-end; gap:10px; }
/* From / Until sit side by side when there's room, and drop to one column only
   when they can't fit — each holds a date + time pair (below), which together
   are too wide to force two across a narrow modal. auto-fit does this without a
   hard breakpoint tied to the modal width. */
.row2 { display:grid; grid-template-columns:repeat(auto-fit,minmax(280px,1fr)); gap:12px; }
/* Paired date + time inputs (one-off booking). The date field needs more room
   than the time field (dd/mm/yyyy + calendar icon), so give it the larger
   share; both clear their native icons. They wrap to two lines only if pinched
   narrower than a single pair can hold. */
.dtpair { display:flex; gap:8px; flex-wrap:wrap; }
.dtpair input { min-width:0; }
.dtpair input[type="date"] { flex:3 1 150px; }
.dtpair input[type="time"] { flex:2 1 100px; }
.oo-head { display:flex; align-items:center; justify-content:space-between; gap:10px; margin:20px 0 10px; }
.oo-head .sub { margin:0; }
button.sm { padding:7px 12px; font-size:13px; }

/* ---- nav ---- */
.nav { display:flex; gap:2px; padding:6px 12px 0; max-width:960px; margin:0 auto; overflow-x:auto; }
.nav-item { display:inline-flex; align-items:center; gap:8px; padding:11px 15px 13px; color:var(--muted); font-size:14px; font-weight:550; border-radius:10px 10px 0 0; position:relative; white-space:nowrap; transition:color .15s,background .15s; }
.nav-item svg { width:19px; height:19px; }
.nav-item:hover { color:var(--ink); background:color-mix(in srgb, var(--surface) 60%, transparent); }
.nav-item.active { color:var(--hue,var(--primary)); }
.nav-item.active::after { content:""; position:absolute; left:12px; right:12px; bottom:0; height:3px; border-radius:3px 3px 0 0; background:var(--hue,var(--primary)); }
.navwrap { border-bottom:1px solid var(--line); background:color-mix(in srgb, var(--surface) 55%, transparent); position:sticky; top:53px; z-index:15; }

main { max-width:960px; margin:22px auto 90px; padding:0 16px; display:grid; gap:16px; }
/* Material bottom navigation on small screens. This block must come after the
   main{} rule above so its margin-bottom override wins. */
@media (max-width:640px){
  .navwrap { position:fixed; bottom:0; left:0; right:0; top:auto; border-top:1px solid var(--line); border-bottom:0; background:var(--surface); z-index:30; padding-bottom:env(safe-area-inset-bottom, 0px); }
  /* Paint the area the bar exposes during iOS rubber-band overscroll in the
     bar's own surface, so the dotted page background never peeks out below it. */
  .navwrap::after { content:""; position:absolute; top:100%; left:0; right:0; height:50vh; background:var(--surface); }
  .nav { justify-content:space-around; padding:0; gap:0; }
  .nav-item { flex-direction:column; gap:3px; font-size:11px; padding:9px 6px 8px; border-radius:0; flex:1; justify-content:center; }
  .nav-item svg { width:22px; height:22px; }
  .nav-item.active::after { top:0; bottom:auto; left:22%; right:22%; border-radius:0 0 3px 3px; }
  /* Reserve the bar's height (~60px) plus a small gap under the last card. */
  main { margin-bottom:calc(76px + env(safe-area-inset-bottom, 0px)); }
  /* A page with the floating "Add booking now" button (bottom 74px, height 52px)
     reserves past its top edge so it can't cover the page's last control. Any
     descendant, not a direct child: the multi-permit chooser wraps its FAB in a
     div, and with the direct-child form the button sat over the last row of the
     roster on a fully scrolled phone (2026-09-10). */
  main:has(.fab) { margin-bottom:calc(142px + env(safe-area-inset-bottom, 0px)); }
}
.card { background:var(--surface); border:1px solid var(--line); border-radius:var(--radius); padding:20px; box-shadow:var(--shadow); animation:rise .28s ease both; }
.card > h2 { margin:0 0 16px; font-size:15px; display:flex; align-items:center; gap:9px; letter-spacing:-.01em; }
.card > h2 .ic { color:var(--hue,var(--muted)); display:inline-flex; }
.sectitle { display:flex; align-items:center; gap:9px; margin:0 0 4px; font-size:19px; font-weight:680; letter-spacing:-.02em; }
.sectitle .ic { width:30px; height:30px; border-radius:9px; display:grid; place-items:center; background:color-mix(in srgb, var(--hue) 14%, transparent); color:var(--hue); }
.subtitle { color:var(--muted); font-size:13.5px; margin:0 0 8px; }
.sub { font-size:12px; font-weight:650; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); margin:0 0 10px; }

.banner { border-radius:var(--radius); padding:13px 16px; display:flex; align-items:center; gap:10px; font-size:14px; animation:rise .28s ease both; }
.banner.ok { background:var(--ok-soft); border:1px solid color-mix(in srgb,var(--ok) 30%,transparent); color:var(--ok); }
.banner.warn { background:var(--warn-soft); border:1px solid color-mix(in srgb,var(--warn) 35%,transparent); color:var(--warn); }
.banner.info { background:var(--info-soft); border:1px solid color-mix(in srgb,var(--info) 35%,transparent); color:var(--info); }
/* Council-side outage status: the amber caution palette (class="banner warn
   council-down") plus a heavier left rule, marking it out as a standing
   system-status note rather than a one-off prompt. */
.banner.council-down { border-left:4px solid var(--warn); align-items:flex-start; }
.banner.council-down > svg { flex:none; margin-top:1px; }
.row { display:flex; gap:10px; align-items:center; flex-wrap:wrap; }
.muted { color:var(--muted); }
.grow { flex:1 1 auto; }
input,select,textarea { font:inherit; padding:9px 11px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--surface); color:var(--ink); transition:border-color .15s,box-shadow .15s; }
textarea { resize:vertical; min-height:96px; width:100%; }
input:focus,select:focus,textarea:focus { outline:0; border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 22%,transparent); }
label.fld { display:flex; flex-direction:column; gap:5px; font-size:12px; color:var(--muted); }
/* Opt-in checkbox row (e.g. save-password) */
label.optin { display:flex; gap:10px; align-items:flex-start; font-size:12.5px; color:var(--muted); line-height:1.45; text-align:left; cursor:pointer; }
label.optin input { margin-top:3px; flex:0 0 auto; width:16px; height:16px; accent-color:var(--primary); }
label.optin strong { color:var(--ink); font-weight:600; }
.pill { display:inline-block; font-size:11px; font-weight:600; padding:1px 8px; border-radius:999px; background:var(--surface-2); border:1px solid var(--line); color:var(--muted); vertical-align:middle; }
.pill.ok { background:var(--ok-soft); border-color:color-mix(in srgb,var(--ok) 30%,transparent); color:var(--ok); }
/* Roomier contact form (landing + /contact) */
.contactform { gap:16px; }
.contactform .fld { font-size:13.5px; gap:7px; }
.contactform input, .contactform textarea { font-size:15px; padding:12px 14px; }
.contactform textarea { min-height:150px; line-height:1.5; }
.contactform button[type=submit] { padding:13px 18px; font-size:15px; margin-top:2px; }
button { font:inherit; font-weight:560; padding:9px 15px; border:0; border-radius:var(--radius-sm); background:var(--primary); color:var(--on-primary); cursor:pointer; display:inline-flex; align-items:center; gap:7px; transition:transform .08s, box-shadow .15s, filter .15s; box-shadow:var(--shadow); }
button:hover { filter:brightness(1.05); box-shadow:var(--shadow-lg); }
button:active { transform:translateY(1px) scale(.99); }
button svg { width:16px; height:16px; }
/* A leading icon sits inside a 16px box its strokes never reach, so with equal
   padding both sides the label reads as shoved right ("+ Book" next to a centred
   "Cancel"). Pull the icon in by the dead space. Icon-only buttons keep their
   centring: they are excluded by class, so a new icon-only button needs one
   (.wkadd-first is excluded because it has no padding to trim). */
button:not(.icon):not(.icon-btn):not(.wkadd):not(.wkadd-first):not(.fab) > svg:first-child { margin-left:-3px; }
button.ghost { background:var(--surface-2); color:var(--ink); box-shadow:none; border:1px solid var(--line); }
button.ghost:hover { background:var(--line); filter:none; box-shadow:none; }
button.danger { background:var(--danger-soft); color:var(--danger); box-shadow:none; }
button.danger:hover { filter:brightness(.97); box-shadow:none; }
button.icon { padding:9px; }

table { width:100%; border-collapse:collapse; }
th,td { text-align:left; padding:11px 6px; border-bottom:1px solid var(--line); font-size:14px; overflow-wrap:anywhere; }
th { color:var(--muted); font-weight:600; font-size:12px; }
tr:last-child td { border-bottom:0; }
.reg { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:12.5px; font-weight:650; letter-spacing:.4px; background:var(--primary-soft); color:var(--primary-2); padding:3px 8px; border-radius:7px; display:inline-block; }
/* Apply-log outcome. Subordinate to the plate above it, so a routine success
   recedes; a failure has to be the thing your eye lands on instead. */
.logst { font-size:12.5px; line-height:1.5; }
.logst .s-success { color:var(--muted); }
.logst .s-error { color:var(--danger); font-weight:650; }
.logst .s-changed { color:var(--warn); font-weight:650; }
:root[data-theme="dark"] .reg { color:var(--primary); }
.veh { display:inline-flex; align-items:center; gap:8px; }
.dot { width:9px; height:9px; border-radius:50%; flex:0 0 auto; box-shadow:0 0 0 3px color-mix(in srgb, currentColor 0%, transparent); }
.status { font-size:14px; color:var(--muted); display:flex; gap:8px; align-items:center; flex-wrap:wrap; }

/* status hero */
.permit-head { display:flex; justify-content:space-between; align-items:flex-start; gap:14px; flex-wrap:wrap; margin-bottom:16px; }
.permit-head h2 { margin:0; font-size:17px; letter-spacing:-.01em; }
.pname { display:flex; flex-direction:column; gap:2px; min-width:0; flex:1 1 auto; }
/* The title line reserves a stable height so swapping the name for the rename
   input (same row) doesn't nudge the subtitle or reflow the card below. */
.pname-title { display:flex; align-items:center; gap:6px; min-width:0; flex-wrap:wrap; min-height:34px; }
.pdetail { font-size:11.5px; color:var(--muted); letter-spacing:.01em; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.pname-title .ghost.icon.sm { opacity:.55; }
.pname-title:hover .ghost.icon.sm { opacity:1; }
.rename-hint { background:none; border:0; box-shadow:none; padding:2px 4px; margin:0; font:inherit; font-size:12px; font-weight:500; color:var(--muted); cursor:pointer; border-radius:6px; white-space:nowrap; }
.rename-hint:hover { color:var(--primary-2); background:var(--surface-2); box-shadow:none; }
.pname-edit { display:flex; align-items:center; gap:8px; flex:1 1 auto; flex-wrap:wrap; min-width:0; }
/* Match the h2 exactly (size/weight/flush-left) with an underline affordance, so
   swapping the heading for this input doesn't shift the text or the row height. */
.pname-edit input { flex:1; min-width:150px; font-size:17px; font-weight:600; letter-spacing:-.01em; padding:1px 2px; border:0; border-bottom:2px solid var(--primary); border-radius:0; background:transparent; color:var(--ink); }
.pname-edit input:focus { outline:none; }
.nowbadge { display:inline-flex; align-items:center; gap:9px; background:var(--surface-2); border:1px solid var(--line); border-radius:999px; padding:7px 14px; font-size:14px; }
.permit-expiry { display:flex; align-items:center; gap:7px; margin:2px 0 0; font-size:12.5px; color:var(--muted); }
.permit-expiry svg { width:15px; height:15px; flex:0 0 auto; }
.permit-expiry.warn { color:var(--warn); font-weight:500; }
/* When the expiry line sits directly above a section heading ("Weekly roster"),
   give the heading room; when a banner or card sits between, they space themselves. */
.permit-expiry + .sub { margin-top:18px; }
.permit-expiry.bad { color:var(--danger); font-weight:500; }
.copysched { margin:14px 0 18px; }
.copysched.lead { margin-bottom:24px; }
.copysched.lead { background:var(--surface-2); border:1px solid var(--line); border-radius:var(--radius-sm); padding:12px 14px; }
.copysched .cs-head { display:flex; align-items:flex-start; gap:8px; }
.copysched .cs-head .cs-note { flex:1 1 auto; }
.copysched .cs-head button { flex:0 0 auto; margin-top:-4px; }
.copysched .cs-note { display:flex; align-items:flex-start; gap:8px; margin:0 0 10px; font-size:13px; color:var(--muted); }
.copysched .cs-note svg { width:16px; height:16px; flex:0 0 auto; margin-top:1px; }
.copysched .cs-form { display:flex; align-items:flex-end; gap:10px; flex-wrap:wrap; margin-top:8px; }
.copysched .cs-form .fld { flex:1 1 200px; }
.copysched .cs-form select { width:100%; }
.expired-permits { opacity:.92; }
.exp-h2 { display:flex; align-items:center; gap:8px; font-size:15px; color:var(--muted); margin:0 0 2px; }
.exp-h2 svg { width:17px; height:17px; }
.explist { list-style:none; padding:0; margin:0; display:grid; gap:8px; }
.exprow { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:10px 12px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--surface-2); }
.exprow .expmain { display:flex; flex-direction:column; gap:2px; min-width:0; }
.exprow .expname { font-weight:600; color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.exprow .expstatus { font-size:12px; color:var(--muted); }

/* legend */
.legend { display:flex; gap:14px; flex-wrap:wrap; margin:2px 0 4px; }
.legend .veh { font-size:13px; }

/* roster */
.roster { display:grid; grid-template-columns:repeat(7,minmax(0,1fr)); gap:8px; }
@media (max-width:640px){ .roster { grid-template-columns:repeat(4,minmax(0,1fr)); } }
.rcell { position:relative; min-width:0; }
.rcell > .day { font-size:11px; color:var(--muted); text-align:center; margin-bottom:6px; font-weight:600; }
.chip { width:100%; border:1px solid var(--line); background:var(--surface); border-radius:var(--radius-sm); padding:10px 8px; min-height:46px; display:flex; align-items:center; justify-content:center; gap:6px; cursor:pointer; font-size:12.5px; color:var(--ink); text-align:center; transition:border-color .15s, box-shadow .15s, transform .08s; }
.chip.empty { color:var(--muted); border-style:dashed; }
.chip:hover { border-color:var(--primary); box-shadow:0 0 0 3px color-mix(in srgb,var(--primary) 14%,transparent); }
.chip:active { transform:scale(.98); }
.chip .lab { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:600; min-width:0; }

/* calendar */
.cal { display:grid; grid-template-columns:repeat(7,minmax(0,1fr)); gap:6px; }
.cday { position:relative; border:1px solid var(--line); border-radius:10px; padding:7px 4px 9px; text-align:center; cursor:pointer; background:var(--surface); transition:border-color .15s, transform .08s; }
.cday:hover { border-color:var(--primary); }
.cday:active { transform:scale(.98); }
.cday.today { border-color:var(--primary); box-shadow:0 0 0 1px var(--primary) inset; }
/* Dim a past day's CONTENTS, not the cell: opacity on the cell created a
   stacking context that trapped its popover's z-index, so a past day's popover
   rendered BEHIND the next calendar row (seen live on the two-row cycling
   calendar). The popover child is excluded so it opens at full strength. */
.cday.past { border-color:color-mix(in srgb, var(--line) 55%, transparent); }
.cday.past > :not(.pop) { opacity:.42; }
/* And a cell with its popover open rises above later-painted cells regardless
   of what future styling does to stacking, mirroring the .card rule below. */
.cday:has(.pop:not([style*="none"])) { z-index:31; }
.cday .cd { font-size:10.5px; color:var(--muted); margin-bottom:4px; white-space:nowrap; font-weight:600; }
/* one-off marker: a fixed centred slot between the date and the bar, so it is
   always the same height (dot present or not) and never widens the cell. */
.cday .omk { height:6px; display:flex; align-items:center; justify-content:center; margin-bottom:5px; }
.cday .oneoff { width:5px; height:5px; border-radius:50%; background:var(--accent); }
.cday .bar { height:6px; border-radius:4px; background:var(--line); }
/* A day covered by a typed one-off plate: no saved-vehicle colour exists, and
     the neutral fallback bar made a covered day look identical to an empty one.
     Accent stripes — the same accent as the .oneoff dot — say "covered, by a
     one-off" without inventing a colour the legend can't explain. */
.cday .bar.adhoc { background:repeating-linear-gradient(-45deg, var(--accent) 0 3px, var(--accent-soft) 3px 7px); }
/* Today's cell when the shown plate is NOT the council-confirmed state: a
     change mid-apply hatches in the day's own colour; polls-exhausted amber.
     Both must beat the cell's inline background colour, hence !important. */
.cday .bar.applying { background:repeating-linear-gradient(-45deg, var(--muted) 0 3px, var(--line) 3px 7px) !important; }
.cday .bar.unconf { background:repeating-linear-gradient(-45deg, var(--warn) 0 3px, var(--warn-soft) 3px 7px) !important; }
.calwarn { color:var(--warn); font-size:12px; }

/* popover — centred with the standalone translate property, NOT transform:
   the rise animation ends at transform:none (fill both), which would wipe a
   transform-based centring and shove the popover half a width off-screen.
   --pop-shift is set by popClamp() on open to pull the popover back inside
   the viewport (body is overflow-x:hidden, so an overhang is clipped, not
   scrollable). */
.pop { position:absolute; z-index:30; top:100%; left:50%; translate:calc(-50% + var(--pop-shift,0px)) 0; margin-top:7px; background:var(--surface); border:1px solid var(--line); border-radius:13px; box-shadow:var(--shadow-lg); padding:13px; min-width:216px; max-width:calc(100vw - 24px); animation:rise .16s ease both; }
.pop .tt { font-size:12px; color:var(--muted); margin:0 0 9px; font-weight:600; }
/* A card whose day popover is open rises above what follows it (the manage-another
   link, the tips): the popover hangs below the card's edge, and a later sibling
   otherwise paints over it. */
.card:has(.pop:not([style*="none"])) { position:relative; z-index:5; }
/* vehicle picker (colour-dotted options) */
.vopts { display:grid; gap:2px; }
.vopt { display:flex; align-items:center; gap:9px; width:100%; text-align:left; background:transparent; color:var(--ink); border:0; box-shadow:none; padding:8px 9px; border-radius:8px; font-weight:500; font-size:13px; }
.vopt:hover { background:var(--surface-2); filter:none; box-shadow:none; }
.vopt.sel { background:var(--primary-soft); color:var(--primary-2); }
:root[data-theme="dark"] .vopt.sel { color:var(--primary); }
.vopt .lab { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.vopt .reg { margin-left:auto; flex:0 0 auto; }
.seg { display:flex; border:1px solid var(--line); border-radius:var(--radius-sm); overflow:hidden; }
.seg button { flex:1; background:var(--surface); color:var(--muted); box-shadow:none; border:0; border-radius:0; font-weight:600; font-size:13px; padding:9px 15px; }
.seg button + button { border-left:1px solid var(--line); }
.seg button.on { background:var(--primary); color:var(--on-primary); border-color:var(--primary); }
.seg button:not(.on):hover { background:var(--surface-2); color:var(--ink); filter:none; box-shadow:none; }
/* Both panes share one grid cell, so the area is always the taller pane's
   height and the modal doesn't resize when switching tabs. */
.segpanes { display:grid; }
.segpanes > .segpane { grid-area:1 / 1; }
.segpane.hide { visibility:hidden; pointer-events:none; }
/* Roster week tabs: the .seg control, sized for its place above the roster grid.
   The + tab stays narrow (it adds a week, it isn't one), and the "now" mark
   names the week the cycle is actually in — a fact, so it keeps its contrast on
   the active tab too. */
.wktabs { margin:0 0 10px; }
.wktabs button { padding:8px 10px; }
.wktabs .wkadd { flex:0 0 auto; padding:8px 12px; }
.wktabs .wkadd svg { width:14px; height:14px; vertical-align:-2px; }
.wknow { display:inline-block; font-size:10px; font-weight:700; letter-spacing:.03em; text-transform:uppercase; padding:1px 6px; border-radius:999px; margin-left:5px; background:color-mix(in srgb, currentColor 14%, transparent); }
.wkremove { margin-top:10px; }
/* The single-week card's doorway into the cycle: the .link-quiet look, on a
   button (it posts). Stripped of button chrome so it reads as a capability the
   page happens to have, not a call to action shown to everyone forever. */
.wkadd-first { margin-top:10px; background:none; border:0; box-shadow:none; border-radius:0; padding:0; color:var(--muted); font-size:13.5px; font-weight:500; display:inline-flex; align-items:center; gap:6px; cursor:pointer; }
.wkadd-first:hover { color:var(--primary); background:none; filter:none; box-shadow:none; }
.undoweek { margin-left:auto; flex:0 0 auto; }
.sub.calwk { margin-bottom:6px; }
.cal + .sub.calwk { margin-top:14px; }
.vpick { position:relative; }
.vpick-btn { display:flex; align-items:center; gap:9px; width:100%; justify-content:flex-start; background:var(--surface); color:var(--ink); border:1px solid var(--line); box-shadow:none; font-weight:500; padding:9px 11px; }
.vpick-btn:hover { filter:none; box-shadow:none; border-color:var(--primary); }
.vpick-btn .ph { color:var(--muted); }
.vpick-caret { margin-left:auto; color:var(--muted); font-size:11px; }
.vpick-list { position:absolute; z-index:5; top:calc(100% + 5px); left:0; right:0; background:var(--surface); border:1px solid var(--line); border-radius:12px; box-shadow:var(--shadow-lg); padding:5px; max-height:230px; overflow:auto; }

/* one-offs list */
.oneoff-list { display:grid; gap:8px; margin:2px 0 4px; }
/* The car keeps its natural width and the detail column absorbs the slack: a
   grid track's default minimum is its content width, so with `1fr auto` an
   unbreakable string (the email in "· by …") pushed the row past its card. Making
   BOTH tracks shrinkable is the obvious fix and the wrong one — the detail column
   then wins the space and crushes the car's name and plate to a few characters.
   Only the second track is flexible, so the long text wraps and the car does not. */
.oneoff-item { display:grid; grid-template-columns:auto minmax(0,1fr) 40px; align-items:center; gap:14px; border:1px solid var(--line); border-radius:var(--radius-sm); padding:11px 13px; background:var(--surface-2); }
.oneoff-item .veh { min-width:0; }
.oneoff-item .veh .lab, .oneoff-item .veh { overflow:hidden; }
/* The cell wraps, but the timestamps inside it do not: a date split across two
   lines is harder to read than a wrapped row is untidy. Only the attribution,
   which is the long part, is allowed to break mid-word. */
.oneoff-item .when { font-size:13px; color:var(--muted); text-align:left; min-width:0; }
.oneoff-item .when .nb { white-space:nowrap; }
.oneoff-item .when .by { overflow-wrap:anywhere; }
.oneoff-item form { justify-self:end; }
@media (max-width:600px){
  .oneoff-item { grid-template-columns:1fr 40px; }
  .oneoff-item .when { grid-column:1 / -1; grid-row:2; }
}
.addcard { border:1px dashed var(--line); border-radius:var(--radius-sm); padding:14px; margin-top:12px; }

.empty-note { color:var(--muted); font-size:13.5px; }
.toggle { display:flex; align-items:center; gap:11px; font-size:14px; cursor:pointer; }
.toggle input[type=checkbox] { width:22px; height:22px; flex:0 0 auto; accent-color:var(--primary); }
/* Refused-toggle feedback (e.g. unticking the last notification channel). */
@keyframes shake { 20%,60% { translate:-4px 0; } 40%,80% { translate:4px 0; } }
.toggle.shake { animation:shake .35s ease; }
@media (prefers-reduced-motion:reduce){ .toggle.shake { animation:none; } }
.toggle span { line-height:1.4; }
.toggle.mini { font-size:13px; color:var(--muted); gap:9px; }
input[type=checkbox] { accent-color:var(--primary); width:17px; height:17px; flex:0 0 auto; cursor:pointer; }
form.stack.notify-form { max-width:none; margin:14px 0 0; gap:15px; }
.notify-test { margin-top:14px; }
.notify-status { font-size:13px; color:var(--muted); margin:0; }
.notify-status.err { color:var(--danger); }
.notify-status.ok { color:var(--ok); }
.quiet { display:flex; flex-direction:column; gap:10px; padding:14px 0 2px; border-top:1px solid var(--line); }
.quiet-times { display:flex; align-items:center; gap:9px; flex-wrap:wrap; font-size:13.5px; color:var(--muted); }
.quiet-times select { padding:7px 9px; font-size:13.5px; }
.quiet-note { font-size:12.5px; color:var(--muted); margin:0; line-height:1.45; max-width:60ch; }
.steps { margin:0 0 2px; padding:16px 18px; background:var(--surface-2); border:1px solid var(--line); border-radius:var(--radius-sm); font-size:14px; }
.steps-lead { margin:0 0 12px; color:var(--muted); font-size:13px; }
.steps ol { margin:0; padding-left:22px; display:grid; gap:10px; }
.steps li { line-height:1.5; padding-left:3px; }
.steps button.sm { margin-top:16px; }
code { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-size:12.5px; background:var(--primary-soft); color:var(--primary-2); padding:3px 7px; border-radius:6px; word-break:break-all; }
/* Server/topic pills: wrap to their own line as a unit on narrow screens
   rather than shattering the URL/topic mid-token. */
.steps code { display:inline-block; max-width:100%; }
:root[data-theme="dark"] code { color:var(--primary); }
.pick { display:grid; gap:10px; }
.pick-item { display:flex; align-items:center; justify-content:space-between; gap:12px; border:1px solid var(--line); border-radius:13px; padding:14px 16px; transition:border-color .15s, box-shadow .15s; }
.pick-item:hover { border-color:var(--primary); box-shadow:var(--shadow); }
/* The one-off permit chooser renders rows as buttons; strip the filled action
   look so they read as pickable cards, like the onboarding permit picker. */
button.pick-item { background:var(--surface); color:var(--ink); box-shadow:none; font-weight:560; }
button.pick-item:hover { filter:none; background:var(--surface-2); box-shadow:var(--shadow); }
.pick-name { font-weight:650; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
button:disabled { opacity:.55; cursor:progress; }
.pick-item.disabled { opacity:.6; background:var(--surface-2); }
.pick-item.dead { background:var(--surface-2); }
.pick-item.dead:hover { border-color:var(--line); box-shadow:none; }
.pick-older { margin:18px 0 8px; font-size:13px; font-weight:650; color:var(--muted); text-transform:uppercase; letter-spacing:.05em; }
.pick-status { display:inline-block; margin-left:6px; padding:1px 8px; border-radius:999px; font-size:11.5px; font-weight:650; vertical-align:middle; background:var(--warn-soft); color:var(--warn); }
.pick-item.disabled:hover { border-color:var(--line); box-shadow:none; }
.pick-reason { font-size:12.5px; color:var(--muted); text-align:right; max-width:200px; flex:0 0 auto; }
.permit-foot { display:flex; justify-content:flex-end; margin-top:20px; padding-top:14px; border-top:1px solid var(--line); }
.permit-foot button { background:none; box-shadow:none; color:var(--muted); font-weight:550; font-size:12.5px; padding:6px 9px; }
.permit-foot button:hover { color:var(--danger); background:var(--danger-soft); filter:none; box-shadow:none; }
.permit-foot button svg { width:14px; height:14px; }
/* Vehicles list: one row per car (label + plate + delete), email form below */
.vlist { display:grid; gap:2px; }
.vrow { padding:14px 0; border-bottom:1px solid var(--line); }
.vrow:first-child { padding-top:2px; }
.vrow:last-child { border-bottom:0; padding-bottom:2px; }
.vmain { display:flex; align-items:center; gap:10px; }
.vmain .veh { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:14.5px; font-weight:550; }
.vmain .plate { flex:0 0 auto; }
.vemail { display:flex; align-items:center; gap:8px; margin-top:6px; }
.vemail input { font-size:13.5px; padding:7px 10px; width:250px; max-width:100%; flex:none; }
.vemail button { padding:7px 12px; font-size:13.5px; }
/* Keep the add-a-vehicle inputs the same size as the per-car email inputs */
.vaddform input { font-size:13.5px; }
/* Number plate: a distinct badge tinted with each car's colour (--plate). */
.plate { font-family:ui-monospace,SFMono-Regular,Menlo,monospace; font-weight:700; font-size:13px; letter-spacing:1px; text-transform:uppercase; padding:4px 9px; border-radius:6px; border:2px solid var(--plate,var(--line)); background:color-mix(in srgb, var(--plate,var(--ink)) 12%, var(--surface)); color:var(--ink); white-space:nowrap; display:inline-block; }
/* The empty-permit state occupies the SAME box as a plate (same padding/border/
   height) so the "on permit now" row keeps identical geometry with or without a
   car — a plain text label sat at a different height and read as misaligned. A
   dashed, muted outline signals "no plate here" rather than naming one. */
.noplate { font-size:13px; letter-spacing:.2px; padding:4px 9px; border-radius:6px; border:2px dashed var(--line); color:var(--muted); white-space:nowrap; display:inline-block; }
/* Guest-pass management */
.grants { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:16px; }
.grants .card { margin:0; }
@media (max-width:560px){ .grants { grid-template-columns:1fr; } }
.card.editing { border-color:var(--sec-vehicles); box-shadow:0 0 0 1px var(--sec-vehicles) inset, var(--shadow); }
/* ---- small-screen refinements for the app pages ---- */
@media (max-width:560px){
  main { padding:0 12px; }
  .card { padding:16px 15px; }
  /* Calendar keeps 7 weekday columns but tightens so they fit narrow screens */
  .cal { gap:4px; }
  .cday { padding:6px 2px 8px; }
  .cday .cd { font-size:9.5px; letter-spacing:-.02em; }
  .cday .bar { height:5px; }
  /* Roster: shrink the chip a touch so a full plate fits a narrow column */
  .chip { font-size:11.5px; gap:5px; padding:9px 4px; }
  /* Vehicle driver-email field: input takes the full first line, Save + status
     wrap to a second line (row-gap gives the button breathing room) so a long
     address never forces horizontal scroll. */
  .vemail { flex-wrap:wrap; row-gap:8px; }
  .vemail input { flex:1 1 100%; width:auto; min-width:0; }
  /* Activity log: stack each row into a readable block instead of a cramped table.
     Flex column rather than per-cell padding, because the plate chip's own
     padding made the gap above it visibly wider than the one below — the rows
     read as three loose lines instead of one block. A single gap is even
     regardless of what each cell contains. */
  table.mtable, table.mtable tbody, table.mtable td { display:block; }
  table.mtable tr { display:flex; flex-direction:column; align-items:flex-start; gap:3px; padding:10px 0; border-bottom:1px solid var(--line); }
  table.mtable tr:last-child { border-bottom:0; }
  table.mtable td { border:0; padding:0; }
  table.mtable td[style*="nowrap"] { font-size:12px; color:var(--muted); }
}
.editbar { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:4px; }
.editbar .eyebrow { font-size:12px; font-weight:650; letter-spacing:.04em; text-transform:uppercase; color:var(--sec-vehicles); }
.glink { display:flex; flex-direction:column; gap:2px; padding:9px 0; border-top:1px solid var(--line); }
.glink code { font-size:12px; word-break:break-all; background:var(--surface-2); padding:6px 8px; border-radius:6px; }
.checks { display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:10px; margin-top:8px; }
.check { display:flex; align-items:center; gap:10px; padding:11px 13px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--surface-2); font-size:13.5px; font-weight:550; color:var(--ink); cursor:pointer; transition:border-color .12s, background .12s; }
.check:hover { border-color:var(--muted); }
.check:has(input:checked) { border-color:var(--primary); background:var(--primary-soft); }
/* A 2x2 grid, everything left aligned. Column one stacks the checkbox above the
   colour dot, both 17px and both centred in that column, so they read as one
   vertical rail; column two stacks the name above its plate. Fixed rows keep
   every cell the same height whatever the name — letting a long one WRAP made
   rows 56px or 89px tall, and one tall cell stretches its whole grid row, so
   the short ones floated in space. The name truncates rather than wrapping. */
.check { display:grid; grid-template-columns:auto 1fr; column-gap:11px; row-gap:6px; align-items:center; padding:12px 13px; }
.check input { grid-area:1 / 1 / 2 / 2; justify-self:center; }
.check .dot { grid-area:2 / 1 / 3 / 2; justify-self:center; width:17px; height:17px; }
.cname { grid-area:1 / 2 / 2 / 3; justify-self:start; min-width:0; max-width:100%;
         overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.check .plate { grid-area:2 / 2 / 3 / 3; justify-self:start; margin-left:0; }
.grecip { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:8px 0; border-top:1px solid var(--line); font-size:13.5px; }
.ro { padding:9px 12px; background:var(--surface-2); border:1px solid var(--line); border-radius:var(--radius-sm); color:var(--muted); font-size:14px; }
.qropts { display:grid; grid-template-columns:1fr 1fr; gap:12px; }
.qropt { display:flex; flex-direction:column; gap:10px; padding:14px; background:var(--surface-2); border:1px solid var(--line); border-radius:var(--radius-sm); }
.qropt-txt { display:flex; flex-direction:column; gap:3px; }
.qropt select { width:100%; }
.qropt button { margin-top:auto; }
/* the add-a-car row is a form, not another car: rule it off from the list */
.vaddform { border-top:1px solid var(--line); padding-top:16px; }
.vstate-chip { color:var(--muted); font-weight:400; font-size:13px; }
.card.hero.share-hero { width:100%; max-width:820px; margin:16px auto 14px; padding:30px 22px 26px; }
.share-hero h2 { justify-content:center; }
.share-hero p { margin:0 auto; max-width:520px; color:var(--muted); }
.card.share-card { width:100%; max-width:820px; margin:0 auto; }
.share-opts { display:grid; grid-template-columns:1fr; gap:12px; }
@media (min-width:760px) { .share-opts { grid-template-columns:repeat(3, minmax(0, 1fr)); } }
.share-opts .qropt-txt .empty-note { font-size:13px; }
/* the action sits at the foot of every card, whatever sits above it */
.share-opts .qropt > :last-child { margin-top:auto; }
.share-opts .qropt > a { display:block; }
.share-opts .qropt form.stack { margin-top:auto; }
/* the action sits at the foot of every card, whatever sits above it */
.share-opts .qropt > :last-child { margin-top:auto; }
.share-opts .qropt form.stack { margin-top:auto; }
.share-opts .qropt > a { display:block; }
.share-ic { width:38px; height:38px; border-radius:11px; display:grid; place-items:center; background:color-mix(in srgb, var(--hue) 14%, transparent); color:var(--hue); }
.share-ic svg { width:20px; height:20px; }
.share-preview { display:flex; align-items:center; gap:12px; padding:10px; background:var(--surface); border:1px solid var(--line); border-radius:10px; font-weight:650; }
.share-preview img { width:72px; height:72px; border-radius:6px; background:#fff; }
@media (max-width:640px){ .qropts { grid-template-columns:1fr; } }
.req { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; padding:12px 0; border-top:1px solid var(--line); }
.req-main { display:flex; align-items:center; gap:12px; min-width:0; }
.req-meta { display:flex; flex-direction:column; gap:1px; min-width:0; }
/* Durable door-QR management rows */
.doorrow { display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; padding:12px 0; border-top:1px solid var(--line); }
.doorrow:first-of-type { border-top:0; padding-top:4px; }
.doorrow-main { display:flex; align-items:center; gap:12px; min-width:0; }
.doorrow-ic { width:36px; height:36px; border-radius:9px; display:grid; place-items:center; background:var(--surface-2); color:var(--muted); flex:none; }
.doorrow-ic svg { width:18px; height:18px; }
.doorrow-meta { display:flex; flex-direction:column; gap:1px; min-width:0; }
@media (max-width:640px){
  /* When the actions wrap under the icon+meta, span the full row instead of
     huddling in the leftover corner. */
  .doorrow > .row { flex:1 0 100%; }
  .doorrow > .row a { flex:1 1 auto; }
  .doorrow > .row a button { width:100%; justify-content:center; }
}
/* Printable door-QR poster (its own 'paper' palette, independent of theme).
   display:block, not main's grid: in a grid the poster's fixed 440px width
   grows the auto track, so max-width:100% resolves against 440px and the
   poster overflows narrow phones instead of shrinking. */
.posterwrap { display:block; max-width:560px; margin:0 auto; padding:18px 16px 44px; }
.posteractions { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; margin-bottom:16px; }
.poster { --pp-paper:#fff; --pp-ink:#132734; --pp-muted:#5c6f7c; --pp-accent:#10b789; --pp-accent-ink:#06231b; --pp-line:#e3e9ec;
  width:440px; max-width:100%; margin:0 auto; background:var(--pp-paper); color:var(--pp-ink);
  border:1px solid var(--pp-line); border-radius:20px; overflow:hidden; box-shadow:var(--shadow);
  text-align:center; -webkit-print-color-adjust:exact; print-color-adjust:exact; }
.poster-band { background:linear-gradient(135deg,var(--pp-accent),#0c9f95); color:var(--pp-accent-ink); padding:14px 22px; display:flex; align-items:center; justify-content:space-between; gap:10px; }
.poster-brand { display:inline-flex; align-items:center; gap:9px; font-family:'Space Grotesk',system-ui,sans-serif; font-weight:600; font-size:18px; letter-spacing:-.02em; }
.poster-brand .dot { color:#fff; }
.guide-list { padding-left:18px; margin:8px 0 0; display:grid; gap:8px; }
.guide-steps { padding-left:22px; margin:6px 0 0; display:grid; gap:8px; }
.guide-steps li { padding-left:4px; }
/* Steps beside their illustration (when the guide has one); stacked on phones. */
.guide-council { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:18px 32px; align-items:start; }
.guide-council .dm { width:250px; margin-top:6px; }
@media (max-width:640px) { .guide-council { grid-template-columns:1fr; } .guide-council .dm { width:auto; max-width:320px; } }
/* Guide index at the top of each guide: a numbered, two-column contents panel.
   The current entry is the one filled row with an accent bar. */
.guide-nav { margin:0 0 22px; padding:10px 10px 8px; border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--surface-2); }
.guide-nav-h { display:block; margin:0 0 6px 6px; font-size:11px; font-weight:600; letter-spacing:.06em; text-transform:uppercase; color:var(--muted); }
.guide-nav ol { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:1fr 1fr; gap:2px 8px; counter-reset:guide; }
.guide-nav li { counter-increment:guide; }
.guide-nav a { display:flex; gap:9px; align-items:baseline; padding:6px 8px; border-radius:6px; font-size:13.5px; line-height:1.35; color:var(--ink); text-decoration:none; }
.guide-nav a::before { content:counter(guide); flex:none; width:14px; font-size:11px; font-weight:700; font-variant-numeric:tabular-nums; color:var(--muted); }
.guide-nav a:hover { background:var(--surface); color:var(--primary-2); }
.guide-nav a[aria-current="page"] { background:var(--primary-soft); color:var(--primary-2); font-weight:600; box-shadow:inset 3px 0 0 var(--primary); }
.guide-nav a[aria-current="page"]::before { color:var(--primary-2); }
@media (max-width:640px) { .guide-nav ol { grid-template-columns:1fr; } }
.guide-pager { display:flex; justify-content:space-between; gap:12px; margin-top:24px; }
.guide-pager a { flex:1 1 0; max-width:48%; border:1px solid var(--line); border-radius:var(--radius-sm); padding:10px 14px; text-decoration:none; color:var(--ink); display:grid; align-content:start; gap:2px; font-size:14px; line-height:1.35; }
.guide-pager a:hover { border-color:var(--primary); }
.guide-pager a .dir { font-size:11px; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); font-weight:600; }
.guide-pager .next { margin-left:auto; text-align:right; }
.poster-qr { display:block; width:240px; height:240px; margin:16px auto 8px; background:#fff; border-radius:12px; padding:10px; box-sizing:content-box; }
.poster-url { text-align:center; font-weight:700; font-size:22px; letter-spacing:.01em; margin:4px 0 14px; color:var(--pp-ink); }
/* the share card: one lead line, the code, the address, a short note — spaced as a card, not a poster */
.poster.share-poster { padding-bottom:26px; }
.poster.share-poster .poster-lead { margin:20px 22px 6px; }
.poster.share-poster .poster-qr { width:200px; height:200px; margin:10px auto 6px; }
.poster.share-poster .poster-note { max-width:360px; margin:0 auto; text-align:center; }
.poster-mark { width:26px; height:26px; border-radius:7px; background:rgba(255,255,255,.28); display:grid; place-items:center; }
.poster-mark svg { width:16px; height:16px; stroke:currentColor; }
.poster-kicker { font-size:11px; font-weight:600; letter-spacing:.14em; text-transform:uppercase; opacity:.85; }
.poster-body { padding:26px 30px 22px; }
.poster-title { font-size:23px; font-weight:700; letter-spacing:-.01em; margin:0 0 4px; color:var(--pp-ink); text-wrap:balance; }
.poster-lead { font-size:15px; font-weight:600; color:var(--pp-ink); margin:0 0 18px; }
.poster-qbox { display:block; width:268px; max-width:100%; margin:0 auto; box-sizing:border-box; padding:14px; background:#fff; border:1px solid var(--pp-line); border-radius:14px; }
.poster-qbox img { width:100%; height:auto; image-rendering:pixelated; display:block; }
/* Steps and note share the same width, left edge and alignment so the lower
     block reads as one tidy left-aligned column under the centred QR. */
.poster-steps { text-align:left; max-width:300px; margin:22px auto 4px; padding-left:24px; display:flex; flex-direction:column; gap:8px; font-size:13.5px; color:var(--pp-muted); }
.poster-steps li { line-height:1.4; }
.poster-steps li::marker { color:var(--pp-accent); font-weight:700; }
.poster-note { text-align:left; font-size:12px; color:var(--pp-muted); margin:14px auto 0; max-width:300px; padding-left:24px; line-height:1.5; }
.poster-foot { border-top:1px solid var(--pp-line); padding:12px 22px; font-size:11px; color:var(--pp-muted); }
.posterfor { color:var(--muted); font-size:13px; margin:0; }
.posterfor strong { color:var(--fg); font-weight:600; }
.posterhint { text-align:center; color:var(--muted); font-size:12.5px; margin:18px auto 0; max-width:420px; line-height:1.5; }
@media (max-width:520px){
  .posterwrap { padding:14px 12px 40px; }
  .posteractions { margin-bottom:12px; }
  .poster-body { padding:22px 18px 18px; }
  .poster-band { padding:12px 16px; }
  .poster-title { font-size:20px; }
  .poster-lead { font-size:14px; }
  .poster-steps, .poster-note { max-width:none; }
}
@media print {
  @page { margin:12mm; }
  html, body { background:#fff !important; margin:0 !important; height:100%; }
  /* Nothing decorative should print: no chrome, and no drop shadows from any
     element (a stray shadow was printing at the poster's corner). */
  .skip-link, #htmx-bar, .appbar, .navwrap, .noprint { display:none !important; }
  * { box-shadow:none !important; }
  /* Fill the whole printable page so the QR prints as one full-page sign —
     print it and stick it up, no cutting. Fold once down the middle if a
     half-page card is wanted instead. */
  .posterwrap { padding:0 !important; max-width:none !important; margin:0 !important; height:100%; display:flex; }
  .poster { border-color:#d7dee1 !important; width:100% !important; max-width:none !important; margin:0 !important; flex:1; display:flex; flex-direction:column; break-inside:avoid; }
  .poster-body { flex:1 1 auto; display:flex; flex-direction:column; justify-content:center; padding:10mm 14mm; }
  .poster-qbox { width:78mm; padding:6mm; }
  .poster-title { font-size:30px; }
  .poster-lead { font-size:17px; }
  .poster-steps, .poster-note { max-width:340px; padding-left:26px; }
  .poster-steps { font-size:15px; gap:11px; margin-top:30px; }
  .poster-note { font-size:13.5px; margin-top:20px; }
}
/* Admin dashboard */
.adminwrap { max-width:1100px; margin:0 auto; padding:18px 16px 60px; }
.adm-health { background:var(--surface); border:1px solid var(--line); border-left:4px solid var(--primary); border-radius:var(--radius-sm); padding:12px 16px; margin:8px 0 18px; display:flex; flex-direction:column; gap:8px; }
.adm-health.bad { border-left-color:var(--danger); }
.adm-stats { display:flex; flex-wrap:wrap; gap:6px 16px; font-size:13px; color:var(--muted); }
.adm-stats .good { color:var(--primary); }
.adm-stats .bad { color:var(--danger); font-weight:650; }
.adm-filters { display:flex; flex-wrap:wrap; gap:6px; margin:0 0 10px; }
.adm-chip { font:inherit; font-size:12px; font-weight:600; padding:5px 11px; border-radius:999px; border:1px solid var(--line); background:var(--surface); color:var(--muted); cursor:pointer; transition:border-color .15s, color .15s; }
.adm-chip:hover { border-color:var(--primary); }
.adm-chip.on { border-color:var(--primary); color:var(--primary); background:color-mix(in srgb, var(--primary) 12%, transparent); }
.adm-chip .n { font-weight:400; margin-left:2px; opacity:.85; }
.adm-tablewrap { overflow-x:auto; border:1px solid var(--line); border-radius:var(--radius-sm); }
table.adm { width:100%; border-collapse:collapse; font-size:13px; white-space:nowrap; }
table.adm th { text-align:left; font-weight:600; font-size:11.5px; letter-spacing:.04em; text-transform:uppercase; color:var(--muted); padding:10px 12px; border-bottom:1px solid var(--line); background:var(--surface-2); position:sticky; top:0; }
table.adm td { padding:10px 12px; border-bottom:1px solid var(--line); vertical-align:top; }
table.adm tr:last-child td { border-bottom:0; }
/* The account cell keeps the address on ONE line: the wrapper already
   scrolls horizontally, so a width cap here only bought mid-word-shattered
   emails ("r…@thehopk\ninsgroup.com.au") in exchange for nothing. The
   phone layout below re-enables wrapping — there each row is a full-width
   card with no scroll, so a long address must break rather than overflow. */
.adm-acct { font-weight:600; }
.adm-sub { font-size:11.5px; color:var(--muted); font-weight:400; }
.adm-notify .tag, table.adm .tag { display:inline-block; font-size:11px; padding:1px 7px; border-radius:999px; background:var(--surface-2); border:1px solid var(--line); color:var(--muted); margin-right:3px; }
table.adm .bad { color:var(--danger); }
.adm-pill { display:inline-block; font-size:11.5px; font-weight:600; padding:2px 9px; border-radius:999px; border:1px solid transparent; }
.adm-pill.ok { background:color-mix(in srgb, var(--primary) 16%, transparent); color:var(--primary); }
.adm-pill.stale, .adm-pill.relink { background:color-mix(in srgb, var(--warn, #b54708) 16%, transparent); color:var(--warn, #b54708); }
.adm-pill.unlinked { background:var(--surface-2); color:var(--muted); }
@media (max-width:640px){
  /* A 10-column admin row can only scroll sideways on a phone. Stack each row into
     a labelled card instead: the column headers are hidden and each cell prints its
     own label from data-label, so nothing needs horizontal scrolling. */
  .adm-tablewrap { overflow-x:visible; border:0; border-radius:0; }
  table.adm { white-space:normal; font-size:13px; }
  table.adm thead { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0 0 0 0); border:0; }
  table.adm, table.adm tbody, table.adm tr, table.adm td { display:block; }
  table.adm tr { border:1px solid var(--line); border-radius:var(--radius-sm); background:var(--surface); padding:10px 14px; margin-bottom:10px; }
  table.adm td { border:0; padding:4px 0; display:flex; gap:8px; align-items:baseline; text-align:right; flex-wrap:wrap; }
  table.adm td::before { content:attr(data-label); margin-right:auto; color:var(--muted); font-size:11px; font-weight:600; letter-spacing:.04em; text-transform:uppercase; text-align:left; flex:0 0 auto; }
  table.adm td.adm-acct { display:block; text-align:left; font-size:15px; margin-bottom:4px; max-width:none; overflow-wrap:break-word; }
  table.adm td.adm-acct::before { content:none; }
  table.adm td.empty-note { display:block; text-align:center; }
  table.adm td.empty-note::before { content:none; }
}
/* Public guest activation page */
.guestwrap { max-width:460px; margin:0 auto; padding:32px 18px 60px; }
.guestcard form.htmx-request button { opacity:.55; pointer-events:none; } /* tap feedback while the live update is in flight */
.gstat { margin-top:2px; }
.gnow .gnuntil { color:var(--muted); font-size:13px; font-weight:500; }
.gsub { display:flex; align-items:center; gap:7px; min-height:28px; margin:2px 0 0; font-size:13px; color:var(--muted); } /* fixed-height slot: applying/settled notes swap here without reflowing the page (28px fits the plate chip) */
.gsub .plate { padding:1px 6px; font-size:12px; }
.gsub .gok { display:inline-flex; align-items:center; gap:6px; color:var(--primary); font-weight:600; }
.gsub .gstall { display:inline-flex; align-items:flex-start; gap:6px; color:var(--accent-ink,#92400e); }
.gsub .gspin { width:14px; height:14px; border-width:2px; border-color:currentColor; border-top-color:transparent; margin:0; flex:none; } /* inline size (the base .gspin is the big wait-page spinner) */
.gcar .gtag.pend { background:var(--surface); color:var(--muted); border:1px solid var(--line); }
@media (min-width: 720px) { /* give desktop viewers more than a phone column */
  .guestwrap { max-width:680px; }
  .gcars { grid-template-columns:1fr 1fr; }
}
.guestcard { background:var(--surface); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); padding:26px 24px; }
.gcredit { text-align:center; margin:14px auto 0; font-size:12.5px; color:var(--muted); max-width:340px; line-height:1.5; }
.gcredit a { color:inherit; text-decoration:underline; }
.guestcard h1 { font-size:20px; letter-spacing:-.02em; margin:2px 0 4px; }
.gbrand { display:flex; align-items:center; gap:10px; margin-bottom:16px; }
.gbrand .mark { width:30px; height:30px; border-radius:9px; background:linear-gradient(135deg,var(--primary),var(--primary-2)); color:var(--on-primary); display:grid; place-items:center; box-shadow:var(--shadow); flex:0 0 auto; }
.gbrand .mark svg { width:18px; height:18px; stroke-width:1.9; }
.gbrand .word { font-family:'Space Grotesk',system-ui,sans-serif; font-weight:600; font-size:19px; letter-spacing:-.02em; line-height:1; }
.gbrand .word .dot { color:var(--primary); }
.gmanaged { font-size:13px; color:var(--muted); margin:0; }
.gnow { font-size:14px; color:var(--muted); margin:8px 0 0; }
.gtip { margin:14px 0 0; padding:11px 13px; border-radius:11px; background:var(--primary-soft); color:var(--primary-2); font-size:13px; line-height:1.5; text-align:center; }
.gref { margin-top:16px; padding-top:14px; border-top:1px solid var(--line); text-align:center; }
.gref .empty-note { margin:0 auto 10px; max-width:320px; }
.grefrow { display:flex; gap:8px; justify-content:center; flex-wrap:wrap; }
/* the mailto is an <a> that must sit visually beside a real button */
.btnlike { display:inline-flex; align-items:center; gap:6px; padding:7px 12px; font-size:13px; border-radius:10px; background:var(--surface-2); color:var(--ink); border:1px solid var(--line); text-decoration:none; font-weight:600; }
.btnlike:hover { background:var(--line); }
.gcars { display:grid; gap:12px; margin-top:6px; }
.gcar { position:relative; justify-content:flex-start; gap:12px; background:var(--surface-2); color:var(--ink); border:1px solid var(--line); border-left:5px solid var(--plate,var(--line)); box-shadow:none; padding:14px 16px; font-size:15px; font-weight:600; }
.gcar:hover { border-color:var(--primary); filter:none; box-shadow:var(--shadow); }
.gcar.on { border-color:var(--primary); box-shadow:0 0 0 1px var(--primary) inset; }
.gcar .plate { margin-left:auto; }
/* Status pills float as corner badges, OUT of the layout flow: appearing or
   swapping ("applying…" ↔ "on now") never resizes or rewraps the button. */
.gcar .gtag { position:absolute; top:-9px; right:10px; font-size:11px; font-weight:650; color:var(--on-primary); background:var(--primary); padding:2px 8px; border-radius:999px; white-space:nowrap; }
.gring { width:56px; height:56px; border-radius:50%; display:flex; align-items:center; justify-content:center; margin:14px auto 12px; }
.gring svg { width:28px; height:28px; fill:none; stroke:currentColor; stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round; }
.gring.ok { background:var(--ok-soft); color:var(--ok); }
.gring.warn { background:var(--warn-soft); color:var(--warn); }
.gresult { font-size:16px; font-weight:600; line-height:1.5; margin:0 0 6px; }
.gspin { width:34px; height:34px; border-radius:50%; border:3px solid var(--line); border-top-color:var(--primary); margin:20px auto 14px; animation:spin .8s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }
/* Fixed-size status slot in the nowbadge: spinner while the background council
   check runs, a tick when settled — identical footprint, so neither state
   moves the pill. */
.pslot { display:inline-flex; width:13px; height:13px; align-items:center; justify-content:center; flex:none; }
.pslot .pchk { width:11px; height:11px; border-radius:50%; border:2px solid var(--line); border-top-color:var(--muted); animation:spin .8s linear infinite; }
.pslot .pok { display:inline-flex; color:var(--ok); }
.pslot .pok svg { width:13px; height:13px; stroke-width:2.4; }
/* The hint beside the status icon: always rendered, min-width sized to the longest
   text it can hold ("checked 12 days ago", 19 characters — ch units, not em, so it is
   measured in this font's own digits) so swapping the words never moves the pill. */
.phint { color:var(--muted); font-size:12px; white-space:nowrap; display:inline-block; min-width:19ch; }
.vstatus { display:inline-flex; align-items:center; min-width:3.5em; font-size:12px; }
.vsaved { color:var(--ok); font-weight:600; white-space:nowrap; }
.vrevert { background:none; border:0; box-shadow:none; padding:0; color:var(--primary); font:inherit; font-size:12px; font-weight:600; cursor:pointer; text-decoration:underline; }
.vnotify { display:flex; align-items:center; gap:8px; margin-top:12px; cursor:pointer; line-height:1.3; }
.vnotify input { flex:none; margin:0; width:15px; height:15px; }
/* Phones: the pill cannot hold label + plate + icon + a 19ch hint on one line
   without breaking the label into two or three lines. Below 560px it becomes a
   full-width, two-row block: plate · "on permit now" on the first row, the status
   icon centred under the plate with its hint beside it on the second. The hint is
   always present, so the height never changes either. */
/* .pcheck is markup-only grouping; it dissolves at every width so the icon and
   hint participate directly in the pill's flex (desktop) or grid (phone). */
.pcheck { display:contents; }
@media (max-width:560px){
  /* Two rows, two columns: plate | label on the first, tick | hint on the
     second. Row two is reserved at a height that fits both the badge and a
     two-line failure note, so no state swap ever moves the layout. */
  .nowbadge { display:grid; grid-template-columns:auto 1fr; grid-template-rows:auto minmax(38px, auto); grid-template-areas:"plate label" "icon hint"; align-items:center; column-gap:10px; row-gap:8px; width:100%; border-radius:16px; padding:12px 14px; }
  .nowbadge > .plate { grid-area:plate; justify-self:start; }
  .nowbadge > .muted { grid-area:label; }
  /* The tick alone wears an UNFILLED echo of the plate badge — same border
     weight, radius and height, stretched to the plate's column width so it
     sits squarely beneath it and the indent reads as deliberate. Width comes
     from the plate, never from the hint, so poll swaps can't resize it. */
  .nowbadge .pslot { grid-area:icon; justify-self:stretch; width:auto; height:auto; min-height:32px; border:2px solid var(--line); border-radius:6px; background:transparent; display:flex; align-items:center; justify-content:center; }
  .nowbadge .phint, .nowbadge .pnote { grid-area:hint; min-width:0; white-space:normal; }
}
.pslot .pwarn { display:inline-flex; color:var(--warn); }
.pslot .pwarn svg { width:13px; height:13px; stroke-width:2.4; }
.pnote { color:var(--warn); font-size:12px; white-space:nowrap; }
.tag { font-size:11px; font-weight:650; color:var(--on-primary); background:var(--primary); padding:2px 9px; border-radius:999px; }
.link-quiet { color:var(--muted); font-size:13.5px; display:inline-flex; align-items:center; gap:6px; position:relative; }
/* Attention state for the "set up your other permit" control: amber-bordered,
   with a glow ring that pulses a few times to catch the eye, then rests. Motion
   is disabled under prefers-reduced-motion by the global rule above; the border
   and fill still mark it out. */
.setup-more { display:inline-flex; align-items:center; gap:7px; padding:9px 15px; border-radius:11px;
  font-size:14px; font-weight:650; text-decoration:none; color:var(--ink);
  background:var(--accent-soft); border:2px solid var(--accent); align-self:flex-start;
  animation:setup-pulse 1.6s ease-out 4; }
.setup-more svg { color:var(--accent-fill); }
.setup-more:hover { background:color-mix(in srgb, var(--accent) 18%, var(--surface)); }
@keyframes setup-pulse {
  0%   { box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 60%, transparent); }
  70%  { box-shadow:0 0 0 10px color-mix(in srgb, var(--accent) 0%, transparent); }
  100% { box-shadow:0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}
/* A tip is a contained strip, so its dismiss belongs to it: icon, text and ×
   all centred on the strip. No margin of its own — the page grid's gap spaces
   the link above and the strips evenly. */
/* A hint wears the colour of the page it points to (--hue = that page's
   section hue, same var the nav tabs use); no --hue falls back to the brand
   primary. Icon + a quiet left accent only — the strip stays a tip, not a banner. */
.share-hint { color:var(--muted); font-size:13.5px; display:flex; align-items:center; gap:10px; margin:0; padding:9px 8px 9px 12px; background:var(--surface-2); border:1px solid var(--line); border-left:3px solid color-mix(in srgb, var(--hue,var(--primary)) 55%, var(--surface-2)); border-radius:11px; }
.share-hint > svg { flex:none; width:16px; height:16px; align-self:center; color:var(--hue,var(--primary)); }
.share-hint > span { flex:1 1 auto; }
.share-hint a { color:inherit; text-decoration:underline; }
.share-hint a:hover { color:var(--primary); }
.share-hint button { flex:none; align-self:center; margin-left:4px; }
.link-quiet:hover { color:var(--primary); }
.link-quiet.active { color:var(--ink); font-weight:650; }
.link-quiet.active::after { content:""; position:absolute; left:0; right:0; bottom:-6px; height:2px; border-radius:2px; background:var(--primary); }
/* Public header: brand left, secondary links + actions clustered right; wraps cleanly on phones. */
.pubbar { flex-wrap:wrap; }
.pubnav { display:inline-flex; align-items:center; gap:18px; margin-left:auto; }
@media (max-width:560px){
  .pubnav { order:3; flex-basis:100%; margin-left:0; justify-content:center; gap:22px; padding-top:2px; }
  .pubnav .link-quiet.active::after { bottom:-3px; }
}

/* hero (onboarding) */
.hero { text-align:center; padding:34px 22px; max-width:520px; margin:26px auto; }
.hero .ring { width:60px; height:60px; border-radius:16px; background:linear-gradient(135deg,var(--primary),var(--primary-2)); color:var(--on-primary); display:inline-flex; align-items:center; justify-content:center; margin-bottom:16px; box-shadow:var(--shadow-lg); }
.hero .ring svg { width:28px; height:28px; }
.hero h2 { margin:0 0 8px; font-size:21px; letter-spacing:-.02em; justify-content:center; }
.hero p { margin:0 auto 18px; max-width:440px; color:var(--muted); }
form.stack { display:grid; gap:11px; max-width:none; margin:0; text-align:left; }
/* Focused sign-in / onboarding form stays narrow and centred in its hero card. */
.card.hero form.stack { max-width:360px; margin:16px auto 0; }

/* landing (public) */
.appbar-r .cta-btn { text-decoration:none; }
.landing { max-width:840px; }
.lhero { text-align:center; padding:44px 10px 8px; }
.lhero h1 { font-size:clamp(28px,5vw,42px); line-height:1.08; letter-spacing:-.03em; margin:0 auto 18px; max-width:680px; font-weight:750; }
.lhero p.lead { color:var(--muted); font-size:17px; max-width:600px; margin:0 auto 26px; line-height:1.55; }
.cta { font-size:16px; padding:14px 24px; border-radius:14px; }
.lnote { font-size:13px; color:var(--muted); margin-top:15px; }
.lsteps { display:grid; grid-template-columns:repeat(3,1fr); gap:16px; margin-top:40px; }
@media (max-width:680px){ .lsteps { grid-template-columns:1fr; } }
.lstep { background:var(--surface); border:1px solid var(--line); border-radius:var(--radius); padding:22px; box-shadow:var(--shadow); animation:rise .3s ease both; }
.lnum { width:34px; height:34px; border-radius:10px; display:grid; place-items:center; font-weight:700; background:color-mix(in srgb,var(--hue) 15%,transparent); color:var(--hue); margin-bottom:13px; }
.lstep h3 { margin:0 0 6px; font-size:16px; letter-spacing:-.01em; }
.lstep p { margin:0; color:var(--muted); font-size:14px; line-height:1.5; }
.ldisclaimer { text-align:center; color:var(--muted); font-size:12.5px; margin:40px auto 0; max-width:580px; line-height:1.5; }
.brand-link { color:inherit; }
/* Quiet way home at the TOP of the signed-in features page — the nav tabs are
   there too, but a page entered from the header deserves an exit before the
   scroll, not only after it. */
.topback { margin:8px 0 -6px; font-size:13.5px; }
/* about */
.about { max-width:820px; }
/* On phones the features/about prose ran too close to the screen edges (main's
   base padding is tuned for cards, which carry their own inner padding). */
@media (max-width:560px){ main.landing { padding-left:22px; padding-right:22px; } }
.about-h1 { font-size:30px; letter-spacing:-.02em; margin:30px 0 6px; font-weight:750; }
.about-h2 { font-size:16px; letter-spacing:-.01em; margin:28px 0 8px; }
/* readable prose panel (used on the why/how-it-works page) */
.paper { background:var(--surface); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); padding:22px 26px; margin:22px 0; }
.paper > :first-child { margin-top:0; }
.paper > :last-child { margin-bottom:0; }
.whyhead { text-align:center; font-size:15px; color:var(--muted); font-weight:600; letter-spacing:.02em; margin:34px 0 4px; }
.about p, .about li { color:var(--ink); font-size:15px; line-height:1.6; }
.about ul { margin:0; padding-left:20px; display:grid; gap:9px; }
.about li { padding-left:2px; }

/* Security page: progressive-disclosure sections. A tidy summary up top, the
   detail folded into expandable rows so the page isn't a wall of text. */
.sec-more { color:var(--muted); font-size:14px; margin:10px 0 4px; }
.sec { border-top:1px solid var(--line); }
.sec:last-of-type { border-bottom:1px solid var(--line); }
.sec > summary { cursor:pointer; list-style:none; padding:14px 2px; font-weight:650; font-size:15px; color:var(--ink); display:flex; align-items:center; justify-content:space-between; gap:14px; }
.sec > summary::-webkit-details-marker { display:none; }
.sec > summary::after { content:""; width:9px; height:9px; border-right:2px solid var(--muted); border-bottom:2px solid var(--muted); transform:rotate(45deg); transition:transform .2s; flex:0 0 auto; margin-right:3px; }
.sec[open] > summary::after { transform:rotate(-135deg); }
.sec > p { margin:0 0 12px; color:var(--ink); font-size:14.5px; line-height:1.6; }
.sec > p:first-of-type { margin-top:2px; }
.sec[open] { padding-bottom:8px; }
@media (prefers-reduced-motion:reduce){ .sec > summary::after { transition:none; } }

/* FAB */
.fab { position:fixed; right:22px; bottom:24px; z-index:25; height:52px; padding:0 20px 0 17px; border:0; border-radius:16px; background:linear-gradient(135deg,var(--accent-fill),color-mix(in srgb,var(--accent-fill) 82%, #000)); color:var(--on-accent); display:inline-flex; align-items:center; gap:9px; font-weight:600; font-size:14.5px; box-shadow:var(--shadow-lg); cursor:pointer; transition:transform .12s, filter .15s, box-shadow .15s; }
.fab svg { width:20px; height:20px; stroke-width:2.1; }
a.fab { text-decoration:none; }
.fab:hover { filter:brightness(1.06); transform:translateY(-2px); }
.fab:active { transform:scale(.97); }
/* The label stays on at every width. A bare "+" circle on a phone told one
   household nothing (2026-09-09: they cleared the plate, added the car, and
   went hunting through Guests for a way to put it on); the pill is 215px and
   fits above the bottom bar on a 375px screen. */
/* Below 640px the nav is a fixed bottom bar (z-index 30), so lift the FAB above
   it. Declared after the base .fab so it wins at equal specificity. */
@media (max-width:640px){ .fab { bottom:calc(74px + env(safe-area-inset-bottom, 0px)); z-index:35; } }
.modal form.stack { max-width:none; margin:0; }
.modal .fld select, .modal .fld input { width:100%; }
/* Visitor-QR modal: the QR has to be readable by someone else's phone camera,
   so the code sits on white regardless of theme and the body is centred. */
.qrmodal { max-width: 400px; }
/* svg is display:block globally, so an icon inside the heading would otherwise
   drop onto its own line above the text. */
.qrmodal .modal-head strong { display:inline-flex; align-items:center; gap:9px; }
.qrbody { text-align:center; padding: 4px 2px 2px; min-height: 220px; display:flex;
          flex-direction:column; align-items:center; justify-content:center; }
.qrbody .subtitle { text-align:center; }
/* Quiet tail on the colour key: the cars whose colours are not on this page.
   Deliberately understated — it is a way out to the full list, not a control. */
.legend-more { font-size:12.5px; color:var(--muted); align-self:center; padding:2px 4px; }
.legend-more:hover { color:var(--ink); text-decoration:underline; }
/* The visitor-QR trigger is FILLED in the ACCENT, same as the FAB.
   In this palette hue carries meaning: --sec-* says which SECTION you are in
   (indigo is Vehicles, violet is Activity), while --accent is the colour of
   "something you can do". A section hue on a button would therefore claim the
   wrong thing — an indigo button on the teal Schedule page reads as Vehicles.
   Both of these are actions, so both are amber; the FAB stays the bigger,
   floating one for a planned booking, this is the inline one for right now.

   The FILL and the ink both flip by theme, via --accent-fill / --on-accent.
   Light mode fills with a deeper amber and writes in white; dark mode fills with
   the bright amber and writes in near-black. Using one amber for both and only
   flipping the ink passed the contrast maths but read as muddy brown-on-orange
   in daylight — legible by the numbers, hard to actually read. .fab uses the
   same two tokens so the pair stays a family. */
/* The QR button and the outlined "Remove <plate>" action share a row below the
   permit title. margin-bottom keeps the buttons off whatever follows (the expiry
   line or the roster) — without it the outlined button crowded the content below. */
.permit-actions { display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin:8px 0 6px; }
.permit-actions form { margin:0; }
.qrtrigger {
  align-self:flex-start;
  background:linear-gradient(135deg, var(--accent-fill), color-mix(in srgb, var(--accent-fill) 84%, #000));
  color:var(--on-accent); border:0; font-weight:600; box-shadow:var(--shadow);
}
.qrtrigger:hover { filter:brightness(1.06); box-shadow:var(--shadow); }
.qrtrigger svg { stroke-width:2; }
