    /* While an answer to "who set this?" or "where is my medication?" is on
       screen, the photograph stands down, so every answer button stays on a
       card that cannot scroll. reminderShowAnswer, UAT 2026-08-10. */
    #reminderNotification.answer-showing #reminderNotifPhoto,
    #reminderNotification.answer-showing .reminder-photo-wrap { display: none !important; }

    /* =====================================================================
       THE WAIT HAS TO BE VISIBLE.
       =====================================================================
       Every card that appears over the Ken's screen holds its buttons for
       1.5 seconds, because a hand with a tremor lands taps nobody intended.
       Until 2026-08-10 nothing on the screen said so. `disabled` was set and
       NO rule in this file matched a disabled button, so a held button looked
       exactly like a live one; `:active` cannot fire on a disabled button, so
       the press produced nothing either; and the fill bar was a 30% white wash
       over the button's own colour, which measured 1.05:1 on "Not taking it",
       1.05:1 on "Remind me later", 1.31:1 on gold "Taken" and 1.47:1 on the
       green "Answer Now", against a 3:1 non-text floor.
       So the person pressed, felt nothing, saw nothing, and had no way to tell
       a waiting Ken from a broken one - on the incoming-call screen, inside a
       15-second countdown. Found by the UAT of 2026-08-10, and the same day's
       fix had just widened the hold from 15 cards to every card that appears.
       Three separate signals, because any one of them can be missed:
         1. the card says "Just a moment..." in words;
         2. the button dims and its edge softens, so it reads as not-yet;
         3. the bar sweeping across it is ink, which reads on every Ken button
            because every Ken button is light.
       ===================================================================== */
    .ken-lock-fill {
      /* A SWEEP ACROSS THE WHOLE BUTTON, IN TWO PARTS: A LIGHT FILM AND A
         SOLID LEADING EDGE. Howard, on the bench, 16 Aug 2026, having seen the
         bottom band on a real Ken: "Put it back to a bar across the whole
         button, but lighter than the original wash."

         Three shapes have now been tried and each failed on ONE of the two
         things this control has to do at once:
           - a 30% white wash (to 10 Aug) measured 1.05:1 on the cream
             medication buttons, so the person pressed, saw nothing, and had no
             way to tell a waiting Ken from a broken one;
           - a near-solid ink fill (to 13 Aug) was plainly visible but he
             photographed it mid-sweep with black label text sitting on
             near-black grey, and asked for it lightened;
           - a 6px band along the bottom edge (to 16 Aug) kept the contrast and
             kept off the label, and reads as a progress bar bolted underneath
             rather than the button filling.

         Both requirements are met by splitting the job. The FILM is ink at 12%
         over the whole button: it darkens label and background by the same
         amount, so their contrast is preserved almost exactly, and the worst
         label on any Ken button stays at 3.15:1 (ink on red; gold 5.64, cream
         11.89, green 3.96) against the 3:1 large-text floor. The film alone is
         deliberately too faint to be the signal. The LEADING EDGE is ink at
         80%, and it is the part that carries the state: it is the boundary the
         eye tracks as it moves, and it measures gold 4.64:1, cream 7.96:1,
         green 3.58:1, red 3.01:1 against the unfilled button, over the 3:1
         non-text floor.

         Every number above is computed by tests/a-held-button-says-it-is-
         holding.mjs, which measures the film, the edge AND the label on the
         film - the last of these being the one nothing measured before, and
         the reason the 13 Aug version reached his hands looking terrible. */
      position: absolute; left: 0; top: 0; bottom: 0; width: 0;
      border-radius: 10px 0 0 10px;
      z-index: 1; pointer-events: none;
      background: rgba(37, 35, 34, 0.25);
      border-right: 3px solid rgba(37, 35, 34, 0.80);
    }
    button.ken-held {
      /* Not `opacity`, which would fade the label too and cost contrast on the
         very screen where reading it matters. Saturation and a softened edge
         say "not yet" while leaving the words at full strength. */
      filter: saturate(0.55);
      box-shadow: inset 0 0 0 2px rgba(37, 35, 34, 0.18);
      cursor: default;
    }
    /* The words. Positioned by the card, not by the button, so a card with
       three held buttons says it once. Hidden until a hold is running. */
    .ken-hold-note { display: none; }
    .ken-holding .ken-hold-note {
      display: block;
      text-align: center;
      color: var(--ink);
      opacity: 0.75;
      font-size: var(--body-sm);
      margin-bottom: 0.4rem;
    }

    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
      /* 2026-05-19: Howard reported a square grey box appearing on top of
         rounded buttons (gold pill, WiFi-network rows) for ~150ms after
         tap. That is Chromium's default tap-highlight overlay — it ignores
         the element's border-radius, so a 90-degree grey rect briefly
         overlays the rounded shape. Making it transparent globally is the
         canonical fix; some specific selectors already had this set but
         not every tap target did. */
      -webkit-tap-highlight-color: transparent;
    }

    /* Kill focus outlines on touch/mouse focus only (keep them on keyboard
       focus via :focus-visible so the device remains screen-reader friendly).
       Without this, Chromium's default focus ring draws a rectangular box
       around rounded buttons, breaking the visual after tap. */
    button:focus:not(:focus-visible),
    [role="button"]:focus:not(:focus-visible),
    .btn:focus:not(:focus-visible),
    a:focus:not(:focus-visible),
    input:focus:not(:focus-visible),
    [tabindex]:focus:not(:focus-visible) {
      outline: none;
    }

    :root {
      --cream: #F5F0E8;
      --warm-white: #FDFAF5;
      --red: #D94A3D;          /* deuteranopia-safe red (reject/destructive only) */
      --green: #2AA06A;        /* deuteranopia-safe green; unread indicators use this, static */
      --ink: #252322;
      --mid: #4A4440;
      --gold: #C4A962;
      /* Primary button surface. NOT gold: gold cannot carry text at our bar of 4
         (real gold sits at luminance ~0.40, warm-white at bar 4 needs <=0.202) and
         the old gold button sat at the red's lightness, which made the two read alike
         for a deuteranope. Decided 2026-07-17. */
      --btn-primary: #4F453A;
      --gold-light: #C4A962;
      --gold-text: #C4A962;
      /* An icon that MEANS something is not decoration, and #C4A962 sits at
         2.19:1 on warm-white - under the 3:1 an icon has to clear before a
         reader with failing sight can make out its shape, which is the whole
         audience. The darker brand gold clears it at 3.9:1 and is the same
         colour the borders and the monogram already use, so nothing on screen
         looks like a second gold. The wordmark keeps #C4A962: it is a logotype,
         it carries no information, and Howard settled it on 5 Aug 2026. */
      --gold-icon: #9A7B4F;
      /* 2026-06-18: A4 paper-grain texture layered over EVERY solid background
         (warm-white / cream / gold / gold-light) so no surface is flat pure
         colour. Tiled 256px transparent overlay. Rule: feedback_paper_texture_backgrounds. */
      --paper: url('assets/paper-texture.png');
      /* Mockup-03 design tokens (P3, 2026-05-25). Additive — existing
         --cream / --mid / --ink / --gold stay untouched so wizard and
         in-call surfaces keep their current visuals. New layered tokens
         drive the post-wizard daily surfaces: Settings hub + sub-pages
         first, then Home / Menu / Help / Messages / Contacts. */
      --paper-2: #F7F2E9;
      --paper-3: #EFE7D3;
      --ink-2: #3F3933;
      --ink-3: #6B6459;
      --rule: rgba(154,123,79,0.22);
      --rule-2: rgba(154,123,79,0.40);
      --gold-deep: #C4A962;
      --body-sm: 22px;
      --body: 22px;
      --body-lg: 28px;
      --section-title: 37px;    /* Settings section headers (Cormorant serif). Must clearly out-weigh the 28px body labels beneath. See CLAUDE.md §9. */
      --subheader: 28px;
      --header: 32px;
      --header-lg: 48px;
      --btn: 28px;
      --date-size: 22px;
      --clock-size: 32px;
      --contact-name: 22px;
      --icon-size: 28px;
      /* Keyboard key size baked at 56px so 10 keys + 9x4px gaps fit 600px portrait
         (10x56 + 9x4 = 596). User no longer chooses size during the wizard;
         Settings still allows override via applyKbKeySize() / chooseKbSize(). */
      --kb-key-size: 56px;
      /* Touch target size tokens (dynamic, like font sizes).
         Scaled by applyTouchSize() based on user's minimum acceptable
         button size preference. Used by every tap target in the app. */
      --touch-min: 64px;        /* minimum for any tappable element */
      --touch-comfortable: 80px; /* standard buttons, nav, rows */
      --touch-large: 96px;      /* primary CTAs, dialog actions */
      --touch-gap: 12px;        /* gap between adjacent tap targets */
      --scroll-btn-size: 40px;  /* scrollbar up/down arrow dimensions */
    }

    html, body {
      width: 100%;
      height: 100%;
      background: var(--warm-white) var(--paper) repeat;
      color: var(--ink);
      font-family: 'Jost', sans-serif;
      overflow: hidden;
      position: relative;
    }

    /* Blinking clock colon (Howard 2026-07-18): a little life on an otherwise
       static home screen, like a traditional digital clock. The colon lives in
       its own persistent span so updateTime() (which runs every second) never
       restarts this.

       9 Aug 2026: the off half was HALF A SECOND, so at any moment there was a
       50/50 chance the screen read "12 11" with no colon at all - and it did,
       in two screenshots of the bench Ken taken a day apart. Twice it was read
       as a missing character rather than a blink, which is exactly how somebody
       with failing sight would read it, since they get one look rather than a
       second one.

       Now 100ms off in every 1000. It still winks; it no longer spends half its
       life absent. Howard chose the number from a live comparison of five rates
       at the real size (The Ken/clock-colon-picker.html). */
    .clock-colon { animation: clockColonBlink 1s steps(1, end) infinite; }
    @keyframes clockColonBlink { 0%, 10% { opacity: 0; } 10.001%, 100% { opacity: 1; } }
    @media (prefers-reduced-motion: reduce) { .clock-colon { animation: none; } }

    /* Accessible scrollbar - scales with touch size token */
    ::-webkit-scrollbar {
      width: var(--scroll-btn-size);
    }
    ::-webkit-scrollbar-track {
      background: transparent;
    }
    ::-webkit-scrollbar-thumb {
      background: rgba(160,155,148,0.2);
      border-radius: 0;
      border: 3px solid transparent;
      min-height: 80px;
    }
    ::-webkit-scrollbar-thumb:hover {
      background: rgba(160,155,148,0.35);
    }
    ::-webkit-scrollbar-button:single-button {
      display: block;
      height: var(--scroll-btn-size);
      width: var(--scroll-btn-size);
      background-color: rgba(160,155,148,0.08);
      border: 1px solid rgba(160,155,148,0.12);
      background-repeat: no-repeat;
      background-position: center;
      background-size: 40% 40%;
    }
    ::-webkit-scrollbar-button:single-button:hover {
      background-color: rgba(160,155,148,0.2);
    }
    /* Up arrow */
    ::-webkit-scrollbar-button:single-button:vertical:decrement {
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(120,115,108,0.4)'%3E%3Cpath d='M12 5l-8 10h16z'/%3E%3C/svg%3E");
    }
    /* Down arrow */
    ::-webkit-scrollbar-button:single-button:vertical:increment {
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='rgba(120,115,108,0.4)'%3E%3Cpath d='M12 19l-8-10h16z'/%3E%3C/svg%3E");
    }

    body::before {
      content: '';
      position: absolute;
      inset: 0;
      background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
      pointer-events: none;
      z-index: 0;
    }

    /* ===== SHARED SETUP STYLES ===== */
    .setup-step {
      display: none;
      position: fixed;
      inset: 0;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 500;
      flex-direction: column;
      align-items: center;
      padding: 48px 32px;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      /* Hide the phantom desktop scrollbar (UAT 2026-05-06 rule). The wizard
         steps occasionally trip overflow-y by 1-5px when an error message
         flips visible (e.g. WiFi password "Could not connect"), and the
         40px-wide --scroll-btn-size scrollbar takes more screen space than
         the pixel of content it would reveal. Touch-scroll still works for
         genuinely long content (xlarge text size). Same pattern as
         .container, .contacts-manage-grid, .messages-list. */
      scrollbar-width: none;            /* Firefox */
      -ms-overflow-style: none;          /* IE/Edge legacy */
    }
    .setup-step::-webkit-scrollbar { display: none; } /* WebKit / Blink */
    /* 2026-06-17: settings-family screens (Settings hub, Help, Give Feedback) are
       touch-scrolled - hide the heavy arrow scrollbar like the wizard steps do.
       It read as a clunky phantom scrollbar (grey bar + up/down arrow buttons). */
    .settings-screen { scrollbar-width: none; -ms-overflow-style: none; }
    .settings-screen::-webkit-scrollbar { display: none; }
    /* 2026-07-16: the contact card scrolls on an INNER body div (fixed header +
       scrolling body), so the .settings-screen rule above never reached it and
       the heavy 40px arrow scrollbar rendered as a band down the right edge -
       ~50px of clutter that unbalanced the whole card (Howard: card "spacing,
       balance, design is poor"). The card is a profile, not a list; hide the
       phantom bar and rely on single-finger swipe like every other settings
       body. */
    .card-scroll { scrollbar-width: none; -ms-overflow-style: none; }
    .card-scroll::-webkit-scrollbar { display: none; }
    /* 2026-07-17, SECOND recurrence of the phantom-band class: Medical Info also
       scrolls on an inner body div (inline-styled, no class), so neither the
       .settings-screen rule nor .card-scroll reached it and the heavy 40px arrow
       band was back down its right edge - shoving the cards off-centre exactly as
       it did on the contact card. Patching one more selector by name is what let
       this recur, so catch the CLASS: any direct-child scrolling body of a
       settings screen hides the bar and relies on single-finger swipe, like every
       other settings body. The deliberate opt-ins (#setupEnterCode, #qrAddScreen)
       scroll on the SCREEN element itself, not a child, so they keep their gold
       bar untouched. */
    .settings-screen > div { scrollbar-width: none; -ms-overflow-style: none; }
    .settings-screen > div::-webkit-scrollbar { display: none; }
    /* 2026-05-09 PM: setupEnterCode opts back IN to a visible scrollbar
       so any genuine vertical overflow (long Spanish title wrapping,
       big text-size + big touch-size combo pushing the keypad off-screen)
       is SIGNALLED to the user. Without this, content past the fold is
       invisible because the scrollbar that would invite a swipe is also
       hidden. The phantom-scrollbar UAT rule was about hiding a 1-5px
       overflow scrollbar; legitimate 100+px overflow needs the cue. */
    /* #qrAddScreen joined 2026-06-26: at a larger text size its content (QR + the
       Device/Code box) overflows, and the hidden scrollbar made the bottom "Code"
       row clip silently - one Ken on a bigger text size showed it cut off while a
       smaller-text Ken showed it whole. Same visible-scroll cue + ensure it scrolls. */
    #setupEnterCode, #qrAddScreen { overflow-y: auto; scrollbar-width: thin !important; scrollbar-color: var(--gold) transparent; }
    #setupEnterCode::-webkit-scrollbar, #qrAddScreen::-webkit-scrollbar { display: block !important; width: 12px; }
    #setupEnterCode::-webkit-scrollbar-thumb, #qrAddScreen::-webkit-scrollbar-thumb { background: var(--gold) var(--paper) repeat; border-radius: 6px; }
    #setupEnterCode::-webkit-scrollbar-track, #qrAddScreen::-webkit-scrollbar-track { background: transparent; }
    /* Small brand wordmark, top-right corner of every wizard step that
       doesn't already feature the logo. Canonical layout: gold italic
       'The' sits in the upper-LEFT of the wordmark, ink 'Ken' below
       and extends right (both LEFT-aligned to each other). Whole
       wordmark sits in the top-right of the screen via absolute
       positioning. Injected via JS on load. Skipped for setupWelcome
       (logo IS the screen) and setupLang (inline wordmark already in
       the header). */
    /* 2026-05-04: switched from CSS-rendered text to the canonical PNG
       (assets/theken-logo-clean.png). Sized small for the corner. The
       PNG is square-ish (1035x1024) with transparent background. */
    .ken-corner-logo {
      position: absolute;
      top: 18px;
      right: 22px;
      width: 72px;
      height: auto;
      z-index: 600;
      pointer-events: none;
    }
    /* When the screen has NO Back button (e.g. setupCheckUpdates,
       finishWizardAndLand, intermediate non-back-able steps), put the
       wordmark TOP-LEFT instead of TOP-RIGHT so it doesn't collide with
       a clock or status pill that may sit at the right. Howard 2026-05-04. */
    .setup-step:not(:has(.setup-back)) .ken-corner-logo {
      right: auto;
      left: 22px;
    }
    /* Steps with a back button need top padding so title clears it */
    .setup-step:has(.setup-back) {
      padding-top: 90px;
    }
    /* ===== THE BRAND MARK, ONE DEFINITION (5 Aug 2026, Howard's spec) =====
       "The" is italic and gold. "Ken" is upright and off-black, and larger. Side
       by side, never stacked - the stacked form only works where the mark has to
       fill a square. Every surface that writes the name in text uses this, so it
       cannot drift per screen: <span class="ken-mark"><i>The</i><b>Ken</b></span>.

       Both halves declare their font-style EXPLICITLY. "Ken" carrying
       font-style:normal looks redundant and is not: it is the only thing stopping
       an italic ancestor, or a font file that turns out not to be the face it
       claims, from flipping the mark. Howard photographed exactly that on 5 Aug -
       "The" upright and "Ken" italic, the wrong way round, from CSS that said the
       opposite (see device/fonts/fonts-local.css).

       Gold is #C4A962 (Howard, 5 Aug 2026, confirming the 17 Jul 2026 rule that
       #C4A962 is the wordmark gold). The artwork used to read #9A795B on the
       "The" and has been recoloured to match, so the drawn mark and the text
       lockup are now the same gold. Ink is --ink #252322, an off-black: the
       artwork uses pure black, but pure black is wrong for typography on a warm
       cream ground. */
    .ken-mark { font-family: 'Cormorant Garamond', serif; line-height: 1; white-space: nowrap; }
    .ken-mark .ken-mark-the {
      font-style: italic;
      font-weight: 400;
      font-size: 0.6em;
      color: #C4A962;
      letter-spacing: 0.01em;
      margin-right: 0.12em;
    }
    .ken-mark .ken-mark-ken {
      font-style: normal;
      font-weight: 600;
      color: var(--ink);
      letter-spacing: 0.005em;
    }

    /* Inside a running sentence the mark is read as words, so the size gap that
       works on a masthead makes "The" look like a mistake rather than a mark.
       Same shapes, same colours, gentler ratio, and "Ken" sized to sit on the
       sentence's own line rather than tower over it. */
    .ken-mark--inline { font-size: 1.06em; }
    .ken-mark--inline .ken-mark-the { font-size: 0.78em; margin-right: 0.1em; }

    .setup-step.active { display: flex; }

    /* WHILE A SETUP STEP IS ON SCREEN, THE APP CHROME IS NOT (5 Aug 2026).
       Howard photographed a freshly flashed Ken sitting on the welcome screen
       with the menu bar showing underneath it and "(Tap anywhere to begin)"
       printed across "Menu" and "Settings". The card had never been set up, so
       init() had hidden the bar; something after it put the home screen and the
       bar back while the wizard was still up, and a clean restart of the app
       showed the correct state, so it is intermittent and depends on the path.
       There are ~20 separate places that add .active to a setup step and a
       similar number that set the nav to flex, so chasing the one that raced is
       the wrong fix - any of the others can do it tomorrow. This makes it
       impossible instead of unlikely: no JS path can leave the chrome painted
       over the wizard, because the rule is a consequence of the wizard being up.
       :has() is already used elsewhere in this file, so it is supported here. */
    body:has(.setup-step.active) .bottom-nav,
    body:has(.setup-step.active) #homeScreen { display: none !important; }
    /* When the no-subscription banner (position:fixed, top:0) is showing, shift
       the active wizard step down by the bar's real height so the corner logo
       and Back button are not hidden behind it (Howard 2026-06-06). The step is
       position:fixed inset:0, so override its top; it scrolls if content needs. */
    body.ken-nosub .setup-step { top: var(--nosub-h, 56px); }
    /* 2026-06-13 (Howard): the fixed no-sub banner (#noSubBar, top:0) only shifted
       the wizard down (the rule above); every other full-screen surface sat under
       it - e.g. the Settings Back button was hidden behind it. Push each screen
       below the banner when it shows, and pad the home container, so nothing is
       hidden under the bar. (#helpScreen is a .settings-screen, so Help is covered.) */
    body.ken-nosub .settings-screen,
    body.ken-nosub .calling-screen,
    body.ken-nosub .call-screen,
    body.ken-nosub .incoming-screen,
    body.ken-nosub .passcode-overlay {
      top: var(--nosub-h, 56px);
      height: calc(100% - var(--nosub-h, 56px));
    }
    body.ken-nosub #getHelpOverlay { top: var(--nosub-h, 56px) !important; }
    body.ken-nosub .container { padding-top: calc(32px + var(--nosub-h, 56px)); }

    .setup-title {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 500;
      /* Cap the title at 52px even when the user picks "large" text. With
         96px reserved each side for the corner-logo footprint, a 64px
         header at "large" wraps to 4 lines in French ("Connecter / Ken
         à / votre / compte.") which dominates the screen. 52px keeps the
         title to 2 lines in every locale without sacrificing readability.
         clamp(min, ideal, max) so small text still scales down naturally. */
      font-size: clamp(36px, var(--header-lg), 52px);
      color: var(--ink);
      text-align: center;
      margin-bottom: 12px;
      /* Reserve horizontal margin so the title can't bleed into the
         corner wordmark. .ken-corner-logo is 72px wide + 22px right
         inset = 94px from the right edge; the title (text-align:center)
         needs enough padding-right so its right edge stays clear of
         the wordmark's left edge. Mirrored on the left so the centred
         title doesn't drift relative to the Back button.
         Tightened from 96px to 72px in the 2026-05-28 second polish
         pass after BlueStacks showed 4-line wraps in EN + FR; on Pi
         Electron with proper font metrics this gives the title enough
         width for 2 words per line in every locale. */
      padding-left: 72px;
      padding-right: 72px;
      box-sizing: border-box;
    }

    .setup-subtitle {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      text-align: center;
      margin-bottom: 40px;
      line-height: 1.4;
    }

    .setup-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--btn);
      /* GOLD, not espresso (Howard 2026-07-18): espresso exists only to avoid gold
         sitting next to red on a screen that has both a green and a red button (the
         gold~red clash a deuteranope sees). The setup wizard has no red/green, so a
         lone primary button is gold - our brand, the "The" in "The Ken". Ink text is
         6.85:1 on #C4A962. */
      color: var(--ink);
      background: var(--gold) var(--paper) repeat;
      border: none;
      border-radius: 12px;
      padding: 14px 28px;
      /* 64px touch floor, NOT var(--touch-min) - that balloons to 88px+ at large
         text and read as a comically over-sized slab around small text. Design
         Principles §3: min-height 64px, no taller; grow the text, not the box.
         The button still grows with --btn, so it stays proportionate to its label. */
      min-height: 64px;
      /* Hug the text (Howard 2026-06-14): the fixed 340px slab still read as
         "fat" - a wide gold box around relatively small text (box-to-text
         imbalance). Size the box to its label and centre it in the stack.
         Generous padding + min-height keep it an easy tap target; text size is
         unchanged so readability for elderly users is preserved. Supersedes the
         2026-06-06 480->340 width call. */
      display: block;
      width: fit-content;
      max-width: 100%;
      margin: var(--touch-gap) auto;
      cursor: pointer;
      box-shadow: 0 1px 3px rgba(0,0,0,0.10);
    }

    /* Wizard footer note shown on text-size and touch-size steps. Uses
       the currently-selected text size so the user sees the note
       rendered at the size they just picked. */
    /* Always pinned to the screen bottom as a footer. The wizard rule
       (2026-05-04): any "N.B." / "This can be changed later" / "You can
       update this in Settings" type info is screen footer, never inline.
       margin-top:auto pushes to the bottom of the flex column. */
    .setup-change-note {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
      text-align: center;
      margin-top: auto;
      padding-top: 16px;
      line-height: 1.4;
      flex-shrink: 0;
    }
    .setup-step { padding-bottom: 80px; }
    /* Email/binding screen fits 1024x600 with NO scroll (Howard 2026-06-25):
       compact header, Skip pinned to the bottom (margin-top:auto), and the email
       keyboard's 6 rows are height-capped in bindingBuildKeyboard, so everything
       sits on one screen. !important overrides the elements' inline margins. */
    #setupBinding.setup-step { padding-top: 24px; padding-bottom: 10px; }
    #setupBinding .setup-title { margin-top: 8px !important; margin-bottom: 6px; }
    #setupBinding .setup-subtitle { margin-bottom: 8px; }
    #setupBinding .input-display { min-height: 40px; margin-bottom: 2px; }
    #setupBinding #bindingKeyboard { margin-top: 6px !important; }
    #setupBinding #bindingSkip { margin-top: auto !important; padding-top: 6px; }
    /* Code-entry + picker: centre the (shorter) content vertically so it isn't
       top-heavy (Howard 2026-06-25). The email phase keeps top-aligned + Skip pinned. */
    #setupBinding.binding-vcenter { justify-content: center; }
    #setupBinding.binding-vcenter #bindingSkip { margin-top: 28px !important; }
    .setup-btn:active { transform: scale(0.98); }

    /* ============================================================
       FIVE CANONICAL CTA STYLES (2026-06-24, Howard)
       Reference proof sheet: device/button-styles.html (owner-approved).
       Every CTA button in the app maps to exactly ONE of these five.
       Each HUGS its label: inline-block, width:auto, max-width:100% -
       never a full-width slab or a fixed min-width on a short label, and
       NEVER black/dark text on a coloured fill. The existing CTA classes
       (.setup-btn, .answer-btn, .reject-btn, etc.) are de-fatted to match.
       ============================================================ */
    .btn-primary, .btn-secondary, .btn-positive, .btn-danger, .btn-quiet {
      display: inline-block;
      width: auto;
      max-width: 100%;
      min-height: 64px;
      padding: 14px 28px;
      border-radius: 14px;
      font-family: 'Jost', sans-serif;
      font-size: var(--body);
      font-weight: 500;
      line-height: 1.1;
      cursor: pointer;
      text-align: center;
      -webkit-tap-highlight-color: transparent;
    }
    .btn-primary   { background: var(--gold) var(--paper) repeat; color: var(--ink); border: none; }               /* main action - GOLD default (Howard 2026-07-18); espresso only where a red button shares the screen */
    .btn-secondary { background: #fff; color: var(--ink); border: 2px solid var(--gold); }                  /* alternative action */
    .btn-positive  { background: #2AA06A; color: #042713; border: none; }                                   /* approve/answer (deuteranopia green + dark text) */
    .btn-danger    { background: #D94A3D; color: #FDFAF5; border: none; }                                      /* reject/end/delete */
    .btn-quiet     { background: transparent; color: var(--mid); border: none; text-decoration: underline; min-height: 48px; padding: 10px 8px; font-weight: 400; } /* back/skip/minor */
    .btn-primary:active, .btn-positive:active, .btn-danger:active { transform: scale(0.98); }
    .btn-secondary:active { background: rgba(154,123,79, 0.1); }
    .btn-quiet:active { color: var(--ink); }

    /* Launch button (final "You're all set" step). Howard 2026-06-21: give it a
       bit of life. A crisp gold ring that expands and fades on a slow loop -
       deliberate, not the faint static halo that was rejected before. box-shadow
       only, so it doesn't fight the :active scale transform. */
    @keyframes launchPulse {
      0%, 100% { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 0 0 0 rgba(154,123,79,0.50); }
      50%      { box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 0 0 12px rgba(154,123,79,0); }
    }
    #launchBtn { animation: launchPulse 1.8s ease-in-out infinite; }
    @media (prefers-reduced-motion: reduce) { #launchBtn { animation: none; } }

    /* Quiet style: low-emphasis "← Back" text-link, underlined per the canonical
       proof sheet (device/button-styles.html). 2026-06-24. */
    .setup-back {
      position: absolute;
      top: 16px;
      left: 16px;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      background: none;
      border: none;
      text-decoration: underline;
      cursor: pointer;
      padding: 12px 16px;
      min-height: var(--touch-min);
      min-width: var(--touch-min);
      display: flex;
      align-items: center;
      gap: 6px;
      z-index: 10;
    }
    .setup-back:active { color: var(--ink); }

    /* ===== STEP 1: LANGUAGE ===== */
    /* 2-col x 4-row grid filling the screen. Big flags with name
       stacked underneath so each card reads as a single language tile. */
    .lang-grid {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 8px;
      width: 100%;
      flex: 1;
      align-content: stretch;
    }

    .lang-option {
      background: rgba(245, 240, 232, 0.4);
      border: 3px solid rgba(154,123,79, 0.3);
      border-radius: 16px;
      padding: 10px 12px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 10px;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: all 0.2s ease;
    }

    .lang-option:active {
      background: rgba(245, 240, 232, 0.7);
      border-color: var(--gold);
      transform: scale(0.97);
    }

    .lang-flag {
      width: 100px;
      height: 68px;
      border-radius: 6px;
      overflow: hidden;
      border: 1px solid rgba(26, 23, 20, 0.1);
      flex-shrink: 0;
    }

    .lang-flag svg {
      width: 100%;
      height: 100%;
      display: block;
    }

    .lang-name {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: max(22px, var(--body));
      color: var(--ink);
      text-align: center;
      line-height: 1.15;
    }

    /* ===== STEP 2: FONT SIZE ===== */
    .font-option {
      width: 100%;
      background: rgba(245, 240, 232, 0.4);
      border: 3px solid rgba(154,123,79, 0.3);
      border-radius: 16px;
      padding: 20px 28px;
      margin-bottom: 24px;
      min-height: var(--touch-large);
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: all 0.2s ease;
      text-align: center;
    }

    /* ===== SETUP STEP: TOUCH TARGET SIZE =====
       Square preview buttons at FIXED sizes so user can compare by tapping.
       Sizes match what the user will actually get. */
    .touch-options-row {
      display: flex;
      align-items: flex-end;
      justify-content: center;
      gap: 24px;
      margin-top: 20px;
    }
    .touch-option {
      background: var(--gold) var(--paper) repeat;
      border: none;
      border-radius: 14px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: all 0.2s ease;
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      color: var(--ink);
    }
    .touch-option:active {
      transform: scale(0.95);
      opacity: 0.85;
    }
    .touch-option .tp-sample {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      /* White on gold is 2.28:1, and this is the sample text somebody reads to
         CHOOSE their button size - the one screen where the person is being
         asked to judge legibility is the one that was hardest to read.
         --ink on gold is 6.85:1 and is what every other gold control uses. */
      color: var(--ink);
      text-align: center;
      line-height: 1.1;
      width: 100%;
      padding: 0 6px;
      box-sizing: border-box;
    }
    .touch-option-standard { width: 64px; height: 64px; }
    .touch-option-standard .tp-sample { font-size: 22px; }
    .touch-option-large    { width: 88px; height: 88px; }
    .touch-option-large    .tp-sample { font-size: 22px; }
    .touch-option-xlarge   { width: 112px; height: 112px; }
    .touch-option-xlarge   .tp-sample { font-size: 28px; }

    .touch-option.selected {
      outline: 4px solid var(--ink);
      outline-offset: 4px;
      transform: scale(1.05);
    }
    .touch-option.selected:active {
      transform: scale(1.05);
      opacity: 1;
    }

    .kb-preview-section {
      display: none;
      width: 100%;
      max-width: 540px;
      margin-top: 24px;
    }
    .kb-preview-wrap {
      background: rgba(154,123,79,0.08);
      border-radius: 16px;
      padding: 20px 16px;
    }
    .kb-preview-title {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body);
      color: var(--ink);
      text-align: center;
      margin-bottom: 12px;
    }
    .kb-preview-row {
      display: flex;
      gap: 4px;
      overflow-x: auto;
      -webkit-overflow-scrolling: touch;
      padding: 4px 2px 8px;
      scrollbar-width: thin;
      scrollbar-color: var(--gold) #e8e4dc;
    }
    .kb-preview-row::-webkit-scrollbar { height: 8px; }
    .kb-preview-row::-webkit-scrollbar-track { background: #e8e4dc; border-radius: 4px; }
    .kb-preview-row::-webkit-scrollbar-thumb { background: var(--gold) var(--paper) repeat; border-radius: 4px; min-width: 40px; }
    .kb-preview-key {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body-sm);
      color: var(--ink);
      background: var(--cream) var(--paper) repeat;
      border: 2px solid rgba(154,123,79,0.25);
      border-radius: 10px;
      flex: 0 0 auto;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .kb-preview-note {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
      text-align: center;
      line-height: 1.5;
      margin-top: 12px;
    }

    /* Nav bar icons - scale with font size */
    .nav-icon {
      width: var(--icon-size);
      height: var(--icon-size);
      display: inline-block;
      vertical-align: middle;
    }

    /* Font sample icons in onboarding */
    .font-sample-icons {
      display: inline-flex;
      gap: 8px;
      align-items: center;
      margin-right: 8px;
      vertical-align: middle;
    }
    .font-sample-icons svg {
      vertical-align: middle;
    }

    /* Keyboard step choice buttons */
    .kb-choice-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body-sm);
      color: var(--ink);
      background: #fff;
      border: 2px solid var(--gold);
      border-radius: 14px;
      padding: 14px 28px;
      min-height: 64px;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: all 0.2s ease;
    }
    .kb-choice-btn:active {
      background: rgba(154,123,79, 0.15);
    }
    .kb-choice-btn.selected {
      border-color: var(--gold);
      background: rgba(154,123,79, 0.12);
    }

    /* Keyboard demo in wizard */
    .kb-demo-wrap {
      width: 100%;
      max-width: 540px;
      background: rgba(154,123,79, 0.08);
      border-radius: 16px;
      padding: 16px 12px;
      margin-top: 16px;
    }
    .kb-demo-row {
      display: flex;
      gap: 4px;
      overflow-x: auto;
      -webkit-overflow-scrolling: touch;
      padding: 4px 2px 8px;
      scrollbar-width: thin;
      scrollbar-color: var(--gold) #e8e4dc;
    }
    .kb-demo-row::-webkit-scrollbar { height: 8px; }
    .kb-demo-row::-webkit-scrollbar-track { background: #e8e4dc; border-radius: 4px; }
    .kb-demo-row::-webkit-scrollbar-thumb { background: var(--gold) var(--paper) repeat; border-radius: 4px; min-width: 40px; }
    .kb-demo-key {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      /* Falls back to --body-sm; individual keys override via inline font-size
         that scales with the key size (see renderKbSizeCards / buildKbDemoRow). */
      font-size: var(--body-sm);
      color: var(--ink);
      background: var(--cream) var(--paper) repeat;
      border: 2px solid rgba(154,123,79, 0.25);
      border-radius: 10px;
      flex: 0 0 auto;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .font-option:active {
      background: rgba(245, 240, 232, 0.7);
      border-color: var(--gold);
      transform: scale(0.98);
    }

    .font-option .sample {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      color: var(--ink);
      line-height: 1.3;
      margin-bottom: 8px;
    }

    .font-option .label {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      color: var(--mid);
    }

    .font-option-standard .sample { font-size: 22px; }
    .font-option-standard .label { font-size: var(--body-sm); }
    .font-option-large .sample { font-size: 28px; }
    .font-option-large .label { font-size: 22px; }
    .font-option-xlarge .sample { font-size: 36px; }
    .font-option-xlarge .label { font-size: 24px; }

    /* ===== STEP 3b: PASSCODE OPT-IN ===== */
    .passcode-choice {
      width: 100%;
      display: flex;
      flex-direction: column;
      gap: 24px;
      margin-top: 20px;
    }

    .choice-btn {
      width: 100%;
      background: rgba(245, 240, 232, 0.4);
      border: 3px solid rgba(154,123,79, 0.3);
      border-radius: 16px;
      padding: 18px 28px;
      min-height: var(--touch-min);
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: all 0.2s ease;
      text-align: center;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--header);
      color: var(--ink);
    }

    .choice-btn:active {
      background: rgba(245, 240, 232, 0.7);
      border-color: var(--gold);
      transform: scale(0.98);
    }

    .choice-btn .choice-desc {
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      margin-top: 8px;
    }

    /* ===== STEP 4: NAME + STEP 5: PASSWORD - shared input styles ===== */
    .input-display {
      width: 100%;
      background: var(--cream) var(--paper) repeat;
      border: 3px solid rgba(154,123,79, 0.4);
      border-radius: 12px;
      padding: 20px 24px;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      text-align: center;
      min-height: 80px;
      margin-bottom: 8px;
      letter-spacing: 0.02em;
    }

    .input-display.password-dots {
      letter-spacing: 0.3em;
    }

    .input-hint {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
      margin-bottom: 16px;
    }

    /* On-screen keyboard */
    .keyboard {
      width: 100%;
      display: flex;
      flex-direction: column;
      gap: 6px;
      margin-top: auto;
    }

    /* ============================================================
       HARD RULE — KEYBOARDS ALWAYS FIT SCREEN WIDTH. NO EXCEPTIONS.
       ============================================================
       Triggered 2026-05-05 (third recurrence): the WiFi password keyboard
       on the new-Pi UAT walk rendered wider than the 600px viewport. Each
       earlier instance was supposedly fixed; each came back via inline-
       styled keyboards built in JS that didn't pick up the .kb-row CSS
       constraints below.

       Every keyboard the device renders — setup wizard, WiFi password,
       setup-code keypad, email-binding keypad, Messages search, contact
       name, passcode overlay, change-name, change-passcode, edit-field,
       edit-phone, factory-reset confirm, ANY future on-screen keyboard —
       MUST fit `100vw` with no horizontal scroll, at every text size and
       every device-pixel-ratio. The hard cap is enforced by THREE layers:

         1. Container — every keyboard parent inherits the *[id$="Keyboard"]
            and .keyboard rules below: width:100%; max-width:100%;
            box-sizing:border-box; overflow:hidden.
         2. Row — .kb-row caps at 100vw with overflow:hidden.
         3. Key — flex:1 1 0; min-width:0 lets keys shrink below the
            ideal --kb-key-size if the row is narrower than sum-of-ideals.
            On 600px portrait, 10 keys at flex:1 each get ~56px; a 9-key
            row each gets ~64px. NEVER set a hard min-width on a key.

       The wildcard selector below is the catch-all — if a future keyboard
       gets built with inline styles that bypass .kb-row, the id ending
       in "Keyboard" still picks up the constraints. Don't bypass this. */
    [id$="Keyboard"], .keyboard {
      width: 100% !important;
      max-width: 100% !important;
      box-sizing: border-box !important;
      overflow-x: hidden !important;
    }
    [id$="Keyboard"] > div, .keyboard > div, .kb-row {
      max-width: 100% !important;
      box-sizing: border-box !important;
      overflow: hidden !important;
    }
    /* --ku = one uniform key width. 10 columns, nine 4px gaps => 36px.
       Task 0 (equal key sizing): every character key is exactly --ku wide and
       does NOT grow, so a 9- or 8-key row no longer stretches wider than the
       10-key row above it. justify-content:center then centres the shorter
       rows under the top row, exactly like a phone keyboard. Percentages in
       flex-basis resolve against the row's content box, so this stays inside
       the 100vw hard-fit rule at every text size. */
    .kb-row {
      display: flex;
      justify-content: center;
      gap: 4px;
      width: 100%;
      max-width: 100vw;
      padding: 0 4px;
      box-sizing: border-box;
      overflow: hidden;
      --ku: calc((100% - 36px) / 10);
    }

    .kb-key {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--kb-font-size, var(--body-sm));
      color: var(--ink);
      background: var(--cream) var(--paper) repeat;
      border: 2px solid rgba(154,123,79, 0.25);
      border-radius: 10px;
      flex: 0 1 var(--ku, calc((100% - 36px) / 10));
      min-width: 0;
      height: var(--kb-key-size);
      min-height: var(--kb-key-size);
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: background 0.15s;
    }

    .kb-key:active {
      background: rgba(154,123,79, 0.3);
      border-color: var(--gold);
    }

    /* Delete / shift kept at 1.5 key-widths (fixed, no grow) so their word/arrow
       label fits while the row still centres like a phone keyboard. */
    .kb-key.wide {
      flex: 0 1 calc(var(--ku, calc((100% - 36px) / 10)) * 1.5);
      font-size: var(--body-sm);
    }

    .kb-key.space {
      flex: 5 1 0;
    }

    .kb-key.shift {
      flex: 0 1 calc(var(--ku, calc((100% - 36px) / 10)) * 1.5);
      font-size: var(--body-sm);
    }
    .kb-key.shift.on {
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      border-color: var(--gold);
    }

    .kb-key.confirm {
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      border-color: var(--gold);
      flex: 2.5 1 0;
      font-size: var(--body-sm);
      font-weight: 400;
    }

    .kb-key.confirm:active {
      background: var(--gold-light) var(--paper) repeat;
    }

    .kb-key.confirm.disabled {
      background: rgba(154,123,79, 0.3);
      color: var(--mid);
      border-color: rgba(154,123,79, 0.2);
      pointer-events: none;
    }

    /* Keyboard case: keys show uppercase/lowercase to match typed output */
    .keyboard.lowercase .kb-key:not(.shift):not(.wide):not(.space):not(.confirm) {
      text-transform: lowercase;
    }

    /* Number keyboard for password */
    .num-keyboard {
      width: 100%;
      display: flex;
      flex-direction: column;
      gap: 12px;
      margin-top: auto;
      max-width: 360px;
      align-self: center;
    }

    .num-row {
      display: flex;
      justify-content: center;
      gap: 12px;
    }

    .num-key {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      background: var(--cream) var(--paper) repeat;
      border: 2px solid rgba(154,123,79, 0.25);
      border-radius: 12px;
      width: 100px;
      height: 72px;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: background 0.15s;
    }

    .num-key:active {
      background: rgba(154,123,79, 0.3);
      border-color: var(--gold);
    }

    .num-key.action {
      font-size: var(--body);
    }

    .num-key.confirm {
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      border-color: var(--gold);
      font-size: var(--body);
      font-weight: 400;
    }

    .num-key.confirm:active {
      background: var(--gold-light) var(--paper) repeat;
    }

    .num-key.confirm.disabled {
      background: rgba(154,123,79, 0.3);
      color: var(--mid);
      border-color: rgba(154,123,79, 0.2);
      pointer-events: none;
    }

    /* ===== BOTTOM NAV BAR ===== */
    .bottom-nav {
      position: fixed;
      bottom: 0;
      left: 0;
      width: 100%;
      display: flex;
      justify-content: center;
      gap: 0;
      background: var(--cream) var(--paper) repeat;
      border-top: 2px solid rgba(154,123,79, 0.25);
      z-index: 50;
      padding: 0;
    }

    .nav-btn {
      flex: 1;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: calc(var(--body-sm) * 1.2);
      color: var(--mid);
      background: none;
      border: none;
      padding: 12px 5px;
      min-height: calc(var(--touch-comfortable) * 1.2);
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      text-align: center;
      border-right: 1px solid rgba(154,123,79, 0.15);
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 6px;
      line-height: 1.1;
      position: relative;
    }

    .nav-btn .nav-icon { flex: 0 0 auto; }
    .nav-btn .nav-label { flex: 0 0 auto; display: block; }

    .nav-btn:last-child { border-right: none; }

    .menu-trigger {
      font-weight: 500;
      letter-spacing: 0.08em;
      flex: 0 0 60%;
    }
    .bottom-nav .menu-trigger ~ .nav-btn { flex: 0 0 18%; }
    .bottom-nav .menu-trigger + .nav-btn { margin-left: auto; }

    .nav-btn.active-nav {
      color: var(--ink);
      font-weight: 400;
    }

    .nav-btn:active {
      background: rgba(154,123,79, 0.15);
    }

    /* Expanded menu */
    .menu-expanded {
      display: none;
      position: fixed;
      bottom: 110px;
      left: 0;
      width: 100%;
      background: var(--paper-2);
      border-top: 2px solid var(--rule-2);
      z-index: 49;
      flex-direction: column;
      will-change: transform, opacity;
    }

    .menu-expanded.visible {
      display: flex;
    }

    @keyframes menuSlideUp {
      from { transform: translateY(100%); opacity: 0; }
      to { transform: translateY(0); opacity: 1; }
    }

    /* Menu items use mockup-03 hub-card chrome at row scale:
       icon tile on the left (paper-3 background, gold-deep stroke),
       label in the middle, chev on the right. Five-item list rather
       than a hub-of-four because the menu is not a hub - it is the
       canonical entry point to Messages / Recent calls / Contacts /
       Medical / Feedback. P3b polish, 2026-05-25. */
    .menu-item {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      background: none;
      border: none;
      border-bottom: 1px solid var(--rule);
      padding: 18px 20px;
      min-height: var(--touch-comfortable);
      text-align: left;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      position: relative;
      display: flex;
      align-items: center;
      gap: 16px;
      width: 100%;
    }

    /* Keep the 1px bottom border on the last item (height parity with the rows
       above, which carry a 1px separator) but make it invisible - otherwise
       "Give Feedback" renders 1px shorter than the others (2026-06-16, Howard). */
    .menu-item:last-child { border-bottom-color: transparent; }

    .menu-item:active {
      background: rgba(154,123,79, 0.15);
      color: var(--ink);
    }

    .menu-item .menu-icon {
      width: 48px;
      height: 48px;
      border-radius: 12px;
      background: var(--paper-3);
      display: flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
    }
    .menu-item .menu-icon svg {
      width: 26px;
      height: 26px;
      stroke: var(--gold-deep);
      stroke-width: 1.8;
      fill: none;
      stroke-linecap: round;
      stroke-linejoin: round;
    }
    .menu-item .menu-label {
      flex: 1;
      min-width: 0;
    }
    .menu-item .menu-chev {
      width: 24px;
      height: 24px;
      color: var(--ink-3);
      flex-shrink: 0;
    }
    .menu-item .menu-chev svg {
      width: 100%;
      height: 100%;
      stroke: currentColor;
      stroke-width: 2;
      fill: none;
      stroke-linecap: round;
      stroke-linejoin: round;
    }

    .menu-badge {
      margin-left: 12px;
    }

    /* WiFi row in menu */
    .menu-wifi-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 14px 24px;
      border-bottom: 1px solid rgba(154,123,79, 0.15);
      background: rgba(154,123,79, 0.05);
    }

    .menu-wifi-info {
      display: flex;
      align-items: center;
      gap: 8px;
    }

    .menu-wifi-icon {
      font-size: var(--body-sm);
      /* Says whether the Ken is online, so it has to be legible, not pretty. */
      color: var(--gold-icon);
    }

    .menu-wifi-icon.offline { color: var(--mid); }

    .menu-wifi-ssid {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--ink);
    }

    .menu-wifi-strength {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
    }

    /* Quiet style (canonical): a minor "Change" text-link, not a bordered box
       (which read as a small competing CTA). 2026-06-24. */
    .menu-wifi-change {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
      background: transparent;
      border: none;
      text-decoration: underline;
      min-height: 48px;
      padding: 10px 8px;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }

    .menu-wifi-change:active {
      color: var(--ink);
    }

    /* ===== HOME SCREEN ===== */
    .container {
      position: relative;
      width: 100%;
      height: 100%;
      display: none;
      flex-direction: column;
      padding: 32px 24px;
      padding-bottom: 180px;
      z-index: 1;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      /* Hide the desktop scrollbar - contact pagination buttons handle overflow.
         UAT 2026-05-01 caught the scrollbar showing on every home screen even
         when contacts fit (the 180px bottom padding always pushed content past
         the viewport, triggering the scroll affordance). */
      scrollbar-width: none;            /* Firefox */
      -ms-overflow-style: none;          /* IE/Edge legacy */
    }
    .container::-webkit-scrollbar { display: none; } /* WebKit / Blink */

    /* Large scroll buttons - fixed above bottom nav */
    .scroll-btn-large {
      position: fixed;
      left: 24px;
      right: 24px;
      height: 72px;
      background: rgba(154,123,79, 0.15);
      border: 3px solid rgba(154,123,79, 0.4);
      border-radius: 16px;
      color: var(--ink);
      font-family: 'Jost', sans-serif;
      font-size: var(--body-lg);
      font-weight: 500;
      cursor: pointer;
      display: none;
      align-items: center;
      justify-content: center;
      gap: 12px;
      z-index: 40;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }
    #scrollDownBtn {
      bottom: 78px;
      background: var(--warm-white) var(--paper) repeat;
      border-bottom: none;
      border-radius: 16px 16px 0 0;
    }
    #scrollUpBtn {
      bottom: 150px;
      background: var(--warm-white) var(--paper) repeat;
    }
    .scroll-btn-large:active {
      background: rgba(154,123,79, 0.3);
      border-color: var(--gold);
    }

    .top-bar {
      display: flex;
      justify-content: space-between;
      align-items: flex-start;
      margin-bottom: 24px;
    }

    .time-display { text-align: left; }

    .clock {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--clock-size);
      color: var(--ink);
      letter-spacing: 0.02em;
      line-height: 1;
    }

    .date {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--clock-size);
      color: var(--ink);
      letter-spacing: 0.02em;
      line-height: 1;
    }

    .call-section {
      display: flex;
      flex-direction: column;
      align-items: center;
      margin-bottom: 32px;
    }

    .phone-icon {
      width: 100px;
      height: 100px;
      margin-bottom: 12px;
    }

    .prompt {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      letter-spacing: 0.02em;
      text-align: center;
      line-height: 1.5;
      max-width: 480px;
    }

    .prompt-example {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      text-align: center;
      margin-top: 4px;
    }

    .contacts-grid {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 16px;
      flex: 0 0 auto;
      align-content: start;
    }
    /* "More Contacts" page-change scroll animation (Howard 2026-06-23): the next
       4 tiles rise gently into place (~0.8s), reading as a scroll to the next set. */
    @keyframes kenPageSlide { from { transform: translateY(54px); opacity: 0.25; } to { transform: translateY(0); opacity: 1; } }
    .contacts-grid.paging { animation: kenPageSlide 0.8s cubic-bezier(0.16, 1, 0.3, 1); }
    @media (prefers-reduced-motion: reduce) { .contacts-grid.paging { animation: none; } }

    .contact-tile {
      background: rgba(245, 240, 232, 0.4);
      border: 2px solid rgba(154,123,79, 0.3);
      border-radius: 16px;
      padding: 20px;
      display: flex;
      flex-direction: column;
      align-items: center;
      cursor: pointer;
      transition: all 0.2s ease;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      position: relative;
    }
    /* Home tile notification "speech bubble" (Howard 2026-06-23): a numbered
       count of what's waiting from this person (messages / missed calls /
       voicemails / video-messages). Brand GOLD - never red: it must be obvious
       and classy, not alarming or addictive. Tappable to see the breakdown;
       tapping the tile itself opens the contact card. */
    /* Notification + birthday badges (Howard 2026-06-23): both top-RIGHT, pulled in
       from the corner so the tile's rounded corner never clips them, and big (56px)
       so they stand out over a photo. The notification is a gently SWINGING bell;
       the cake has a FLICKERING candle. When a tile shows both, the bell sits to the
       left of the cake (.with-bday). */
    .tile-notif-bubble {
      position: absolute;
      top: 9px;
      right: 13px;
      width: 56px;
      height: 56px;
      box-sizing: border-box;
      border-radius: 999px;
      background: var(--gold);
      border: 2.5px solid var(--warm-white);
      display: flex;
      align-items: center;
      justify-content: center;
      box-shadow: 0 1px 6px rgba(26,23,20,0.28);
      cursor: pointer;
      z-index: 3;
    }
    .tile-notif-bubble.with-bday { right: 73px; }
    .tile-notif-bubble:active { transform: scale(0.96); }
    /* Birthday cake badge - top-RIGHT, the primary corner slot. */
    .tile-birthday-bubble {
      position: absolute;
      top: 9px;
      right: 13px;
      width: 56px;
      height: 56px;
      border-radius: 999px;
      background: var(--gold);
      border: 2.5px solid var(--warm-white);
      display: flex;
      align-items: center;
      justify-content: center;
      box-shadow: 0 1px 6px rgba(26,23,20,0.28);
      z-index: 3;
    }
    /* The swinging bell + flickering candle (Howard 2026-06-23): gentle, on-brand,
       transform/opacity only so it's cheap on the Pi. Disabled under reduced motion.
       The bell pivots from its top (where it would hang); the flame wobbles + dims
       from its base like a real candle, with a warm glow. */
    .notif-bell {
      transform-origin: 50% 12%;
      animation: bell-swing 2s ease-in-out infinite;
    }
    @keyframes bell-swing {
      0%, 100% { transform: rotate(-5deg); }
      50%      { transform: rotate(5deg); }
    }
    .candle-flame {
      transform-box: fill-box;
      transform-origin: 50% 100%;
      filter: drop-shadow(0 0 1.2px rgba(255,176,74,0.9));
      animation: candle-flicker 1.5s ease-in-out infinite;
    }
    @keyframes candle-flicker {
      0%   { transform: scale(1, 1) translateY(0); opacity: 1; }
      18%  { transform: scale(1.15, 0.88) translate(0.35px, 0); opacity: 0.9; }
      37%  { transform: scale(0.9, 1.12) translate(-0.3px, -0.2px); opacity: 1; }
      55%  { transform: scale(1.1, 0.93) translate(0.25px, 0); opacity: 0.85; }
      78%  { transform: scale(0.95, 1.07) translate(-0.3px, -0.1px); opacity: 1; }
    }
    @media (prefers-reduced-motion: reduce) {
      .notif-bell, .candle-flame { animation: none; }
    }
    /* Home birthday prompt - warm, celebratory, brand gold. A positive nudge to
       send a video message, never an alarm. Tappable to open that contact. */
    /* On-brand birthday celebration on the contact card (Howard 2026-06-23): shown
       when the contact's birthday is within the reminder window. The card already
       carries Call (video/voice) + Video Voicemail below, so this is the warm nudge. */
    .card-bday-celebrate {
      background: linear-gradient(180deg, rgba(154,123,79,0.16), rgba(154,123,79,0.09));
      border: 1.5px solid rgba(154,123,79,0.5);
      border-radius: 16px;
    }
    .card-bday-cake { color: var(--gold-icon); display: flex; justify-content: center; margin-bottom: 4px; }
    .card-bday-when { font-family: 'Cormorant Garamond', serif; font-size: var(--body-lg); color: var(--ink); font-weight: 600; line-height: 1.15; } /* 600 not 500: 500 renders as 400 (fonts-local.css) */
    .card-bday-sub { font-family: 'Jost', sans-serif; font-size: var(--body-sm); color: var(--mid); margin-top: 6px; line-height: 1.4; }

    .contact-tile:active {
      background: rgba(245, 240, 232, 0.7);
      border-color: var(--gold);
      transform: scale(0.98);
    }

    .contact-photo {
      width: 160px;
      height: 160px;
      border-radius: 50%;
      border: 3px solid var(--cream);
      background: var(--cream) var(--paper) repeat;
      margin-bottom: 16px;
      overflow: hidden;
      box-shadow: 0 2px 8px rgba(26, 23, 20, 0.08);
    }

    .contact-photo img { width: 100%; height: 100%; object-fit: cover; }

    .contact-photo-fallback {
      width: 100%;
      height: 100%;
      background: linear-gradient(135deg, var(--cream), var(--gold-light));
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 64px;
      color: var(--mid);
    }

    /* The Ken user's OWN face, shown back to them on the "You're all set"
       screen and in the photo journey (Howard 2026-07-31). Deliberately the
       same round tile as .contact-photo and it carries the SAME
       .contact-photo-fallback inside, so when they remove their photo the
       first letter of their name appears exactly as it does on a contact
       tile - one component, one look, no second implementation to drift. */
    .ken-own-photo {
      width: 132px;
      height: 132px;
      border-radius: 50%;
      border: 3px solid var(--gold);
      background: var(--cream) var(--paper) repeat;
      margin-bottom: 16px;
      overflow: hidden;
      box-shadow: 0 2px 8px rgba(26, 23, 20, 0.08);
    }

    .ken-own-photo img { width: 100%; height: 100%; object-fit: cover; }
    .ken-own-photo .contact-photo-fallback { font-size: 58px; }

    .contact-name {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--contact-name);
      color: var(--ink);
      text-align: center;
      line-height: 1.2;
      /* A long full name ("Sarah Bennett") must wrap inside the tile, not
         spill past its edge, at any text size (2026-06-04 field-test). */
      max-width: 100%;
      overflow-wrap: break-word;
      word-break: break-word;
      hyphens: auto;
    }

    /* Relationship label under the name on a home tile (italic, secondary). Moved
       from an inline style to a class so the fixed-height home tile can cap it -
       see the home-fit override below. */
    .contact-relationship {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-style: italic;
      font-size: var(--body-sm);
      color: var(--mid);
      text-align: center;
      margin-top: 2px;
    }

    /* ===== EXAMPLE / SPARSE-HOME CONTACT SLOTS ===== */
    /* Sparse mode: 0-6 contacts render a fixed 6-up grid (real tiles + example
       "tap to add" tiles). Compact the tiles so all six fit without scrolling. */
    .contactsGrid.home-sparse,
    .contacts-grid.home-sparse {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 12px;
    }

    .home-sparse .contact-tile,
    .home-sparse .example-tile { padding: 12px; }

    .home-sparse .contact-photo,
    .home-sparse .example-photo {
      width: 72px;
      height: 72px;
      margin-bottom: 8px;
    }

    /* ===== Fit-the-screen home (2026-06-16, Howard) =====
       The home grid lives in a scrolling container (.container, overflow-y:auto,
       padding-bottom:180px), so six tall tiles overflowed and the bottom row
       clipped behind the fixed nav. In .home-fit the home is bounded to the
       viewport above the nav and the 2-col grid divides the remaining height into
       three equal rows, so all six always sit on screen. #homeFooterBand holds the
       secondary affordance below the grid and above the nav (the portal line +
       mini-QR for <=6, the More button for >6). Robust to screen height and
       contact count: the rows fill whatever height is left. */
    #homeScreen.home-fit {
      box-sizing: border-box;
      height: 100vh;
      overflow: hidden;
      padding-bottom: var(--home-nav-reserve, 92px);
    }
    /* When the "More Contacts" tab is shown (>4 contacts), reserve extra space
       below the grid so the bottom tile row clears the raised tab. 2026-07-23
       (Howard): +34px left the bottom tiles TOUCHING the tab - the tab's top
       edge sits at nav-reserve - 13px + 63px tall = nav-reserve + 50px, so the
       clearance must be 50px + the grid's own 12px row gap = 62px, making the
       tile-to-tab gap identical to the tile-to-tile gap. */
    #homeScreen.home-fit.has-more {
      padding-bottom: calc(var(--home-nav-reserve, 92px) + 62px);
    }
    #homeScreen.home-fit .contacts-grid.home-sparse {
      flex: 1 1 auto;
      min-height: 0;
      grid-template-columns: repeat(2, 1fr);
      grid-template-rows: repeat(2, 1fr);
      /* SQUARE tiles, centred (Howard 2026-06-25). The grid cells are wide (two
         columns of a 1024px screen), but each tile is a SQUARE sized to the row
         height and centred in its cell - via tile height:100% + aspect-ratio:1
         below. The square tracks whatever vertical space is left after the
         masthead/greeting/footer, so it adapts to the real screen instead of
         stretching into a wide rectangle (the old align-content:stretch did). */
      justify-items: center;
      align-items: center;
      gap: 12px;
      margin-top: 4px;
    }
    #homeScreen.home-fit .home-sparse .contact-tile,
    #homeScreen.home-fit .home-sparse .example-tile {
      aspect-ratio: 5 / 4;   /* slightly wider than square (Howard 2026-06-25) - fills the row better */
      height: 100%;
      width: auto;
      max-width: 100%;
      min-height: 0;
      overflow: hidden;
      padding: 8px;
      justify-content: center;
    }
    /* The FACE is the primary recognition cue for low-vision users (Howard
       2026-07-21: "far more important than the name"). The real-contact photo now
       FILLS the tile's spare height as a circle instead of a fixed 116px, so it
       renders roughly double in the common 4-tile home and shrinks gracefully when
       the tile is smaller (Get-help button shown, the >4-contacts More band, or
       xlarge text). Name + relationship below stay fixed-height, so they never
       clip. Width is capped at the tile width and the circle stays round via
       aspect-ratio. */
    #homeScreen.home-fit .home-sparse .contact-photo {
      flex: 1 1 0;         /* basis 0 so grow drives the height - fills the tile's spare room */
      width: auto;
      height: auto;
      aspect-ratio: 1 / 1;
      max-width: 100%;
      /* THE FACE YIELDS, BUT IT NEVER DISAPPEARS (7 Aug 2026).
         This was min-height:0 - "the FACE yields first; the name never clips" -
         which held on the four-tile home where a tile is 195px tall. On the paged
         view with eight contacts the tiles are far shorter, so the face yielded
         all the way: photographed on K66389, it was an 8px gold dot above the
         name. Big faces instead of names is the entire premise of this product,
         and the screen had quietly inverted it.
         72px is the floor at which a face still reads as a person across a room.
         Below that the RELATIONSHIP gives up its line instead (rule after this
         one) - it is the least load-bearing thing on the tile. */
      min-height: 72px;
      margin-bottom: 14px;
      align-self: center;
      container-type: inline-size;   /* so the fallback monogram scales with the circle */
    }
    /* EMPTY example tiles carry an extra "Tap to add" line that the real contact
       tiles do NOT, so at the 116px face + 2-line 27px name the three items
       overflowed the fixed-height tile and the relationship label ("Brother",
       "Daughter"...) was clipped behind "Tap to add" (Howard 2026-07-18, "such a
       stupid mistake"). Give the placeholder a smaller face so face + name + add
       all fit inside the tile. The face only holds a 46px person outline, so 88px
       is ample. */
    #homeScreen.home-fit .home-sparse .example-photo {
      width: 88px;
      height: 88px;
      margin-bottom: 6px;
      /* ADAPTIVE (Howard 2026-07-19): the earlier 88px fix only held for the short
         "Good morning" greeting; "Good afternoon"/"Good evening" wrap more, shrink
         the tile, and re-clipped the label. So the placeholder FACE gives up space
         first (it is just a generic outline), guaranteeing the relationship label +
         "Tap to add" always show whatever the greeting does to the tile height. */
      flex-shrink: 1;
      min-height: 0;   /* the FACE absorbs ALL the squeeze - down to nothing if it must */
    }
    #homeScreen.home-fit .home-sparse .example-photo svg { max-width: 100%; max-height: 100%; }
    /* The label + "Tap to add" NEVER shrink and NEVER clip - they are the meaning of
       the tile; the placeholder face above gives up its space instead. The label is
       a single line (relationship words are short). This holds at any greeting length
       (Howard 2026-07-19: "Good afternoon" wraps more than "Good morning" and shrank
       the tile until the label re-clipped - the face now yields, the text does not). */
    #homeScreen.home-fit .home-sparse .example-name {
      -webkit-line-clamp: 1;
      flex-shrink: 0;
      margin-bottom: 0;
    }
    #homeScreen.home-fit .home-sparse .example-add { flex-shrink: 0; }
    /* The monogram letter scales WITH the now-variable circle (Howard 2026-07-16
       "sensibly fill that box"): 56% of the circle's width keeps the serif capital
       reading as a portrait, not a stamp, at every tile height. Placeholder faces
       stay a fixed size. */
    #homeScreen.home-fit .home-sparse .contact-photo-fallback {
      font-size: 56cqw;
    }
    #homeScreen.home-fit .home-sparse .example-photo .contact-photo-fallback {
      font-size: 56px;
    }
    /* A very long name (e.g. a double-barrelled surname) wraps to 3+ lines and, in
       a height-bounded tile, would squeeze the photo into an ellipse. Clamp the name
       to two lines so the face stays round; the tile's overflow:hidden trims any
       tail. (2026-06-16 UAT P-7.) */
    #homeScreen.home-fit .home-sparse .contact-name,
    #homeScreen.home-fit .home-sparse .example-name {
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
      /* 24px (was 27) so the relationship label + "Tap to add" both fit under the
         placeholder face without clipping - the empty tile has one more line than a
         real tile (Howard 2026-07-18). Still above the 22px older-eyes floor. */
      font-size: 24px;
      font-weight: 500;
      line-height: 1.15;
      margin-bottom: 2px;
      flex-shrink: 0;   /* the face yields its height, the name never does */
    }
    /* The relationship label is the SAME clip class as the name: an uncapped
       var(--body-sm) line balloons to 30px at xlarge and can wrap to two lines,
       clipping behind the fixed-height tile's overflow:hidden (Howard's home-fix
       capped the name + empty-tile face but not this). Cap it to a single 22px
       line (the older-eyes floor) with an ellipsis so face + name + relationship
       always fit. 2026-07-18 clip-class audit. */
    #homeScreen.home-fit .home-sparse .contact-relationship {
      font-size: 22px;
      line-height: 1.15;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      max-width: 100%;
      flex-shrink: 0;   /* the face yields its height, the label never does */
    }
    /* >4 contacts: the More band takes a little height, so tiles shrink a touch.
       The real face still flex-fills its (smaller) tile and, like the base case,
       yields its height so the name + relationship never clip - even at xlarge text,
       where the enlarged greeting leaves the shortest tiles. Only the placeholder is
       a fixed size. */
    #homeScreen.home-fit.has-more .home-sparse .example-photo {
      width: 98px;
      height: 98px;
    }
    /* The paged >4 view is more compact than the 4-primary home, so the name was
       capped to a single line here, on the reasoning that "the ellipsis handles a
       long name".
       It does not. Measured on K66389 with eight contacts, 6 Aug 2026:
       "Dr Aleksandra Wojciechowska" rendered as "Dr...". Three characters and a
       full stop is not a name, and on a tile with no photo set the name is the
       only thing identifying who you are about to ring. An ellipsis tells you
       something has been cut; it does not tell you what.
       Two lines, the same as the unpaged home. The face flex-fills whatever
       height is left and yields it, so a second line costs the photo about 27px
       and costs the person nothing. */
    #homeScreen.home-fit.has-more .home-sparse .contact-name {
      -webkit-line-clamp: 2;
    }
    /* The paged view is the tightest tile on the device, and now the face has a
       floor something else has to give. It is this: on a tile you reached by
       paging, you are looking for a face and a name. "Granddaughter" was being
       clipped to "Granddaughte" anyway (photographed on K66389, 7 Aug 2026), so
       it was already failing to do its job while costing the face its height.
       The relationship stays on the four-tile home, where there is room for it. */
    #homeScreen.home-fit.has-more .home-sparse .contact-relationship {
      display: none;
    }
    #homeFooterBand {
      flex: 0 0 auto;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      padding: 8px 0 2px;
      color: #6B6459;
      font-family: 'Jost', sans-serif;
      /* 2026-07-17: was 14px - the smallest user-facing text on the device, on the
         HOME screen, telling a first-time user where to add contacts
         ("or add at theken.uk/portal/K#####"). 14px grey on cream is exactly the
         "thin/hard to read" complaint. The More/Back-to-top button inside this
         band sets its own label size (.more-tab-label), so this only governs
         that portal line. */
      font-size: var(--body-sm);
    }
    #homeFooterBand:empty { display: none; }
    /* "More Contacts" bar (Howard 2026-06-23): slim, central ~60% of the screen,
       ~1/3 the height of the nav bar (nav min-height = touch-comfortable * 1.2,
       so 0.4x is about a third). Sits in #homeFooterBand (flex-centred), where
       the first-setup QR used to be. Pages through the remaining contacts. */
    /* "More Contacts" as a stylised extension of the bottom nav bar (Howard
       2026-06-23): a raised central tab sitting on the nav's top edge, covering
       ~56% of the width. Same cream/paper + gold border as the nav, flat bottom
       (no border) so it reads as part of the bar. Fixed-positioned so it leaves
       the grid flow - the grid reclaims that height. */
    /* "More Contacts" tab (Howard 2026-06-23 sketch): a gold outline that rises
       from the nav bar's top edge with concave shoulders into a flat plateau,
       then back down - a raised extension of the bar, not a separate button.
       Drawn as an inline SVG (CSS corners can't do concave curves). The cream
       fill overlaps ~3px onto the bar so it covers the nav's straight top border
       under the tab, leaving only the curved rise. Fixed so it leaves the grid flow. */
    #homeMoreBtn {
      position: fixed;
      left: 50%;
      transform: translateX(-50%);
      bottom: calc(var(--home-nav-reserve, 96px) - 13px);
      z-index: 51;
      box-sizing: border-box;
      width: 64%;
      height: 63px;
      background: none;
      border: none;
      padding: 0;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    #homeMoreBtn .more-tab-svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
    #homeMoreBtn .more-tab-fill { fill: var(--cream); }
    #homeMoreBtn .more-tab-line {
      fill: none;
      stroke: var(--gold);
      stroke-width: 2;
      vector-effect: non-scaling-stroke;
      stroke-linejoin: round;
      stroke-linecap: round;
    }
    #homeMoreBtn .more-tab-label {
      position: absolute;
      inset: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body);
      color: var(--ink);
    }
    #homeMoreBtn:active {
      background: rgba(154, 123, 79, 0.2);
      border-color: var(--gold);
      transform: scale(0.98);
    }

    /* Shown ABOVE the add-a-contact grid while the Ken cannot yet read its own
       contacts - never instead of it. It used to replace the grid, which took
       the four contact tiles off the home screen (Howard, 16 Aug 2026: they have
       been the home screen since June and that was not up for changing). It
       spans the full width because it is a sentence, not a tile, and because a
       sentence sitting in one of four cells reads as a broken tile. Body size,
       ink on the page, no border and no icon: nothing here is tappable and
       nothing here is a fault. Padding is tight because it now shares the screen
       with the grid rather than owning it - 32px of it pushed the fourth tile
       off a 600x1024 display. */
    .contacts-still-loading {
      /* The home grid is laid out differently on the sparse and full home
         screens, so this claims the full width three ways rather than betting on
         which container it lands in. A sentence wrapped into one 160px cell is
         unreadable for the person this is written for. */
      /* flex: 0 0 auto, NOT flex-basis: 100%. It used to live inside the grid,
         where 100% meant the row's width; the home screen is a COLUMN flex, so
         as a child of that the same declaration asked for 100% of the screen
         HEIGHT and crushed the four tiles from 251px to 42px. Measured at
         600x1024, not inferred. */
      flex: 0 0 auto;
      width: 100%;
      box-sizing: border-box;
      align-self: center;
      text-align: center;
      padding: 0 16px 14px;
      font-size: var(--body);
      line-height: 1.4;
      color: var(--ink);
    }

    .example-tile {
      background: rgba(154, 123, 79, 0.05);
      border: 2px dashed #CBBDA3;
      border-radius: 16px;
      display: flex;
      flex-direction: column;
      align-items: center;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: all 0.2s ease;
    }

    .example-tile:active {
      transform: scale(0.98);
      border-color: var(--gold);
    }

    .example-photo {
      width: 104px;
      height: 104px;
      border-radius: 50%;
      border: 2px dashed #CBBDA3;
      background: #F5F0E8 var(--paper) repeat;
      display: flex;
      align-items: center;
      justify-content: center;
      position: relative;
      margin-bottom: 10px;
    }

    .example-plus {
      position: absolute;
      right: 0;
      bottom: 0;
      width: 30px;
      height: 30px;
      border-radius: 50%;
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 20px;
      line-height: 1;
      border: 3px solid var(--warm-white, #FDFAF5);
      font-family: 'Jost', sans-serif;
    }

    .example-name {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--contact-name);
      color: var(--ink-3, #6B6459);
      text-align: center;
      line-height: 1.2;
    }

    .example-add {
      font-family: 'Jost', sans-serif;
      font-size: var(--body-sm, 22px);
      color: var(--mid);
      text-align: center;
      margin-top: 2px;
    }

    .example-qr-mini {
      width: 36px;
      height: 36px;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      vertical-align: middle;
      margin-left: 8px;
      cursor: pointer;
    }

    .example-qr-mini svg { width: 36px; height: 36px; }

    .loading, .error {
      grid-column: 1 / -1;
      text-align: center;
      padding: 40px;
      color: var(--mid);
      font-size: var(--body);
    }

    /* ===== CALLING SCREEN ===== */
    .calling-screen {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 100;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }
    .calling-screen.active { display: flex; }

    .calling-name {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 400;
      font-size: var(--header-lg);
      color: var(--ink);
      margin-bottom: 16px;
    }

    .calling-status {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--mid);
      margin-bottom: 60px;
    }

    .calling-icon {
      width: 120px;
      height: 120px;
      margin-bottom: 60px;
      animation: pulse 1.5s ease-in-out infinite;
    }

    @keyframes pulse {
      0%, 100% { transform: scale(1); opacity: 1; }
      50% { transform: scale(1.08); opacity: 0.7; }
    }

    .calling-cancel {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--btn);
      color: var(--mid);
      background: var(--cream) var(--paper) repeat;
      border: 2px solid rgba(154,123,79, 0.3);
      border-radius: 12px;
      padding: 16px 32px;
      min-height: var(--touch-min);
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      min-width: 0;
      text-align: center;
    }

    .calling-cancel:active {
      background: rgba(154,123,79, 0.2);
      border-color: var(--gold);
    }

    /* ===== VIDEO CALL SCREEN ===== */
    .call-screen {
      display: none;
      position: fixed;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      background: #000;
      z-index: 200;
      flex-direction: column;
    }
    .call-screen.active { display: flex; }

    .video-container { flex: 1; position: relative; background: #000; overflow: hidden; }
    .remote-video { position: absolute; inset: 0; overflow: hidden; }
    .remote-video video { width: 100%; height: 100%; object-fit: cover; }

    /* ===== Group call (multi-tile SFU) - 2026-06-25 ===== */
    .group-video-grid { flex: 1; display: grid; gap: 6px; padding: 6px; background: #000; overflow: hidden; align-content: stretch; }
    .group-video-tile { position: relative; background: #111; border-radius: 10px; overflow: hidden; min-width: 0; min-height: 0; }
    .group-video-tile video { width: 100%; height: 100%; object-fit: cover; }
    .group-video-tile.self video { transform: scaleX(-1); } /* mirror own preview, as on the 1:1 screen */
    /* Name label sits TOP-left of each tile, not bottom - the bottom-row names
       used to hide under the video/mic/End-Call bar (Howard 2026-07-16). */
    .group-video-tile .gv-name { position: absolute; top: 4px; left: 6px; font-family: 'Jost', sans-serif; font-size: var(--body-sm); font-weight: 500; color: #FDFAF5; background: rgba(0,0,0,0.5); padding: 2px 8px; border-radius: 8px; max-width: 90%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .contact-tile.group-tile .contact-photo-fallback { background: linear-gradient(135deg, #C4A962, #C4A962); color: var(--ink); }

    /* 1:1 / K2K (<=2 people) on a PORTRAIT screen: the remote person FILLS the
       screen and the self preview is a SQUARE at the bottom-LEFT (2/5 screen
       width), never the thin left/right split the 2-col grid produced. 3+
       participants keep the grid. Controls sit OVER the video: video+mic bottom
       -LEFT (spanning the self-square width), End Call bottom-RIGHT - so End Call
       never overlays the toggles. Scoped to #groupCallScreen; the native 1:1
       screen is untouched. Howard 2026-07-16. */
    .group-video-grid.two-up { display: block; position: relative; padding: 0; gap: 0; }
    .group-video-grid.two-up > .group-video-tile { position: absolute; inset: 0; border-radius: 0; }
    .group-video-grid.two-up > .group-video-tile.self {
      inset: auto; left: 16px; bottom: 96px;               /* bottom-left, lifted clear of the centre controls (never touching) */
      width: 36vw; height: 36vw; max-width: 234px; max-height: 234px;   /* 10% smaller (was 40vw) */
      border-radius: 16px; border: 2px solid var(--gold); z-index: 6;
    }
    #groupCallScreen .call-controls { position: absolute; inset: auto 0 0 0; background: transparent; padding: 0; pointer-events: none; }
    /* video + mic in the CENTRE of the bottom; End Call stays bottom-right; self
       square sits bottom-left. Buttons are End-Call height (64px) with LARGER
       icons inside. Howard 2026-07-16. */
    #groupCallScreen .call-controls-left { position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%); display: flex; gap: 20px; pointer-events: auto; }
    #groupCallScreen #groupVideoBtn, #groupCallScreen #groupMuteBtn { width: 64px !important; height: 64px !important; }
    #groupCallScreen #groupVideoBtn svg, #groupCallScreen #groupMuteBtn svg { width: 36px !important; height: 36px !important; }
    #groupCallScreen .end-call-btn { position: absolute; right: 16px; bottom: 24px; pointer-events: auto; }

    /* 3+ participants: equal-size boxes, but an INCOMPLETE row is centre-justified
       and sits on TOP (flex + per-tile order + a full-width break), so 3 people =
       one box centred on top and two below - never a left-aligned tile next to an
       empty black cell. --cols/--rows are set per-call by repositionGroupTiles.
       Howard 2026-07-16. */
    .group-video-grid.grid-mode { display: flex; flex-wrap: wrap; justify-content: center; align-content: center; gap: 6px; padding: 6px; }
    .group-video-grid.grid-mode > .group-video-tile {
      flex: 0 0 calc((100% - (var(--cols) - 1) * 6px) / var(--cols));
      height: calc((100% - (var(--rows) - 1) * 6px) / var(--rows));
    }
    .group-video-grid.grid-mode > .grid-break { flex-basis: 100%; height: 0; margin: 0; }

    .local-video {
      position: absolute;
      bottom: 120px;
      right: 16px;
      width: 150px;
      height: 200px;
      border-radius: 12px;
      border: 2px solid var(--gold);
      overflow: hidden;
      background: #1a1714;
    }
    .local-video video { width: 100%; height: 100%; object-fit: cover; transform: scaleX(-1); }

    .call-controls {
      display: flex;
      justify-content: center;
      padding: 20px;
      background: rgba(0, 0, 0, 0.85);
    }

    .end-call-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--btn);
      /* Dark text on the red per Howard's deuteranopia rule (never white on #D94A3D). 2026-06-27. */
      color: #252322;
      background: #D94A3D;
      border: none;
      border-radius: 14px;
      padding: 14px 28px;
      min-height: 64px;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      text-align: center;
    }
    .end-call-btn:active { background: #A8432D; transform: scale(0.97); }

    /* ===== SUBTITLES ON A CALL (2026-08-11) =====================
       One set of rules for every call screen: the markup is built from one
       template by kenInstallCaptionSurfaces(), because two hand-written copies
       is how the group call screen ended up with no subtitles at all while its
       own comment claimed parity with the 1:1 screen.

       THE BUTTON. It used to be the letters "CC" in a 40%-black circle with a
       half-transparent white ring, sitting bottom-right over dark video. Judged
       as somebody in their eighties looking at a lit screen from a metre back,
       that is not a control, it is camouflage - and "CC" is a broadcast-
       television code nobody has to know. It is now a solid pill with a light
       border, carrying a glyph AND the word, standing clear of the call
       controls where there is room for a real label.

       THE SIZE. --caption-size is its OWN setting with its own default (44px),
       deliberately larger than the interface's --body-lg (28px at the standard
       text size), because on a call the person is sitting further back than
       when they are tapping the screen. It is written by applyCaptionSize()
       from the single stored value; nothing else sets it. */
    .caption-mount { position: absolute; inset: 0; pointer-events: none; z-index: 12; }

    :root { --caption-size: 44px; --call-controls-h: 104px; }

    .captions-btn {
      position: absolute;
      left: 16px;
      bottom: calc(var(--call-controls-h) + 16px);
      pointer-events: auto;
      display: flex;
      align-items: center;
      gap: 12px;
      min-height: var(--touch-comfortable);
      padding: 0 22px 0 18px;
      border-radius: 40px;
      border: 2px solid #FDFAF5;
      background: #2A2622;
      color: #FDFAF5;
      font-family: 'Jost', sans-serif;
      font-size: var(--body);
      font-weight: 500;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    /* On = gold border and a gold-tinted body, PLUS the label changes wording,
       so the state never rests on colour alone (deuteranopia rule). */
    .captions-btn.on { border-color: #C4A962; background: #4F453A; }
    .captions-btn:active { transform: scale(0.98); }
    .captions-btn-label { white-space: nowrap; }

    /* The self-view normally sits at bottom:120px, which is inside the band the
       subtitles occupy. While they are running it moves clear. */
    .call-screen.captions-on .local-video { bottom: 330px; }

    .caption-overlay {
      position: absolute;
      left: 12px;
      right: 12px;
      bottom: calc(var(--call-controls-h) + 16px + var(--touch-comfortable) + 14px);
      text-align: center;
      pointer-events: none;
      display: none;
    }
    .caption-status {
      display: none;
      max-width: 96%;
      margin: 0 auto 8px;
      font-family: 'Jost', sans-serif;
      font-size: var(--body);
      font-weight: 400;
      color: #FDFAF5;
      background: rgba(0, 0, 0, 0.86);
      padding: 8px 20px;
      border-radius: 16px;
      line-height: 1.3;
    }
    .caption-text {
      display: inline-block;
      max-width: 100%;
      font-family: 'Jost', sans-serif;
      font-size: var(--caption-size);
      font-weight: 500;
      color: #FDFAF5;
      /* 0.86 rather than 0.7: the words sit over whatever the caller's room
         happens to look like, and a bright window behind them used to wash the
         line out completely. */
      background: rgba(0, 0, 0, 0.86);
      padding: 10px 20px;
      border-radius: 16px;
      line-height: 1.25;
    }

    /* The live preview in Settings. Deliberately the SAME treatment as the real
       thing on a call - cream on near-black, same weight, same rounding - so
       what the person sees while choosing is what they get. Its font-size is
       written by renderCaptionSizeControls() from the one stored value. */
    .caption-preview {
      margin-top: 10px;
      padding: 12px 18px;
      border-radius: 16px;
      background: rgba(0, 0, 0, 0.86);
      color: #FDFAF5;
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      line-height: 1.25;
      text-align: center;
      min-height: 1.25em;
    }

    .connection-status {
      display: none;
      position: absolute;
      top: 40px;
      left: 50%;
      transform: translateX(-50%);
      font-family: 'Jost', sans-serif;
      font-size: var(--body);
      color: var(--warm-white);
      background: rgba(0, 0, 0, 0.6);
      padding: 12px 32px;
      border-radius: 24px;
    }
    .connection-status.visible { display: block; }

    .call-timer {
      position: absolute;
      top: 16px;
      right: 16px;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: rgba(255, 255, 255, 0.7);
      background: rgba(0, 0, 0, 0.4);
      padding: 6px 16px;
      border-radius: 16px;
    }

    .waiting-message {
      position: absolute;
      inset: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      color: var(--warm-white);
    }
    .waiting-message .name {
      font-family: 'Cormorant Garamond', serif;
      font-size: var(--header-lg);
      margin-bottom: 12px;
    }
    .waiting-message .status {
      font-family: 'Jost', sans-serif;
      font-size: var(--body-lg);
      font-weight: 400;
      color: rgba(255, 255, 255, 0.7);
    }

    .waiting-dots::after {
      content: '';
      animation: dots 1.5s steps(4, end) infinite;
    }
    @keyframes dots {
      0% { content: ''; }
      25% { content: '.'; }
      50% { content: '..'; }
      75% { content: '...'; }
    }

    /* ===== SETTINGS SCREEN ===== */
    .settings-screen {
      display: none;
      position: fixed;
      top: 0; left: 0;
      width: 100%;
      height: 100%;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 400;
      flex-direction: column;
      overflow-y: auto;
      font-family: 'Jost', sans-serif;
      font-size: var(--body-sm);
      font-weight: 400;
      -webkit-overflow-scrolling: touch;
    }
    .settings-screen.active { display: flex; }

    /* 2026-05-06 vertical kerning: extra bottom padding gives the header
       its own breathing room before the first .settings-section card.
       Combined with .settings-section { margin: 12px 16px } the first
       card now sits ~36px clear of the header rule. */
    /* 2026-07-23 (Howard): compacted from 24px vertical - two words were
       taking ~127px of a 1024px screen at the largest text size. Tightened
       again same day (8px) so the 5-card settings hub fits one page. */
    .settings-header {
      display: flex;
      align-items: center;
      position: relative;
      padding: 8px 24px;
      border-bottom: 2px solid var(--cream);
      margin-bottom: 10px;
    }

    /* The brand mark at the right edge of the Settings header, sized and placed
       to match .sub-page-brand-mark on every page beneath it, so the top of the
       screen holds still as the user moves in and out of the sub-pages. */
    .settings-header-mark {
      position: absolute;
      right: 24px;
      top: 50%;
      transform: translateY(-50%);
      height: calc(var(--header) * 1.55);
      width: auto;
      display: block;
    }

    /* Hub page marker ("1 / 2"). Removed from the Settings header 4 Aug 2026
       (the flip tab names the page in words); the rule stays for any other
       paginated hub that still uses the class. */
    .settings-page-no {
      position: absolute;
      right: 24px;
      top: 50%;
      transform: translateY(-50%);
      font-family: 'Jost', sans-serif;
      font-size: var(--body-sm);
      color: var(--mid);
    }

    /* Canonical back button - see device/UI_BUTTON_CRIBSHEET.md. ONE look
       everywhere: "← Back" text-link, top-left, low-emphasis. Was a cream-filled
       bordered box, which made Settings backs look unlike the wizard's grey
       "← Back" (Howard 2026-06-23). Now visually identical to .setup-back; stays
       in-flow because it lives in a .settings-header flex row beside the title. */
    .settings-back {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      background: none;
      border: none;
      text-decoration: underline;
      cursor: pointer;
      padding: 12px 16px;
      min-height: var(--touch-min);
      display: flex;
      align-items: center;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }

    .settings-back:active { color: var(--ink); }

    .settings-heading {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 400;
      font-size: var(--header);
      color: var(--ink);
      flex: 1;
      text-align: center;
      padding-right: 60px;
      overflow-wrap: break-word;
      word-break: break-word;
      min-width: 0;
    }

    .settings-section {
      padding: 20px 24px;
      margin: 12px 16px;
      border: 2px solid rgba(154,123,79, 0.5);
      border-radius: 20px;
      background: var(--warm-white) var(--paper) repeat;
    }

    /* 2026-06-12: section titles unified to the Cormorant serif heading
       language (same family as .settings-heading / .sub-page-title) at one
       step below the 32px page title, so a sub-view reads page-title >
       section-title > label > help. Was Jost 600 32px gold, which sat at the
       SAME size as the page title and read as heavy/unstyled next to the
       serif headings above it (Howard, Privacy section). gold-deep lifts
       contrast on warm-white for older eyes. */
    .settings-section-title {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 600;
      font-size: var(--section-title, 31px);
      color: var(--ink);
      letter-spacing: 0.01em;
      padding-bottom: 14px;
      margin-bottom: 18px;
      border-bottom: 2px solid var(--rule-2);
    }

    /* Section-header hairline for a title that shares its row with a control
       (toggle / scan button): the rule must span the whole row, not just the
       title text. Pairs with the .settings-section-title size bump above.
       Rule: "section headers must out-weigh their body" (CLAUDE.md §9). */
    .settings-title-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding-bottom: 14px;
      margin-bottom: 18px;
      border-bottom: 2px solid var(--rule-2);
    }
    .settings-title-row .settings-section-title {
      margin-bottom: 0;
      padding-bottom: 0;
      border-bottom: none;
    }

    .settings-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 20px;
      padding: 16px 0;
      border-bottom: 2px solid rgba(154,123,79, 0.2);
    }

    .settings-row:last-child { border-bottom: none; }

    /* A row whose label is a sentence rather than a word. The default row lets
       the label shrink so the control can sit beside it, which is right for
       "Keyboard" and wrong for "Go back to the home screen after": that label
       was being squeezed into half the row and wrapping to three ragged lines,
       making one row 181px against its neighbours' 88px. Stacked, the sentence
       gets the full width and its control sits underneath on the right.
       (Howard on the bench, 6 Aug 2026: "Page 2 of the display settings looks
       terrible, re how many minutes before the Home screen appears".) */
    .settings-row.stacked {
      flex-direction: column;
      align-items: stretch;
      gap: 10px;
    }
    .settings-row.stacked > :last-child { justify-content: flex-end; }

    /* 2026-05-04: label and value share the same size so they read as a
       matched pair on the row. Value uses --mid grey for visual separation
       (left/right justification + colour, no size mismatch).
       2026-07-16: the row had NO gap and the value could not shrink/wrap, so a
       long value (e.g. an activation-status string) collided with the label -
       Howard saw "Status" overlapping its value. gap:20px guarantees a channel;
       the label holds its width, the value takes the rest and wraps right-aligned
       instead of overrunning the label. */
    .settings-label {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      flex: 0 1 auto;
    }

    .settings-value {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--mid);
      flex: 1 1 auto;
      min-width: 0;
      text-align: right;
      word-break: break-word;
      overflow-wrap: anywhere;
    }

    /* Explanatory / caption text inside a settings section. Distinct from
       .settings-value, which is the SHORT right-aligned value on a
       label|value row (28px weight 500). Long privacy / help paragraphs use
       this instead: lighter weight, tertiary ink, readable 22px - so guidance
       reads as secondary, not as bold body copy. Stays >=22px for older eyes;
       hierarchy comes from weight + colour, not a tiny size. */
    .settings-help {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink-3);
      line-height: 1.5;
    }

    /* Gentle presence-transparency line. Shown in Privacy when "let family see
       when I'm online" is on. Reassurance, never a warning: warm gold tint,
       dignified copy, tied to the toggle state (JS: setPresenceReassure). */
    .settings-reassure {
      display: flex;
      align-items: flex-start;
      gap: 12px;
      margin-top: 16px;
      padding: 14px 16px;
      background: rgba(154,123,79, 0.08);
      border: 1px solid var(--rule-2);
      border-radius: 14px;
    }
    .settings-reassure svg { flex: none; margin-top: 2px; }
    .settings-reassure .r1 { font-weight: 500; font-size: var(--body); color: var(--ink); line-height: 1.4; }
    .settings-reassure .r2 { font-weight: 400; font-size: var(--body); color: var(--ink-3); line-height: 1.4; margin-top: 2px; }

    /* 2026-05-04: dialled back from chunky-gold-fill button to a sleek
       outline. Gold reads orange/cheap when used as a thick fill at
       button scale; thin border + cream fill keeps it on-brand without
       the oversaturated look. Reduced padding + min-height too. */
    .settings-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
      background: transparent;
      border: 1.5px solid rgba(154,123,79, 0.35);
      border-radius: 8px;
      padding: 10px 16px;
      /* 56px = WCAG 2.5.5 floor (44px) + ~25% headroom for trembling
         fingers. Was 40px - elderly user audit 2026-05-26 EVE. */
      min-height: 56px;
      min-width: 64px;
      line-height: 1.2;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: background 0.15s ease, border-color 0.15s ease;
    }

    .settings-btn:active {
      background: rgba(154,123,79, 0.12);
      border-color: var(--gold);
    }

    .settings-btn.danger {
      color: #FDFAF5;
      background: #D94A3D;
      border-color: #D94A3D;
      font-weight: 500;
    }

    .settings-btn.danger:active {
      background: #A84830;
      border-color: #A84830;
    }

    .card-cancel-btn {
      /* Was cream-on-grey #6B6459 until 17 Aug 2026. The waiting bar's ink
         edge reads 2.24:1 on that grey - on the one button class the hold
         most often lands on, the sweep was invisible (found when the bar
         test's button table was corrected to the labels buttons actually
         carry). Restyled to the quiet cream cancel the calling screen
         already uses, where the edge reads 7.96:1 and siblings match. */
      color: var(--mid);
      background: var(--cream) var(--paper) repeat;
      border-color: rgba(154,123,79, 0.3);
      font-weight: 400;
    }

    /* Emergency call button on home screen */
    .emergency-home-btn {
      display: none;
      width: auto;
      max-width: min(100% - 48px, 320px);
      margin: 0 auto 16px;
      padding: 18px 24px;
      min-height: var(--touch-comfortable);
      background: var(--gold) var(--paper) repeat;   /* gold, not the old terracotta #9B6B4A - it is a button on a red/green-free screen (Howard 2026-07-19) */
      border: none;
      border-radius: 16px;
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body-lg);
      color: var(--ink);
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      text-align: center;
      letter-spacing: 0.5px;
    }
    .emergency-home-btn:active {
      background: var(--gold) var(--paper) repeat;
      filter: brightness(0.92);
    }

    /* Get help (emergency) screen tiles - 2026-06-12 (Howard). Big, calm,
       high-contrast call buttons; terracotta accent matches .emergency-home-btn
       (distinct + deuteranopia-safe, not red/green). */
    .get-help-tile {
      display: flex;
      align-items: center;
      gap: 18px;
      width: 100%;
      margin: 0 0 14px;
      padding: 16px 20px;
      min-height: var(--touch-large);
      background: var(--warm-white) var(--paper) repeat;
      border: 3px solid #9B6B4A;
      border-radius: 18px;
      cursor: pointer;
      text-align: left;
      font-family: 'Jost', sans-serif;
      -webkit-tap-highlight-color: transparent;
    }
    .get-help-tile:active { background: var(--cream) var(--paper) repeat; }
    .get-help-photo {
      width: 72px;
      height: 72px;
      min-width: 72px;
      border-radius: 50%;
      overflow: hidden;
      background: var(--cream) var(--paper) repeat;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: var(--body-lg);
      font-weight: 500;
      color: var(--mid);
    }
    .get-help-photo img { width: 100%; height: 100%; object-fit: cover; }
    .get-help-name {
      font-size: var(--body-lg);
      font-weight: 500;
      color: var(--ink);
    }
    .get-help-rel {
      font-size: var(--body);
      font-weight: 400;
      color: var(--mid);
      margin-top: 2px;
    }

    /* Stepper for numeric values (auto-answer seconds etc) */
    .stepper {
      display: flex;
      align-items: center;
      gap: var(--touch-gap);
    }

    .stepper-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      background: var(--cream) var(--paper) repeat;
      border: 3px solid rgba(154,123,79, 0.4);
      border-radius: 9px;
      width: var(--touch-min);
      height: var(--touch-min);
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      line-height: 1;
    }

    .stepper-btn:active {
      background: rgba(154,123,79, 0.3);
      border-color: var(--gold);
      transform: scale(0.95);
    }

    .stepper-value {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      min-width: 80px;
      text-align: center;
    }

    /* ===== PASSCODE ENTRY OVERLAY (for settings access) ===== */
    .passcode-overlay {
      display: none;
      position: fixed;
      top: 0; left: 0;
      width: 100%;
      height: 100%;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 450;
      flex-direction: column;
      align-items: center;
      padding: 48px 32px;
      overflow-y: auto;            /* 2026-05-02: factory-reset overlay overflows
                                      on 600x1024 portrait at large text size - let it scroll */
      -webkit-overflow-scrolling: touch;
      /* Hide the phantom desktop scrollbar (UAT 2026-05-06 rule). Touch-scroll
         still works at xlarge text size where the overlay genuinely overflows. */
      scrollbar-width: none;
      -ms-overflow-style: none;
    }
    .passcode-overlay::-webkit-scrollbar { display: none; }
    .passcode-overlay.active { display: flex; }

    /* ===== INCOMING CALL SCREEN ===== */
    .incoming-screen {
      display: none;
      position: fixed;
      top: 0; left: 0;
      width: 100%;
      height: 100%;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 300;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }
    .incoming-screen.active { display: flex; }

    /* Incoming call flash: alternates black-on-white ↔ white-on-black */
    /* Incoming-call flash (Howard 2026-07-25): a GENTLE brightness pulse, not a
       colour inversion. The old rule flipped the whole screen to near-black with
       white text - dramatic and unsettling. Now the screen simply dims and
       brightens between two brightness levels (default 80% <-> 50%), set from
       Settings > Calls > Flash Strength via the --ken-flash-* custom properties.
       Colours never change, so nothing reads as a different screen. */
    .incoming-screen.ken-flashing {
      filter: brightness(var(--ken-flash-high, 0.8));
      transition: filter 220ms ease-in-out;
    }
    .incoming-screen.ken-flashing.flash-state-b {
      filter: brightness(var(--ken-flash-low, 0.5));
    }
    @media (prefers-reduced-motion: reduce) {
      .incoming-screen.ken-flashing { transition: none; }
    }
    /* (The old flash-state-b colour overrides - white text on a near-black
       screen - were removed 2026-07-25 with the inversion they belonged to.
       The flash is brightness-only now, so colours must stay untouched.) */

    .incoming-photo {
      width: 200px;
      height: 200px;
      border-radius: 50%;
      border: 4px solid var(--gold);
      background: var(--cream) var(--paper) repeat;
      overflow: hidden;
      margin-bottom: 24px;
      box-shadow: 0 4px 16px rgba(26, 23, 20, 0.12);
    }

    .incoming-photo img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    .incoming-name {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 400;
      font-size: var(--header-lg);
      color: var(--ink);
      margin-bottom: 8px;
    }

    .incoming-status {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--mid);
      margin-bottom: 16px;
    }

    .incoming-countdown {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: 64px;
      color: var(--ink);
      margin-bottom: 48px;
    }

    .incoming-buttons {
      display: flex;
      flex-direction: column;
      gap: 20px;
      width: 100%;
      max-width: 400px;
    }

    /* 2026-06-05 (Howard): Answer/Reject are the deuteranopia-approved green/red
       pair (#2AA06A / #D94A3D) with dark text + a text label, so answer vs
       decline is never carried by colour alone. Answer = green. */
    .answer-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--btn);
      color: #042713;
      background: #2AA06A;
      border: none;
      border-radius: 14px;
      padding: 14px 28px;
      min-height: 64px;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      text-align: center;
    }

    .answer-btn:active {
      background: #228659;
      transform: scale(0.97);
    }

    /* 2026-06-17: the feedback / edit-contact / reply screens borrowed .answer-btn
       (the green "Answer call" control), so ordinary text actions rendered as tall
       green full-width slabs - wrong colour AND the fat box-vs-text pattern. The
       category buttons get a calm neutral list-row style; the other in-screen text
       actions drop the call-control min-width/padding so they stop reading as
       oversized. (#id .class outranks the single .answer-btn class regardless of order.) */
    /* Feedback categories to the card standard (2026-07-17 sweep): they were flat
       sans-label buttons sitting next to the serif icon-cards used everywhere else
       (Settings hub, General Help), which is what read as "basic". Serif title +
       soft chevron brings them into line. CSS-only - the markup is untouched. */
    #feedbackScreen .feedback-cat-btn {
      background: var(--warm-white) var(--paper) repeat;
      border: 2px solid var(--rule);
      color: var(--ink);
      font-family: 'Cormorant Garamond', serif;
      font-weight: 600;                 /* 600 not 500: only 400/600 are self-hosted, so 500 renders as 400 (see fonts-local.css) */
      font-size: var(--body-lg);
      text-align: left;
      padding: 18px 22px;
      min-height: var(--touch-large);
      min-width: 0;
      border-radius: 16px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 14px;
      transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
    }
    /* Chevron drawn in CSS so the markup stays as-is. */
    #feedbackScreen .feedback-cat-btn::after {
      content: '';
      width: 22px;
      height: 22px;
      flex-shrink: 0;
      background-color: var(--ink-3);
      -webkit-mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 6 15 12 9 18'/%3E%3C/svg%3E");
      mask: no-repeat center / contain url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 6 15 12 9 18'/%3E%3C/svg%3E");
    }
    #feedbackScreen .feedback-cat-btn:active {
      background: var(--paper-2);
      border-color: var(--gold);
      transform: scale(0.98);
    }
    #feedbackScreen .answer-btn:not(.feedback-cat-btn),
    #editContactScreen .answer-btn,
    #replyScreen .answer-btn,
    #msgNotification .answer-btn {
      min-width: 0;
      min-height: var(--touch-min);
      padding: 14px 28px;
      background: var(--gold) var(--paper) repeat;  /* GOLD primary (Howard 2026-07-18); no red button on editContact/reply/msgNotification, so gold not espresso */
      color: var(--ink);
    }

    /* Reject = the deuteranopia-approved red (#D94A3D) with DARK text + label
       (dark, never white on the red - Howard's rule; corrected 2026-06-27). */
    .reject-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--btn);
      color: #252322;
      background: #D94A3D;
      border: none;
      border-radius: 14px;
      padding: 14px 28px;
      min-height: 64px;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      text-align: center;
    }

    .reject-btn:active {
      background: #B83D31;
    }

    /* Voicemail is the neutral third option on the incoming screen - cream/outline,
       NOT red, so only Answer (green) and Reject (red) carry colour meaning. */
    .voicemail-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--btn);
      color: var(--mid);
      background: var(--cream) var(--paper) repeat;
      border: 2px solid rgba(154,123,79, 0.3);
      border-radius: 16px;
      padding: 24px;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      text-align: center;
    }

    .voicemail-btn:active {
      background: rgba(154,123,79, 0.2);
    }

    /* ===== CONTACTS MANAGEMENT ===== */
    /* Add contact, as a banner under the header rather than a tile in the grid
       (Howard, 7 Aug 2026). 80% of the width so it reads as a band across the
       screen and not as another face; a thin gold border because gold is the
       accent here, and a filled gold button would outrank the people below it. */
    .contacts-add-banner {
      display: block;
      width: 80%;
      margin: 14px auto 18px;
      padding: 16px 20px;
      min-height: var(--touch-comfortable);
      background: var(--warm-white) var(--paper) repeat;
      border: 2px solid var(--gold);
      border-radius: 14px;
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body);
      color: var(--ink);
      text-align: center;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }
    .contacts-add-banner:active { background: var(--cream) var(--paper) repeat; }

    .contacts-manage-grid {
      display: grid;
      grid-template-columns: repeat(2, 1fr);
      gap: 16px;
      padding: 24px;
      flex: 1;
      align-content: start;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      padding-bottom: 180px;
      /* Hide phantom scrollbar (UAT 2026-05-01 rule - see UAT report).
         Same overflow-y:auto + padding-bottom:180px pattern as .container. */
      scrollbar-width: none;
      -ms-overflow-style: none;
    }
    .contacts-manage-grid::-webkit-scrollbar { display: none; }

    .manage-tile {
      background: rgba(245, 240, 232, 0.4);
      border: 2px solid rgba(154,123,79, 0.3);
      border-radius: 16px;
      padding: 16px;
      display: flex;
      flex-direction: column;
      align-items: center;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }

    .manage-tile:active {
      background: rgba(245, 240, 232, 0.7);
      border-color: var(--gold);
    }

    .manage-photo {
      width: 100%;
      aspect-ratio: 1;
      border-radius: 50%;
      border: 3px solid var(--cream);
      background: var(--cream) var(--paper) repeat;
      margin-bottom: 12px;
      overflow: hidden;
      box-shadow: 0 2px 8px rgba(26, 23, 20, 0.08);
    }

    .manage-photo img { width: 100%; height: 100%; object-fit: cover; }

    .manage-name {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--contact-name);
      color: var(--ink);
      text-align: center;
    }

    .manage-relationship {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      text-align: center;
      margin-top: 4px;
    }

    .manage-home-badge {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      margin-top: 8px;
    }

    .add-tile {
      background: none;
      border: 3px dashed rgba(154,123,79, 0.4);
      border-radius: 16px;
      padding: 20px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      min-height: 200px;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }

    .add-tile:active {
      border-color: var(--gold);
      background: rgba(154,123,79, 0.08);
    }

    .add-tile-icon {
      font-size: 64px;
      color: var(--ink);
      line-height: 1;
      margin-bottom: 8px;
    }

    .add-tile-label {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink);
      text-align: center;
    }

    /* ===== CONTACT CARD (redesigned 2026-07-16) =====
       A purposeful profile, not a giant letter. Two-column header (identity
       left, photo/monogram right at ~40% row width so neither dominates), then
       Messages / Call / Videomails sections. See CLAUDE.md §9 + the redesign
       spec. Older-eyes floor honoured: every body string stays >=22px; hierarchy
       comes from serif/size/weight, never colour alone. */

    /* Scroll container - no phantom scrollbar (rule: .card-scroll). */
    .card-scroll {
      overflow-y: auto;
      flex: 1;
      scrollbar-width: none;            /* Firefox */
      padding-bottom: 16px;
    }
    .card-scroll::-webkit-scrollbar { width: 0; height: 0; }   /* Chromium/WebKit */

    /* Header: identity (name + relationship) left, photo/monogram right. */
    .card-header {
      display: flex;
      align-items: flex-start;
      gap: 16px;
      padding: 16px 20px 20px;
    }
    .card-header-identity {
      flex: 1 1 auto;
      min-width: 0;                     /* long names wrap, never overflow 100vw */
      display: flex;
      flex-direction: column;
      justify-content: center;
    }
    .card-header-media {
      flex: 0 0 40%;
      max-width: 40%;                   /* photo never dominates the row */
      display: flex;
      flex-direction: column;
      gap: 10px;
    }

    .card-name {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 600;                 /* 600 not 500: 500 silently renders as 400 (fonts-local.css). This is the contact's name - the identity of the whole card. */
      font-size: 40px;                  /* large serif; sized for the ~58% column */
      line-height: 1.1;
      color: var(--ink);
      word-break: break-word;
    }

    .card-relationship {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body);           /* 22px - older-eyes floor */
      color: var(--ink);
      margin-top: 8px;
    }

    /* Subtle "Add relationship" link shown when no relationship is set - never
       an empty gap. Underlined, tap-target sized, gold-ink (no red/green). */
    .card-relationship-add {
      align-self: flex-start;
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body-sm);
      color: var(--mid);
      background: none;
      border: none;
      padding: 8px 0;
      margin-top: 6px;
      min-height: var(--touch-min);
      text-align: left;
      text-decoration: underline;
      text-underline-offset: 3px;
      cursor: pointer;
    }

    /* Photo / monogram tile - square, rounded (brand), ~40% row width. */
    .card-photo-area {
      width: 100%;
      aspect-ratio: 1 / 1;
      border-radius: 20px;
      overflow: hidden;
      background: var(--cream) var(--paper) repeat;
    }
    .card-photo-area img { width: 100%; height: 100%; object-fit: cover; }

    .card-photo-fallback-lg {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: 'Cormorant Garamond', serif;
      font-weight: 500;
      font-size: 96px;                  /* sized to the smaller tile, not 120px */
      color: var(--mid);
      background: linear-gradient(135deg, var(--cream), var(--gold-light));
    }

    /* Add / Change photo button under the tile - uses the Ken camera. */
    .card-photo-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body-sm);
      color: var(--mid);
      background: transparent;
      border: 1.5px solid var(--rule-2);
      border-radius: 12px;
      padding: 10px 8px;
      min-height: var(--touch-min);
      width: 100%;
      cursor: pointer;
      line-height: 1.2;
    }

    /* Big green primary call button - the one proceed control (no red anywhere
       on a proceed path; calling is green #2AA06A per the deuteranopia rule). */
    .card-call-btn {
      width: 100%;
      font-family: 'Jost', sans-serif;
      font-weight: 600;
      font-size: var(--body-lg);
      color: #042713;
      background: #2AA06A;
      border: none;
      border-radius: 14px;
      padding: 16px;
      min-height: var(--touch-large);
      cursor: pointer;
    }

    /* Recent-item rows inside Messages / Call / Videomails sections. */
    .card-recent {
      padding: 12px 0;
      border-bottom: 2px solid rgba(154,123,79, 0.18);
    }
    .card-recent:last-child { border-bottom: none; }
    .card-recent-from {
      font-family: 'Jost', sans-serif;
      font-weight: 600;
      font-size: var(--body);
      color: var(--ink);
    }
    .card-recent-text {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      margin-top: 2px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    .card-recent-meta {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--ink-3);
      margin-top: 4px;
    }
    .card-empty {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink-3);
      padding: 4px 0;
    }

    /* "See all" affordance on a section title that opens the full list. */
    .card-see-all {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body-sm);
      color: var(--mid);
      background: none;
      border: none;
      padding: 6px 4px;
      min-height: var(--touch-min);
      cursor: pointer;
      white-space: nowrap;
    }

    /* ===== Contact card redesign, 2026-07-22 (Howard: Call was clipped off the
       bottom, Add-photo stacked under the monogram pushing everything down, the
       Messages box dwarfed its text). New order: identity -> CALL (primary, high,
       never clipped) -> Messages/Videomails -> a clearly-separated Manage zone.
       Portrait kiosk 600x1024, older-eyes floor >=22px, tap targets >=64px. ===== */

    /* Identity band: monogram/photo left at a fixed, contained size (no longer a
       giant letter), name + relationship beside it. Compact so Call sits high. */
    .card-identity {
      display: flex;
      align-items: center;
      gap: 18px;
      padding: 12px 24px 8px;
    }
    .card-identity .card-photo-area {
      flex: 0 0 128px;
      width: 128px;
      height: 128px;
      aspect-ratio: 1 / 1;
      border-radius: 22px;
    }
    .card-identity .card-photo-fallback-lg {
      font-size: 68px;                  /* sized to the 128px tile, not a 96px giant */
    }
    .card-identity-text {
      flex: 1 1 auto;
      min-width: 0;
      display: flex;
      flex-direction: column;
      justify-content: center;
    }

    /* Primary action: two big green call buttons, always visible. Icon over
       label; deuteranopia positive-action green #2AA06A with dark ink #042713. */
    .card-call-block {
      display: flex;
      gap: 14px;
      padding: 6px 24px 4px;
    }
    .card-call-primary {
      flex: 1 1 0;
      min-width: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 8px;
      min-height: 106px;                /* well above the 80px comfortable target */
      padding: 12px 10px;
      background: #2AA06A;
      color: #042713;
      border: none;
      border-radius: 18px;
      font-family: 'Jost', sans-serif;
      font-weight: 600;
      font-size: var(--body-lg);        /* 28px */
      line-height: 1.15;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }
    .card-call-primary svg { flex: none; }
    .card-call-primary:active { filter: brightness(0.94); }

    /* Last-call line under the call buttons (JS-filled; empty otherwise). */
    .card-lastcall { padding: 0 24px; }
    .card-lastcall:empty { display: none; }
    .card-lastcall .card-recent { padding: 8px 0; }

    /* Compact settings-section: tighter than the default so Messages / Videomails
       hug their content instead of ballooning into a big near-empty box. */
    .settings-section.card-compact {
      padding: 14px 20px;
      margin: 10px 16px;
    }
    .card-compact .settings-title-row {
      padding-bottom: 10px;
      margin-bottom: 12px;
    }
    .card-compact #cardMessagesList,
    .card-compact #cardVideomailsList { min-height: 0; }

    /* Thin rule that separates the daily call path above from the family-only
       Manage controls below. */
    .card-manage-divider {
      height: 2px;
      background: var(--rule);
      margin: 12px 24px 2px;
      border-radius: 1px;
    }

    /* Add photo + Add relationship: small secondary actions, side by side. */
    .card-secondary-actions {
      display: flex;
      gap: 12px;
      padding: 6px 24px 4px;
    }
    .card-secondary-btn {
      flex: 1 1 0;
      min-width: 0;
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body-sm);
      color: var(--mid);
      background: transparent;
      border: 1.5px solid var(--rule-2);
      border-radius: 12px;
      padding: 12px 10px;
      min-height: var(--touch-min);
      line-height: 1.2;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }
    .card-secondary-btn:active {
      background: rgba(154,123,79, 0.12);
      border-color: var(--gold);
    }

    /* Manage card: one bordered card holding all family-only controls (photo,
       relationship, home-screen, position, phone, delete) as compact rows, so
       the call path above always fits one screen. */
    .card-manage .card-secondary-actions {
      padding: 0 0 10px;
    }
    .card-manage-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 16px;
      padding: 10px 0;
      border-bottom: 2px solid rgba(154,123,79, 0.2);
    }
    .card-manage-row:last-child { border-bottom: none; }
    .card-manage .settings-label { font-size: var(--body); }
    .card-manage .settings-value { font-size: var(--body); }
    .card-position-row .settings-btn { min-height: 52px; padding: 8px 14px; }

    /* Relationship quick-pick overlay: chips for the common relationships plus
       a free-text "Other". Single tap only; chips wrap and never overflow. */
    .rel-picker {
      position: fixed;
      inset: 0;
      z-index: 10080;
      background: var(--warm-white) var(--paper) repeat;
      display: none;
      flex-direction: column;
      padding: 28px 24px;
    }
    .rel-picker.active { display: flex; }
    .rel-picker-title {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 600;
      font-size: var(--header);
      color: var(--ink);
      line-height: 1.2;
      margin-bottom: 20px;
    }
    .rel-chips {
      display: flex;
      flex-wrap: wrap;
      gap: 12px;
      overflow-y: auto;
      flex: 1;
      align-content: flex-start;
    }
    .rel-chip {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body);
      color: var(--ink);
      background: var(--cream) var(--paper) repeat;
      border: 2px solid var(--rule-2);
      border-radius: 16px;
      padding: 14px 20px;
      min-height: var(--touch-comfortable);
      cursor: pointer;
    }
    .rel-chip.selected {
      border-color: var(--gold);
      background: rgba(154,123,79, 0.14);
    }
    .rel-chip.rel-chip-other {
      border-style: dashed;
      color: var(--mid);
    }
    .rel-picker-cancel {
      margin-top: 18px;
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body);
      color: var(--mid);
      background: transparent;
      border: 1.5px solid var(--rule-2);
      border-radius: 12px;
      padding: 14px;
      min-height: var(--touch-comfortable);
      cursor: pointer;
    }

    /* Incoming-call relationship eyebrow ("Daughter") above the caller's name -
       the highest-value place to name who is ringing for a memory-loss user. */
    .incoming-relationship {
      font-family: 'Jost', sans-serif;
      font-weight: 600;
      font-size: var(--body-lg);
      color: var(--ink);
      text-align: center;
      margin-bottom: 2px;
    }

    .card-actions {
      display: flex;
      gap: 16px;
      padding: 0 24px;
      margin-bottom: 24px;
    }

    .card-actions button { flex: 1; }

    .card-details {
      padding: 0 24px 12px;
      border-top: 2px solid var(--cream);
    }

    .card-detail-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 16px 0;
      border-bottom: 1px solid rgba(154,123,79, 0.15);
    }

    .card-detail-row:last-child { border-bottom: none; }

    .card-detail-label {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
    }

    .card-detail-value {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink);
    }

    .card-order-row {
      display: flex;
      gap: 12px;
      padding: 16px 24px;
    }

    .card-order-row button { flex: 1; }

    .card-delete-row {
      padding: 12px 24px 32px;
    }

    /* ===== MESSAGE BADGE ===== */
    .message-badge {
      display: none;
      position: absolute;
      top: -8px;
      right: -8px;
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      width: 36px;
      height: 36px;
      border-radius: 50%;
      align-items: center;
      justify-content: center;
    }
    .message-badge.visible { display: flex; }

    .nav-badge {
      display: none;
      background: var(--green);
      color: #042713;
      font-weight: 500;
      /* D5-F05 fix: was 14px, violates master §14 body min 22px + R8. Bind
         to --body-sm so the badge scales with the user's chosen text size. */
      font-size: var(--body-sm, 22px);
      min-width: calc(var(--body-sm, 22px) + 10px);
      height: calc(var(--body-sm, 22px) + 10px);
      padding: 0 6px;
      border-radius: calc((var(--body-sm, 22px) + 10px) / 2);
      align-items: center;
      justify-content: center;
      vertical-align: middle;
    }
    .bottom-nav .nav-btn .nav-badge {
      position: absolute;
      top: 6px;
      right: 14px;
      margin: 0;
    }
    /* 2026-06-12 (Howard): unread indicators are obvious but NOT red and NOT
       animated - we don't want users hooked on the device. Green static badge;
       pulse animation removed. */
    .nav-badge.visible { display: inline-flex; }

    @keyframes badgePulse {
      0%, 100% { transform: scale(1); }
      50% { transform: scale(1.25); }
    }

    /* Make the Messages nav button flash gold when there are unread messages */
    .nav-btn.has-unread {
      color: var(--ink);
      font-weight: 400;
    }

    /* ===== PILL OPTIONS (multi-choice inline) ===== */
    .pill-option {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
      background: var(--cream) var(--paper) repeat;
      border: 2px solid rgba(154,123,79, 0.25);
      border-radius: 10px;
      padding: 8px 14px;
      cursor: pointer;
      flex: 1;
      text-align: center;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      transition: all 0.2s;
    }
    .pill-option.active {
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      border-color: var(--gold);
      font-weight: 500;
    }

    /* ===== TOGGLE SWITCH ===== */
    .toggle-switch {
      position: relative;
      width: 88px;
      height: 48px;
      background: var(--mid);
      border-radius: 24px;
      cursor: pointer;
      transition: background 0.3s;
      border: none;
      padding: 0;
      flex-shrink: 0;
      min-height: 48px;
    }
    .toggle-switch.on {
      background: var(--gold) var(--paper) repeat;
    }
    .toggle-switch::after {
      content: '';
      position: absolute;
      top: 4px;
      left: 4px;
      width: 40px;
      height: 40px;
      background: var(--warm-white) var(--paper) repeat;
      border-radius: 50%;
      transition: transform 0.3s;
    }
    .toggle-switch.on::after {
      transform: translateX(40px);
    }
    .toggle-dependents {
      overflow: hidden;
      max-height: 0;
      transition: max-height 0.4s ease, opacity 0.3s ease;
      opacity: 0;
    }
    .toggle-dependents.expanded {
      max-height: 600px;
      opacity: 1;
    }

    /* ===== MESSAGES SCREEN ===== */
    .messages-list {
      padding: 24px;
      flex: 1;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      padding-bottom: 180px;
      /* Hide phantom scrollbar (UAT 2026-05-01 rule - see UAT report).
         Same overflow-y:auto + padding-bottom:180px pattern as .container. */
      scrollbar-width: none;
      -ms-overflow-style: none;
    }
    .messages-list::-webkit-scrollbar { display: none; }

    .msg-item {
      background: rgba(245, 240, 232, 0.4);
      border: 2px solid rgba(154,123,79, 0.3);
      border-radius: 16px;
      padding: 20px;
      margin-bottom: 16px;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }

    .msg-item:active {
      background: rgba(245, 240, 232, 0.7);
      border-color: var(--gold);
    }

    .msg-item.unread {
      border-color: var(--gold);
      background: rgba(154,123,79, 0.08);
    }

    .msg-from {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink);
      margin-bottom: 4px;
    }

    .msg-text {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink);
      line-height: 1.4;
      margin-bottom: 8px;
    }

    .msg-time {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--date-size);
      color: var(--mid);
    }

    /* ===== WARM EMPTY STATE (2026-07-17 sweep) =====
       Reusable across screens. An empty screen used to be one line of grey text
       stranded in a void; this gives it a card, a gold icon and room to breathe,
       so "nothing here yet" still looks like the product rather than a blank.
       Pair with a `.ken-empty-icon` span + a `.ken-empty-text` span carrying the
       data-t (never put data-t on the parent - t() would wipe the icon). */
    .ken-empty {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      gap: 16px;
      text-align: center;
      max-width: 460px;
      margin: 48px auto;
      padding: 36px 28px;
      background: var(--warm-white) var(--paper) repeat;
      border: 2px solid var(--rule);
      border-radius: 18px;
    }
    .ken-empty-icon {
      width: 64px;
      height: 64px;
      flex-shrink: 0;
      border-radius: 16px;
      background: var(--paper-3);
      display: flex;
      align-items: center;
      justify-content: center;
      /* 9 Aug 2026: was --gold-deep (#C4A962) at 1.85:1 on --paper-3, and this
         icon is the whole content of an empty state - the only thing on the
         screen. --gold-icon exists for exactly this and the comment beside it
         at the top of this file argues the case; the fix simply never reached
         here. */
      color: var(--gold-icon);
    }
    .ken-empty-icon svg { width: 34px; height: 34px; }
    .ken-empty-text {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink-3);
      line-height: 1.45;
    }

    .msg-empty {
      text-align: center;
      padding: 60px 24px;
      color: var(--mid);
      font-weight: 400;
      font-size: var(--body);
    }
    /* .msg-empty / .call-history-empty now also carry .ken-empty: the card styling
       wins over their old bare-text padding. */
    .msg-empty.ken-empty,
    .call-history-empty.ken-empty { padding: 36px 28px; color: var(--ink-3); }
    /* A search box with nothing to search is a dead control: hide it while the
       empty state is on screen. .msg-empty only exists when the list is empty. */
    #messagesScreen:has(.msg-empty) .msg-search-bar { display: none; }
    /* Centre the empty card in the space it has, instead of stranding it at the top
       with a void beneath (the same "content should fill its box" note Howard made
       about the home tiles). */
    #messagesScreen:has(.msg-empty) .messages-list,
    #callHistoryScreen:has(.call-history-empty) .call-history-list {
      display: flex;
      flex-direction: column;
      justify-content: center;
      flex: 1 1 auto;
      min-height: 0;
    }

    .msg-unread-badge {
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      font-size: var(--body-sm);
      font-weight: 400;
      min-width: 32px;
      height: 32px;
      border-radius: 50%;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      padding: 0 10px;
      float: right;
      margin-top: 2px;
    }

    /* 2026-05-06: 16px bottom padding gives the search bar its own zone
       above the message list, instead of bleeding into the first item. */
    .msg-search-bar {
      padding: 16px 24px;
      display: flex;
      gap: 12px;
      align-items: center;
    }

    .msg-search-input {
      flex: 1;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      padding: 14px 20px;
      border: 3px solid rgba(154,123,79, 0.3);
      border-radius: 16px;
      background: rgba(245, 240, 232, 0.4);
      color: var(--ink);
      outline: none;
      -webkit-appearance: none;
    }

    .msg-search-input:focus {
      border-color: var(--gold);
      background: rgba(245, 240, 232, 0.7);
    }

    .msg-search-input::placeholder {
      color: var(--mid);
      opacity: 0.6;
    }

    .msg-search-clear {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      padding: 14px 20px;
      border: 3px solid rgba(100, 100, 89, 0.3);
      border-radius: 16px;
      background: rgba(100, 100, 89, 0.08);
      color: var(--mid);
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      user-select: none;
    }

    .msg-search-clear:active {
      background: rgba(100, 100, 89, 0.15);
    }

    /* On-screen keyboard for message search (kiosk has no physical keyboard) */
    .msg-search-kb {
      position: fixed;
      left: 0;
      right: 0;
      bottom: calc(var(--touch-comfortable) * 1.2 + 8px); /* above bottom nav */
      background: var(--cream) var(--paper) repeat;
      border-top: 2px solid rgba(154,123,79, 0.3);
      padding: 12px 8px 16px;
      z-index: 100;
      display: none;
    }
    .msg-search-kb.visible { display: block; }

    .msg-thread-item {
      background: rgba(245, 240, 232, 0.6);
      border: 2px solid rgba(154,123,79, 0.15);
      border-radius: 16px;
      padding: 16px 20px;
      margin-bottom: 12px;
      max-width: 82%;
      margin-right: auto;          /* a message FROM someone sits to the LEFT */
    }

    /* the user's own messages: sit to the RIGHT, with a clearly visible warm tint */
    .msg-thread-item.is-reply {
      background: rgba(154,123,79, 0.22);
      border-color: rgba(154,123,79, 0.40);
      margin-left: auto;
      margin-right: 0;
    }

    /* group threads: each sender gets a thin full coloured border (colour set per
       person inline via border-color) so people are told apart at a glance */
    .msg-thread-item.has-accent {
      border-width: 2px;
    }

    .msg-thread-from {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--date-size);
      color: var(--ink);
      margin-bottom: 4px;
    }

    .msg-thread-text {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink);
      line-height: 1.4;
    }

    .msg-thread-time {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
      margin-top: 4px;
    }

    /* Tick status - single tick design (2026-05-21).
       Grey by default (sent), blue when read. */
    .msg-tick {
      color: var(--mid);
      font-weight: 500;
      font-size: var(--body-sm);
      margin-left: 6px;
      opacity: 0.7;
    }
    .msg-tick-read {
      color: #3B5F8A;
      opacity: 1;
    }

    .typing-indicator {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
      height: 28px;
      line-height: 28px;
      padding: 0 20px;
      opacity: 0;
      transition: opacity 0.3s ease;
      pointer-events: none;
    }
    .typing-indicator.visible {
      opacity: 1;
    }

    /* ===== MEDICAL INFO ===== */
    .medical-content {
      padding: 8px 0;
    }
    .medical-empty {
      color: var(--mid);
      font-weight: 400;
      font-size: var(--body);
      padding: 8px 0;
    }
    .medical-item {
      background: rgba(245, 240, 232, 0.4);
      border: 2px solid rgba(154,123,79, 0.15);
      border-radius: 12px;
      padding: 14px 18px;
      margin-bottom: 8px;
    }
    .medical-item-name {
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink);
    }
    .medical-item-detail {
      font-weight: 400;
      font-size: var(--date-size);
      color: var(--mid);
      margin-top: 2px;
    }
    .medical-gp-row {
      font-size: var(--body);
      color: var(--ink);
      padding: 6px 0;
      font-weight: 400;
    }
    .medical-gp-label {
      font-weight: 400;
      color: var(--mid);
      font-size: var(--date-size);
    }
    .medical-care-notes {
      font-size: var(--body);
      color: var(--ink);
      line-height: 1.5;
      white-space: pre-wrap;
    }
    .medical-updated {
      text-align: center;
      font-size: var(--body-sm);
      color: var(--mid);
      font-weight: 400;
      padding: 16px 0;
    }

    /* ===== SINGLE MESSAGE VIEW ===== */
    .msg-view-body {
      padding: 32px 24px;
      flex: 1;
      display: flex;
      flex-direction: column;
      align-items: center;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
    }

    .msg-view-photo {
      width: 150px;
      height: 150px;
      border-radius: 50%;
      overflow: hidden;
      margin-bottom: 16px;
      border: 4px solid var(--gold);
      flex-shrink: 0;
    }

    .msg-view-photo img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    .msg-view-photo-fallback {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      background: rgba(154,123,79, 0.15);
      font-family: 'Cormorant Garamond', serif;
      font-size: 64px;
      color: var(--mid);
      font-weight: 400;
    }

    .msg-view-from {
      font-family: 'Cormorant Garamond', serif;
      /* 2026-07-17: was 400 - the thinnest serif face - directly above .msg-view-time,
         which is Jost and renders at its 500 file. The SENDER'S NAME read lighter
         than the timestamp beneath it, which is backwards: the name is why the
         screen exists. 600 (500 would silently render 400 - see fonts-local.css). */
      font-weight: 600;
      font-size: 40px;
      color: var(--ink);
      margin-bottom: 4px;
      text-align: center;
    }

    .msg-view-time {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      margin-bottom: 24px;
      text-align: center;
    }

    .msg-view-text {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      line-height: 1.5;
      flex: 1;
      max-width: 520px;
      width: 100%;
      text-align: left;
    }

    .msg-view-actions {
      display: flex;
      gap: 16px;
      padding: 24px;
      padding-bottom: 32px;
      width: 100%;
    }

    .msg-view-actions button { flex: 1; }

    .read-aloud-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--btn);
      color: var(--ink);
      background: var(--gold) var(--paper) repeat;
      border: none;
      border-radius: 16px;
      min-width: 0;
      min-height: var(--touch-min);
      padding: 14px 28px;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      text-align: center;
    }

    .read-aloud-btn:active {
      background: var(--gold-light) var(--paper) repeat;
      transform: scale(0.97);
    }

    /* ===== NEW MESSAGE NOTIFICATION OVERLAY ===== */
    .msg-notification {
      display: none;
      position: fixed;
      inset: 0;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 351;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 48px 32px;
    }
    .msg-notification.active { display: flex; }

    .msg-notif-icon {
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: rgba(154,123,79, 0.15);
      display: flex;
      align-items: center;
      justify-content: center;
      margin-bottom: 24px;
    }

    .msg-notif-icon svg {
      width: 48px;
      height: 48px;
      fill: var(--gold);
    }

    .msg-notif-label {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--mid);
      margin-bottom: 8px;
    }

    .msg-notif-from {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 400;
      font-size: var(--header-lg);
      color: var(--ink);
      margin-bottom: 24px;
      text-align: center;
    }

    .msg-notif-text {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--header);
      color: var(--ink);
      text-align: center;
      line-height: 1.5;
      max-width: 500px;
      margin-bottom: 40px;
      flex: 1;
      display: flex;
      align-items: center;
    }

    .msg-notif-actions {
      width: 100%;
      display: flex;
      flex-direction: column;
      gap: 16px;
    }

    /* ===== VOICEMAIL NOTIFICATION OVERLAY ===== */
    .vm-notification {
      display: none;
      position: fixed;
      inset: 0;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 353;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 48px 32px;
    }
    .vm-notification.active { display: flex; }

    .vm-notif-icon {
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: rgba(154,123,79, 0.15);
      display: flex;
      align-items: center;
      justify-content: center;
      margin-bottom: 24px;
    }

    .vm-notif-icon svg {
      width: 48px;
      height: 48px;
      fill: var(--gold);
    }

    .vm-notif-label {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--mid);
      margin-bottom: 8px;
    }

    .vm-notif-from {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 400;
      font-size: var(--header-lg);
      color: var(--ink);
      margin-bottom: 16px;
      text-align: center;
    }

    .vm-notif-badge {
      display: inline-block;
      background: rgba(154,123,79, 0.15);
      color: var(--mid);
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      padding: 6px 16px;
      border-radius: 20px;
      margin-bottom: 32px;
    }

    .vm-notif-actions {
      width: 100%;
      display: flex;
      flex-direction: column;
      gap: 16px;
    }

    /* ===== VOICEMAIL PLAYBACK SCREEN ===== */
    .vm-playback {
      display: none;
      position: fixed;
      inset: 0;
      background: var(--ink);
      z-index: 370;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 32px;
    }
    .vm-playback.active { display: flex; }

    .vm-playback video {
      width: 100%;
      max-height: 70vh;
      border-radius: 16px;
      background: #000;
    }

    .vm-playback-audio {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 24px;
    }

    .vm-playback-name {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 400;
      font-size: var(--header-lg);
      color: var(--cream);
      text-align: center;
    }

    .vm-playback-pulse {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      height: 120px;
    }

    .vm-playback-bar {
      width: 10px;
      background: var(--gold) var(--paper) repeat;
      border-radius: 5px;
      animation: vmPlayPulse 1.2s ease-in-out infinite;
    }
    .vm-playback-bar:nth-child(1) { height: 30px; animation-delay: 0s; }
    .vm-playback-bar:nth-child(2) { height: 50px; animation-delay: 0.15s; }
    .vm-playback-bar:nth-child(3) { height: 70px; animation-delay: 0.3s; }
    .vm-playback-bar:nth-child(4) { height: 50px; animation-delay: 0.45s; }
    .vm-playback-bar:nth-child(5) { height: 30px; animation-delay: 0.6s; }

    @keyframes vmPlayPulse {
      0%, 100% { transform: scaleY(0.4); }
      50% { transform: scaleY(1); }
    }

    /* Whose message this is - the first thing read on the playback screen.
       2026-07-28: before this the sender's name only appeared for an AUDIO
       message (inside the pulse block); a video message played with nothing on
       screen saying who it was from. */
    .vm-playback-from {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 600;   /* 600 not 500: 500 renders as 400 (fonts-local.css) */
      font-size: var(--header-lg);
      color: var(--cream);
      text-align: center;
      line-height: 1.15;
      margin-bottom: 16px;
      max-width: 100%;
    }
    .vm-playback-from:empty { display: none; }

    /* The only two controls: full-size, labelled, side by side. */
    .vm-playback-actions {
      display: flex;
      gap: 14px;
      justify-content: center;
      flex-wrap: wrap;
      margin-top: 24px;
    }

    .vm-close-btn {
      margin-top: 0;
      min-width: 0;
      min-height: var(--touch-comfortable, 60px);
      padding: 14px 28px;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--btn);
      color: var(--ink);
      background: var(--gold) var(--paper) repeat;
      border: none;
      border-radius: 14px;
      cursor: pointer;
    }
    .vm-close-btn:active {
      background: var(--gold-light) var(--paper) repeat;
      transform: scale(0.97);
    }
    /* Close is the quieter of the two - outline on the dark playback ground. */
    .vm-close-btn-secondary {
      background: transparent;
      color: var(--cream);
      border: 2px solid var(--gold);
    }
    .vm-close-btn-secondary:active {
      background: rgba(196, 169, 98, 0.22);
      transform: scale(0.97);
    }

    /* ===== VIDEOMAILS LIST (2026-07-28) =====
       Reuses the Recent Calls list chrome. Each row is one whole tap target
       (single tap, no swipe, nothing hidden behind a gesture). A row still
       waiting to be watched carries a gold left edge plus the words "Not
       watched yet" - never colour alone, and never a red dot. */
    .vm-row {
      display: flex;
      align-items: center;
      gap: 16px;
      width: 100%;
      box-sizing: border-box;
      text-align: left;
      background: var(--warm-white) var(--paper) repeat;
      border: 1.5px solid rgba(154, 123, 79, 0.3);
      border-radius: 16px;
      padding: 16px 20px;
      margin-bottom: 12px;
      min-height: var(--touch-comfortable, 60px);
      font-family: 'Jost', sans-serif;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .vm-row:active { background: var(--cream) var(--paper) repeat; transform: scale(0.99); }
    .vm-row.unwatched { border-left: 8px solid var(--gold); }
    .vm-row-icon {
      flex: 0 0 auto;
      width: 44px;
      height: 44px;
      border-radius: 999px;
      background: var(--gold) var(--paper) repeat;
      display: flex;
      align-items: center;
      justify-content: center;
      color: var(--ink);
    }
    .vm-row-body { flex: 1 1 auto; min-width: 0; }
    /* display:block on both: these are <span>s, so without it the sender's name
       and the "when / how long / watched or not" line ran together as one
       paragraph, and the meta line's margin-top did nothing at all (margin-top
       is inert on an inline box). Measured on ken-pi-3b27b0, 2026-07-28. */
    .vm-row-from {
      display: block;
      font-size: var(--body);
      font-weight: 500;
      color: var(--ink);
      line-height: 1.2;
    }
    .vm-row-meta {
      display: block;
      font-size: var(--body-sm);
      color: var(--mid);
      margin-top: 4px;
      line-height: 1.3;
    }
    .vm-row-cta {
      flex: 0 0 auto;
      font-size: var(--body-sm);
      font-weight: 500;
      color: var(--ink);
      background: rgba(154, 123, 79, 0.18);
      border-radius: 10px;
      padding: 10px 16px;
    }

    /* ===== REPLY SCREEN ===== */
    .reply-screen {
      display: none;
      position: fixed;
      inset: 0;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 450;
      flex-direction: column;
    }
    .reply-screen.active { display: flex; }

    .reply-header {
      padding: 20px 24px 16px;
      border-bottom: 2px solid var(--cream);
      text-align: center;
    }

    .reply-to {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
    }

    .reply-content-area {
      flex: 1;
      overflow-y: auto;
      min-height: 0;
    }

    .reply-quick-area {
      padding: 16px 12px 8px;
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      justify-content: center;
    }

    .quick-reply-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink);
      background: rgba(245, 240, 232, 0.6);
      border: 3px solid rgba(154,123,79, 0.35);
      border-radius: 14px;
      padding: 16px 20px;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
      min-width: 120px;
      text-align: center;
    }

    .quick-reply-btn:active {
      background: rgba(154,123,79, 0.2);
      border-color: var(--gold);
      transform: scale(0.97);
    }

    .reply-divider {
      display: flex;
      align-items: center;
      gap: 12px;
      padding: 4px 24px;
      color: var(--mid);
      font-size: var(--body-sm);
      font-weight: 400;
    }

    .reply-divider::before,
    .reply-divider::after {
      content: '';
      flex: 1;
      height: 1px;
      background: rgba(154,123,79, 0.25);
    }

    .reply-compose {
      padding: 8px 12px;
      flex: 0;
    }

    .reply-text-display {
      width: 100%;
      background: var(--cream) var(--paper) repeat;
      border: 3px solid rgba(154,123,79, 0.4);
      border-radius: 12px;
      padding: 14px 18px;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      min-height: 56px;
      margin-bottom: 6px;
      word-wrap: break-word;
    }

    .reply-keyboard {
      width: 100%;
      display: flex;
      flex-direction: column;
      gap: 6px;
      padding: 0 4px 8px;
      flex-shrink: 0;
    }

    /* Reply keyboard same fit-screen-width treatment as .kb-row.
       2026-05-01: flex-distribute, never overflow, no scroll. */
    .reply-kb-row {
      display: flex;
      justify-content: center;
      gap: 4px;
      width: 100%;
      max-width: 100vw;
      padding: 0 4px;
      box-sizing: border-box;
      overflow: hidden;
      --ku: calc((100% - 36px) / 10);
    }

    .reply-kb-key {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--kb-font-size, var(--body-sm));
      color: var(--ink);
      background: var(--cream) var(--paper) repeat;
      border: 2px solid rgba(154,123,79, 0.25);
      border-radius: 8px;
      flex: 0 1 var(--ku, calc((100% - 36px) / 10));
      min-width: 0;
      min-height: var(--kb-key-size);
      height: var(--kb-key-size);
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      user-select: none;
      -webkit-tap-highlight-color: transparent;
    }

    .reply-kb-key:active {
      background: rgba(154,123,79, 0.3);
      border-color: var(--gold);
    }

    .reply-kb-key.wide {
      flex: 0 1 calc(var(--ku, calc((100% - 36px) / 10)) * 1.5);
      font-size: var(--body-sm);
    }

    .reply-kb-key.space-key {
      min-width: 160px;
      flex: 1 0 160px;
    }

    .reply-kb-key.send-key {
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      border-color: var(--gold);
      min-width: calc(var(--kb-key-size) * 1.25);
      font-size: var(--body-sm);
    }

    .reply-kb-key.send-key:active {
      background: var(--gold-light) var(--paper) repeat;
    }

    .reply-kb-key.send-key.disabled {
      background: rgba(154,123,79, 0.3);
      color: var(--mid);
      pointer-events: none;
    }

    /* ===== Task 1: system-keyboard language / layout / accents panel =====
       Opened by the inline-SVG globe key on the system typing keyboards.
       Reuses the existing .toggle-switch + .toggle-dependents components for
       the two on/off controls; language (1-of-9) uses a single-tap chip grid
       because a two-state toggle cannot express nine choices. No red/green
       semantics here, so the cream/gold/ink palette applies. */
    .kenkb-panel {
      position: fixed;
      inset: 0;
      z-index: 10090;
      display: flex;
      align-items: center;
      justify-content: center;
      background: rgba(26,23,20,0.5);
      font-family: 'Jost', sans-serif;
      padding: 20px;
    }
    .kenkb-card {
      background: var(--warm-white) var(--paper) repeat;
      border-radius: 20px;
      padding: 22px 22px 20px;
      width: 100%;
      max-width: 520px;
      max-height: 92vh;
      overflow-y: auto;
      box-shadow: 0 24px 60px rgba(0,0,0,0.32);
      box-sizing: border-box;
    }
    .kenkb-title {
      font-family: 'Cormorant Garamond', serif;
      font-size: 28px;
      font-weight: 600;
      color: var(--ink);
      text-align: center;
      margin-bottom: 14px;
    }
    .kenkb-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 14px;
      padding: 14px 0;
      border-bottom: 1px solid rgba(154,123,79, 0.18);
    }
    .kenkb-label { font-size: var(--body); color: var(--ink); font-weight: 500; }
    .kenkb-sub   { font-size: var(--body-sm); color: var(--mid); margin-top: 3px; line-height: 1.3; }
    .kenkb-langgrid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 8px;
      margin: 12px 0 2px;
    }
    .kenkb-langchip {
      padding: 12px 6px;
      border: 2px solid rgba(154,123,79, 0.3);
      border-radius: 12px;
      background: var(--cream) var(--paper) repeat;
      color: var(--ink);
      font-size: var(--body-sm);
      font-weight: 500;
      text-align: center;
      cursor: pointer;
      min-height: var(--touch-min);
      display: flex;
      align-items: center;
      justify-content: center;
      -webkit-tap-highlight-color: transparent;
    }
    .kenkb-langchip.sel {
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      border-color: var(--gold);
    }

    /* Task 2 — curated emoji picker (tap-paged categories, no scroll). */
    .kenemoji-tabs {
      display: flex;
      gap: 6px;
      flex-wrap: wrap;
      justify-content: center;
      margin-bottom: 12px;
    }
    .kenemoji-tab {
      padding: 8px 14px;
      border: 2px solid rgba(154,123,79, 0.3);
      border-radius: 999px;
      background: var(--cream) var(--paper) repeat;
      color: var(--ink);
      font-size: var(--body-sm);
      font-weight: 500;
      cursor: pointer;
      min-height: var(--touch-min);
      display: flex;
      align-items: center;
      -webkit-tap-highlight-color: transparent;
    }
    .kenemoji-tab.sel {
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      border-color: var(--gold);
    }
    .kenemoji-grid {
      display: grid;
      grid-template-columns: repeat(4, 1fr);
      gap: 8px;
    }
    .kenemoji-cell {
      height: 76px;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 40px;
      line-height: 1;
      background: var(--cream) var(--paper) repeat;
      border: 1px solid rgba(154,123,79, 0.25);
      border-radius: 12px;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .kenemoji-cell:active { background: rgba(154,123,79, 0.25); }

    /* ===== CALL HISTORY SCREEN ===== */
    .call-history-list {
      flex: 1;
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      padding: 8px 16px 24px;
    }
    .call-history-item {
      display: flex;
      align-items: center;
      gap: 14px;
      padding: 16px 12px;
      border-bottom: 2px solid var(--cream);
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .call-history-item:active {
      background: rgba(154,123,79, 0.1);
    }
    .call-history-photo {
      width: 52px;
      height: 52px;
      border-radius: 50%;
      overflow: hidden;
      flex-shrink: 0;
      background: var(--cream) var(--paper) repeat;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
    }
    .call-history-photo img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    .call-history-info {
      flex: 1;
      min-width: 0;
    }
    .call-history-name {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink);
    }
    .call-history-meta {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--mid);
      margin-top: 2px;
    }
    .call-history-direction {
      font-weight: 400;
    }
    .call-history-direction.incoming { color: var(--ink); }
    .call-history-direction.outgoing { color: var(--mid); }
    .call-history-direction.missed { color: var(--red); }
    .call-history-empty {
      text-align: center;
      padding: 64px 24px;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
    }

    /* ===== VOICE-TO-TEXT BUTTON ===== */
    .speak-btn {
      width: auto;
      max-width: 100%;
      min-height: 64px;
      padding: 14px 28px;
      background: var(--gold) var(--paper) repeat;
      color: var(--ink);
      border: none;
      border-radius: 14px;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      display: inline-flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      margin-bottom: 8px;
    }
    .speak-btn:active {
      background: var(--gold-light) var(--paper) repeat;
      transform: scale(0.98);
    }
    .speak-btn.listening {
      background: #C46262;
      animation: speakPulse 1.2s ease-in-out infinite;
    }
    @keyframes speakPulse {
      0%, 100% { transform: scale(1); opacity: 1; }
      50% { transform: scale(1.03); opacity: 0.85; }
    }
    .speak-btn svg {
      width: 28px;
      height: 28px;
      fill: var(--warm-white);
    }

    /* ===== MESSAGE PHOTO ===== */
    .msg-view-image {
      width: 100%;
      max-height: 400px;
      border-radius: 12px;
      margin-top: 16px;
      object-fit: contain;
      cursor: pointer;
      border: 2px solid var(--cream);
    }
    .msg-thread-image {
      max-width: 200px;
      max-height: 160px;
      border-radius: 8px;
      margin-top: 6px;
      object-fit: cover;
      border: 2px solid var(--cream);
    }
    .photo-fullscreen-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: rgba(0, 0, 0, 0.92);
      z-index: 900;
      align-items: center;
      justify-content: center;
      flex-direction: column;
      cursor: pointer;
    }
    .photo-fullscreen-overlay.active {
      display: flex;
    }
    .photo-fullscreen-overlay img {
      max-width: 95%;
      max-height: 85%;
      object-fit: contain;
      border-radius: 8px;
    }
    .photo-fullscreen-close {
      position: absolute;
      top: 24px;
      right: 24px;
      width: 56px;
      height: 56px;
      background: rgba(255,255,255,0.15);
      border: none;
      border-radius: 50%;
      color: #fff;
      font-size: var(--body-lg);
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    /* ===== MISSED CALL BANNER BUTTONS ===== */
    .missed-call-buttons {
      display: flex;
      gap: 12px;
      margin-top: 10px;
      justify-content: center;
    }
    .missed-call-btn {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      border: none;
      border-radius: 12px;
      padding: 12px 28px;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
      /* 64px touch floor (literal, not var(--touch-min), per the note on
         .wizard-btn above - the token balloons past 88px at large text).
         Measured 2026-07-28 on ken-pi-3b27b0: these rendered 56px tall, under
         the Ken's own floor. Shared by the missed-call banner and the
         waiting-Videomail banner, so both clear it. */
      min-height: 64px;
    }
    .missed-call-btn.callback {
      background: var(--warm-white) var(--paper) repeat;
      color: var(--ink);
    }
    .missed-call-btn.callback:active {
      background: var(--cream) var(--paper) repeat;
    }
    .missed-call-btn.dismiss {
      background: rgba(255, 255, 255, 0.25);
      color: var(--ink);
    }
    .missed-call-btn.dismiss:active {
      background: rgba(255, 255, 255, 0.4);
    }

    /* ===== QR ADD SCREEN ===== */
    .qr-body {
      display: flex;
      flex-direction: column;
      align-items: center;
      /* flex-start (was center) + tighter padding so the Device/Code box stays
         on-screen at a large text size instead of being pushed off the bottom.
         2026-06-26: the fit IS the fix; the scroll cue is only an extreme-size net. */
      justify-content: flex-start;
      padding: 18px 28px;
      flex: 1;
    }

    .qr-code-box {
      background: #fff;
      padding: 16px;
      border-radius: 16px;
      border: 3px solid var(--cream);
      margin-bottom: 14px;
      box-shadow: 0 4px 16px rgba(26, 23, 20, 0.08);
    }

    .qr-instruction {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--ink);
      text-align: center;
      margin-bottom: 12px;
    }

    .qr-url {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      text-align: center;
      word-break: break-all;
    }

    /* ===== OFFLINE BANNER =====
       Redesigned 2026-04-22 - previous version used gold text on a
       gold-tinted background (2.19:1, fails AA per THEKEN_MASTER §9
       "gold text on cream = banned"). Now a solid gold pill with ink
       text matching the voice-indicator visual language. Icon before
       the title makes the state scannable at a glance. */
    .offline-banner {
      position: fixed;
      top: 52px;
      left: 16px;
      right: 16px;
      z-index: 45;
      background: var(--gold) var(--paper) repeat;
      border-radius: 16px;
      text-align: center;
      padding: 14px 20px;
      transform: translateY(calc(-100% - 60px));
      opacity: 0;
      transition: transform 0.4s ease, opacity 0.4s ease;
      pointer-events: none;
      box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
    }
    .offline-banner.visible {
      transform: translateY(0);
      opacity: 1;
      pointer-events: auto;
    }
    .offline-banner__title {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body);
      color: var(--ink);
      line-height: 1.25;
      display: inline-flex;
      align-items: center;
      gap: 10px;
      justify-content: center;
    }
    .offline-banner__title::before {
      content: '';
      display: inline-block;
      width: 22px;
      height: 22px;
      background-color: var(--ink);
      flex-shrink: 0;
      -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M2.28 3 1 4.27l2.47 2.47C2 7.59.65 8.57 0 9.53l12 15 4.36-5.44 3.37 3.37L21 21.14 2.28 3zM12 19.53 3.13 8.47a11.74 11.74 0 0 1 2.76-1.84l9.56 9.56L12 19.53zM23.64 7C18.69 2.05 10.75 1.57 5.22 5.57L7.14 7.5c4.31-2.75 10.11-2.04 13.77 1.63L23.64 7zm-4.6 4.6 2.43-2.43c-1.24-1.24-2.83-2.1-4.55-2.55l2.12 2.12c.68.24 1.33.55 1.91.93L19.04 11.6z'/></svg>") no-repeat center;
      mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M2.28 3 1 4.27l2.47 2.47C2 7.59.65 8.57 0 9.53l12 15 4.36-5.44 3.37 3.37L21 21.14 2.28 3zM12 19.53 3.13 8.47a11.74 11.74 0 0 1 2.76-1.84l9.56 9.56L12 19.53zM23.64 7C18.69 2.05 10.75 1.57 5.22 5.57L7.14 7.5c4.31-2.75 10.11-2.04 13.77 1.63L23.64 7zm-4.6 4.6 2.43-2.43c-1.24-1.24-2.83-2.1-4.55-2.55l2.12 2.12c.68.24 1.33.55 1.91.93L19.04 11.6z'/></svg>") no-repeat center;
      -webkit-mask-size: contain;
      mask-size: contain;
    }
    .offline-banner__subtitle {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--ink);
      margin-top: 4px;
      opacity: 0.82;
    }
    /* "Tap here for help" subtitle - second subtitle already bolded
       inline. Underline gives it a tappable feel without a button shape. */
    .offline-banner__subtitle + .offline-banner__subtitle {
      margin-top: 6px;
      opacity: 1;
      text-decoration: underline;
      text-underline-offset: 3px;
    }

    /* ===== OFFLINE FULL-SCREEN OVERLAY ===== */
    .offline-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 350;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 48px 32px;
      overflow-y: auto;                  /* xlarge text safety net */
      -webkit-overflow-scrolling: touch;
      /* Hide the phantom desktop scrollbar (UAT 2026-05-06 rule). Touch-scroll
         still works at xlarge text size where the overlay genuinely overflows. */
      scrollbar-width: none;
      -ms-overflow-style: none;
    }
    .offline-overlay::-webkit-scrollbar { display: none; }
    .offline-overlay.active { display: flex; }

    .offline-overlay__icon {
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: rgba(154,123,79, 0.15);
      display: flex;
      align-items: center;
      justify-content: center;
      margin-bottom: 32px;
    }
    .offline-overlay__icon svg {
      width: 48px;
      height: 48px;
      fill: var(--gold);
    }
    .offline-overlay__title {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 400;
      font-size: var(--header-lg);
      color: var(--ink);
      margin-bottom: 16px;
      text-align: center;
    }
    .offline-overlay__subtitle {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--mid);
      text-align: center;
    }

    /* ===== VOICE COMMAND INDICATOR ===== */
    .voice-indicator {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      height: 44px;
      background: var(--gold) var(--paper) repeat;
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 10px;
      z-index: 9990;
      transform: translateY(-100%);
      transition: transform 0.3s ease;
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-sm);
      color: var(--ink);
    }
    .voice-indicator.visible {
      transform: translateY(0);
    }
    .voice-indicator__mic {
      width: 20px;
      height: 20px;
      border-radius: 50%;
      background: var(--ink);
      animation: voice-pulse 1.2s ease-in-out infinite;
    }
    @keyframes voice-pulse {
      0%, 100% { opacity: 0.5; transform: scale(0.9); }
      50% { opacity: 1; transform: scale(1.1); }
    }

    /* ===== IDLE CLOCK OVERLAY ===== */
    .idle-overlay {
      display: none;
      position: fixed;
      inset: 0;
      z-index: 5;
      background: var(--ink);
    }
    .idle-overlay.active { display: flex; flex-direction: column; align-items: center; justify-content: center; }

    /* ===== TRUE NIGHT FACE (2026-07-25) =====
       The panel has no controllable backlight (/sys/class/backlight is empty on
       the GeeekPi HDMI screen), so the nightlight CSS filter alone still left the
       full cream UI lighting the room. This face collapses emitted light: near-
       black background, one dim warm clock. Sits ABOVE the noSubBar (10050) so no
       lit chrome leaks at night, but call screens must always win: JS hides the
       face on every ring/notification path, and the :has() rules below are the
       CSS-level guarantee even if a JS path is ever missed. The body brightness
       filter stays applied on top (extra reduction); the amber values are chosen
       to stay legible through it. */
    .night-face-overlay {
      display: none;
      position: fixed;
      inset: 0;
      z-index: 10060;
      background: #000;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }
    .night-face-overlay.active { display: flex; }
    .night-face-time {
      font-family: 'Jost', sans-serif;
      font-weight: 300;
      font-size: 96px;
      color: #C89B5E;   /* warm amber - survives the nightlight brightness filter */
      letter-spacing: 2px;
      line-height: 1;
    }
    .night-face-date {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: 24px;
      color: #7A5F3C;
      margin-top: 18px;
    }
    /* SAFETY: an incoming/active call screen must NEVER sit under the night
       face. JS hides the face on every call path; this is the guarantee. */
    body:has(.incoming-screen.active) .night-face-overlay,
    body:has(.calling-screen.active) .night-face-overlay,
    body:has(.call-screen.active) .night-face-overlay {
      display: none !important;
    }

    /* ===== MEDICATION REMINDER NOTIFICATION ===== */
    .reminder-notification {
      display: none;
      position: fixed;
      inset: 0;
      background: var(--warm-white) var(--paper) repeat;
      z-index: 352;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 48px 32px;
    }
    .reminder-notification.active { display: flex; }

    .reminder-notif-icon {
      width: 100px;
      height: 100px;
      border-radius: 50%;
      background: rgba(154,123,79, 0.15);
      display: flex;
      align-items: center;
      justify-content: center;
      margin-bottom: 24px;
    }

    .reminder-notif-icon svg {
      width: 48px;
      height: 48px;
      fill: var(--gold);
    }

    .reminder-notif-label {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body-lg);
      color: var(--mid);
      margin-bottom: 8px;
    }

    .reminder-notif-time {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      margin-bottom: 24px;
    }

    .reminder-notif-text {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--header);
      color: var(--ink);
      text-align: center;
      line-height: 1.5;
      max-width: 500px;
      margin-bottom: 40px;
      flex: 1;
      display: flex;
      align-items: center;
    }

    .reminder-notif-actions {
      width: 100%;
      display: flex;
      flex-direction: column;
      gap: 16px;
    }

    /* ============================================================
       Mockup-03 chrome — Hub of Four + Sub-page navigation
       ============================================================
       Used by Settings first (P3a, 2026-05-25), then extended to Home /
       Menu / Help / Messages / Contacts as the all-surfaces restructure
       lands (P3b). The pattern: each top-level surface presents a hub of
       four large tap-tiles; tapping a tile shows its sub-page (back-button
       + title + optional status strip + grouped sections). Sub-pages are
       sibling divs toggled via display rather than navigated as separate
       screens, so all existing settings logic / handlers / IDs continue
       to work unchanged. */

    .hub-of-four {
      padding: 12px 16px 24px;
      display: flex;
      flex-direction: column;
      gap: 14px;
    }
    /* Paginated hub (2026-07-23). NB .hub-view { display:block } is declared
       LATER in this sheet and overrides .hub-of-four's flex on the hub
       container itself (the old zero-gap between cards came from exactly
       that), so the column+gap lives on the page wrappers instead. */
    #settingsHubPage1, #settingsHubPage2 {
      display: flex;
      flex-direction: column;
      gap: 10px;
    }
    /* Single-page hub compaction (Howard 2026-07-23): all five cards on one
       page at every text size. Tighter card padding + line-height than the
       generic hub-card; min-height floor dropped (the content sets it). */
    #settingsHub .hub-card {
      padding: 14px 20px;
      min-height: 0;
    }
    #settingsHub .hub-card .hub-sub { line-height: 1.25; }
    /* "Your photo" as a hub card: the round photo sits in the icon slot,
       over a person glyph that shows until the photo loads. */
    .hub-photo-circle {
      border-radius: 50%;
      position: relative;
      overflow: hidden;
    }
    .hub-photo-circle img {
      position: absolute;
      inset: 0;
      width: 100%;
      height: 100%;
      object-fit: cover;
    }
    /* Hub page flipper - the home "More Contacts" tab verbatim (#homeMoreBtn,
       the Howard 2026-06-23 sketch; "THAT is the design", Howard 2026-07-23):
       gold-outline SVG tab with concave shoulders, cream fill, flush to the
       screen bottom (settings has no nav bar), 58% wide (home's 64% minus the
       10% Howard asked for). Shares the .more-tab-* part classes. */
    .ken-more-tab {
      position: fixed;
      left: 50%;
      transform: translateX(-50%);
      bottom: 0;
      z-index: 60;
      box-sizing: border-box;
      width: 58%;
      height: 63px;
      background: none;
      border: none;
      padding: 0;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;
    }
    .ken-more-tab .more-tab-svg { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
    .ken-more-tab .more-tab-fill { fill: var(--cream); }
    .ken-more-tab .more-tab-line {
      fill: none;
      stroke: var(--gold);
      stroke-width: 2;
      vector-effect: non-scaling-stroke;
      stroke-linejoin: round;
      stroke-linecap: round;
    }
    .ken-more-tab .more-tab-label {
      position: absolute;
      inset: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body);
      color: var(--ink);
    }
    .ken-more-tab:active { transform: translateX(-50%) scale(0.98); }
    /* Page indicator (Howard 2026-07-23): "Page n/N" small + muted, pinned to the
       right of the tab's flat plateau (shoulders start ~82% width) so the user
       sees their position beside the button they tap. Label stays centred. */
    /* 2026-07-27: pinned to the SCREEN's bottom-right, not inside the tab. The
       tab is 58% wide and centred, so its right edge sits at ~79% of the
       viewport - this clears it entirely and can never collide with the centred
       label. Vertically centred on the 63px tab so it reads as one pairing. */
    /* Sits just OUTSIDE the tab's right edge, vertically centred on it, in the
       tab's own stacking context. The tab is 58% wide and centred, so its right
       edge is at ~79% of the screen; -104px clears it with room to spare and can
       never touch the centred label however long it is. */
    #kenPageBadge {
      position: absolute;
      right: -104px;
      top: 50%;
      transform: translateY(-50%);
      padding: 4px 10px;
      border-radius: 999px;
      background: var(--cream);
      border: 1px solid var(--gold);
      font-family: 'Jost', sans-serif;
      font-weight: 600;
      font-size: 15px;
      color: var(--ink);
      line-height: 1;
      white-space: nowrap;
      pointer-events: none;
    }
    .ken-more-tab .more-tab-page, #homeMoreBtn .more-tab-page {
      display: flex;
      align-items: center;
      font-family: 'Jost', sans-serif;
      font-weight: 600;
      /* Absolute, not em: 0.66em resolved to ~8.8px on the tab and was
         illegible for a cataracts persona (UAT 2026-07-24). 15px is readable
         and still clearly secondary to the centred label. */
      font-size: 15px;
      letter-spacing: 0.01em;
      color: var(--ink);
      pointer-events: none;
    }
    /* Version-row update button feedback: one gentle turn on tap. Respects
       reduced-motion (spins are decorative; the result line is the signal). */
    @keyframes ken-spin-once { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
    #settingsUpdateBtn.spin-once svg { animation: ken-spin-once 0.9s ease; }
    @media (prefers-reduced-motion: reduce) { #settingsUpdateBtn.spin-once svg { animation: none; } }
    #settingsUpdateBtn:active { background: var(--paper-3) var(--paper) repeat; border-color: var(--gold); }
    /* Paged help: content must clear the fixed bottom tab. */
    #helpPage1, #helpPage2, #helpPage3, #helpPage4, #helpPage5 { padding-bottom: 84px; }
    .hub-card {
      background: var(--warm-white) var(--paper) repeat;
      border: 2px solid var(--rule);
      border-radius: 18px;
      padding: 22px 24px;
      display: flex;
      align-items: center;
      gap: 18px;
      min-height: 110px;
      cursor: pointer;
      transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
      text-align: left;
      font-family: inherit;
      width: 100%;
    }
    .hub-card:active {
      background: var(--paper-2);
      border-color: var(--gold);
      transform: scale(0.98);
    }
    .hub-card .hub-icon {
      width: 56px;
      height: 56px;
      border-radius: 14px;
      background: var(--paper-3);
      display: flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
    }
    .hub-card .hub-icon svg {
      width: 30px;
      height: 30px;
      stroke: var(--gold-deep);
      stroke-width: 1.8;
      fill: none;
      stroke-linecap: round;
      stroke-linejoin: round;
    }
    .hub-card .hub-body { flex: 1; min-width: 0; }
    /* 2026-05-21: Settings hub-card text was hard-coded at 28px / 17px,
       so it ignored the user's chosen text size completely. The 17px
       subtitle was below the system's own smallest body token (22px) -
       Howard caught it as "the minimum text size isn't being honoured".
       Both now drive off the body tokens so they scale with the rest
       of the device: Compact 22/28 -> Medium 28/34 -> Large 36/42. */
    .hub-card .hub-ttl {
      font-family: 'Cormorant Garamond', serif;
      /* 2026-07-17: was 400. Cormorant at 400 has far thinner strokes than Jost at
         400, so the title read LIGHTER than the sans subtitle under it even though
         it is 6px bigger and darker - Howard's "the titles of each section aren't
         as prominent as the description" and "our branding has some thin writing"
         both land here, on the main Settings hub.
         600, NOT 500: we self-host only cormorant-garamond-400 and -600, so a
         request for 500 silently matches the 400 file and changes NOTHING. Proved
         on K50723 - rendered width of the same string was byte-identical at 400 and
         500 (350.281px) and only moved at 600 (381.516px). See the trap note in
         fonts/fonts-local.css. */
      font-weight: 600;
      font-size: var(--body-lg);
      color: var(--ink);
      letter-spacing: -0.005em;
      margin-bottom: 2px;
      line-height: 1.1;
    }
    .hub-card .hub-sub {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--ink-3);
      line-height: 1.4;
    }
    .hub-card .hub-chev {
      width: 28px;
      height: 28px;
      color: var(--ink-3);
      flex-shrink: 0;
    }
    .hub-card .hub-chev svg {
      width: 100%;
      height: 100%;
      stroke: currentColor;
      stroke-width: 2;
      fill: none;
      stroke-linecap: round;
      stroke-linejoin: round;
    }

    /* ===== HELP TOPIC CARDS (General Help redesign 2026-07-16) =====
       Full-width tappable rows mirroring the Settings hub cards, so the Help
       screen leads with warm, legible how-to topics instead of a diagnostics
       table. Icon + serif title + soft chevron. */
    .help-topic-card {
      background: var(--warm-white) var(--paper) repeat;
      border: 2px solid var(--rule);
      border-radius: 16px;
      padding: 18px 20px;
      margin: 0 0 12px;
      display: flex;
      align-items: center;
      gap: 16px;
      min-height: var(--touch-large);
      width: 100%;
      cursor: pointer;
      text-align: left;
      font-family: inherit;
      transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
    }
    .help-topic-card:active {
      background: var(--paper-2);
      border-color: var(--gold);
      transform: scale(0.98);
    }
    .help-topic-icon {
      width: 52px;
      height: 52px;
      border-radius: 13px;
      background: var(--paper-3);
      display: flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
    }
    .help-topic-icon svg { width: 28px; height: 28px; stroke: var(--gold-deep); fill: none; }
    .help-topic-ttl {
      flex: 1;
      min-width: 0;
      font-family: 'Cormorant Garamond', serif;
      /* 2026-07-17: this said 500 "heavier than the thin 400 serif so it stays
         legible for older eyes" - but it rendered at 400 and always had, so the
         General Help readability fix never actually landed. Only 400 and 600 are
         self-hosted; 500 silently falls back to 400. 600 is the real thing. */
      font-weight: 600;
      font-size: var(--body-lg);
      color: var(--ink);
      line-height: 1.15;
    }
    .help-topic-chev { width: 26px; height: 26px; color: var(--ink-3); flex-shrink: 0; }
    .help-topic-chev svg { width: 100%; height: 100%; stroke: currentColor; fill: none; }

    /* Muted "for our support team" block at the foot of the Help screen: the
       technical IDs a non-technical user should never have to parse, kept
       available but visually secondary. */
    .help-support-details {
      margin-top: 20px;
      padding-top: 8px;
      opacity: 0.75;
    }
    .help-support-label {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: var(--body-sm);
      letter-spacing: 0.04em;
      text-transform: none;
      color: var(--ink-3);
      margin-bottom: 4px;
    }

    .sub-page-header {
      display: flex;
      align-items: center;
      gap: 14px;
      padding: 18px 20px 6px;
    }
    /* Howard, 1 Aug 2026: the sub-view title sits at the RIGHT-hand end, which
       frees the space beside Back for the save note that used to be a
       full-width panel of its own below. */
    .sub-page-header .sub-page-title { margin-left: auto; text-align: right; }
    /* Back, with "save changes" beneath it - Howard, 4 Aug 2026. The first
       attempt set the two words "and save" alongside Back, one per line, and he
       called it off-brand and undesigned: two stacked fragments read as a
       column of loose words rather than one control. It is now a single quiet
       line under the word Back - a caption to the button, not a second label
       competing with it. Left-aligned to the B of Back so the two share an
       edge, lighter and italic so the eye takes Back first and the promise
       second. */
    .sub-page-back--save {
      flex-direction: column;
      align-items: flex-start;
      justify-content: center;
      gap: 1px;
      /* The tap target keeps its full height even though the label is now two
         lines: the older-hands touch floor is not negotiable for a control this
         central. */
      padding-top: 8px;
      padding-bottom: 8px;
    }
    .sub-page-back-word { white-space: nowrap; line-height: 1.05; }
    .sub-page-back-save {
      display: block;
      white-space: nowrap;
      font-style: italic;
      /* Lighter than Back, and a touch smaller, but still driven off the
         chosen text size - the older-eyes floor applies to every string on this
         device, this one included. Never a fixed pixel value. */
      font-weight: 300;
      font-size: calc(var(--body-sm) * 0.82);
      line-height: 1.1;
      color: var(--mid);
      opacity: 0.85;
      /* Sits under the B of Back rather than under the arrow, so the caption
         hangs off the word it describes. */
      padding-left: 1.15em;
    }
    /* The brand mark at the right-hand end of a sub-page header. Howard,
       4 Aug 2026: this was a CSS-set "The Ken" in Cormorant, which is not the
       brand - the brand is the drawn mark in
       OneDrive\...\The Ken\Brand\The Ken logo textured.png, and the textured
       cut is the one we agreed. It shipped in assets/ since May and every
       surface reached for the clean cut instead. Height is tied to --header so
       it grows with the user's chosen text size like the title it replaced. */
    .sub-page-brand-mark {
      margin-left: auto;
      height: calc(var(--header) * 1.55);
      width: auto;
      display: block;
      flex-shrink: 0;
      align-self: center;
    }

    .sub-page-savenote {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      /* The older-eyes floor still applies: this tracks the chosen text size
         rather than shrinking to fit, and wraps to two lines if it must. */
      font-size: var(--body-sm);
      color: #15532D;
      line-height: 1.25;
      flex: 1 1 auto;
      min-width: 0;
    }
    /* Canonical back button (device/UI_BUTTON_CRIBSHEET.md). Was a 56px circle
       with a chevron icon - read as a different control from the wizard /
       settings backs (Howard 2026-06-23). Now the same "← Back" text-link as
       .setup-back / .settings-back; kept in the .sub-page-header flex row. */
    .sub-page-back {
      font-family: 'Jost', sans-serif;
      font-weight: 400;
      font-size: var(--body);
      color: var(--mid);
      background: none;
      border: none;
      cursor: pointer;
      padding: 12px 16px;
      min-height: var(--touch-min);
      min-width: var(--touch-min);
      display: flex;
      align-items: center;
      flex-shrink: 0;
    }
    .sub-page-back:active { color: var(--ink); }
    .sub-page-back svg {
      width: 28px;
      height: 28px;
      stroke: var(--ink);
      stroke-width: 2.2;
      fill: none;
      stroke-linecap: round;
      stroke-linejoin: round;
    }
    .sub-page-title {
      font-family: 'Cormorant Garamond', serif;
      font-weight: 400;
      /* 2026-05-21: was hard-coded 30px; now drives off --header so
         it tracks the user's text-size choice (32 / 40 / 48). */
      font-size: var(--header);
      color: var(--ink);
      letter-spacing: -0.01em;
      line-height: 1.1;
    }

    /* .sub-status-strip removed 1 Aug 2026 - the save note now lives in the
       sub-page header beside Back (see .sub-page-savenote above). */

    /* Sub-page group label. Sentence case (not ALL CAPS) per CLAUDE.md
       no-ALL-CAPS rule; the mockup uses tracked uppercase at 14px but
       Howard's rule caps that pattern at 10px. Sentence case + tracking
       gives a similar feel without breaking the rule. */
    .sub-group-label {
      font-family: 'Jost', sans-serif;
      font-weight: 500;
      font-size: 14px;
      color: var(--ink-3);
      letter-spacing: 0.05em;
      padding: 22px 28px 8px;
      margin: 0;
    }

    /* Sub-page wrappers toggle via display. Settings inherits this; later
       surfaces (Home / Menu / etc) will reuse the same machinery. */
    .sub-page {
      display: none;
    }
    .sub-page.active {
      display: block;
    }
    .hub-view {
      display: block;
    }
    .hub-view.hidden {
      display: none;
    }

    /* data-sub-hidden = "hidden because the current hub sub-view does not
       include this section". Uses !important so it overrides any inline
       display from existing handlers (Settings sections with
       display:flex / display:none for admin-only). The admin-only inline
       display:none on the Developer section continues to win when the
       attribute is removed, because the inline rule still applies. */
    [data-sub-hidden] {
      display: none !important;
    }

    /* data-capability-off = "this control belongs to a capability that is
       switched off in capability-flags.js". Stamped once at boot by
       kenApplyCapabilityGates(); nothing removes it while the flag is off.
       !important because two other things in this file write display on the
       same elements and would otherwise bring a dead control back: the hub
       sub-view logic removes data-sub-hidden when its group is shown, and
       kenPaginate() resets every block it measures to display:''. A gate that
       any later line can undo is not a gate. */
    [data-capability-off] {
      display: none !important;
    }

/* ============================================================
   FONT-SIZE REFACTOR (2026-05-28 R8 polish)
   Each rule below replaces an inline `font-size:` declaration that
   was previously a magic number in index.html. Naming is semantic
   (named for use, not size) so sibling-parity is enforceable.
   ============================================================ */

.boot-launching-text {
  font-size: 28px;   /* the framebuffer splash's size - see boot-splash.html */
  font-weight: 400;
  color: #252322;
  letter-spacing: 0.04em;
}
.boot-advisory-text {
  position: absolute;
  bottom: 90px;
  left: 0;
  right: 0;
  text-align: center;
  color: #6B6459;
  /* 2026-07-17: was a hardcoded 20px, under the 22px older-eyes floor. This is the
     one line that tells a worried user the blank screen is normal - it has to be
     readable by the person least able to read it. */
  font-size: var(--body-sm);
  line-height: 1.4;
  letter-spacing: 0.02em;
}
.welcome-eyebrow {
  font-family: 'Cormorant Garamond', serif;
  font-weight: 400;
  font-size: 28px;
  color: var(--mid);
  font-style: italic;
  text-align: center;
  line-height: 1;
  margin: 0 0 6px;
}
.setup-code-display-large {
  font-family: 'Jost', sans-serif;
  font-size: 64px;
  font-weight: 600;
  letter-spacing: 12px;
  color: var(--ink);
  background: #fff;
  border: 3px solid var(--gold);
  border-radius: 16px;
  padding: 32px 40px;
  margin: 32px 0 16px 0;
  text-align: center;
}
.setup-code-display-medium {
  font-family: 'Jost', sans-serif;
  font-size: 44px;
  font-weight: 600;
  letter-spacing: 12px;
  color: var(--ink);
  background: #fff;
  border: 2px solid #e8e4dc;
  border-radius: 12px;
  padding: 14px 24px;
  margin: 12px 0 6px 0;
  min-width: 420px;
  text-align: center;
  min-height: 72px;
  box-sizing: border-box;
}
.idle-clock-time {
  font-family: 'Cormorant Garamond', serif;
  font-weight: 500;
  font-size: 90px;
  color: var(--cream);
  line-height: 1;
}
.home-hearing-label {
  font-size: var(--body-sm);
  font-weight: 500;
}
.passcode-update-new-code {
  font-family: monospace;
  font-size: 96px;
  font-weight: 600;
  letter-spacing: 24px;
  color: var(--ink);
  background: #FDF8F0;
  padding: 24px 32px;
  border-radius: 16px;
  display: inline-block;
}
.passcode-update-recovery-code {
  font-family: monospace;
  font-size: 48px;
  font-weight: 500;
  letter-spacing: 12px;
  color: var(--ink);
}
.night-greeting-text {
  font-family: 'Cormorant Garamond', serif;
  font-size: 42px;
  font-weight: 400;
  color: var(--ink);
}
.night-greeting-time {
  font-family: 'Jost', sans-serif;
  font-size: 64px;
  font-weight: 500;
  color: var(--ink);
  margin-top: 8px;
}
.ken-overlay-success-tick {
  font-size: 48px;
  margin-bottom: 16px;
}
.ken-overlay-title {
  font-size: 28px;
  font-weight: 400;
  margin-bottom: 16px;
}
.ken-overlay-title-bold {
  font-size: 28px;
  font-weight: 500;
  margin-bottom: 16px;
}
.ken-overlay-body {
  font-size: var(--body);
  color: #6B6459;
  margin-bottom: 32px;
}
.ken-overlay-body-wrap {
  font-size: var(--body);
  color: #6B6459;
  max-width: 480px;
  line-height: 1.5;
}
/* 2026-07-18 (Howard): reversed back to GOLD. The 2026-07-17 gold-to-espresso
   sweep is undone by the new rule - espresso only where a red button shares the
   screen. This recovery card has no red button, so it is gold. Ink on #C4A962 is
   6.85:1, and the label is var(--btn) (28px+), clear of the 24px floor. */
.ken-overlay-restart-btn {
  padding: 14px 24px;
  min-height: var(--touch-min);
  background: var(--gold) var(--paper) repeat;
  color: var(--ink);
  border: none;
  border-radius: 14px;
  font-size: var(--btn);
  font-weight: 500;
  cursor: pointer;
}
.lang-overlay-cover {
  position: fixed;
  inset: 0;
  background: #FDFAF5;
  z-index: 9998;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: 'Cormorant Garamond', serif;
  font-size: 48px;
  color: #252322;
}
.contact-tile-name {
  font-size: 22px;
  font-weight: 500;
  color: var(--ink);
}
.contact-tile-fit-badge {
  font-size: var(--body-sm);
  font-weight: 400;
}
.contact-tile-fit-badge.fits {
  color: var(--ink);
}
.contact-tile-fit-badge.no-fit {
  color: var(--mid);
}
.toggle-glyph {
  font-size: 24px;
}

/* ============================================================
   SETTINGS > ACCESSIBILITY (9 Aug 2026)
   ============================================================
   An Admin or a Carer records what the person finds difficult and the Ken
   suggests what to switch on. Two routes into the same result: a plain list of
   the seven difficulties, and a guided walk that asks about one per screen.

   No control in here depends on fine motor control. Every difficulty and every
   suggestion is a full-width button with an 80px floor, chosen or switched by a
   single tap; there is no slider, no drag, no long-press and no double-tap.
   State is never carried by colour alone - a chosen difficulty and a switched-on
   suggestion each carry a tick AND the word ("Chosen" / "On"), and the green is
   the deuteranopia-safe --green. Red does not appear here at all: nothing on
   this screen destroys anything. */
.acc-hidden { display: none !important; }

.acc-lead { padding: 18px 20px 6px; }
.acc-lead-ttl {
  font-family: 'Cormorant Garamond', serif;
  font-weight: 600;
  font-size: var(--section-title);
  color: var(--ink);
  line-height: 1.15;
  margin-bottom: 6px;
}
.acc-lead-sub {
  font-family: 'Jost', sans-serif;
  font-weight: 400;
  font-size: var(--body);
  color: var(--ink-3);
  line-height: 1.35;
}

/* A difficulty, in the plain list. The whole row is the tap target. */
.acc-need {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  width: calc(100% - 40px);
  margin: 10px 20px;
  padding: 16px 18px;
  min-height: var(--touch-comfortable);
  background: var(--warm-white) var(--paper) repeat;
  border: 2px solid var(--rule);
  border-radius: 18px;
  text-align: left;
  font-family: inherit;
  cursor: pointer;
}
.acc-need:active { background: var(--paper-2); border-color: var(--gold); }
.acc-need.chosen { border-color: var(--green); background: var(--paper-2) var(--paper) repeat; }
.acc-need-mark {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 12px;
  border: 2px solid var(--rule-2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  line-height: 1;
  color: var(--green);
  background: var(--warm-white);
}
.acc-need.chosen .acc-need-mark { border-color: var(--green); }
/* These are spans inside a button, so they are inline until told otherwise -
   without this the title, the help line and the state run together on one line. */
.acc-need-body { flex: 1; min-width: 0; display: block; }
.acc-need-ttl,
.acc-need-help,
.acc-need-state { display: block; }
.acc-need-ttl {
  font-family: 'Jost', sans-serif;
  font-weight: 500;
  font-size: var(--body-lg);
  color: var(--ink);
  line-height: 1.2;
  margin-bottom: 4px;
}
.acc-need-help {
  font-family: 'Jost', sans-serif;
  font-weight: 400;
  font-size: var(--body-sm);
  color: var(--ink-3);
  line-height: 1.35;
}
.acc-need-state {
  font-family: 'Jost', sans-serif;
  font-weight: 500;
  font-size: var(--body-sm);
  color: var(--mid);
  margin-top: 6px;
}
.acc-need.chosen .acc-need-state { color: var(--green); }

/* The two buttons that move the screen on. */
.acc-primary,
.acc-secondary {
  display: block;
  width: calc(100% - 40px);
  margin: 14px 20px;
  min-height: var(--touch-comfortable);
  padding: 16px 22px;
  border-radius: 16px;
  font-family: 'Jost', sans-serif;
  font-size: var(--body);
  font-weight: 500;
  line-height: 1.3;
  cursor: pointer;
}
/* Gold with ink, the house default. These were espresso, which CLAUDE.md
   reserves for a screen showing BOTH a green button and a red one - the
   Accessibility screen has neither, and the rule records having been misapplied
   twice before. Caught by the UAT of 2026-08-10. Ink on gold is 6.85:1. */
.acc-primary {
  background: var(--gold) var(--paper) repeat;
  color: var(--ink);
  border: none;
}
.acc-secondary {
  background: var(--warm-white) var(--paper) repeat;
  color: var(--ink);
  border: 2px solid var(--gold);
}
.acc-primary:active { background: #3E362D; }
.acc-secondary:active { background: var(--paper-2); }
.acc-note {
  margin: 0 20px 10px;
  font-family: 'Jost', sans-serif;
  font-size: var(--body-sm);
  color: var(--mid);
  line-height: 1.35;
}

/* The guided walk: one difficulty per screen. */
.acc-guide-count {
  padding: 18px 20px 0;
  font-family: 'Jost', sans-serif;
  font-size: var(--body-sm);
  font-weight: 500;
  color: var(--mid);
}
.acc-guide-block {
  margin: 14px 20px;
  padding: 22px;
  background: var(--warm-white) var(--paper) repeat;
  border: 2px solid var(--rule);
  border-radius: 18px;
}
.acc-guide-label {
  font-family: 'Cormorant Garamond', serif;
  font-weight: 600;
  font-size: var(--header);
  color: var(--ink);
  line-height: 1.15;
  margin-bottom: 10px;
}
.acc-guide-help {
  font-family: 'Jost', sans-serif;
  font-weight: 400;
  font-size: var(--body);
  color: var(--ink-3);
  line-height: 1.4;
}
.acc-guide-ask {
  margin: 6px 20px 4px;
  font-family: 'Jost', sans-serif;
  font-size: var(--body-lg);
  font-weight: 500;
  color: var(--ink);
}
.acc-guide-actions {
  display: flex;
  gap: 14px;
  margin: 10px 20px;
}
.acc-yes,
.acc-no {
  flex: 1 1 0;
  min-width: 0;
  min-height: 96px;
  border-radius: 16px;
  font-family: 'Jost', sans-serif;
  font-size: var(--body-lg);
  font-weight: 500;
  cursor: pointer;
}
/* Gold with ink, for the same reason as .acc-primary above. */
.acc-yes {
  background: var(--gold) var(--paper) repeat;
  color: var(--ink);
  border: none;
}
.acc-no {
  background: var(--warm-white) var(--paper) repeat;
  color: var(--ink);
  border: 2px solid var(--rule-2);
}
.acc-yes:active { background: #3E362D; }
.acc-no:active { background: var(--paper-2); }

/* What the Ken suggests, grouped under the difficulty it answers.
   The heading and every row are SIBLINGS, not a wrapper full of rows: the
   pager splits an over-tall block one level into its children, so a group of
   six suggestions inside a wrapper would keep one page to itself and strand its
   last rows below the fold. Flat, each row pages on its own. */
.acc-group-ttl {
  font-family: 'Cormorant Garamond', serif;
  font-weight: 600;
  font-size: var(--section-title);
  color: var(--ink);
  line-height: 1.15;
  margin: 12px 20px 8px;
}
.acc-feature {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  width: calc(100% - 40px);
  margin: 0 20px 10px;
  padding: 16px 18px;
  min-height: var(--touch-comfortable);
  background: var(--warm-white) var(--paper) repeat;
  border: 2px solid var(--rule);
  border-radius: 18px;
  text-align: left;
  font-family: inherit;
  cursor: pointer;
}
.acc-feature:active { background: var(--paper-2); border-color: var(--gold); }
.acc-feature.on { border-color: var(--green); }
/* A feature with no switch is not a button and must not look like one. */
.acc-feature.fixed { cursor: default; border-style: dashed; }
.acc-feature-mark {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: 12px;
  border: 2px solid var(--rule-2);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
  line-height: 1;
  color: var(--green);
  background: var(--warm-white);
}
.acc-feature.on .acc-feature-mark,
.acc-feature.fixed .acc-feature-mark { border-color: var(--green); }
/* Spans inside a button: inline until told otherwise, and inline would run the
   name, the reason and the state together on one line. */
.acc-feature-body { flex: 1; min-width: 0; display: block; }
.acc-feature-ttl,
.acc-feature-what,
.acc-feature-state { display: block; }
.acc-feature-ttl {
  font-family: 'Jost', sans-serif;
  font-weight: 500;
  font-size: var(--body-lg);
  color: var(--ink);
  line-height: 1.2;
  margin-bottom: 4px;
}
.acc-feature-what {
  font-family: 'Jost', sans-serif;
  font-weight: 400;
  font-size: var(--body-sm);
  color: var(--ink-3);
  line-height: 1.35;
}
.acc-feature-state {
  font-family: 'Jost', sans-serif;
  font-weight: 500;
  font-size: var(--body-sm);
  color: var(--mid);
  margin-top: 6px;
}
.acc-feature.on .acc-feature-state { color: var(--green); }

/* What the recommendation actually changed, named one per line. */
.acc-result {
  margin: 0 20px 14px;
  padding: 18px 20px;
  background: var(--paper-2) var(--paper) repeat;
  border: 2px solid var(--green);
  border-radius: 18px;
}
.acc-result-ttl {
  font-family: 'Jost', sans-serif;
  font-weight: 500;
  font-size: var(--body-lg);
  color: var(--ink);
  line-height: 1.25;
  margin-bottom: 8px;
}
.acc-result-item {
  font-family: 'Jost', sans-serif;
  font-weight: 400;
  font-size: var(--body);
  color: var(--ink);
  line-height: 1.4;
  padding-left: 4px;
}

/* ============================================================
   The two questions on the medication card (9 Aug 2026)
   ============================================================
   Side by side in one row so the card gains 80px, not 192px: the three answers
   below already stand at 100 + 100 + 80, and this card must fit 600x1024 at
   Large text with nothing below the fold. Either button is absent when the Ken
   has no answer for it, and the remaining one takes the row on its own. */
.reminder-questions {
  width: 100%;
  display: flex;
  gap: 12px;
  margin-bottom: 14px;
}
.reminder-q-btn {
  flex: 1 1 0;
  min-width: 0;
  min-height: var(--touch-comfortable);
  padding: 12px 14px;
  border-radius: 14px;
  background: var(--warm-white) var(--paper) repeat;
  color: var(--ink);
  border: 2px solid var(--gold);
  font-family: 'Jost', sans-serif;
  font-size: var(--body-sm);
  font-weight: 500;
  line-height: 1.25;
  cursor: pointer;
}
.reminder-q-btn:active { background: var(--paper-2); }
/* The dose answers carry what the Ken will DO with the answer, underneath the
   answer itself (UAT 2026-08-10). Not a whisper: --body-sm is the same size as
   the Ken's ordinary body text, on ink rather than a grey, because "this dose
   is recorded as not taken" is not fine print. It never says who gets told -
   Howard, 11 Aug 2026. Block spans so label and note stack whatever the
   button's own font-size is. */
.reminder-btn-label { display: block; }
.reminder-btn-note {
  display: block;
  margin-top: 8px;
  font-family: 'Jost', sans-serif;
  font-size: var(--body-sm);
  font-weight: 500;
  line-height: 1.25;
  color: var(--ink);
}
.reminder-btn-note:empty { display: none; margin-top: 0; }

/* SPACING GIVES WAY BEFORE ANYTHING A PERSON READS OR PRESSES (11 Aug 2026).
   Measured at the largest text size, the card could not hold the medication
   name, the two questions and three answers at once on the final "Remind me
   later", and the fit ladder was spending "Who set this reminder?" and "Where
   is my medication?" to make room. Somebody on the largest text is exactly the
   person who most needs those two answered. This is the rung that goes first
   instead: whitespace only, no control and no sentence. Worth ~88px, which is
   more than the deficit it exists to cover. */
.reminder-notification.reminder-tight { padding-top: 20px; padding-bottom: 20px; }
.reminder-notification.reminder-tight .reminder-notif-actions { gap: 10px; }
.reminder-notification.reminder-tight .reminder-notif-text { margin-top: 8px; margin-bottom: 8px; }
.reminder-notification.reminder-tight .reminder-notif-label { margin-bottom: 4px; }
.reminder-notification.reminder-tight .reminder-notif-icon { margin-bottom: 12px; }
.reminder-answer {
  width: 100%;
  margin-bottom: 14px;
  padding: 16px 18px;
  background: var(--paper-2) var(--paper) repeat;
  border: 2px solid var(--rule-2);
  border-radius: 14px;
  font-family: 'Jost', sans-serif;
  font-size: var(--body);
  font-weight: 400;
  color: var(--ink);
  line-height: 1.35;
  text-align: center;
}
/* ============================================================
   THE PHOTOGRAPH OF THE PILL (9 Aug 2026, Howard)
   ============================================================
   A name and a dose are a reading task. A picture of the actual tablet is
   recognition, which is what still works when reading and memory do not.

   WHY IT STANDS WHERE THE BELL WAS. The bell icon says "a reminder" to somebody
   already looking at a reminder - it earns none of the 124px it takes. The
   photograph goes in its place and takes the space that was doing nothing.

   THE SIZE IS DERIVED, NOT CHOSEN. The panel is a 10.1" 1024x600 (221.3 x
   129.7mm), so a pixel is 0.216mm across whichever way it is mounted. At 320px
   the picture is 69mm on the glass; a 10mm tablet filling the usual third of a
   phone photo lands at about 21mm, which at arm's length (600mm) looks the size
   the same tablet does held 250mm from the eye - i.e. roughly twice life size.
   Below 180px it stops being recognition and becomes decoration, so the fitting
   code drops the picture rather than shrink past it (see reminderFitPhoto).

   NOTHING BELOW THE FOLD. The card is fixed to the screen and never scrolls, so
   the photograph is sized to what is left after the three answers and the two
   questions, measured live at whatever text size the person has chosen. */
.reminder-photo-wrap {
  display: none;
  flex: 0 1 auto;
  min-height: 0;
  width: 100%;
  align-items: center;
  justify-content: center;
  margin-bottom: 20px;
}
.reminder-notification.has-photo .reminder-photo-wrap { display: flex; }
/* The bell has nothing to add once a real picture is there. */
.reminder-notification.has-photo .reminder-notif-icon { display: none; }
/* The name stops absorbing the slack so the picture can have it. */
.reminder-notification.has-photo .reminder-notif-text { flex: 0 1 auto; }
/* A photograph earns the padding the decoration was spending. */
.reminder-notification.has-photo { padding: 24px 32px; }
.reminder-photo {
  width: auto;
  max-width: 100%;
  object-fit: contain;
  border-radius: 18px;
  border: 3px solid var(--rule-2);
  background: var(--paper-2) var(--paper) repeat;
}

/* The medication name is the tallest thing on the card and the only one that
   grows with the text size, so it is the one that has to give when the two
   questions and an answer are on screen. min-height:0 lets it. */
.reminder-notif-text { min-height: 0; }

/* ============================================================
   SETTINGS > MEDICATION PHOTOGRAPHS (9 Aug 2026, Howard)
   ============================================================
   The carer's side of the same feature: the screen where the picture on the
   reminder card is taken, with the Ken's own camera.

   It borrows the Videomails list chrome (.vm-row and friends) rather than
   inventing a row style, so the tap target, the contrast and the single-tap
   guarantee all come for free. Only what is genuinely different is written
   here: the two status flags, the picture frame and the button column. */
.med-photo-intro {
  padding: 4px 24px 12px;
  font-family: 'Jost', sans-serif;
  font-size: var(--body-sm);
  color: var(--mid);
  line-height: 1.4;
}
/* The two states are told in words on every row. The colour is a second cue on
   top of the sentence, never the signal itself, because the person who owns
   this product cannot separate red from green. */
.med-photo-flag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--mid);
}
.med-photo-flag.has { color: #2AA06A; }
.med-photo-glyph { display: inline-flex; align-items: center; }
.med-photo-glyph svg { width: 20px; height: 20px; }

.med-photo-one {
  padding: 8px 24px 24px;
  max-width: 720px;
  margin: 0 auto;
  width: 100%;
  box-sizing: border-box;
  text-align: center;
}
.med-photo-one-name {
  font-family: 'Cormorant Garamond', serif;
  font-weight: 600;
  font-size: var(--header);
  color: var(--ink);
  line-height: 1.15;
}
.med-photo-one-when {
  font-family: 'Jost', sans-serif;
  font-size: var(--body);
  color: var(--mid);
  margin-top: 4px;
  margin-bottom: 16px;
}
/* A fixed frame, so the preview, the still just taken and the picture already
   on the reminder all occupy exactly the same space. Without it the buttons
   below jumped every time the picture changed, and a button that moves under a
   finger already on its way down is the tremor failure this device exists to
   avoid. */
.med-photo-shot {
  width: 100%;
  height: 300px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--paper-2) var(--paper) repeat;
  border: 3px solid var(--rule-2);
  border-radius: 18px;
  overflow: hidden;
  margin-bottom: 16px;
}
.med-photo-img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
}
/* Never a bare colour: this is a sentence, and the colour only backs it up. */
.med-photo-status {
  display: none;
  font-family: 'Jost', sans-serif;
  font-size: var(--body-sm);
  line-height: 1.4;
  color: var(--ink);
  background: rgba(42, 160, 106, 0.10);
  border-left: 4px solid #2AA06A;
  border-radius: 8px;
  padding: 12px 16px;
  margin-bottom: 16px;
  text-align: left;
}
.med-photo-status.bad {
  background: rgba(217, 74, 61, 0.10);
  border-left-color: #D94A3D;
}
.med-photo-btn {
  width: 100%;
  margin-bottom: 14px;
}
.med-photo-secondary {
  background: transparent;
  color: var(--ink);
  border: 2px solid var(--mid);
}
