/* ==========================================================================
   MyStayDays — Warm Horizon
   ==========================================================================

   The design system for the application and the public site. One stylesheet,
   no build step, no dependencies — the same constraint as the rest of the
   project, and the reason every colour below is a CSS custom property rather
   than a preprocessor variable.

   THE FEELING WE ARE AFTER. Somebody opening this product is planning a
   holiday, not filing a return. Warm cream and amber over deep indigo — dusk
   over water rather than a government form. Confidence without severity,
   optimism without noise.

   ONE SCHEME, AND IT IS THE DARK ONE. This palette was indigo ink on sunlit
   paper, with a dark variant behind `prefers-color-scheme`. It is now a
   single dark scheme that everybody gets, because the dark treatment IS the
   brand: og-card.svg — the image every share of this product renders as — is
   drawn on ink-900 with a sand-050 headline and an accent-300 second line,
   and a product whose social card and whose website are different colours
   reads as two different products.

   Three rules inverted when the ground did, and each one is worth stating
   because each was load-bearing in the other direction:

     - SAND IS THE INK NOW. The sand ramp used to be the surfaces — the paper
       everything sat on. Surfaces are steps of the ink ramp instead, and
       sand-050 is what body text is written in. That is the same warmth
       doing the same job from the other side, and it is the whole difference
       between this and a generic dark mode: a cool near-white on navy reads
       as a terminal, and a warm one reads as an evening.
     - THE SURFACES ARE THE CARD'S OWN BANDS. --surface-sunken is ink-900,
       which is the card's ground; --surface is ink-800 and --surface-raised
       is ink-700, which are its two ridges. Nothing here was chosen to look
       dark. It was chosen to be the picture people already have of us.
     - HOVER LIFTS RATHER THAN SINKS. On paper a hovered row goes darker than
       the page, so --surface-sunken did for both jobs. On ink it has to go
       lighter or the hover reads as the control switching off, so the two
       jobs split: --surface-hover is the only surface token this scheme
       needed that the light one did not.

   WHAT DID NOT CHANGE, deliberately. The status vocabulary, the amber accent,
   the 18px corners, the top-lit gradients, and the fixed --status-*-solid
   family. A change of ground is not a reason to move anything users have
   already learned.

   THE ONE REAL TENSION, stated because it is a live risk rather than a
   resolved one: the accent is amber and the "notice" compliance status is
   also amber-ish. A user must never wonder whether an amber thing is an
   invitation or a warning. Four rules keep them apart, and all four are
   load-bearing:

     1. The accent is only ever a FILLED button with a verb on it. Status is
        never a filled amber button.
     2. Status is always colour AND a shape AND a word — .badge carries a dot
        and a label, never colour alone. Roughly one man in twelve cannot
        reliably distinguish these hues.
     3. The status ambers are deliberately duller and darker than the accent
        (#C9A227 / #FB923C against #FFC53D), so they read as different
        materials even at a glance.

        THIS RULE NEARLY DIED IN THE MOVE TO DARK, and it is the reason
        --status-notice is not the value it was. The old dark block lifted
        notice to #FBBF24 against an accent of #FFC53D: two golds a shade
        apart, which is rule 3 gone. It survived only because almost nobody
        saw the dark scheme. Making dark the only scheme would have made a
        documented "must never happen" permanent, so notice is an ochre now —
        still legible at 6.4:1 on its own panel, and plainly a duller, older
        gold than the accent beside it.
     4. A status colour used as a FILL under white text comes from the
        --status-*-solid family, never from the token the status is written
        in. The two are different jobs; conflating them put white on a pastel
        in dark mode on five components, the worst at 1.74:1.

   WHAT GOING DARK-ONLY EXPOSED. The dark block was a second scheme almost
   nobody looked at, so it carried defects that only a light-mode developer
   could ship. Every one of these is now the only thing a user can see, and
   every one is fixed here rather than left for somebody to find:

     - --status-muted was never overridden at all. It stayed ink-500, a dark
       slate, and every neutral badge, verdict panel and milestone icon drew
       it on a dark panel at 2.24:1. It is ink-200 now: 8.3:1.
     - --lagoon-600 is the link HOVER, and it stayed at the light scheme's
       #0891B2 while --lagoon-700 flipped bright. Hovering a link made it
       darker and dropped it to 4.28:1 — backwards, and failing. The ramp
       inverts in dark, so 600 is now the lighter of the two.
     - .meter__track was ink-100, a near-white bar, because the allowance
       meter is the one component whose track is a colour rather than a
       token binding.

   TYPEFACE. The system stack, not a webfont. A webfont means either an
   external origin — which the Content-Security-Policy forbids and which would
   be the product's only third-party dependency — or a self-hosted binary in a
   repository that deliberately has no build step. The system stack renders as
   Segoe UI Variable, SF Pro or Roboto depending on the machine, all of which
   are the right kind of face for this, and it costs nothing and never blocks
   a paint.

   Contents
     1.  Tokens
     2.  Reset and base
     3.  Layout — the shell, the sidebar, mobile chrome
     4.  Typography and page furniture
     5.  Buttons
     6.  Cards and surfaces
     7.  The answer, the verdict, the big number, the meter
     8.  Status — badges, alerts, attention
     9.  Forms and pickers
     10. Tables
     11. Lists — steps, checklist, next steps, month grid
     12. Authentication pages
     13. Marketing site
     14. Charts — the timeline
     15. The operator console
     16. Utilities
   ========================================================================== */


/* ==========================================================================
   1. Tokens
   ========================================================================== */

:root {
    /* ---- Ink — the ground, and now nearly everything else. --------------
       Indigo-navy rather than the cold slate it was. Every step is a hue, so
       the dark chrome has depth instead of looking like unpainted metal —
       which mattered when this ramp only had to paint a sidebar, and matters
       far more now that it paints every surface in the product.

       The dark end is the og-card, step for step: 900 is the ground it is
       drawn on, 800 and 700 are its two ridges. 950 is the one step this
       scheme had to add — the sidebar, the footer and the operator bar are
       bands that have to sit BELOW the page, and on paper the page was white
       so there was always somewhere lower to go. On ink-900 there was not.

       The light end is no longer surfaces. 050 down to 200 are text and
       hairlines now; 000 is used only where something genuinely must be pure
       white on a saturated fill. */
    --ink-950: #0A0F22;
    --ink-900: #0F1730;
    --ink-800: #16203F;
    --ink-700: #24304F;
    --ink-600: #3A4666;
    --ink-500: #4F5B7A;
    --ink-400: #626E8D;
    --ink-300: #9AA4BE;
    --ink-200: #C7CEDD;
    --ink-100: #E4E8F0;
    --ink-050: #F2F4F8;
    --ink-025: #F9FAFC;
    --ink-000: #FFFFFF;

    /* ---- Sand — the warm light INK. -------------------------------------
       These were the surfaces: the paper everything sat on. They are the
       writing now, and it is the same warmth doing the same job from the
       other side of the page.

       This is the single most important thing in the file. A dark theme whose
       text is a cool near-white is a terminal; a dark theme whose text is
       cream is an evening, and this product is about holidays. sand-050 is
       the exact value the og-card's headline is set in, which is not a
       coincidence — it is the point.

       Still reached for only through the bindings below, never by a
       component, so there remains exactly one place to change what "warm"
       means here. 200 and 300 are the reason the ramp keeps its cooler steps:
       they are what the card's second pill is filled with, and they are held
       for chips and fills that want to read as paper on ink rather than as
       another light grey. */
    --sand-050: #FFFBF2;
    --sand-100: #FBF3E1;
    --sand-200: #F0E2C4;
    --sand-300: #E3D0A7;

    /* ---- Accent — amber. Primary actions only. See the note above.
       A sunset orange-gold rather than a corporate amber.

       300, 400 and 500 DID NOT MOVE when the ground went dark, and resisting
       the urge to move them is why the card and the site match. They are
       already bright: 300 carries ink text at 11.2:1 and reads at 10:1
       against a card, which is exactly what the og-card demonstrates. The old
       dark block lifted 300 to #FFD666 — not because #FFC53D was too dark,
       but because it had shifted 400 down onto 300's value and the gradient
       stops had collapsed into each other. Fixing that by moving the brand
       amber was treating the symptom, and it quietly put the site a shade off
       its own social card.

       050 and 100 DID move, and had to. They are pale washes — the fill
       behind a feature icon, the plan flag, the hero's warm corner — and a
       cream wash on ink is a bright blot rather than a tint. They are the
       same warmth burnt down to something a dark card can hold.

       600 and 700 are the dark ambers that let text sit on white. Nothing
       sits on white any more, so nothing uses them; they stay because a ramp
       with a hole in it is worse than a ramp with a quiet end. */
    --accent-700: #92400E;
    --accent-600: #B45309;
    --accent-500: #E8720A;
    --accent-400: #FF9F1C;
    --accent-300: #FFC53D;
    --accent-200: #FFDE85;
    --accent-100: #3D1F06;
    --accent-050: #241205;

    /* ---- Orchid — a third holiday hue, decorative only. ------------------
       Used nowhere near compliance status: it exists so the marketing site's
       feature icons are not a wall of one colour. A tropical flower, not a
       fourth thing to disambiguate from red/amber/green.

       600 is the light-on-ink value and 050 is its wash, both flipped for the
       dark ground. 700 was the deep value for text on white and now has
       nothing to do; kept for the same reason accent-700 is. */
    --orchid-700: #86198F;
    --orchid-600: #E879F9;
    --orchid-050: #3B0764;

    /* ---- Lagoon — links, information, the calm secondary. ---------------
       Turquoise, because a travel product's "calm secondary" should read as
       warm sea rather than as a bank.

       THE RAMP INVERTS HERE, and half of it inverting was a real bug. On
       white, 700 was the dark turquoise text sat on and 600 was a step
       brighter for hover. On ink both have to be light, and only 700 was
       ever flipped — so `a:hover` reached for the light scheme's #0891B2 and
       every hovered link went DARKER, at 4.28:1, which fails outright. 600 is
       the lighter of the two now, so hover lifts. If either is ever touched,
       that ordering is the thing to preserve.

       500 did not move and must not: it is the vivid brand turquoise, it is
       the value the og-card's third pill is filled with, and it is the one
       step here saturated enough to be a FILL — the focus ring and the
       checkbox accent both want a mid-tone, not a pastel.

       800 IS NOT USED BY ANY RULE IN THIS FILE, and neither are accent-600,
       accent-700 or orchid-700. They are the deep steps — the ones dark
       enough to carry text on white — and this product has no white left.

       They stay because it still has ONE light surface: the transactional
       email. No mail client supports custom properties, so that template
       writes its palette out as literal hex and copies it from here, and
       320_mail.php fails the build if it ever copies a colour this file does
       not declare. That check is the only thing standing between us and an
       off-brand inbox, and it is worth more than the tidiness of deleting
       four unused declarations. 800 exists because the email's fallback link
       needs a turquoise that reads on white, and after the ramp inverted
       there was none — which is exactly the drift the check caught. */
    --lagoon-800: #0E7490;
    --lagoon-700: #67E8F9;
    --lagoon-600: #A5F3FC;
    --lagoon-500: #06B6D4;
    --lagoon-100: #164E63;
    --lagoon-050: #083344;

    /* ---- Semantic status ------------------------------------------------
       Compliance vocabulary, not colour names. A rule evaluator returns a
       status; the UI maps it here. Adding a rule family never adds a colour.
       The MEANINGS are carried forward unchanged — green, amber and red mean
       things in this product that users have learned, and a change of ground
       is not a reason to move them. The VALUES all had to flip, because every
       one of them is now read against ink rather than against paper: the four
       status colours are the light-on-dark versions, and every -bg and -line
       is a deep tint of its own hue instead of a pastel.

       TWO OF THESE WERE WRONG in the old dark block and are fixed here:

       --status-notice was #FBBF24 against an accent of #FFC53D. Two golds a
       shade apart is rule 3 at the top of this file gone, and rule 3 is what
       stops a user asking whether an amber thing is an offer or a warning.
       It is an ochre now: 6.4:1 on its own panel, 6.5:1 on a card, and
       plainly a duller and older gold than the accent.

       --status-muted was never overridden AT ALL. It stayed bound to ink-500,
       a dark slate meant for text on paper, and it is the colour of every
       neutral badge, every unclassified verdict panel and every milestone
       icon — all of which draw it on a dark surface. Measured at 2.24:1. It
       is ink-200 now, at 8.3:1. Nothing reported this: the dark block did
       override --status-muted-bg and --status-muted-line, so the panel looked
       deliberate and only the writing on it was unreadable. */
    --status-ok:          #4ADE80;
    --status-ok-bg:       #0C2A18;
    --status-ok-line:     #14532D;
    --status-notice:      #C9A227;
    --status-notice-bg:   #2A2410;
    --status-notice-line: #4A3C10;
    --status-warning:     #FB923C;
    --status-warning-bg:  #2D1A0E;
    --status-warning-line:#4A2A12;
    --status-breach:      #F87171;
    --status-breach-bg:   #2D1214;
    --status-breach-line: #4C1D1D;
    --status-muted:       var(--ink-200);
    --status-muted-bg:    var(--ink-700);
    --status-muted-line:  var(--ink-600);

    /* ---- Status, FILLED ---------------------------------------------------
       The colour a status chip is painted with when white text sits on it, as
       opposed to the colour that status is written in.

       Those are different jobs and they were being done by one token, which
       broke silently in dark mode. `--status-breach` flips to a light red for
       text on a dark surface — correct — and the attention badge was filling
       itself with that same light red and putting white on top: 2.77:1. The
       "done" ticks in the setup checklist were worse at 1.74:1, and the
       counted days in the calendar 2.26:1. Five elements, all invisible to
       anybody developing in light mode.

       These are the one family in the file that is a literal rather than a
       binding, and they survive the move to a single dark scheme untouched —
       which is the whole reason they exist. A saturated mid-tone carries
       white text at 5:1 or better on ink or on paper, and still reads as a
       filled shape against either (3:1+). Had they been bound to the status
       colours above, every one of them would just have inverted again. */
    --status-ok-solid:      #15803D;
    --status-warning-solid: #C2410C;
    --status-breach-solid:  #B91C1C;

    /* ---- Surfaces --------------------------------------------------------
       Four steps of the ink ramp, and they are the og-card's own bands: the
       page is the ground it is drawn on, and a card is its first ridge.

       The ORDER is what carries depth, and on ink it runs the opposite way to
       paper. Sunken is the page, surface is a card lifted off it, raised is a
       card that wants to be noticed. Deeper than the page there is only
       --surface-ink, which is the sidebar, the footer and the operator bar —
       bands that anchor rather than float.

       --surface-hover is the token this scheme needed and the light one did
       not. On paper a hovered row goes darker, so --surface-sunken did both
       jobs; on ink darker reads as the control switching off, so hover is a
       step UP. Every :hover in this file that used to take --surface-sunken
       takes this instead, and the two are no longer the same colour.

       --border deliberately sits two steps above the surface it draws on.
       One step is a hairline nobody can see on a dark card, and a card with
       an invisible border is a card that has stopped being a card. */
    --surface:        var(--ink-800);
    --surface-sunken: var(--ink-900);
    --surface-raised: var(--ink-700);
    --surface-hover:  var(--ink-700);
    --surface-inset:  var(--ink-900);
    --surface-ink:    var(--ink-950);
    --border:         var(--ink-600);
    --border-strong:  var(--ink-400);

    /* Cream, not a cool near-white — see the sand note above. --text-muted and
       --text-subtle stay on the ink ramp: it is the HEADLINE weight that wants
       warmth, and running the whole page cream flattens the hierarchy the two
       quieter greys exist to create. Measured on a card: 15.2:1, 10.0:1 and
       6.3:1, and on the page behind it 17.2:1, 11.2:1 and 7.1:1. */
    --text:          var(--sand-050);
    --text-muted:    var(--ink-200);
    --text-subtle:   var(--ink-300);
    --text-inverse:  var(--ink-900);
    --text-on-accent: var(--ink-900);

    /* The amber in the wordmark, as a semantic token rather than a scale step.
       It existed because brand amber on white measures 2.15:1, so light surfaces
       needed the darker amber and ink surfaces the bright one — and the sidebar and
       footer rebound it rather than carrying a `.sidebar .brand span` override.

       Every surface is ink now, so it is the bright amber everywhere and those two
       rebindings are no-ops. The token stays: it is what "Stay" in the wordmark, the
       eyebrow, the plan flag and the feature icons all read from, and collapsing it
       back into --accent-300 would scatter the decision across four components the
       next time anything moves. This is the exact amber the og-card is set in. */
    --brand-accent:  var(--accent-300);

    /* ---- Typography ------------------------------------------------------ */
    --font-sans: system-ui, -apple-system, "Segoe UI Variable Text", "Segoe UI",
                 Roboto, "Helvetica Neue", Arial, sans-serif;
    --font-mono: ui-monospace, "Cascadia Mono", Consolas, monospace;

    --text-xs:   0.8125rem;
    --text-sm:   0.9375rem;
    --text-base: 1.0625rem;
    --text-lg:   1.25rem;
    --text-xl:   1.5rem;
    --text-2xl:  1.875rem;
    --text-3xl:  2.375rem;
    --text-4xl:  3rem;
    --text-5xl:  3.75rem;

    --leading-tight: 1.15;
    --leading-snug:  1.35;
    --leading-body:  1.6;

    --numeric: "tnum" 1, "lnum" 1;

    /* ---- Space (4px base) ------------------------------------------------ */
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.5rem;
    --space-6: 2rem;
    --space-7: 3rem;
    --space-8: 4rem;
    --space-9: 6rem;

    /* ---- Radii and elevation --------------------------------------------
       Rounder again. A 14px radius was most of the difference between "form"
       and "product"; 18px is most of the difference between "product" and
       "product somebody enjoys opening". Corners are the cheapest warmth in a
       user interface and they cost no contrast, which is more than can be said
       for any of the other levers. */
    --radius-sm: 8px;
    --radius:    12px;
    --radius-lg: 18px;
    --radius-xl: 26px;
    --radius-pill: 999px;

    /* Black, and much heavier than the ink-hued shadows this used to carry. A
       5%-black shadow under a white card is a soft edge; the same shadow under
       a navy card on a navy page is nothing at all, because there is no light
       left for it to take away. Depth on ink comes from the surface step and
       the border — the shadow only has to stop a card looking pasted on. */
    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow:    0 1px 3px rgba(0, 0, 0, 0.5), 0 10px 28px -10px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 2px 8px rgba(0, 0, 0, 0.5), 0 28px 56px -18px rgba(0, 0, 0, 0.6);

    /* The inner ring is the SURFACE, not white. A white halo was right when the
       thing behind it was white; on ink it is a glare that reads as the damage
       it is drawing attention away from. */
    --focus-ring: 0 0 0 3px var(--surface), 0 0 0 5px var(--lagoon-500);

    --sidebar-width: 16rem;
    --content-max:   78rem;
    --prose-max:     42rem;

    --tabbar-height: 4.25rem;
}

/* ---- There is no second scheme, and that is the point -------------------
   A `@media (prefers-color-scheme: dark)` block used to sit here, and removing
   it is most of what this pass did.

   Two schemes meant two palettes, and only one of them was ever looked at. The
   consequence was not theoretical: three defects listed at the top of this file
   lived exclusively in the dark half, one of them for as long as the block
   existed, and none of them could be seen by anybody working in light mode. A
   scheme nobody develops in is a scheme nobody tests.

   It also meant the product had no fixed colour. Somebody arriving from a
   shared link saw the og-card — ink, cream and amber — and then landed on a
   cream-and-indigo website, which is a different product wearing the same
   wordmark.

   So: one palette, above, and the components below read from it. If light ever
   comes back it comes back as a user-facing CHOICE with a control and a stored
   preference, not as a silent branch on an OS setting — and it comes back with
   whatever measures both halves, because the honest lesson here is that the
   second scheme was never free. */


/* ==========================================================================
   2. Reset and base
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; }

html {
    -webkit-text-size-adjust: 100%;
    scroll-behavior: smooth;
}

@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* The two-wash treatment the marketing hero and the sign-in page already used,
   now behind the application itself. Without it "vibrant" landed almost
   entirely on the marketing site and on links — the screens people actually
   work in are dominated by status colour, which is frozen, so this was the
   lever left to make the app itself read as warm rather than as a form.
   Purely decorative: every card floating on it is still solid white, so no
   text sits directly against the wash except what already sat on plain sand. */
body {
    font-family: var(--font-sans);
    font-size: var(--text-base);
    line-height: var(--leading-body);
    color: var(--text);
    background:
        radial-gradient(46rem 24rem at 6% -8rem, var(--accent-050), transparent 62%),
        radial-gradient(42rem 22rem at 100% -6rem, var(--lagoon-050), transparent 64%),
        var(--surface-sunken);
    background-repeat: no-repeat;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

img, svg, video { display: block; max-width: 100%; }
svg { flex: none; }

input, button, textarea, select { font: inherit; color: inherit; }

h1, h2, h3, h4 {
    line-height: var(--leading-tight);
    letter-spacing: -0.02em;
    font-weight: 700;
    text-wrap: balance;
}

p { text-wrap: pretty; }

a {
    color: var(--lagoon-700);
    text-decoration-thickness: 1px;
    text-underline-offset: 0.18em;
}

a:hover { color: var(--lagoon-600); }

/* The `hidden` attribute is how the country filter removes a row. Components
   that set their own display would otherwise ignore it, and a filtered list
   that still shows everything is worse than no filter. */
[hidden] { display: none !important; }

/* One focus style everywhere, and it is never removed. The double ring reads
   on both light and dark surfaces without needing a variant per component. */
:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    border-radius: var(--radius-sm);
}

.skip-link {
    position: absolute;
    left: var(--space-4);
    top: -4rem;
    z-index: 100;
    padding: var(--space-3) var(--space-4);
    background: var(--surface);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
    font-weight: 600;
    text-decoration: none;
}

.skip-link:focus { top: var(--space-4); }

.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}


/* ==========================================================================
   3. Layout — the shell, the sidebar, mobile chrome

   Desktop is a fixed sidebar beside a scrolling main column. Below 60rem the
   sidebar is replaced by a slim top bar and a bottom tab bar — the shape
   people expect from an app on a phone, and one that needs no JavaScript at
   all, so it cannot fail closed the way a scripted drawer can.
   ========================================================================== */

.shell { min-height: 100vh; }

.sidebar { display: none; }

.shell__main {
    padding: var(--space-5) var(--space-4);
    padding-bottom: calc(var(--tabbar-height) + var(--space-6));
    max-width: var(--content-max);
    margin: 0 auto;
}

/* ---- Mobile top bar ---------------------------------------------------- */

.topbar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

/* The right-hand end: who you are, and the way out. `min-width: 0` is what lets
   the name column shrink instead of pushing the button off a 320px screen —
   without it a flex item refuses to go below its content width and the button
   is the thing that overflows. */
.topbar__end {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

/* THE FORM IS THE FLEX ITEM, NOT THE BUTTON — and `flex: none` on the button
   alone did nothing to stop it being squeezed, because a shrinking <form> takes
   its child with it. At 320px that broke "Sign out" across two lines inside a
   button 51px wide. The rule belongs on the element that is actually a child of
   the flex container; the nowrap is the belt to that braces. */
.topbar__end form { flex: none; }

/* 44px, not the 36px .btn--sm is by design — the same override the public header
   makes, for the same reason and on the only chrome that is ALWAYS on a phone.
   The compact padding is what fits beside a name at 320px; the compact height is
   not, and this is a thumb target beside a tab bar whose every link is 44px+.
   Height only, so .btn--sm is unchanged everywhere else. */
.topbar__end .btn { min-height: 2.75rem; white-space: nowrap; }

.topbar__user {
    font-size: var(--text-xs);
    color: var(--text-muted);
    text-align: right;
    line-height: 1.3;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.topbar__user strong {
    display: block;
    color: var(--text);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* A max-width query, and the only one in this file — every other breakpoint here
   is mobile-first. It earns the exception: on the narrowest phones the wordmark
   and the button leave the name column about 37px, which renders as one letter
   and an ellipsis and says less than nothing. Who you are is one thumb away on
   the Account tab; the way out is not, so the button keeps the room. */
@media (max-width: 22rem) {
    .topbar__user { display: none; }
}

/* ---- Mobile tab bar ----------------------------------------------------
   Five destinations, thumb-reachable, each a 44px+ target. `env(safe-area-…)`
   keeps it clear of the home indicator on a notched phone. */

.tabbar {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    z-index: 30;
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    background: var(--surface);
    border-top: 1px solid var(--border);
    padding-bottom: env(safe-area-inset-bottom, 0);
}

.tabbar__link {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    min-height: var(--tabbar-height);
    padding: var(--space-2) var(--space-1);
    font-size: 0.6875rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: var(--text-muted);
    text-decoration: none;
}

.tabbar__link[aria-current="page"] { color: var(--lagoon-700); }
.tabbar__link svg { width: 22px; height: 22px; }

.tabbar__badge {
    position: absolute;
    top: 0.55rem;
    left: 50%;
    margin-left: 0.35rem;
    min-width: 1.1rem;
    padding: 0 0.25rem;
    border-radius: var(--radius-pill);
    background: var(--status-breach-solid);
    color: #fff;
    font-size: 0.625rem;
    line-height: 1.1rem;
    text-align: center;
    font-variant-numeric: var(--numeric);
}

/* ---- Desktop ------------------------------------------------------------ */

@media (min-width: 60rem) {
    .topbar, .tabbar { display: none; }

    .shell {
        display: grid;
        grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
    }

    /*
     * The sidebar is a DEEPER SURFACE, and it rebinds the text tokens locally
     * rather than restyling the components that sit on it.
     *
     * This is the payoff of writing every component against tokens. .btn--ghost is
     * `color: var(--text-muted)`, which was tuned for ink on white; on this background
     * it measured 2.48:1 — a "Sign out" button most people would struggle to read.
     * Rebinding a handful of custom properties fixes that button, and anything else
     * ever placed here, without a single `.sidebar .something` override.
     *
     * IT USED TO BE AN INVERSION AND IS NOT ANY MORE. The page behind it is ink now,
     * so this is a band a step DEEPER than the page rather than a dark panel on a
     * light one, and --surface-ink dropping to ink-950 is what keeps it a band at all.
     * Two of the rebindings below are consequently no-ops — --text-muted and
     * --brand-accent are already these values globally — and they stay written out on
     * purpose: they say what this subtree needs, so nothing here breaks if the global
     * ones ever move again.
     *
     * The translucent whites are the exception worth keeping literal. A fixed step of
     * the ink ramp would have to be chosen against ink-950 specifically; a white at 8%
     * is correct on whatever this band is painted, which is the property that made
     * these survive the change untouched.
     */
    .sidebar {
        display: flex;
        flex-direction: column;
        position: sticky;
        top: 0;
        height: 100vh;
        padding: var(--space-5) var(--space-4);
        background: var(--surface-ink);

        /* The hairline is not decoration. On paper this column was ink against sand
           and needed no edge — the inversion WAS the edge. Against an ink page it
           separates by 1.19:1, which is a real step and not a reliable one: it lands
           inside the difference a cheap panel, a colour profile or a bright room can
           swallow. Measured at 1.46:1 composited, which is what a divider on ink looks
           like. --border-strong rather than --border for the same reason: at 10% white
           the line came out at 1.20:1 and was doing nothing the band was not. */
        border-right: 1px solid var(--border-strong);

        --text:          var(--ink-050);
        --text-muted:    var(--ink-300);
        --text-subtle:   var(--ink-400);
        --brand-accent:  var(--accent-300);
        --surface:       transparent;
        --surface-sunken: rgba(255, 255, 255, 0.08);
        --surface-hover: rgba(255, 255, 255, 0.08);
        --border:        rgba(255, 255, 255, 0.10);
        --border-strong: rgba(255, 255, 255, 0.16);

        color: var(--text-muted);
    }

    .shell__main {
        padding: var(--space-7) var(--space-6);
        padding-bottom: var(--space-8);
        max-width: none;
    }

    .shell__inner { max-width: var(--content-max); margin: 0 auto; }
}

.sidebar__top { margin-bottom: var(--space-6); }
.sidebar__foot { margin-top: auto; padding-top: var(--space-5); }

.sidebar__user {
    font-size: var(--text-xs);
    line-height: 1.4;
    color: var(--ink-300);
    padding: var(--space-3);
    background: rgba(255, 255, 255, 0.05);
    border-radius: var(--radius);
}

.sidebar__user strong {
    display: block;
    color: var(--ink-050);
    font-weight: 600;
    font-size: var(--text-sm);
}

/* ---- Brand -------------------------------------------------------------- */

.brand {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-lg);
    font-weight: 750;
    letter-spacing: -0.035em;
    color: var(--text);
    text-decoration: none;
}

.sidebar .brand { color: var(--ink-000); }

/* The word is ONE flex item. As three (two bare text nodes either side of a span)
   the container's 8px gap fell between "My", "Stay" and "Days" as well as after
   the globe, and the logotype read "My Stay Days" everywhere in the product. */
.brand__word { white-space: nowrap; }

/* A named class, not `.brand span`. That selector was written for this and also
   matched .brand__mark, where at (0,1,1) it beat the mark's own (0,1,0) rule and
   drew the globe in dark amber on the amber tile — 2.34:1. */
.brand__accent { color: var(--brand-accent); }

/* A disc, not a rounded square. The square was an app-icon convention borrowed
   from a phone home screen; a circle holding a sunrise is a porthole, and it is
   the friendlier of the two by some distance.

   The gradient lives here rather than in the SVG on purpose: an inline <svg>
   gradient needs a <defs> id, and the logo appears twice on every public page,
   so the id would be duplicated in the document. A CSS box cannot collide with
   itself. Both ends of it clear 5.7:1 against the ink glyph, measured. */
.brand__mark {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    flex: none;
    border-radius: var(--radius-pill);
    background: linear-gradient(145deg, var(--accent-300), var(--accent-500));
    color: var(--ink-900);
    box-shadow: var(--shadow-sm);
}

.brand__glyph { width: 22px; height: 22px; }

/* ---- Sidebar navigation ------------------------------------------------- */

.nav { display: grid; gap: 2px; }

.nav__link {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3);
    border-radius: var(--radius);
    font-size: var(--text-sm);
    font-weight: 550;
    color: var(--ink-300);
    text-decoration: none;
    transition: background 0.12s ease, color 0.12s ease;
}

.nav__link svg { width: 18px; height: 18px; opacity: 0.9; }
.nav__link:hover { background: rgba(255, 255, 255, 0.06); color: var(--ink-000); }

.nav__link[aria-current="page"] {
    background: rgba(255, 255, 255, 0.10);
    color: var(--ink-000);
}

/* The one spot of colour in an otherwise monochrome sidebar: the mobile tab
   bar already marks its current page in lagoon (below), and the desktop
   sidebar had no equivalent — just white on a darker white. */
.nav__link[aria-current="page"] svg { color: var(--accent-300); opacity: 1; }

.nav__count {
    margin-left: auto;
    min-width: 1.35rem;
    padding: 0 var(--space-2);
    border-radius: var(--radius-pill);
    background: var(--status-breach-solid);
    color: #fff;
    font-size: var(--text-xs);
    font-weight: 700;
    text-align: center;
    font-variant-numeric: var(--numeric);
}


/* ==========================================================================
   4. Typography and page furniture
   ========================================================================== */

.page-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    margin-bottom: var(--space-6);
}

.page-head__title {
    font-size: var(--text-3xl);
    letter-spacing: -0.035em;
}

.page-head__sub {
    margin-top: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
    max-width: var(--prose-max);
}

.page-head__actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.breadcrumb {
    margin-bottom: var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-subtle);
}

.breadcrumb a { color: var(--text-muted); text-decoration: none; }
.breadcrumb a:hover { color: var(--lagoon-700); text-decoration: underline; }

.section { margin-top: var(--space-7); }

.section__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-bottom: var(--space-4);
}

.section__title {
    font-size: var(--text-lg);
    letter-spacing: -0.02em;
}

.legal {
    margin-top: var(--space-8);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border);
    font-size: var(--text-xs);
    color: var(--text-subtle);
    max-width: var(--prose-max);
}

/* Under the disclaimer in the application shell. The signed-in area is where
   somebody is standing when they decide whether to type a passport number in,
   so the privacy policy has to be reachable from there and not only from the
   marketing footer they saw once before signing up. */
.legal__links {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-4);
    margin-top: var(--space-3);
}

.legal__links a { color: inherit; text-decoration: none; }
.legal__links a:hover { color: var(--text-muted); text-decoration: underline; }

.rule {
    border: 0;
    border-top: 1px solid var(--border);
    margin: var(--space-5) 0;
}


/* ==========================================================================
   5. Buttons

   Four kinds, and the hierarchy is the point: exactly one primary per screen,
   pointing at the thing we want somebody to do next.
   ========================================================================== */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 2.75rem;            /* 44px — the smallest comfortable target */
    padding: 0 var(--space-5);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-sm);
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: transform 0.08s ease, box-shadow 0.12s ease, background 0.12s ease;
}

.btn svg { width: 17px; height: 17px; }
.btn:hover { background: var(--surface-hover); }
.btn:active { transform: translateY(1px); }

/* A gradient rather than a flat fill — the same top-lit sun the mark is made of,
   and the difference between a button that is amber and a button that looks warm.
   Both stops carry the ink label at better than 8:1, measured, so the hierarchy
   rule at the top of this file is untouched: this is still the only filled amber
   thing on a screen, and it still always has a verb on it. */
.btn--primary {
    background: linear-gradient(180deg, var(--accent-300), var(--accent-400));
    border-color: var(--accent-400);
    color: var(--text-on-accent);
    box-shadow: var(--shadow-sm);
}

.btn--primary:hover {
    background: linear-gradient(180deg, var(--accent-200), var(--accent-300));
    border-color: var(--accent-300);
    color: var(--text-on-accent);
    box-shadow: var(--shadow);
}

.btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--text-muted);
}

.btn--ghost:hover { background: var(--surface-hover); color: var(--text); }

.btn--danger {
    background: var(--surface);
    border-color: var(--status-breach-line);
    color: var(--status-breach);
}

.btn--danger:hover { background: var(--status-breach-bg); }

.btn--sm {
    min-height: 2.25rem;
    padding: 0 var(--space-3);
    font-size: var(--text-xs);
}

.btn--lg {
    min-height: 3.25rem;
    padding: 0 var(--space-6);
    font-size: var(--text-base);
    border-radius: var(--radius-lg);
}

.btn--block { display: flex; width: 100%; }

.btn-row {
    display: flex;
    gap: var(--space-3);
    flex-wrap: wrap;
    align-items: center;
    margin-top: var(--space-5);
}

.btn-row.centred { justify-content: center; }

.inline-form { display: inline; }


/* ==========================================================================
   6. Cards and surfaces
   ========================================================================== */

.card {
    padding: var(--space-5);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.card + .card { margin-top: var(--space-4); }
.card--flush { padding: 0; overflow: hidden; }

.card__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    margin-bottom: var(--space-4);
}

.card__head:last-child { margin-bottom: 0; }

.card__title {
    font-size: var(--text-base);
    font-weight: 650;
    letter-spacing: -0.015em;
}

.card__sub {
    margin-top: var(--space-1);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.grid { display: grid; gap: var(--space-4); }

@media (min-width: 48rem) {
    .grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* ---- Empty states -------------------------------------------------------
   Never a dead end. Every one says what is missing, why it matters, and
   offers the single action that fixes it. */

.empty {
    display: grid;
    justify-items: center;
    gap: var(--space-3);
    padding: var(--space-8) var(--space-5);
    text-align: center;
    background: var(--surface-raised);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius-lg);
}

.empty__icon {
    display: grid;
    place-items: center;
    width: 3.5rem;
    height: 3.5rem;
    border-radius: var(--radius-pill);
    background: var(--accent-050);
    color: var(--brand-accent);
    margin-bottom: var(--space-1);
}

.empty__icon svg { width: 26px; height: 26px; }
.empty__title { font-size: var(--text-lg); font-weight: 650; }

.empty__detail {
    color: var(--text-muted);
    font-size: var(--text-sm);
    max-width: 34rem;
}

.empty .btn-row { margin-top: var(--space-3); }


/* ==========================================================================
   7. The answer, the verdict, the big number, the meter

   The loudest things in the product, because they are the reason somebody
   opened it.
   ========================================================================== */

.verdict {
    display: flex;
    align-items: center;
    gap: var(--space-5);
    flex-wrap: wrap;
    padding: var(--space-6);
    border-radius: var(--radius-xl);
    border: 1px solid var(--status-muted-line);
    background: var(--status-muted-bg);
    margin-bottom: var(--space-6);
}

.verdict--ok      { border-color: var(--status-ok-line);      background: var(--status-ok-bg); }
.verdict--notice  { border-color: var(--status-notice-line);  background: var(--status-notice-bg); }
.verdict--warning { border-color: var(--status-warning-line); background: var(--status-warning-bg); }
.verdict--breach  { border-color: var(--status-breach-line);  background: var(--status-breach-bg); }

/* ---- The forward line on a traveller card --------------------------------
   Sits directly under the meter, and carries its own status colour, which is
   frequently NOT the card's: somebody comfortably inside the limit today with
   a trip that breaks it is green above and red here. That contrast is the
   message, so this is tinted rather than left as plain text.

   A LEFT BORDER RATHER THAN A FILLED BOX. A second filled status block inside
   a card that already has a badge and a meter reads as a third alarm; a rule
   down the side says "this qualifies the thing above it" without competing
   with it. Single-sided, so no border-radius — a rounded corner on one edge
   renders as a mistake. */
.outlook {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin-top: var(--space-4);
    padding: var(--space-2) var(--space-3);
    border-left: 3px solid var(--status-muted-line);
    background: var(--surface-inset);
    font-size: var(--text-sm);
    line-height: var(--leading-body);
}

.outlook svg { width: 18px; height: 18px; flex: none; margin-top: 0.1rem; }
.outlook .muted { display: block; font-size: var(--text-xs); }

.outlook--ok      { border-left-color: var(--status-ok-line);      color: var(--status-ok); }
.outlook--notice  { border-left-color: var(--status-notice-line);  color: var(--status-notice); }
.outlook--warning { border-left-color: var(--status-warning-line); color: var(--status-warning); }
.outlook--breach  { border-left-color: var(--status-breach-line);  color: var(--status-breach); }

/* The date and the trailing note stay readable rather than inheriting the
   status hue — a whole sentence in amber is harder to read than a black one
   with an amber marker, and the colour has already done its job at the icon. */
.outlook strong { color: var(--text); }
.outlook .muted { color: var(--text-muted); }

.verdict__icon {
    display: grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    flex: none;
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--status-muted);
    box-shadow: var(--shadow-sm);
}

.verdict__icon svg { width: 24px; height: 24px; }
.verdict--ok      .verdict__icon { color: var(--status-ok); }
.verdict--notice  .verdict__icon { color: var(--status-notice); }
.verdict--warning .verdict__icon { color: var(--status-warning); }
.verdict--breach  .verdict__icon { color: var(--status-breach); }

.verdict__body { flex: 1 1 18rem; }

.verdict__headline {
    font-size: var(--text-2xl);
    font-weight: 700;
    line-height: var(--leading-tight);
    letter-spacing: -0.03em;
}

.verdict--ok      .verdict__headline { color: var(--status-ok); }
.verdict--notice  .verdict__headline { color: var(--status-notice); }
.verdict--warning .verdict__headline { color: var(--status-warning); }
.verdict--breach  .verdict__headline { color: var(--status-breach); }

.verdict__detail {
    margin-top: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text);
    max-width: 46rem;
}

/* ---- The answer card ----------------------------------------------------- */

.answer {
    padding: var(--space-6);
    border-radius: var(--radius-xl);
    border: 1px solid var(--border);
    background: var(--surface-raised);
    box-shadow: var(--shadow);
}

.answer__eyebrow {
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-subtle);
}

.answer__headline {
    margin-top: var(--space-2);
    font-size: var(--text-2xl);
    font-weight: 700;
    letter-spacing: -0.03em;
    line-height: var(--leading-tight);
}

.answer--yes            .answer__headline { color: var(--status-ok); }
.answer--tight          .answer__headline { color: var(--status-warning); }
.answer--no             .answer__headline { color: var(--status-breach); }
.answer--unknown        .answer__headline,
.answer--not_applicable .answer__headline { color: var(--text-muted); }

.answer__detail {
    margin-top: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
    max-width: 42rem;
}

/* ---- The big number ------------------------------------------------------ */

.figure { display: flex; align-items: baseline; gap: var(--space-2); }

.figure__value {
    font-size: var(--text-4xl);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.045em;
    font-variant-numeric: var(--numeric);
}

.figure__unit {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-muted);
}

.figure__detail {
    margin-top: var(--space-3);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

/* ---- Allowance meter ------------------------------------------------------ */

.meter { margin-top: var(--space-4); }

/* The track is --border-strong rather than a step of the ink ramp. It was
   ink-100, which is a near-white bar: correct under a light card and a glaring
   white stripe on a dark one. This is the only component in the file whose
   track was a scale step rather than a binding, which is exactly why it did not
   follow the surfaces when they moved. */
.meter__track {
    height: 0.625rem;
    background: var(--border-strong);
    border-radius: var(--radius-pill);
    overflow: hidden;
}

.meter__fill {
    height: 100%;
    background: var(--status-ok);
    border-radius: var(--radius-pill);
    transition: width 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}

.meter--notice  .meter__fill { background: var(--status-notice); }
.meter--warning .meter__fill { background: var(--status-warning); }
.meter--breach  .meter__fill { background: var(--status-breach); }

.meter__legend {
    display: flex;
    justify-content: space-between;
    gap: var(--space-3);
    margin-top: var(--space-2);
    font-size: var(--text-xs);
    color: var(--text-muted);
    font-variant-numeric: var(--numeric);
}

/* Width as a CLASS, not an inline style. The Content-Security-Policy is
   style-src 'self', which forbids inline style attributes — an inline width
   would be silently dropped and the bar would render empty in every browser
   that enforces the policy. A server-side test cannot see that; a user can.
   Twenty-one steps of 5% is more precision than a progress bar needs. */
.meter__fill--0   { width: 0; }
.meter__fill--5   { width: 5%; }
.meter__fill--10  { width: 10%; }
.meter__fill--15  { width: 15%; }
.meter__fill--20  { width: 20%; }
.meter__fill--25  { width: 25%; }
.meter__fill--30  { width: 30%; }
.meter__fill--35  { width: 35%; }
.meter__fill--40  { width: 40%; }
.meter__fill--45  { width: 45%; }
.meter__fill--50  { width: 50%; }
.meter__fill--55  { width: 55%; }
.meter__fill--60  { width: 60%; }
.meter__fill--65  { width: 65%; }
.meter__fill--70  { width: 70%; }
.meter__fill--75  { width: 75%; }
.meter__fill--80  { width: 80%; }
.meter__fill--85  { width: 85%; }
.meter__fill--90  { width: 90%; }
.meter__fill--95  { width: 95%; }
.meter__fill--100 { width: 100%; }


/* ==========================================================================
   8. Status — badges, alerts, attention

   Colour is never the only carrier. Every badge has a dot and a word.
   ========================================================================== */

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    border: 1px solid var(--status-muted-line);
    background: var(--status-muted-bg);
    color: var(--status-muted);
    font-size: var(--text-xs);
    font-weight: 650;
    white-space: nowrap;
}

.badge__dot {
    width: 0.5rem;
    height: 0.5rem;
    border-radius: var(--radius-pill);
    background: currentColor;
    flex: none;
}

.badge--ok      { border-color: var(--status-ok-line);      background: var(--status-ok-bg);      color: var(--status-ok); }
.badge--notice  { border-color: var(--status-notice-line);  background: var(--status-notice-bg);  color: var(--status-notice); }
.badge--warning { border-color: var(--status-warning-line); background: var(--status-warning-bg); color: var(--status-warning); }
.badge--breach  { border-color: var(--status-breach-line);  background: var(--status-breach-bg);  color: var(--status-breach); }

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 0.125rem var(--space-2);
    border-radius: var(--radius-sm);
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    color: var(--text-muted);
    font-size: var(--text-xs);
    font-weight: 550;
    white-space: nowrap;
}

/* For a chip that is a footnote rather than a label — "Not booked" beside a
   trip, where the fact is worth stating and worth not competing with the
   verdict two inches to its right. */
.chip--quiet { background: transparent; font-weight: 500; }

/* ---- What your trips ahead do --------------------------------------------
   A list of decisions, not a grid of fields. This replaced a four-column
   table whose "Status" column held the TRIP's status — Planned, Booked —
   sitting in the place a reader scans for whether it is a problem.

   Two of the columns are now sentences of different lengths and the important
   one is a verdict, which a table renders badly and a phone renders worse:
   `.table` collapses to stacked `data-label` rows at narrow widths, and a
   verdict reading "Status: Over the limit on 20 March" under three other
   stacked cells is the least legible form of the most important fact. */
.trip-outlook { list-style: none; }

.trip-outlook__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-2) var(--space-4);
    padding: var(--space-4) var(--space-5);
    border-top: 1px solid var(--border);
}

.trip-outlook__row:first-child { border-top: 0; }

/* The rows that break a limit are sorted to the top, so the tint is a
   confirmation of where the eye has already been sent rather than the thing
   doing the sending. */
.trip-outlook__row--breach { background: var(--status-breach-bg); }

.trip-outlook__what    { min-width: 0; }
.trip-outlook__verdict { text-align: right; margin-left: auto; }

.trip-outlook__who {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    font-weight: 550;
}

.trip-outlook__when,
.trip-outlook__note { font-size: var(--text-xs); margin-top: var(--space-1); }

.trip-outlook__headline {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-sm);
    font-weight: 600;
}

.trip-outlook__headline svg { width: 18px; height: 18px; flex: none; }

/* Coloured by the ROW's state rather than by a utility class on the element.
   This file has no `.text-*` utilities and adding one for a single use would be
   the start of a second vocabulary running alongside the components. */
.trip-outlook__row--breach .trip-outlook__headline { color: var(--status-breach); }

/* Below 34rem the verdict drops under the trip rather than being squeezed
   into a column two words wide. Left-aligned there, because a right-aligned
   line under a left-aligned one reads as belonging to something else. */
@media (max-width: 34rem) {
    .trip-outlook__verdict { text-align: left; margin-left: 0; }
}

.alert {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    margin-bottom: var(--space-4);
    border-radius: var(--radius);
    border: 1px solid var(--status-muted-line);
    background: var(--status-muted-bg);
    font-size: var(--text-sm);
}

.alert--success { border-color: var(--status-ok-line);     background: var(--status-ok-bg);     color: var(--status-ok); }
.alert--error   { border-color: var(--status-breach-line); background: var(--status-breach-bg); color: var(--status-breach); }
.alert--notice  { border-color: var(--lagoon-100);            background: var(--lagoon-050);          color: var(--lagoon-700); }
.alert svg { width: 18px; height: 18px; margin-top: 0.1rem; }
.alert strong { font-weight: 650; }

/* ---- Attention list ------------------------------------------------------ */

.attention { display: grid; gap: var(--space-3); list-style: none; padding: 0; }

.attention__item {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex-wrap: wrap;
    padding: var(--space-4) var(--space-5);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-left: 4px solid var(--status-muted);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-sm);
}

.attention__item--notice  { border-left-color: var(--status-notice); }
.attention__item--warning { border-left-color: var(--status-warning); }
.attention__item--breach  { border-left-color: var(--status-breach); }

.attention__body { flex: 1 1 18rem; min-width: 0; }
.attention__title { font-weight: 650; }

.attention__detail {
    margin-top: var(--space-1);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.attention__actions {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    flex-wrap: wrap;
}


/* ==========================================================================
   9. Forms and pickers
   ========================================================================== */

.form { display: grid; gap: var(--space-5); }

.field { display: grid; gap: var(--space-2); }

.field__label {
    font-size: var(--text-sm);
    font-weight: 650;
    letter-spacing: -0.01em;
}

.field__hint {
    font-size: var(--text-xs);
    color: var(--text-muted);
    line-height: 1.5;
}

.field__error {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--status-breach);
}

.input, .select, .textarea {
    width: 100%;
    min-height: 2.875rem;
    padding: var(--space-3);
    background: var(--surface);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius);
    color: var(--text);
    font-size: var(--text-base);
    transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.input::placeholder, .textarea::placeholder { color: var(--text-subtle); }
.textarea { min-height: 7rem; resize: vertical; line-height: var(--leading-body); }

.input:hover, .select:hover, .textarea:hover { border-color: var(--ink-300); }

.input:focus-visible, .select:focus-visible, .textarea:focus-visible {
    border-color: var(--lagoon-500);
    box-shadow: var(--focus-ring);
}

.input[aria-invalid="true"], .select[aria-invalid="true"], .textarea[aria-invalid="true"] {
    border-color: var(--status-breach);
}

.input:disabled, .select:disabled, .textarea:disabled {
    background: var(--surface-sunken);
    color: var(--text-subtle);
    cursor: not-allowed;
}

.select { appearance: none; padding-right: var(--space-7); }

/* The chevron is a background image, which style-src does not govern —
   background-image in a stylesheet is fine; it is inline STYLE attributes the
   policy forbids. Encoded rather than fetched, so there is no request.

   ITS COLOUR CANNOT BE A TOKEN. A data URI is an opaque string to CSS, so
   var() does not reach inside it and this one hex has to be written out. It
   was #64748B, a slate picked to read on a white field, and it sat at 2.9:1
   on the dark one — the only mark in the file that could not follow the
   surfaces because nothing binds it to them. It is ink-300's value now, at
   6.3:1. If the ink ramp moves, this moves by hand. */
.select {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239AA4BE' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--space-3) center;
    background-size: 18px;
}

.field-row { display: grid; gap: var(--space-4); }

@media (min-width: 40rem) {
    .field-row--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.field--inline {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    background: var(--surface-inset);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.field--inline input[type="checkbox"] { margin-top: 0.2rem; }
.field--inline .field__label { display: grid; gap: var(--space-1); cursor: pointer; }

/* accent-color takes lagoon-500, not 600. 600 is a text value and is now the
   LIGHTER of the two — a near-white box with a near-white tick. 500 is the
   saturated mid-tone, which is the one thing a control fill can be while still
   letting the browser pick a tick that contrasts with it. */
input[type="checkbox"], input[type="radio"] {
    width: 1.15rem;
    height: 1.15rem;
    accent-color: var(--lagoon-500);
    flex: none;
    cursor: pointer;
}

/* ---- Country / nationality picker ---------------------------------------- */

.picker {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface-inset);
    padding: var(--space-3);
}

/* The search box only exists once JavaScript has wired it up. A search field
   that does nothing is worse than none: the list below it is complete either
   way. */
.picker__search-label,
.picker .js-filter { display: none; }

.picker--live .picker__search-label {
    display: block;
    font-size: var(--text-xs);
    font-weight: 650;
    color: var(--text-muted);
    margin-bottom: var(--space-1);
}

.picker--live .js-filter { display: block; margin-bottom: var(--space-3); }

.picker__list {
    max-height: 19rem;
    overflow-y: auto;
    padding-right: var(--space-2);
    overscroll-behavior: contain;
}

.picker__group + .picker__group { margin-top: var(--space-4); }

.picker__group-title {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--surface-inset);
    padding: var(--space-2) 0;
    margin-bottom: var(--space-1);
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--text-muted);
    border-bottom: 1px solid var(--border);
}

.picker__option {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: 2.25rem;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    font-size: var(--text-sm);
    cursor: pointer;
}

.picker__option:hover { background: var(--surface-hover); }

.picker__empty {
    padding: var(--space-4);
    font-size: var(--text-sm);
    color: var(--text-muted);
    text-align: center;
}

@media (min-width: 40rem) {
    .picker__group { columns: 2; column-gap: var(--space-5); }
    .picker__group-title { column-span: all; }
    .picker__option { break-inside: avoid; }
}


/* ==========================================================================
   10. Tables — real tables, which become stacked cards on a phone
   ========================================================================== */

.table-wrap { overflow-x: auto; }

.table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }

.table th {
    padding: var(--space-3) var(--space-4);
    text-align: left;
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
    color: var(--text-subtle);
    background: var(--surface-sunken);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}

.table td {
    padding: var(--space-4);
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

.table tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--surface-hover); }

.table__primary { font-weight: 650; color: var(--text); text-decoration: none; }
.table__primary:hover { color: var(--lagoon-700); text-decoration: underline; }

.table__num { font-variant-numeric: var(--numeric); white-space: nowrap; }
.table__meta { color: var(--text-muted); font-size: var(--text-xs); }
.table__actions { text-align: right; white-space: nowrap; }

@media (max-width: 47.99rem) {
    .table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

    .table tr {
        display: grid;
        gap: var(--space-1);
        padding: var(--space-4);
        border-bottom: 1px solid var(--border);
    }

    .table td {
        display: flex;
        justify-content: space-between;
        gap: var(--space-4);
        padding: var(--space-1) 0;
        border: 0;
        text-align: right;
    }

    .table td::before {
        content: attr(data-label);
        font-size: var(--text-xs);
        font-weight: 650;
        color: var(--text-subtle);
        text-align: left;
    }

    .table td:not([data-label]) { justify-content: flex-end; }
    .table__actions { text-align: left; }

    /* On a phone the row's primary link is the tap target, and a bare line of text
       is a 22px one. Padding it out costs nothing and makes the row feel like a row
       rather than like a word that happens to be tappable. */
    .table__primary { display: inline-block; padding: var(--space-2) 0; }
}


/* ==========================================================================
   11. Lists — steps, checklist, next steps, day grid
   ========================================================================== */

.setup { max-width: 48rem; margin: 0 auto; }
.setup__skip { margin-top: var(--space-5); text-align: center; }

.steps {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-5);
    list-style: none;
    margin-bottom: var(--space-6);
    padding: 0;
    font-size: var(--text-sm);
}

.steps__item { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--text-subtle); }

.steps__marker {
    display: grid;
    place-items: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-strong);
    font-size: var(--text-xs);
    font-weight: 700;
    font-variant-numeric: var(--numeric);
}

.steps__label { white-space: nowrap; }

.steps__item--current { color: var(--text); font-weight: 650; }

.steps__item--current .steps__marker {
    border-color: var(--accent-400);
    background: var(--accent-400);
    color: var(--ink-900);
}

.steps__item--done .steps__marker {
    border-color: var(--status-ok-solid);
    background: var(--status-ok-solid);
    color: #fff;
}

.checklist { display: grid; gap: var(--space-2); list-style: none; padding: 0; margin-top: var(--space-4); }

.checklist__item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    padding: var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius);
}

.checklist__item--done { border-style: dashed; color: var(--text-muted); }

.checklist__marker {
    display: grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border-strong);
    font-size: var(--text-xs);
    font-weight: 700;
}

.checklist__item--done .checklist__marker {
    border-color: var(--status-ok-solid);
    background: var(--status-ok-solid);
    color: #fff;
}

.checklist__body { flex: 1 1 16rem; }
.checklist__title { font-weight: 650; }
.checklist__item--done .checklist__title { font-weight: 550; text-decoration: line-through; }
.checklist__detail { margin-top: var(--space-1); font-size: var(--text-sm); color: var(--text-muted); }

.next-steps { display: grid; gap: var(--space-3); list-style: none; padding: 0; }

.next-steps__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    padding: var(--space-4) var(--space-5);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
}

.next-steps__title { font-weight: 650; }
.next-steps__detail { margin-top: var(--space-1); font-size: var(--text-sm); color: var(--text-muted); }

/* ---- Explanation lists ---------------------------------------------------- */

.explain { display: grid; gap: var(--space-3); }

.explain__statement {
    font-size: var(--text-lg);
    font-weight: 600;
    line-height: var(--leading-snug);
    letter-spacing: -0.015em;
}

.explain__list { display: grid; gap: var(--space-3); list-style: none; padding: 0; }

.explain__list li {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-4);
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--border);
    font-size: var(--text-sm);
}

.explain__list li:last-child { border-bottom: 0; padding-bottom: 0; }
.explain__list b { font-weight: 650; text-align: right; }

/* ---- The rule in plain English -------------------------------------------- */

.plain-rule { border-left: 3px solid var(--accent-400); padding-left: var(--space-4); }

.plain-rule__eyebrow {
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-subtle);
}

.plain-rule__statement {
    margin-top: var(--space-2);
    font-size: var(--text-lg);
    font-weight: 600;
    line-height: var(--leading-snug);
    letter-spacing: -0.015em;
}

.plain-rule__points {
    margin-top: var(--space-3);
    padding-left: var(--space-5);
    display: grid;
    gap: var(--space-2);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.plain-rule__source { margin-top: var(--space-4); font-size: var(--text-xs); color: var(--text-subtle); }

/* ---- The day grid on the "why" page ---------------------------------------- */

.month { margin-bottom: var(--space-5); }

.month__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    margin-bottom: var(--space-2);
}

.month__name { font-weight: 650; font-size: var(--text-sm); }
.month__count { font-size: var(--text-xs); color: var(--text-muted); }
.month__days { display: flex; flex-wrap: wrap; gap: 3px; }

.day {
    width: 1.25rem;
    height: 1.25rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-sunken);
    font-size: 0.5625rem;
    line-height: 1.15rem;
    text-align: center;
    color: var(--text-subtle);
    font-variant-numeric: var(--numeric);
}

/* The status modifiers for a day square are NOT here. They sit at the end of
   section 14, after .cal__cell, because both are single-class selectors and the
   later one wins — see the note there. Moving them back would silently uncolour
   the calendar. */

.legend { display: flex; flex-wrap: wrap; gap: var(--space-4); font-size: var(--text-xs); color: var(--text-muted); }
.legend__item { display: inline-flex; align-items: center; gap: var(--space-2); }
.legend__swatch { width: 0.85rem; height: 0.85rem; border-radius: var(--radius-sm); border: 1px solid var(--border); }


/* ==========================================================================
   12. Authentication pages
   ========================================================================== */

.auth {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: var(--space-6) var(--space-4);
    background:
        radial-gradient(60rem 30rem at 20% -10rem, var(--accent-050), transparent 70%),
        radial-gradient(52rem 28rem at 100% 10rem, var(--lagoon-050), transparent 68%),
        var(--surface-sunken);
}

.auth__panel { width: 100%; max-width: 27rem; }

.auth__brand { display: grid; justify-items: center; gap: var(--space-2); margin-bottom: var(--space-6); }

.auth__title { font-size: var(--text-xl); font-weight: 700; letter-spacing: -0.025em; }
.auth__sub { color: var(--text-muted); font-size: var(--text-sm); }

.auth__foot { text-align: center; margin-top: var(--space-5); font-size: var(--text-sm); color: var(--text-muted); }

.auth__reassurance {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
    font-size: var(--text-xs);
    color: var(--text-subtle);
    text-align: center;
}

.auth__reassurance svg { width: 14px; height: 14px; }

/* Outside the panel, under it. These four links belong on the page a stranger
   is standing on when they decide whether to hand over an email address — but
   they are not part of the task, so they sit below the card and read quietly. */
.auth__legal {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2) var(--space-4);
    margin-top: var(--space-6);
    font-size: var(--text-xs);
    color: var(--text-subtle);
}

.auth__legal a { color: inherit; text-decoration: none; }
.auth__legal a:hover { color: var(--text-muted); text-decoration: underline; }


/* ==========================================================================
   13. Marketing site

   The public pages, and the only ones in the product meant to be indexed.

   They share the tokens and the components with the application on purpose:
   the answer card in the hero is the real .answer component with example
   numbers in it, not a picture of one. It cannot go out of date, it reads
   correctly to a screen reader, it is sharp on any display, and what a
   visitor is shown before signing up is literally what they get afterwards.

   Wider and airier than the app, because these pages are read at arm's
   length while an application is worked in. Everything else — buttons,
   cards, the status colours, the amber rules at the top of this file — is
   unchanged, including the one about not adding a fourth amber thing: the
   early-access notice on the upgrade page is lagoon, like every other piece
   of information in this product.
   ========================================================================== */

.site { background: var(--surface); }

.site-wrap {
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    padding-inline: var(--space-4);
}

/* ---- Header -------------------------------------------------------------
   One row on a desktop, two on a phone: the navigation simply wraps onto a
   line of its own. No script, no drawer, nothing to open — the same rule the
   application shell follows, for the same reason. A menu that has to be
   opened is a menu that can fail to open. */

.site-head {
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.site-head__inner {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    padding: var(--space-2) var(--space-4);
}

.site-head__nav {
    order: 3;
    flex-basis: 100%;
    display: flex;
    gap: var(--space-4);
    overflow-x: auto;
}

.site-head__actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.site-head__link {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;              /* 44px, the same target as .btn */
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--text-muted);
    text-decoration: none;
    white-space: nowrap;
}

.site-head__link:hover { color: var(--text); }
.site-head__link[aria-current="page"] { color: var(--lagoon-700); }

/* Measured at 375px, where both came out short of the 44px target: the wordmark
   at 32px and "Start free" — the single most important control on the site — at
   36px, because .btn--sm is 36px by design. The compact PADDING is what the
   header needs at that width; the compact HEIGHT is not, and three of them side
   by side still fit. Height only, so .btn--sm is unchanged everywhere else. */
.site-head .brand,
.site-head .btn--sm,
.site-foot .brand { min-height: 2.75rem; }

@media (min-width: 48rem) {
    .site-wrap { padding-inline: var(--space-6); }
    .site-head__inner { padding-inline: var(--space-6); }

    .site-head__nav {
        order: 2;
        flex-basis: auto;
        margin-inline: auto;
        gap: var(--space-5);
        overflow-x: visible;
    }

    .site-head__actions { order: 3; margin-left: 0; }
}

/* ---- Hero ---------------------------------------------------------------
   Two warm washes rather than a photograph. There is no image pipeline in
   this project and no CDN to serve one from, and a hero that is pure CSS
   never blocks a paint or arrives at the wrong resolution.

   THE ONE PLACE THAT REACHES PAST ITS SECTION FOR A SURFACE. Everything else
   on the marketing site sits on --surface, and this sits on --surface-sunken
   — ink-900, the deepest ground in the palette. That is deliberate and it is
   the whole reason this pass exists: ink-900 is the exact colour the og-card
   is drawn on, and this is the first thing somebody sees after clicking the
   card. Anything lighter here and the shared image and the page it opens are
   two different products.

   The header above it stays on --surface, so it reads as chrome laid over the
   hero rather than as part of it. */

.site-hero {
    position: relative;
    overflow: hidden;
    padding: var(--space-7) 0 var(--space-9);
    background:
        radial-gradient(48rem 28rem at 12% -8rem, var(--accent-050), transparent 70%),
        radial-gradient(42rem 26rem at 95% -2rem, var(--lagoon-050), transparent 68%),
        var(--surface-sunken);
    border-bottom: 1px solid var(--border);
}

.site-hero__inner {
    position: relative;
    z-index: 1;
    display: grid;
    gap: var(--space-7);
    align-items: center;
}

/* ---- The horizon --------------------------------------------------------
   Full-bleed along the bottom of the hero, behind the content, which is why
   the hero's bottom padding is a step larger than its top. Three tints of ONE
   hue is what makes the ridges recede; opacity rather than three separate
   colour tokens, so it survives the dark-mode flip without a second set. */

.site-horizon {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 0;
    width: 100%;
    height: auto;                     /* 12:1 — it is 1/12th of the viewport wide */
    pointer-events: none;
}

.site-horizon__sun { fill: var(--accent-400); opacity: 0.42; }

.site-horizon__ridge { fill: var(--lagoon-700); }
.site-horizon__ridge--far  { opacity: 0.10; }
.site-horizon__ridge--mid  { opacity: 0.16; }
.site-horizon__ridge--near { opacity: 0.24; }

.site-horizon__arc {
    stroke: var(--lagoon-700);
    stroke-width: 2.5;
    stroke-linecap: round;
    stroke-dasharray: 2 11;
    opacity: 0.45;
}

.site-horizon__pip { fill: var(--accent-500); opacity: 0.7; }
.site-horizon__pip--end { fill: var(--lagoon-700); }

.site-hero__copy { max-width: 38rem; }

.site-hero__title {
    margin-top: var(--space-3);
    font-size: var(--text-3xl);
    letter-spacing: -0.04em;
}

.site-hero__lead {
    margin-top: var(--space-4);
    max-width: 34rem;
    font-size: var(--text-lg);
    color: var(--text-muted);
}

.site-hero__demo { display: grid; }

@media (min-width: 48rem) {
    .site-hero__title { font-size: var(--text-4xl); }
}

@media (min-width: 62rem) {
    /* Bottom padding clears the horizon rather than sitting on it. The art is a
       twelfth of the viewport tall, so at 1440px it is 120px; 8rem leaves a gap.
       Measured, because text over the ridges is text whose real contrast is not
       the contrast a computed style reports. */
    .site-hero { padding: var(--space-9) 0 calc(var(--space-9) + var(--space-6)); }

    .site-hero__inner {
        grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
        gap: var(--space-8);
    }

    .site-hero__title { font-size: var(--text-5xl); }
}

/* The eyebrow uses --brand-accent rather than --accent-600 directly. The
   darker amber is legible on warm white and measures 3.4:1 on the dark
   surface, which fails; --brand-accent is the token that flips to the bright
   amber where the surface does. */
.site-eyebrow {
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--brand-accent);
}

.site-trust {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-5);
    list-style: none;
    padding: 0;
    margin-top: var(--space-6);
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.site-trust__item { display: inline-flex; align-items: center; gap: var(--space-2); }
.site-trust__item svg { width: 15px; height: 15px; color: var(--text-subtle); }

.site-demo { box-shadow: var(--shadow-lg); }

.site-demo__foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    margin-top: var(--space-5);
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
}

/* ---- Sections ------------------------------------------------------------ */

.site-section { padding: var(--space-8) 0; }
.site-section--tight { padding-top: var(--space-6); }

.site-section--sunken {
    background: var(--surface-sunken);
    border-top: 1px solid var(--border);
    border-bottom: 1px solid var(--border);
}

.site-section__head { max-width: 46rem; margin-bottom: var(--space-6); }

.site-section__title {
    font-size: var(--text-2xl);
    letter-spacing: -0.03em;
}

.site-section__lead {
    margin-top: var(--space-3);
    color: var(--text-muted);
}

@media (min-width: 48rem) {
    .site-section { padding: var(--space-9) 0; }
    .site-section__title { font-size: var(--text-3xl); }
}

.site-page-head { padding: var(--space-7) 0 var(--space-4); }

.site-page-head__title {
    margin-top: var(--space-3);
    max-width: 40rem;
    font-size: var(--text-3xl);
    letter-spacing: -0.035em;
}

.site-page-head__lead {
    margin-top: var(--space-4);
    max-width: 44rem;
    font-size: var(--text-lg);
    color: var(--text-muted);
}

@media (min-width: 48rem) {
    .site-page-head__title { font-size: var(--text-4xl); }
}

/* ---- Feature cards, steps, points ---------------------------------------- */

/* Content sits at the top of the card, so three cards of unequal length still
   line their headings up. The grid stretches them to equal height for free. */
.site-feature { display: grid; align-content: start; }

.site-feature__icon {
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    margin-bottom: var(--space-4);
    border-radius: var(--radius);
    background: var(--accent-050);
    color: var(--brand-accent);
}

/* Three feature cards, three holiday hues — sun, sea and orchid — so the grid
   is not a wall of one colour. Decorative only: nowhere near a status class,
   so there is no fourth thing to disambiguate from red/amber/green. */
.site-feature__icon--lagoon { background: var(--lagoon-050); color: var(--lagoon-700); }
.site-feature__icon--orchid { background: var(--orchid-050); color: var(--orchid-600); }

.site-feature__icon svg { width: 22px; height: 22px; }

.site-feature__title {
    font-size: var(--text-base);
    font-weight: 650;
    letter-spacing: -0.015em;
}

.site-feature__body {
    margin-top: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.site-steps {
    display: grid;
    gap: var(--space-6);
    list-style: none;
    padding: 0;
}

.site-steps__item { display: grid; gap: var(--space-3); align-content: start; }

.site-steps__marker {
    display: grid;
    place-items: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: var(--radius-pill);
    background: var(--accent-400);
    color: var(--text-on-accent);
    font-weight: 700;
    font-variant-numeric: var(--numeric);
}

.site-steps__title {
    font-size: var(--text-lg);
    letter-spacing: -0.02em;
}

.site-steps__body { color: var(--text-muted); font-size: var(--text-sm); }

@media (min-width: 48rem) {
    .site-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.site-split { display: grid; gap: var(--space-6); }
.site-split__copy { max-width: 34rem; align-self: start; }

/* ---- The rolling-window diagram ------------------------------------------
   Informative rather than decorative, so it is sized to be READ: it scales
   with the column and the caption repeats the point in words for anybody who
   cannot see it. Text inside an SVG does not inherit the page font, so the
   family is set explicitly — otherwise it renders in the browser's serif
   default and looks like a different product. */

.site-diagram {
    max-width: 46rem;
    margin: var(--space-7) auto 0;
    padding: var(--space-5);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);

    /* The diagram scrolls inside its own card on a narrow screen, the way the
       tables in this product already do. The alternative was letting it shrink
       to fit, and text inside an SVG shrinks with the drawing: measured at 375px
       the labels came out at 5px. A diagram nobody can read is worse than a
       diagram that has to be nudged sideways. */
    overflow-x: auto;
}

.site-diagram__svg {
    display: block;
    width: 100%;
    min-width: 33rem;             /* the width below which the labels stop being legible */
    max-width: 40rem;             /* and above which they start shouting */
    height: auto;
    margin-inline: auto;
    font-family: var(--font-sans);
}

.site-diagram__window {
    fill: var(--lagoon-050);
    stroke: var(--lagoon-700);
    stroke-width: 1.5;
    stroke-dasharray: 5 4;
    opacity: 0.9;
}

.site-diagram__track { stroke: var(--border-strong); stroke-width: 2; stroke-linecap: round; }
.site-diagram__trip { fill: var(--accent-400); }

/* The trip that has fallen out of the window: outlined, not filled, so it is
   still there and plainly no longer counting. */
.site-diagram__trip--dropped {
    fill: var(--surface);
    stroke: var(--border-strong);
    stroke-width: 1.5;
    stroke-dasharray: 4 3;
}

.site-diagram__now { stroke: var(--status-breach); stroke-width: 2; stroke-linecap: round; }

/* Sized against the 720-unit drawing, not against the screen: at the 33rem floor
   these land at roughly 14px and 11px, and at the 40rem cap at 17px and 13px. */
.site-diagram__label {
    fill: var(--text);
    font-size: 19px;
    font-weight: 650;
}

.site-diagram__caption { fill: var(--text-muted); font-size: 15px; }

.site-diagram__figcaption {
    margin-top: var(--space-4);
    color: var(--text-muted);
    font-size: var(--text-sm);
    line-height: var(--leading-body);
}

.site-points {
    display: grid;
    gap: var(--space-5);
    list-style: none;
    padding: 0;
}

.site-points__item { display: flex; gap: var(--space-3); align-items: flex-start; }

.site-points__icon {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    flex: none;
    border-radius: var(--radius-pill);
    background: var(--lagoon-050);
    color: var(--lagoon-700);
}

.site-points__icon svg { width: 18px; height: 18px; }
.site-points__title { font-size: var(--text-sm); font-weight: 650; }

.site-points__body {
    margin-top: var(--space-1);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

@media (min-width: 48rem) {
    .site-points { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 62rem) {
    .site-split {
        grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
        gap: var(--space-8);
        align-items: start;
    }
}

/* ---- Prose --------------------------------------------------------------- */

.site-prose { max-width: 46rem; }

.site-prose > p {
    margin-top: var(--space-4);
    color: var(--text-muted);
}

.site-prose strong { color: var(--text); font-weight: 650; }
.site-prose em { font-style: italic; }

.site-list {
    display: grid;
    gap: var(--space-3);
    margin-top: var(--space-4);
    padding-left: var(--space-5);
    color: var(--text-muted);
}

.site-list li::marker { color: var(--text-subtle); }
.site-list strong { color: var(--text); font-weight: 650; }

.site-list--limits,
.site-list--roadmap { list-style: none; padding-left: 0; }

.site-list--limits li {
    padding-left: var(--space-4);
    border-left: 2px solid var(--border-strong);
}

/* Dashed, because nothing on this list is built yet and the border should say
   so before the sentence above it has to. */
.site-list--roadmap li {
    padding-left: var(--space-4);
    border-left: 2px dashed var(--border-strong);
    font-size: var(--text-sm);
}

.site-facts { margin-top: var(--space-5); }
.site-facts th[scope="row"] { font-weight: 600; color: var(--text); }

.site-qa { margin-top: var(--space-5); }

.site-qa__q {
    margin-top: var(--space-6);
    font-size: var(--text-base);
    font-weight: 650;
    letter-spacing: -0.015em;
}

.site-qa__q:first-child { margin-top: 0; }

.site-qa__a {
    margin-top: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* ---- The contact form ----------------------------------------------------
   A form on a marketing page, which is the only one on the public site. It is
   narrower than site-prose: a message box the full width of a desktop screen
   produces lines nobody wants to write or read, and the fields above it would
   be stretched to match. */

.site-form-wrap { max-width: 42rem; }

.site-form-lead {
    margin-top: var(--space-3);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

.site-form-card { margin-top: var(--space-5); }
.site-form-card p + p { margin-top: var(--space-3); }
.site-form-card .textarea { min-height: 11rem; }

/* THE HONEYPOT.

   A field a person must never see, hidden HERE rather than with a `style`
   attribute — the Content-Security-Policy this application sends is
   `style-src 'self'`, which forbids inline styles, so the inline version would
   simply not apply and the trap would render in the middle of the form asking a
   real person for their website.

   `position: absolute` and off-screen rather than `display: none`. A handful of
   bots specifically skip fields that are display:none, having learned exactly
   this trick; moving it out of the viewport leaves it "visible" to anything
   reading the DOM. It is taken out of the accessibility tree and the tab order
   in the markup instead, which is where those two belong. */
.field--trap {
    position: absolute;
    left: -9999px;
    top: auto;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

/* ---- Plans and the upgrade page ------------------------------------------------------------- */

/* Slightly more air between two big decisions than between three small cards. */
.site-plans { gap: var(--space-5); }

.site-plan { display: flex; flex-direction: column; gap: var(--space-3); }
.site-plan--full { padding: var(--space-6); }

.site-plan--featured {
    border-color: var(--accent-300);
    box-shadow: var(--shadow);
}

.site-plan__flag {
    align-self: flex-start;
    padding: var(--space-1) var(--space-3);
    border-radius: var(--radius-pill);
    background: var(--accent-050);
    color: var(--brand-accent);
    font-size: var(--text-xs);
    font-weight: 700;
}

.site-plan__name { font-size: var(--text-lg); font-weight: 650; }

.site-plan__price {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    flex-wrap: wrap;
}

.site-plan__amount {
    font-size: var(--text-3xl);
    font-weight: 700;
    letter-spacing: -0.04em;
    font-variant-numeric: var(--numeric);
}

.site-plan__period { font-size: var(--text-sm); font-weight: 600; color: var(--text-muted); }
.site-plan__tagline { color: var(--text-muted); font-size: var(--text-sm); }

.site-plan__list {
    display: grid;
    gap: var(--space-3);
    list-style: none;
    padding: 0;
    margin-top: var(--space-2);
}

.site-plan__item {
    display: flex;
    gap: var(--space-3);
    align-items: flex-start;
    font-size: var(--text-sm);
    line-height: var(--leading-snug);
}

.site-plan__item svg {
    width: 18px;
    height: 18px;
    margin-top: 0.1rem;
    color: var(--status-ok);
}

.site-plan__foot { margin-top: auto; padding-top: var(--space-4); }

.site-plan__note {
    margin-top: var(--space-3);
    font-size: var(--text-xs);
    color: var(--text-subtle);
    text-align: center;
}

.site-plans--teaser .site-plan { text-align: center; }
.site-plans--teaser .site-plan__price { justify-content: center; }

/* Lagoon, not amber. An amber panel here would be the fourth amber thing,
   and the visitor would have to work out whether it was an offer or a
   warning. */
.site-notice {
    display: flex;
    gap: var(--space-4);
    align-items: flex-start;
    padding: var(--space-5);
    border: 1px solid var(--lagoon-100);
    border-radius: var(--radius-lg);
    background: var(--lagoon-050);
}

.site-notice__icon {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    flex: none;
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--lagoon-700);
    box-shadow: var(--shadow-sm);
}

.site-notice__icon svg { width: 18px; height: 18px; }
.site-notice__title { font-weight: 650; }

.site-notice__body {
    margin-top: var(--space-2);
    color: var(--text-muted);
    font-size: var(--text-sm);
}

/* ---- Closing call to action ---------------------------------------------- */

.site-cta {
    padding: var(--space-8) 0;
    background:
        radial-gradient(46rem 24rem at 50% 130%, var(--accent-050), transparent 70%),
        var(--surface-sunken);
    border-top: 1px solid var(--border);
}

.site-cta__inner { max-width: 40rem; text-align: center; }

/* The error page renders in two shells — the application's and this one — so its
   wrapper has to be correct in both. Centred and width-limited works either way. */
.page-error {
    max-width: 46rem;
    margin-inline: auto;
    padding: var(--space-7) var(--space-4);
}

.site-cta__title {
    font-size: var(--text-2xl);
    letter-spacing: -0.03em;
}

.site-cta__lead { margin-top: var(--space-3); color: var(--text-muted); }

@media (min-width: 48rem) {
    .site-cta__title { font-size: var(--text-3xl); }
}

/* ---- Footer --------------------------------------------------------------
   The ink band that anchors the page, styled the way the sidebar is: the
   tokens are rebound for this subtree rather than each rule being overridden,
   so every component inside it is correct without knowing it is on ink.

   --text-subtle is deliberately rebound to the SAME value as --text-muted.
   The ink-400 grey the sidebar uses measures 3.95:1 against ink-900, which is
   fine for the 15px it is used at there and not fine for the 13px legal note
   here. Two names, one value, on purpose. */

.site-foot {
    padding: var(--space-8) 0 var(--space-6);
    background: var(--surface-ink);
    color: var(--text-muted);

    --text:         var(--ink-050);
    --text-muted:   var(--ink-300);
    --text-subtle:  var(--ink-300);
    --brand-accent: var(--accent-300);
    --border:       rgba(255, 255, 255, 0.10);
}

.site-foot__inner { display: grid; gap: var(--space-6); }
.site-foot__brand { align-self: start; }

.site-foot__tagline {
    margin-top: var(--space-3);
    max-width: 22rem;
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.site-foot__nav { display: grid; gap: var(--space-1); align-content: start; }

.site-foot__heading {
    margin-bottom: var(--space-2);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--ink-200);
}

.site-foot__nav a {
    display: inline-flex;
    align-items: center;
    min-height: 2.75rem;              /* 44px — a footer link is still a tap target */
    font-size: var(--text-sm);
    color: var(--text-muted);
    text-decoration: none;
}

.site-foot__nav a:hover { color: var(--text); text-decoration: underline; }

.site-foot__legal {
    max-width: 52rem;
    margin-top: var(--space-7);
    padding-top: var(--space-5);
    border-top: 1px solid var(--border);
    font-size: var(--text-xs);
    color: var(--text-subtle);
}

.site-foot__meta {
    margin-top: var(--space-4);
    font-size: var(--text-xs);
    color: var(--text-subtle);
}

/* Brand, then the three link columns. At 48rem the brand shares a row with
   two of them and the third wraps; at 64rem there is room for all four, which
   is where the footer stops looking like a list and starts looking like a
   footer. */
@media (min-width: 48rem) {
    .site-foot__inner {
        grid-template-columns: 2fr 1fr 1fr;
        gap: var(--space-7);
    }
}

@media (min-width: 64rem) {
    .site-foot__inner { grid-template-columns: 2fr 1fr 1fr 1fr; }
}


/* ==========================================================================
   14. Charts — the timeline

   The one part of this product that cannot be checked by reading the page.
   Two rules follow from that and both are load-bearing:

     Every coordinate is computed in PHP and tested there. Nothing in a
     template does arithmetic, so the drawing can be asserted without a
     browser.

     Text inside an SVG scales with the drawing. At a phone's width a 15px
     label renders at 5px, which is how the marketing diagram was caught, so
     the chart has a minimum width and scrolls inside its own box rather than
     shrinking below the point where its own axis is legible.
   ========================================================================== */

.chart { overflow-x: auto; }

.chart__svg {
    display: block;
    width: 100%;
    min-width: 46rem;
    height: auto;
    font-family: var(--font-sans);
}

.chart__grid  { stroke: var(--border); stroke-width: 1; }
.chart__axis  { fill: var(--text-muted); font-size: 16px; }

/* The curve is lagoon, not amber: amber on this page means "a trip", and
   the two are drawn on top of each other. */
.chart__area  { fill: var(--lagoon-700); opacity: 0.1; }

.chart__line {
    stroke: var(--lagoon-700);
    stroke-width: 2.5;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.chart__breach { fill: var(--status-breach); opacity: 0.12; }

.chart__today { stroke: var(--accent-500); stroke-width: 2; stroke-dasharray: 4 4; }

.chart__today-label {
    fill: var(--brand-accent);
    font-size: 15px;
    font-weight: 700;
}

.chart__trip { fill: var(--accent-400); }

/* Planned travel is hollow. A projection drawn identically to a record is a
   projection somebody will remember as a record. */
.chart__trip--planned {
    fill: var(--accent-100);
    stroke: var(--accent-500);
    stroke-width: 1.5;
    stroke-dasharray: 4 3;
}

.legend__swatch--line    { background: var(--lagoon-700); border-color: var(--lagoon-700); }
.legend__swatch--trip    { background: var(--accent-400); border-color: var(--accent-500); }
.legend__swatch--planned { background: var(--accent-100); border-color: var(--accent-500); border-style: dashed; }
.legend__swatch--breach  { background: var(--status-breach-bg); border-color: var(--status-breach-line); }

/* ---- Milestones ---------------------------------------------------------
   The chart in words. Everything drawn above is also written here, which is
   what makes the drawing an aid rather than the only carrier. */

.milestones {
    display: grid;
    gap: var(--space-3);
    list-style: none;
    padding: 0;
}

@media (min-width: 48rem) {
    .milestones { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.milestone {
    display: flex;
    align-items: flex-start;
    gap: var(--space-3);
    padding: var(--space-4);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
}

.milestone__icon {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 2.25rem;
    flex: none;
    border-radius: var(--radius-pill);
    background: var(--status-muted-bg);
    color: var(--status-muted);
}

.milestone__icon svg { width: 18px; height: 18px; }

.milestone--ok      .milestone__icon { background: var(--status-ok-bg);      color: var(--status-ok); }
.milestone--notice  .milestone__icon { background: var(--status-notice-bg);  color: var(--status-notice); }
.milestone--warning .milestone__icon { background: var(--status-warning-bg); color: var(--status-warning); }
.milestone--breach  .milestone__icon { background: var(--status-breach-bg);  color: var(--status-breach); }

.milestone__label {
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-subtle);
}

.milestone__date {
    margin-top: var(--space-1);
    font-size: var(--text-lg);
    font-weight: 650;
    letter-spacing: -0.02em;
}

.milestone__detail {
    margin-top: var(--space-1);
    font-size: var(--text-sm);
    color: var(--text-muted);
}


/* ---- The calendar -------------------------------------------------------
   A real <table>, because a month grid IS tabular: the columns mean weekdays
   and a screen reader announcing "Wednesday" alongside the date is doing
   something a stack of divs cannot.

   The colour modifiers are the SAME classes the /why page uses — day--counted
   and friends — so the vocabulary is learned once. Only the geometry differs,
   which is all .cal__cell adds. */

.cal { width: 100%; border-collapse: separate; border-spacing: 4px; table-layout: fixed; }

/* Seven columns is not negotiable and neither is the width of a phone, so the
   only room left to find is padding. Measured at 375px: 37px cells inside the
   default card, 42px once the card and the gaps give some back. 44 is not
   reachable — 7 × 44 plus gaps exceeds the viewport — and pretending otherwise
   would mean a horizontally scrolling month, which is worse than a square two
   pixels narrow. The height is a full 44. */
@media (max-width: 30rem) {
    .cal { border-spacing: 3px; }
    .cal__card { padding-inline: var(--space-3); }
}

.cal__bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-bottom: var(--space-4);
}

.cal__title { font-size: var(--text-lg); letter-spacing: -0.02em; }
.cal__return { margin-top: calc(var(--space-3) * -1); margin-bottom: var(--space-4); font-size: var(--text-sm); }

.cal__weekday {
    padding-bottom: var(--space-1);
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--text-subtle);
    text-align: center;
}

.cal__slot { padding: 0; }

.cal__cell {
    display: grid;
    place-items: center;
    width: 100%;
    min-height: 2.75rem;              /* 44px — every square is a link */
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-sunken);
    color: var(--text-muted);
    font-size: var(--text-sm);
    font-variant-numeric: var(--numeric);
    text-decoration: none;
}

.cal__cell--blank { background: transparent; border-color: transparent; }
.cal__cell:hover { border-color: var(--border-strong); }

/* Planned travel is hatched rather than filled, the same distinction the
   timeline draws: a projection that looks identical to a record is a
   projection somebody will remember as a record. */
.cal__cell--planned { border-style: dashed; border-width: 2px; }

.cal__cell--breach { outline: 2px solid var(--status-breach); outline-offset: -2px; }

.cal__cell--selected {
    box-shadow: var(--focus-ring);
    position: relative;
    z-index: 1;
}

.cal__number { font-weight: 600; }

/* ---- The shared day-status modifiers ------------------------------------
   ORDER IS LOAD-BEARING, and getting it wrong shipped a calendar with every
   square the same colour.

   `.day--counted` and its siblings are used by two components: the small strip
   on the /why page (.day) and the month grid here (.cal__cell). All of them are
   single-class selectors, so specificity is identical and the LAST rule in the
   file wins. With the modifiers up in section 11 beside .day, `.cal__cell`'s own
   background and colour — declared here, three sections later — quietly beat
   them, and every counted day rendered as a plain cream square.

   Nothing detected it. The markup was right, the class was on the element, the
   contrast measured fine — because what it measured was a plain cell.

   So they live here, after both bases. 310_design_system.php asserts the
   ordering, because this is not a mistake anybody would make twice on purpose
   and is exactly the kind that comes back during a tidy-up. */

.day--counted {
    background: var(--status-warning-solid);
    border-color: var(--status-warning-solid);
    color: #fff;
    font-weight: 700;
}

.day--outside { background: transparent; border-color: var(--border-strong); border-style: dashed; }
.day--exempt  { background: var(--status-muted-bg); border-color: var(--status-muted-line); }
.day--today   { outline: 2px solid var(--lagoon-500); outline-offset: 1px; }

/* ---- The month index ----------------------------------------------------
   Without this the calendar opens on the current month and a traveller whose
   last trip ended in July sees thirty-one blank squares in August with nothing
   on the page suggesting any travel exists. The data was right and the page was
   useless. A month view needs an index. */

.cal__empty {
    margin-top: var(--space-4);
    padding: var(--space-4);
    background: var(--surface-sunken);
    border: 1px dashed var(--border-strong);
    border-radius: var(--radius);
    font-size: var(--text-sm);
    color: var(--text-muted);
}

.cal__months {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-top: var(--space-4);
}

.cal__months-label {
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-subtle);
    margin-right: var(--space-1);
}

.cal__month {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 2.25rem;
    padding: 0 var(--space-3);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-sm);
    text-decoration: none;
}

.cal__month:hover { background: var(--surface-hover); color: var(--text); }

.cal__month--current {
    border-color: var(--accent-400);
    background: var(--accent-050);
}

.cal__month-count {
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--text-muted);
    font-variant-numeric: var(--numeric);
}


/* ==========================================================================
   15. The operator console

   A DELIBERATELY DIFFERENT CHROME. This is not the customer product and must
   not be mistakable for it: the actions on these pages affect other people's
   accounts, and "which application am I looking at?" should never be a question
   somebody has to stop and answer.

   IT USED TO WEAR "a dark ink bar across the top that the customer application
   never has", and that sentence stopped being true the day the customer
   application went dark. The bar is --surface-ink on an ink page now: measured
   at 1.07:1, which is not a distinction, it is a coincidence of two dark things
   being adjacent. Saying so here rather than leaving the old claim standing,
   because a comment that describes a safeguard the code no longer provides is
   worse than no comment — somebody reads it and stops checking.

   WHAT ACTUALLY CARRIES THE DISTINCTION NOW is structural rather than tonal,
   and it is stronger than the colour ever was:

     - The SHAPE. The customer application is a left sidebar beside a scrolling
       column. This is a top bar with no sidebar at all. They do not look alike
       at any width, and a shape is not something a display profile can wash out.
     - The WORDMARK IS ABSENT. There is deliberately no logo here — the word
       "Operator" in letterspaced amber, and no MyStayDays porthole anywhere on
       the page. That is the single clearest signal on the screen, and it is the
       one a customer page can never accidentally show.

   The border is --border-strong rather than the ink-700 it was. At ink-700 the
   edge measured 1.36:1 against the page and the bar had no boundary at all;
   this is 3.49:1, so the band reads as a band. It is the smallest change that
   makes the bar an object again rather than a slightly different patch of dark.

   IF THIS EVER NEEDS TO BE LOUDER, the honest lever is a hue the customer
   product does not use as chrome, not a darker dark — there is no darker dark
   left. That is a decision about the console rather than about the palette, so
   it is not made here.

   Contrast on this page was re-measured against the single scheme: worst
   pairing is the stat label at 5.2:1; the bar's own text runs 7.6:1 to 13.9:1.
   ========================================================================== */

.opsbar {
    background: var(--surface-ink);
    border-bottom: 1px solid var(--border-strong);
}

.opsbar__inner {
    display: flex;
    align-items: center;
    gap: var(--space-5);
    flex-wrap: wrap;
    max-width: var(--content-max);
    margin: 0 auto;
    padding: var(--space-3) var(--space-5);
}

/* The word, not a logo. A wordmark here would make this look like the product;
   a plain label makes it look like a tool, which is what it is. */
.opsbar__mark {
    font-size: var(--text-sm);
    font-weight: 750;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--accent-300);
}

.opsbar__end {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-left: auto;
}

/* An anchor, and it keeps the muted colour rather than taking the link one. `a` is
   (0,0,1) and this is (0,1,0), so the colour below already wins without !important or a
   descendant selector; only the underline has to be turned off. The hover pair is (0,2,0)
   against `a:hover` at (0,1,1), which is why that wins too. */
.opsbar__who {
    font-size: var(--text-sm);
    color: var(--ink-300);
    text-decoration: none;
}

.opsbar__who:hover { color: var(--ink-000); text-decoration: underline; }

.opsnav { display: flex; gap: var(--space-1); flex-wrap: wrap; }

.opsnav__link {
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius);
    font-size: var(--text-sm);
    font-weight: 550;
    color: var(--ink-300);
    text-decoration: none;
}

.opsnav__link:hover { background: rgba(255, 255, 255, 0.08); color: var(--ink-000); }

.opsnav__link[aria-current="page"] {
    background: rgba(255, 255, 255, 0.12);
    color: var(--ink-000);
}

/* The waiting count on the Enquiries tab. Solid rather than the tinted .badge
   used inside pages: this sits on the ink bar, where a tinted background tuned
   for a white page reads as a smudge. Same treatment as .nav__count in the
   customer sidebar, which solved the same problem on the same surface. */
.opsnav__count {
    display: inline-block;
    min-width: 1.25rem;
    margin-left: var(--space-2);
    padding: 0 0.3rem;
    border-radius: var(--radius-pill);
    background: var(--status-breach-solid);
    color: #fff;
    font-size: 0.6875rem;
    line-height: 1.25rem;
    text-align: center;
    font-variant-numeric: var(--numeric);
}

.ops { padding: var(--space-6) var(--space-5) var(--space-8); }

.ops__inner { max-width: var(--content-max); margin: 0 auto; }

/*
   The standing "Stripe is in test mode" banner.

   THE NOTICE TOKENS, not the accent, and not a new colour. `notice` is what this
   is: a true statement about the installation that an operator should act on
   eventually, not an error and not a success. Light-on-dark, so the text token
   is the one the status is written in rather than a -solid fill — nothing here
   is white text on a colour, which is the case that needs the solid family.
*/
.ops-warning {
    background: var(--status-notice-bg);
    border: 1px solid var(--status-notice-line);
    border-radius: var(--radius);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-5);
    color: var(--text);
    font-size: var(--text-sm);
    line-height: var(--leading-body);
}

.ops-warning strong { color: var(--status-notice); }

/* ---- Enquiries from the contact form -------------------------------------
   A stack of cards rather than a table. The message is the content, it is a
   paragraph rather than a cell, and a table row that wraps to five lines is a
   table pretending to be a list. */

.ops-enquiry { margin-bottom: var(--space-4); }

/* Dealt with, and therefore not what the eye should land on. Faded rather than
   hidden: "I answered that last week" is a thing an operator looks up, and a
   filter that removed it entirely would make the answer harder to find. */
.ops-enquiry--handled { opacity: 0.62; }
.ops-enquiry--handled:hover,
.ops-enquiry--handled:focus-within { opacity: 1; }

.ops-enquiry__from { margin-bottom: var(--space-3); font-size: var(--text-sm); }

/* pre-wrap, so the paragraphing somebody typed survives without their text ever
   being turned into markup. The inset panel is what says "these are their words,
   not ours" — the same job the quoted block does in the forwarded email. */
.ops-enquiry__message {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    padding: var(--space-4);
    margin-bottom: var(--space-4);
    background: var(--surface-inset);
    border-left: 3px solid var(--border);
    border-radius: 0 var(--radius) var(--radius) 0;
    line-height: var(--leading-body);
}

.ops-enquiry__handled {
    font-size: var(--text-xs);
    color: var(--text-muted);
    align-self: center;
}

.ops-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--space-4);
    margin-bottom: var(--space-5);
}

.ops-stat {
    padding: var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: var(--surface-raised);
}

.ops-stat__label {
    font-size: var(--text-xs);
    font-weight: 650;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-subtle);
}

.ops-stat__value {
    margin-top: var(--space-2);
    font-size: var(--text-2xl);
    font-weight: 700;
    letter-spacing: -0.02em;
    font-variant-numeric: var(--numeric);
    color: var(--text);
}

/* A timezone is not a quantity. Same slot, ordinary size, so it does not read
   as a number that happens to contain letters. */
.ops-stat__value--text { font-size: var(--text-base); font-weight: 600; }

.ops-stat__note {
    margin-top: var(--space-1);
    font-size: var(--text-xs);
    color: var(--text-muted);
}

.ops-dl { display: grid; grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); gap: var(--space-4); }
.ops-dl dt { font-size: var(--text-xs); color: var(--text-muted); }
.ops-dl dd { font-size: var(--text-lg); font-weight: 650; font-variant-numeric: var(--numeric); }

.ops-search { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; }
.ops-search .input, .ops-search .select { flex: 1 1 16rem; }

.ops-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-4);
    margin-top: var(--space-5);
    font-size: var(--text-sm);
}

.ops-note {
    margin-top: var(--space-5);
    padding: var(--space-4);
    border-left: 3px solid var(--border-strong);
    background: var(--surface-inset);
    font-size: var(--text-sm);
    color: var(--text-muted);
    max-width: var(--prose-max);
}


/* ==========================================================================
   15b. Cookies — the banner, and the settings screen

   THE BANNER IS A STRIP, NOT AN OVERLAY, and every rule here is chosen to keep
   it that way. There is no backdrop, no `position: fixed` over the content, no
   scroll lock and no focus trap. It sits at the bottom of the viewport, the
   page behind it stays readable and clickable, and a visitor who ignores it
   entirely loses nothing — they get what "essential only" would have given them.

   It is FIRST IN THE MARKUP and moved here visually, so that tabbing or
   listening reaches the question before the page rather than after every link
   on it. That is the only reason this is positioned at all.

   BOTH ANSWERS LOOK THE SAME. "Accept all" and "Essential only" are the same
   element with the same class, so nothing in this file can quietly make
   refusing less inviting than agreeing — which is the failure mode this whole
   component exists to avoid.
   ========================================================================== */

.cookie-banner {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 40;
    background: var(--surface-raised);
    border-top: 1px solid var(--border-strong);
    box-shadow: var(--shadow-lg);
    padding: var(--space-4);
}

.cookie-banner__inner {
    max-width: var(--content-max);
    margin: 0 auto;
    display: flex;
    gap: var(--space-4);
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
}

.cookie-banner__text { flex: 1 1 28rem; }

.cookie-banner__title {
    font-size: var(--text-lg);
    line-height: var(--leading-snug);
    margin: 0 0 var(--space-1) 0;
    color: var(--text);
}

.cookie-banner__body {
    margin: 0;
    font-size: var(--text-sm);
    line-height: var(--leading-body);
    color: var(--text-muted);
    max-width: 62ch;
}

.cookie-banner__actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    align-items: center;
}

/*
   On a phone the row becomes a column and every control goes full width, so
   the two answers stay exactly as reachable as each other rather than one of
   them ending up below the fold.
*/
@media (max-width: 40rem) {
    .cookie-banner { padding: var(--space-3); }
    .cookie-banner__actions { width: 100%; }
    .cookie-banner__actions .btn { flex: 1 1 100%; justify-content: center; }
}

/*
   The tab bar owns the bottom of the screen in the application shell below
   60rem, so the banner sits above it rather than on top of it.

   `:has()` and not a sibling combinator. The banner is deliberately FIRST in
   the body — before .shell — so `.shell ~ .cookie-banner` would have matched
   nothing at all and the banner would have covered the tab bar on every phone,
   while the stylesheet looked like it handled the case.
*/
@media (max-width: 60rem) {
    body:has(.tabbar) .cookie-banner { bottom: var(--tabbar-height); }
}

/* ---- The settings screen ------------------------------------------------- */

.cookie-category { margin-bottom: var(--space-4); }

.cookie-category__head {
    display: flex;
    gap: var(--space-3);
    align-items: center;
    cursor: pointer;
}

/*
   A REAL CHECKBOX, scaled up to a comfortable target rather than replaced by a
   styled span. It keeps its keyboard behaviour, its focus ring, and whatever
   the reader's own accessibility settings do to form controls — all of which a
   hand-built switch has to reimplement and usually gets half right.

   THE ATTRIBUTE SELECTOR IS LOAD-BEARING, and not decoration. The base control
   rule is `input[type="checkbox"], input[type="radio"]` at 1.15rem, which is
   specificity 0-1-1 and beats a bare class at 0-1-0 wherever it sits in the
   file. Written as `.cookie-category__toggle` alone this rule lost silently:
   the stylesheet said 1.5rem and the browser rendered 18.4px, which is under
   the 24px minimum target size and looks like nothing at all in a screenshot.
   Found by measuring the rendered box rather than by reading the CSS.
*/
.cookie-category__toggle[type="checkbox"] {
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
    accent-color: var(--brand-accent);
    cursor: pointer;
}

.cookie-category__toggle:disabled { cursor: default; }

.cookie-category__label {
    font-weight: 650;
    font-size: var(--text-lg);
    color: var(--text);
}

.cookie-category__state {
    margin-left: auto;
    font-size: var(--text-xs);
    color: var(--text-muted);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    padding: 0.15rem var(--space-2);
    white-space: nowrap;
}

.cookie-category__summary {
    margin: var(--space-3) 0 0 0;
    color: var(--text-muted);
    line-height: var(--leading-body);
    max-width: var(--prose-max);
}

.cookie-category__empty {
    margin: var(--space-3) 0 0 0;
    padding: var(--space-3);
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    color: var(--text-subtle);
    font-size: var(--text-sm);
    line-height: var(--leading-body);
}

.cookie-table { margin-top: var(--space-3); }
.cookie-table code { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--text); }


/* ==========================================================================
   16. Utilities — a deliberately short list. Anything used three times
       becomes a component instead.
   ========================================================================== */

.muted   { color: var(--text-muted); }
.small   { font-size: var(--text-sm); }
.tiny    { font-size: var(--text-xs); }
.num     { font-variant-numeric: var(--numeric); }
.strong  { font-weight: 650; }
.stack   { display: grid; gap: var(--space-3); }
.stack--tight { gap: var(--space-2); }
.row     { display: flex; gap: var(--space-3); align-items: center; flex-wrap: wrap; }
.spread  { display: flex; gap: var(--space-3); align-items: center; justify-content: space-between; flex-wrap: wrap; }
.mt-4    { margin-top: var(--space-4); }
.mb-4    { margin-bottom: var(--space-4); }
.centred { justify-content: center; }
.plain   { border: 0; background: transparent; }
