* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

html {
    /* PREVENT: Automatic zooming and text size adjustment */
    -webkit-text-size-adjust: 100%;
    -ms-text-size-adjust: 100%;
    text-size-adjust: 100%;
    /* PREVENT: User zoom */
    -webkit-user-zoom: disabled;
    user-zoom: disabled;
}

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
}

/* Body uses .elastic-ambient from elastic-design.css (warm dark background, Inter, tokens) */
body {
    margin: 0 !important;
    padding: 0 !important;
}

/* App wrapper — design system tokens inherited from body.elastic-ambient */
.app-wrapper {
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 20px;
}

/* Auth overlay — glass modal, accent CTA (align with main site) */
.auth-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.75);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}
.auth-modal {
    background: var(--tile-fill);
    border: 1px solid var(--tile-border);
    border-radius: var(--r-card);
    padding: 2.5rem;
    text-align: center;
    max-width: 400px;
    width: 90%;
    color: var(--text-primary);
}
.auth-modal h2 { color: var(--text-primary); font-family: var(--sans); font-weight: 300; margin-bottom: 0.5rem; }
.auth-modal p { color: var(--text-secondary); font-size: 0.95rem; margin-bottom: 1.5rem; }
.auth-input {
    width: 100%;
    padding: 12px 14px;
    margin: 6px 0;
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    background: var(--glass-fill);
    color: var(--text-primary);
    font-family: var(--sans);
    font-size: 1rem;
    box-sizing: border-box;
}
.auth-input::placeholder { color: var(--text-faint); }
.auth-input:focus { outline: none; border-color: var(--accent); }
.auth-submit {
    width: 100%;
    padding: 12px;
    margin: 10px 0;
    background: var(--accent);
    color: #fff;
    border: none;
    border-radius: var(--r-pill);
    font-family: var(--sans);
    font-weight: 500;
    font-size: 1rem;
    cursor: pointer;
    transition: all 0.2s ease;
}
/* (hover: hover) gate, 2026-08-22: without it, a :hover rule that moves the element
   (transform) makes iOS Safari treat a touchscreen's first tap as "entering hover"
   rather than a click — the tap is swallowed and needs a second one to register. Same
   bug, same fix, on .btn-primary/.debug-button/.birthday-button below — traced from
   Luke's real-phone report that Save Entry "seemingly would not submit at all". */
@media (hover: hover) and (pointer: fine) {
    .auth-submit:hover { background: var(--accent-hover); transform: translateY(-1px); }
}
.auth-toggle { color: var(--text-secondary); cursor: pointer; font-size: 0.9rem; margin-top: 0.75rem; }
.auth-toggle:hover { color: var(--accent); }
.auth-error { color: #e8787a; font-size: 0.85rem; margin-top: 0.5rem; display: none; }

/* Now / Fleeting Now / Calibration — one exclusive full-screen takeover, modeled on
   .auth-overlay above (position:fixed, full viewport, z-index:1000 — same layer; both are
   exclusive full-screen states). Deliberately NOT boxed in a card like .auth-modal — this
   wants open, breathing space (chaos -> stillness -> presence), not a dialog. Ported from
   public/app/now-calibration-preview.html/.css, browser-verified before this was wired in. */
.now-overlay {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    background: rgba(0, 0, 0, 0.86);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}
.now-view {
    display: flex; flex-direction: column; align-items: center; text-align: center;
    padding: 32px; position: relative; width: 100%; max-width: 420px;
}
.close-x {
    position: absolute; top: -8px; right: 8px; width: 30px; height: 30px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center; color: var(--text-faint);
    cursor: pointer; font-size: 17px; border: 1px solid var(--tile-border);
}
@media (hover: hover) and (pointer: fine) {
    .close-x:hover { color: var(--text-primary); border-color: var(--text-faint); }
}
.fleeting-now {
    font-family: var(--mono); font-size: 20px; letter-spacing: 2px;
    color: var(--text-faint); margin-bottom: 20px; min-height: 26px; filter: blur(0px);
}
.now-label { font-family: var(--sans); font-size: 27px; font-weight: 300; color: var(--text-primary); margin-bottom: 10px; }
.now-sub {
    font-family: var(--sans); font-size: 14px; color: var(--text-secondary);
    max-width: 280px; line-height: 1.55; margin-bottom: 30px;
}
.live-reading {
    font-family: var(--mono); font-size: 12px; letter-spacing: 1px;
    color: var(--accent); margin-bottom: 26px; min-height: 16px;
}
.now-start, .now-again {
    background: var(--accent); color: #fff; border: none; border-radius: var(--r-tile);
    padding: 14px 36px; font-family: var(--sans); font-size: 15px; font-weight: 600; cursor: pointer;
    transition: transform 0.15s ease, opacity 0.15s ease;
}
@media (hover: hover) and (pointer: fine) {
    .now-start:hover, .now-again:hover { opacity: 0.88; transform: translateY(-1px); }
}
.now-session { position: relative; }
.fleeting-now-bg {
    position: absolute; top: 30%; left: 50%; transform: translate(-50%, -50%);
    font-family: var(--mono); font-size: 42px; letter-spacing: 3px;
    color: rgba(255, 255, 255, 0.32); pointer-events: none;
    transition: opacity 1.4s ease-in-out; filter: blur(0px);
}
.session-center { position: relative; z-index: 2; }
.session-phase {
    font-family: var(--mono); font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.14em;
    color: var(--text-faint); margin-bottom: 14px;
}
.session-block { font-family: var(--mono); font-size: 13px; letter-spacing: 1px; color: var(--text-faint); margin-bottom: 6px; }
.session-count {
    font-family: var(--sans); font-size: 96px; font-weight: 200; line-height: 1; color: var(--text-primary);
    font-variant-numeric: tabular-nums;
}
@keyframes breatheCount { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.035); } }
.session-count.hundred { font-size: 80px; animation: breatheCount 3.2s ease-in-out infinite; }
.session-unit {
    font-family: var(--mono); font-size: 12px; letter-spacing: 0.18em; color: var(--accent);
    margin-top: 6px; font-weight: 600;
}
.session-caption { font-family: var(--sans); font-size: 14px; color: var(--text-secondary); margin-top: 18px; max-width: 260px; }
.complete-mark {
    width: 56px; height: 56px; border-radius: 50%; background: var(--color-day-orange);
    display: flex; align-items: center; justify-content: center;
    font-size: 26px; color: #fff; margin-bottom: 20px;
}
.complete-title { font-family: var(--sans); font-size: 22px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px; }
.complete-sub { font-family: var(--sans); font-size: 14px; color: var(--text-secondary); margin-bottom: 30px; }

/* Navigation — tile bar, accent active (main site style) */
.app-nav {
    display: block;
    background: var(--tile-fill);
    border: 1px solid var(--tile-border);
    border-radius: var(--r-card);
    padding: 6px;
    margin: 0 0 20px 0;
}

.nav-pills { display: flex; gap: 6px; }

.nav-pill {
    flex: 1;
    padding: 12px 16px;
    border: none;
    border-radius: var(--r-tile);
    background: transparent;
    color: var(--text-secondary);
    font-family: var(--mono);
    font-size: 0.75rem;
    letter-spacing: 0.08em;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.2s ease;
    text-align: center;
}
.nav-pill:hover { color: var(--text-primary); }
.nav-pill.active {
    background: var(--accent-faint);
    color: var(--accent);
}

/* Global user bar — tile, no green */
.global-user-bar {
    background: var(--tile-fill);
    border: 1px solid var(--tile-border);
    border-radius: 0 0 var(--r-card) var(--r-card);
    margin: 0px 0 10px 0;
    color: var(--text-primary);
    z-index: 100;
    position: relative;
}

/* Status Rail — theme+participation (left/primary) + condensed account chip
   (right), replacing the old visible-email + two-button bar. Ported from the
   approved mockup, docs/status-rail-preview-v3.html, onto this app's real
   design tokens. See docs/status-rail-points-mvp-brief.md. */
.status-rail {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 20px;
    font-size: 0.9em;
    position: relative;
}

.rail-primary {
    display: flex;
    flex-direction: column;
    /* stretch, not flex-start: flex-start shrinks each row to its own content
       width, leaving .points-feedback (longer than .points-quiet) no reserved
       box to render into — cramped especially at mobile widths. */
    align-items: stretch;
    gap: 1px;
    flex: 1 1 auto;
    min-width: 0;
}

.theme-kicker {
    font-size: 0.7em;
    color: var(--text-secondary);
    letter-spacing: 0.02em;
}
.theme-kicker .theme-word {
    color: var(--accent);
    font-weight: 500;
}

.points-row {
    font-size: 1.05em;
    font-weight: 500;
    color: var(--text-primary);
    position: relative;
    min-height: 22px;
    display: flex;
    align-items: center;
    width: 100%;
}
.points-quiet {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
    transition: opacity .35s ease, transform .35s ease;
}
.points-quiet .spark { color: var(--accent); }
.points-feedback {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--accent);
    font-weight: 500;
    white-space: nowrap; /* never wraps or gets squeezed */
    position: absolute;
    left: 0;
    right: 0; /* full-width box to sit in, not just a point */
    opacity: 0;
    transform: translateY(4px);
    transition: opacity .35s ease, transform .35s ease;
}
.points-row.feedback-active .points-quiet { opacity: 0; transform: translateY(-4px); }
.points-row.feedback-active .points-feedback { opacity: 1; transform: translateY(0); }

/* Groups the new Now header button with the existing account cluster so
   .status-rail's justify-content:space-between still only sees two children
   (rail-primary left, this group right) — see now-calibration-preview.html,
   browser-verified before this was wired in. */
.header-right { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.now-header-btn {
    border: 1px solid var(--tile-border);
    border-radius: var(--r-pill);
    padding: 6px 16px;
    font-family: inherit;
    font-size: 0.85em;
    color: var(--text-primary);
    background: var(--tile-fill);
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
@media (hover: hover) and (pointer: fine) {
    .now-header-btn:hover { background: var(--tile-fill-h); border-color: var(--accent); }
}
.pop-item.now-item { color: var(--accent); font-weight: 600; }

.account-cluster { position: relative; flex: 0 0 auto; }
.account-chip {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background: var(--tile-border);
    color: var(--text-primary);
    border: 1px solid transparent;
    font-family: inherit;
    font-size: 0.75em;
    font-weight: 500;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {
    .account-chip:hover { border-color: var(--color-day-orange); }
}
.account-popover {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    /* Solid fill, not --tile-fill: this floats above scrolled content, same
       bleed-through risk as sticky/fixed bars — see --tile-fill-solid's
       usage elsewhere in this file. */
    background: var(--tile-fill-solid);
    border: 1px solid var(--tile-border);
    border-radius: var(--r-tile);
    padding: 10px;
    width: 200px;
    display: none;
    flex-direction: column;
    gap: 6px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
    z-index: 5;
}
.account-popover.open { display: flex; }
.account-popover .pop-email {
    font-size: 0.8em;
    color: var(--text-secondary);
    padding-bottom: 6px;
    border-bottom: 1px solid var(--tile-border);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.account-popover button {
    background: none;
    border: none;
    color: var(--text-primary);
    font-family: inherit;
    font-size: 0.85em;
    text-align: left;
    padding: 6px 4px;
    border-radius: 6px;
    cursor: pointer;
    width: 100%;
}
@media (hover: hover) and (pointer: fine) {
    .account-popover button:hover { background: rgba(255, 255, 255, 0.06); }
}

.container {
    display: grid;
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
    gap: 0;
    margin: 0;
    padding: 0;
    min-height: calc(100vh - 120px);
}

/* Content sections — glass/tile (main site style) */
.content-section {
    /* Exposed so full-bleed children (Journey's .year-divider/.day-divider)
       can negative-margin out to the edge and re-pad back in, instead of
       hardcoding 30px/15px/10px to match this rule's padding at each
       breakpoint below. */
    --content-padding: 30px;
    background: var(--tile-fill);
    border: 1px solid var(--tile-border);
    border-radius: var(--r-card);
    padding: var(--content-padding);
    margin: 0 0 20px 0;
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    /* Was overflow-y: auto. On desktop this box has no capped height (only
       min-height above), so its content always fits and it never actually
       scrolls — the page/body scrolls instead, same as it always has. But
       `overflow: auto` establishes a CSS "scroll container" for sticky
       positioning purposes regardless of whether it ever actually scrolls,
       so any position: sticky descendant (e.g. Journey's .year-divider/
       .day-divider) was binding to THIS inert, never-scrolling box instead
       of the real scrolling ancestor — meaning it just scrolled away like
       ordinary static content instead of sticking to the viewport. Desktop
       doesn't need this element to be its own scroll container at all, so
       overflow-y: visible removes it from the containing-block chain and
       lets sticky descendants bind to the real page scroll. Mobile's
       overflow-y: scroll (its own dedicated internal-scroll design, see the
       @media (max-width: 768px) rule below) is untouched — this box DOES
       need to be the real scroll container there.

       overflow-x must ALSO be visible, not hidden: per spec, overflow-x and
       overflow-y can't independently be `hidden` and `visible` — a browser
       silently coerces `visible` back to `auto` when the other axis is
       `hidden` (auto still establishes a scroll container, so this
       "reverted" the fix above and cost real time to trace). Nothing in
       this box's children is expected to overflow horizontally (flex-wrap
       + max-width are used where needed instead), so losing the horizontal
       clip here should be a no-op — but this is the thing to check first if
       an unwanted horizontal scrollbar ever shows up on desktop. 2026-08-21. */
    overflow: visible;
    min-height: calc(100vh - 200px);
}

/* MOBILE: Specific scrolling behavior */
@media (max-width: 768px) {
    .content-section {
        /* CORRECTED: Proper mobile scrolling */
        height: calc(100vh - 130px);
        height: calc(100dvh - 130px);
        /* svh (small viewport height) last, 2026-08-22: 100dvh live-recalculates as the
           on-screen keyboard opens/closes, which resizes this element — the exact
           element (#journalSection IS .content-section) that a real-device diagnostic
           traced as swallowing taps meant for Save Entry, a known iOS Safari hit-test
           desync triggered by resizing a scrolling container out from under a pending
           touch. svh is the viewport's smallest stable state (keyboard already
           accounted for), so it doesn't change again once the keyboard is up — nothing
           here needs to reflow mid-interaction. Falls back to the dvh line above on
           browsers that don't support svh. */
        height: calc(100svh - 130px);
        overflow-y: scroll;
        overflow-x: hidden;
        /* -webkit-overflow-scrolling: touch removed, 2026-08-22: that's a legacy
           pre-2015 iOS momentum-scroll hack that runs scrolling on its own compositor
           layer, and that layer is a well-documented source of touch hit-testing
           landing on stale scroll-container coordinates instead of the actual child
           under the finger — confirmed on Luke's real device via Web Inspector: a tap
           squarely inside Save Entry's own getBoundingClientRect(), with the keyboard
           fully closed and no live resize in play, still routed to #journalSection
           (this element) instead of the button. Modern iOS Safari does native momentum
           scrolling on plain overflow-y: scroll without this property; removing it
           avoids the compositor layer entirely rather than trying to out-precision a
           platform bug in it. */
        scroll-behavior: smooth;
    }

    /* 2026-08-20, per Luke on real mobile: the last button in a tab (Save Entry, Save
       as Journal Entry, etc.) sat right at the scroll container's edge with no reach
       room. First attempt was padding-bottom on .content-section itself — didn't work,
       because this is a flex column (display: flex; flex-direction: column) with
       overflow-y: scroll, and padding-bottom on a flex+overflow container is a known
       cross-engine gotcha (WebKit/mobile Safari in particular clips it from the
       scrollable area instead of extending scrollHeight). A ::after spacer becomes an
       actual flex child instead, so it reliably extends the scrollable content — the
       fix Luke asked about ("should it be margin or applied to a container?"): neither,
       it needs to be a real element in the flow. */
    .content-section::after {
        content: '';
        display: block;
        flex-shrink: 0;
        height: calc(160px + env(safe-area-inset-bottom, 0px));
    }
}

.section-header { margin-bottom: 20px; flex-shrink: 0; }
.section-header h1 {
    font-size: 2.5em;
    color: var(--text-primary);
    margin-bottom: 10px;
    font-weight: 300;
    font-family: var(--sans);
}

/* Remove old journal-specific user-info styles */
.journal-section .user-info {
    display: none;
}

/* Elastic Time Display — tile, theme dot + mono (main site style) */
.elastic-time-display {
    background: var(--tile-fill);
    border: 1px solid var(--tile-border);
    border-radius: var(--r-card);
    padding: 2em;
    margin-bottom: 2em;
    text-align: center;
    position: relative;
    overflow: hidden;
    flex-shrink: 0;
}

.day-theme {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    margin-bottom: 1.2em;
}
.day-theme .theme-dot { flex-shrink: 0; }
.day-theme .theme-name {
    font-family: var(--mono);
    font-size: 0.7rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--text-primary);
}

/* Block gauge, 2026-08-23 (Luke's call — Option A) — replaces the earlier
   .time-info/.time-box/.block-meter-* linear bar. A single semicircle arc
   filling to today's Block value, in var(--accent) — same live-phase token
   the original bar used, so only ever one color, no phase-mixing question.
   Path built/updated by app.js's updateBlockGauge(). Center number keeps
   roughly the same hero-scale weight the old .time-value (4em/2.5em/2em) had
   at each breakpoint — see the two mobile overrides below. */
.block-gauge-wrap {
    position: relative;
    width: 190px;
    height: 165px;
    margin: 1.6em auto 0.5em;
}

.block-gauge {
    width: 190px;
    height: 114px;
    display: block;
}

.block-gauge-center {
    position: absolute;
    top: 85px;
    left: 50%;
    transform: translate(-50%, -50%);
    /* Without an explicit width, left:50%+transform centering shrink-to-fits
       this box to (containing-block width − left offset) instead of its
       content's natural width — at the small-mobile breakpoint that capped
       it at 65px against "Day · Block"'s 76px, wrapping the tag to two lines
       and pushing it down into the arc. max-content sizes to the label
       itself regardless of that offset math. */
    width: max-content;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

.block-gauge-num {
    font-family: var(--sans);
    font-size: 2.75rem;
    font-weight: 400;
    color: var(--text-primary);
    line-height: 1;
    font-variant-numeric: tabular-nums;
}

.block-gauge-tag {
    font-family: var(--mono);
    font-size: 0.55rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--text-accent);
    margin-top: 0.35rem;
}

.solar-times {
    font-family: var(--mono);
    font-size: 0.8rem;
    color: var(--text-secondary);
    margin-top: 1em;
    text-align: center;
    padding: 10px;
    background: var(--glass-fill);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
}

/* Sunrise/Solar Noon/Sunset/Nadir icon chips, 2026-08-23 (Luke's call) —
   replaces the old spelled-out "Sunrise: ... | Solar Noon: ... | Sunset: ..."
   text to cut the line's width. Markup built in app.js's solarChip(). */
.solar-chip-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.9em;
    flex-wrap: wrap;
}

.solar-item {
    display: inline-flex;
    align-items: center;
    gap: 0.3em;
}

.solar-icon {
    width: 1.15em;
    height: 1.15em;
    flex: 0 0 auto;
}

.solar-time {
    white-space: nowrap;
}

.solar-times-notice {
    margin-top: 0.5em;
    font-family: var(--sans);
    font-size: 0.9em;
    color: var(--text-secondary);
}

.days-since-birthday {
    font-size: 1em;
    color: var(--text-secondary);
    margin-top: 1em;
    font-family: var(--sans);
}

/* Journal Entry — glass surface (main site style) */
.journal-entry {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

.journal-energy-row {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    margin-bottom: 0.75rem;
}
.journal-energy-label {
    font-family: var(--mono);
    font-size: 0.65rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-secondary);
}
.journal-energy-options {
    display: flex;
    gap: 4px;
}
.journal-energy-btn {
    padding: 6px 14px;
    border: 1px solid var(--tile-border);
    border-radius: var(--r-tile);
    background: var(--glass-fill);
    color: var(--text-secondary);
    font-family: var(--sans);
    font-size: 0.85rem;
    cursor: pointer;
    transition: all 0.2s ease;
}
.journal-energy-btn:hover {
    border-color: var(--accent);
    color: var(--text-primary);
}
.journal-energy-btn.active {
    background: var(--accent-faint);
    border-color: var(--accent);
    color: var(--accent);
}

.journal-entry textarea {
    flex: 1;
    width: 100%;
    min-height: 120px;
    padding: 1.25rem;
    padding-bottom: 2.5rem; /* room for .journal-moment-badge sitting in the corner */
    border: 1px solid var(--glass-border);
    border-radius: var(--r-card);
    background: var(--glass-fill);
    color: var(--text-primary);
    font-family: var(--sans);
    font-size: 1rem;
    font-weight: 300;
    line-height: 1.7;
    resize: vertical;
    box-sizing: border-box;
}
.journal-entry textarea::placeholder { color: var(--text-faint); }
.journal-entry textarea:focus {
    outline: none;
    border-color: var(--accent);
}

/* Wrapper only exists so .journal-moment-badge has something to anchor to — textarea
   itself can't host an absolutely-positioned child (it has no light-DOM content model
   for one), and resize:vertical means the wrapper needs to size to the textarea's
   current (possibly user-dragged) height, not a fixed guess. */
.journal-textarea-wrap {
    position: relative;
}

/* pointer-events: none, 2026-08-23 — this badge overlays the textarea's own corner, and
   given everything this session surfaced about touch targets on this app, it must never
   be able to intercept a tap/type meant for the textarea beneath it. Purely a passive,
   glanceable indicator: quiet at rest, present enough while writing to nudge toward
   finishing (Luke's reasoning) without being another interactive thing to worry about. */
.journal-moment-badge {
    position: absolute;
    left: 12px;
    bottom: 12px;
    padding: 4px 12px;
    border: 1px solid var(--accent);
    border-radius: var(--r-pill);
    background: var(--tile-fill-solid);
    color: var(--accent);
    font-family: var(--mono);
    font-size: 0.75rem;
    letter-spacing: 0.05em;
    pointer-events: none;
}

.journal-actions {
    display: flex;
    gap: 1em;
    margin-top: 1em;
    flex-shrink: 0;
}

/* Buttons — accent primary, ghost secondary (main site) */
.btn {
    position: relative;
    padding: 0.8em 1.5em;
    border-radius: var(--r-tile);
    font-family: var(--sans);
    font-size: 0.9em;
    font-weight: 500;
    text-align: center;
    cursor: pointer;
    transition: all 0.2s ease;
    /* Without these, a tap that lingers even slightly on a real touchscreen gets
       interpreted as a text-selection gesture instead of a click, swallowing the tap
       entirely — reproduced 2026-08-22 on Save Entry (Luke: "seemingly would not
       submit at all" on mobile), traced to this base .btn class having neither. */
    user-select: none;
    -webkit-touch-callout: none;
    touch-action: manipulation;
}
/* Hit-slop, 2026-08-22, per Luke: "click surfaces should be as big or bigger than
   their visual surfaces" — the tappable area shouldn't rely on exactly matching the
   painted box. This ::after is a transparent layer extending past the button's own
   edges; taps on it still count as clicks on the button itself since a pseudo-element
   has no independent event target. Doesn't fix a hit-test engine that resolves to the
   wrong element entirely (see the .content-section comment on the real bug found this
   round), but gives every button a margin of safety against the ordinary case of a
   tap landing a few px outside its visual box. */
.btn::after {
    content: '';
    position: absolute;
    inset: -8px;
}

.btn-primary {
    background: var(--accent);
    color: #fff;
    border: none;
}
/* (hover: hover) gate — see the comment on .auth-submit:hover above. */
@media (hover: hover) and (pointer: fine) {
    .btn-primary:hover {
        background: var(--accent-hover);
        transform: translateY(-1px);
    }
}

.btn-secondary {
    background: transparent;
    border: 1px solid var(--tile-border);
    color: var(--text-secondary);
}
.btn-secondary:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.btn-warning {
    background: #ff9800;
    color: white;
}

.btn-warning:hover {
    background: #f57c00;
}

/* Filters — collapsed by default behind a native <details> disclosure so
   the Journey page opens straight to entries, not a bank of filter fields. */
.filters-disclosure {
    margin-bottom: 20px;
    flex-shrink: 0;
}

.filters-disclosure summary {
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    width: fit-content;
    padding: 6px 14px;
    font-size: 0.82em;
    font-weight: 500;
    color: var(--text-secondary);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    background: var(--glass-fill);
}

.filters-disclosure summary::-webkit-details-marker { display: none; }

.filters-disclosure summary::before {
    content: '▸';
    font-size: 0.7em;
    transition: transform 0.15s ease;
}

.filters-disclosure[open] summary::before { transform: rotate(90deg); }

.filters-disclosure summary:hover {
    border-color: var(--accent);
    color: var(--accent);
}

.filters-disclosure .filters { margin-top: 12px; }

/* Year/Day dividers — inline sticky section headers within the scroll
   itself (like iOS Contacts' alphabet index), emitted per-entry by
   journey.js's dividerHTML() wherever age/dayOfLife changes from the entry
   before. Both use var(--tile-fill-solid) + backdrop-filter, not
   var(--tile-fill) — see style-sticky-fixed-needs-solid-fill: --tile-fill is
   only 7% opaque, so scrolled entry text visibly shows through a sticky bar
   using it.

   Nested stacking (Day sticks directly under Year, like a two-level pinned
   header) works through plain CSS position: sticky with NO JS — .year-divider
   sticks at top: 0, .day-divider sticks at top: <.year-divider's height>, and
   because both are normal-flow siblings in DOM order, each new divider at the
   same top offset naturally displaces the previous one as it scrolls into
   place. This only works because .year-divider's height is a fixed constant
   (44px) that .day-divider's `top` hardcodes to match — if you resize either,
   update the other's number too.

   KNOWN RISK, 2026-08-21: this codebase already hit a real bug once before
   with position: sticky inside .content-section's scroll box — wrong on
   first paint, self-correcting only after a scroll (mobile Safari). See
   .ai-input-section's history comment above, where the fix was switching to
   position: fixed instead — not an option here, since these headers must
   swap out as you scroll rather than stay put. Needs real-device
   verification, not just this sandboxed preview. */
/* Full-bleed: negative-margins out to .content-section's true edge (past
   its own --content-padding), then pads back in, so a stuck divider reads
   as a pinned bar spanning the card's full width rather than an inset chunk
   of content that happens to stick. 2026-08-22, per Luke.

   Left padding is --content-padding + 24px, not just --content-padding, so
   the "45" lines up with where "Block"'s number starts on the entries below
   (.timeline-entry: 4px border + 20px padding-left = 24px) — one straight
   number column down the page instead of Year/Day/Block each starting at a
   different x. .year-divider::before (the hollow ring) sits in that 24px
   gap — see its comment below for why hollow, not filled like entries' own
   dots. */
.year-divider {
    position: sticky;
    top: 0;
    z-index: 6;
    height: 44px;
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 calc(-1 * var(--content-padding));
    padding: 0 var(--content-padding) 0 calc(var(--content-padding) + 24px);
    background: var(--tile-fill-solid);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-bottom: 1px solid var(--glass-border);
}

/* A year boundary is a bigger milestone than any single entry, so it gets
   its own marker distinct from .timeline-entry::before's small solid dots —
   hollow/outline instead of filled, and a little larger, rather than just
   reusing the entry-dot treatment verbatim. position: sticky still counts
   as a positioned element, so this can anchor directly to .year-divider
   with no extra position: relative needed. Centered in the 24px gap the
   padding-left above opens up, roughly on the same x as .day-divider's/
   .timeline-entry's own accent border-left, so it reads as marking the top
   of that thread rather than a disconnected decoration. 2026-08-22, per
   Luke. */
.year-divider::before {
    content: '';
    position: absolute;
    /* 25px at desktop's 30px --content-padding — Luke's own live DevTools
       tweak, kept as a formula (rather than a flat 25px) so it stays that
       same distance from the edge at mobile's smaller --content-padding
       too, instead of drifting out of alignment there. 2026-08-22. */
    left: calc(var(--content-padding) - 5px);
    /* Vertically centered in .year-divider's own 44px row (not pinned to
       its bottom edge, which was tried first) — Luke's live-tested
       correction, reads better than sitting right at the line's very top
       edge. 2026-08-22, per Luke. */
    top: 50%;
    transform: translateY(-50%);
    width: 14px;
    height: 14px;
    border-radius: 50%;
    border: 2px solid var(--accent);
    background: var(--tile-fill-solid);
    z-index: 1;
}

.year-divider-number {
    font-family: var(--mono);
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1;
}

.year-divider-label {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-faint);
}

/* top must equal .year-divider's height (44px) so they stack without gap or
   overlap — see the shared comment above .year-divider.

   box-shadow is only on .day-divider, not .year-divider: .year-divider's
   bottom edge always meets .day-divider (another opaque bar), so a shadow
   there would be invisible — .day-divider's bottom edge is the one that
   actually meets scrolling entry content. This is a plain always-on
   box-shadow, not JS-detected "is this actually stuck right now" (no
   :stuck support to rely on) — good enough as a first pass; dial back or
   swap for an IntersectionObserver-driven class if it doesn't read right
   live. */
.day-divider {
    position: sticky;
    top: 44px;
    z-index: 5;
    height: 40px;
    display: flex;
    margin: 0 calc(-1 * var(--content-padding));
    padding: 0 var(--content-padding);
    background: var(--tile-fill-solid);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border-bottom: 1px solid var(--glass-border);
    box-shadow: 0 6px 12px -4px rgba(0, 0, 0, 0.4);
}

/* Accent-bordered relative to .year-divider (which has no border of its
   own, just the hollow ring above) — a visual cue that Day is nested inside
   Year, not a peer. Border width/color AND the 4px + 20px = 24px total
   indent match .timeline-entry's own border-left + padding-left exactly, so
   "156" lines up with "45" above it and "Block"'s number below it — one
   straight column, with the accent line reading as continuing on down into
   that day's entries rather than a disconnected header treatment.
   .day-divider is a plain (default align-items: stretch) flex row so this,
   its only child, stretches to the full 40px height for the border to span
   the whole bar, not just the text line. 2026-08-22: was 12px padding
   (a smaller indent than .year-divider/.timeline-entry, deliberately, back
   when indent depth alone was carrying the nesting cue) — bumped to 20px to
   align with Block instead, per Luke; the accent border + hollow ring now
   carry that distinction instead. */
.day-divider-inner {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    border-left: 4px solid var(--accent);
    padding-left: 20px;
}

.day-divider-main {
    display: flex;
    align-items: baseline;
    gap: 6px;
}

.day-divider-number {
    font-family: var(--mono);
    font-size: 1.1rem;
    font-weight: 700;
    color: var(--text-primary);
    line-height: 1;
}

.day-divider-label {
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-faint);
}

/* Weekday theme (e.g. "Share") — used to be a per-entry pill up in
   .entry-header-row; moved here (to the right of the Day-of-Year label, in
   its divider) per Luke, since the theme is really a property of the day,
   not of any one entry within it, and repeating it on every entry in that
   day was redundant with the divider now grouping them anyway. Sized down
   from the old .entry-theme pill (which was built for the roomier per-entry
   row) to fit this divider's compact 40px bar without overflowing it. */
.day-divider-theme {
    background: var(--accent-faint);
    color: var(--accent);
    padding: 2px 8px;
    border-radius: var(--r-tile);
    font-size: 0.62rem;
    font-weight: 500;
    white-space: nowrap;
}

/* dayOfLife/365 is approximate (birthday-years run 365 or 366 days) — fine
   for a glance-level bar. */
.day-divider-bar {
    height: 3px;
    border-radius: 2px;
    background: var(--glass-fill);
    border: 1px solid var(--glass-border);
    overflow: hidden;
}

.day-divider-bar-fill {
    height: 100%;
    border-radius: 2px;
    background: var(--text-secondary);
}

.filters {
    display: flex;
    gap: 15px;
    flex-wrap: wrap;
    margin-bottom: 15px;
    flex-shrink: 0;
}

.filter-group {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.filter-group label {
    font-size: 0.8em;
    color: var(--text-secondary);
    font-weight: 500;
    font-family: var(--sans);
}

.filter-input {
    padding: 8px 12px;
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    background: var(--glass-fill);
    color: var(--text-primary);
    font-size: 0.9em;
    min-width: 120px;
}
.filter-input:focus { outline: none; border-color: var(--accent); }

/* Timeline */
/* Was flex: 1 + overflow-y: auto — its own internal scroller on desktop too
   (mobile's copy of this same bug was already fixed earlier, see the
   .timeline-content comment in the @media (max-width: 768px) block below).
   Same root cause here: .content-section is the single scroll context (or,
   on desktop, the real page/body scroll — see .content-section's comment
   above), and any flex:1 + overflow:auto descendant of it becomes a second,
   competing scroll container — inert in the sense that it never actually
   needed to scroll on desktop (unlike the mobile cramped-200px bug), but it
   silently breaks position: sticky for .year-divider/.day-divider inside it
   (an overflow: auto box counts as a sticky containing block regardless of
   whether it ever scrolls). 2026-08-21. */
.timeline-content {
    padding-bottom: 20px;
}

.timeline-entry {
    border-left: 4px solid var(--accent);
    padding: 16px 34px 16px 20px; /* right padding reserves room for .entry-delete-btn */
    margin-bottom: 18px;
    position: relative;
}

.timeline-entry::before {
    content: '';
    position: absolute;
    left: -8px;
    top: 22px;
    width: 10px;
    height: 10px;
    background: var(--accent);
    border-radius: 50%;
    border: 2px solid var(--tile-fill);
    box-shadow: 0 0 6px var(--accent);
}

/* Soft-delete, per entry — tap-to-arm/tap-to-confirm instead of a modal (Luke,
   2026-08-24: "confirm, light weight"). journey.js's DELETE_CONFIRM_WINDOW_MS
   auto-reverts the armed state if the second tap doesn't come. */
.entry-delete-btn {
    position: absolute; top: 16px; right: 0;
    width: 26px; height: 26px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    background: none; border: 1px solid var(--tile-border); border-radius: 999px;
    font-size: 12px; line-height: 1; color: var(--text-faint); cursor: pointer;
    transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease, width 0.15s ease, padding 0.15s ease;
}
.entry-delete-btn:hover { color: var(--text-secondary); border-color: var(--accent-dim); }
.entry-delete-btn.confirming {
    width: auto; padding: 0 10px;
    background: #c0392b; border-color: #c0392b; color: #fff;
    font-family: var(--mono); font-size: 10.5px; font-weight: 600; letter-spacing: 0.02em;
}

.entry-header {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 10px;
}

.entry-header-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px 12px;
}

/* Per-entry row: just Block + Moment now — Year of life and Day of year
   moved out to the inline sticky dividers above (.year-divider/.day-divider)
   since they only change once a year/day and repeating them on every single
   entry was noise; Block/Moment are the two values that genuinely vary
   entry-to-entry. Aligned on shared rows instead of relying on flex-end +
   matching box heights (which is what made an earlier version look uneven —
   items with a bar vs. without had different natural heights, so
   "bottom-align everything" produced different top edges). Both share the
   same two-row shape: .time-cell-main (number + label, fixed height) then
   .time-cell-bar-row (fixed height, empty for Moment which has no bar) — so
   alignment comes from the shared row height, not each item's own content. */
.entry-time-primary {
    display: flex;
    align-items: flex-start;
    gap: 18px;
    margin-bottom: 6px;
}

.time-cell-main {
    display: flex;
    align-items: flex-end;
    gap: 6px;
    height: 2rem;
}

.time-cell-bar-row {
    height: 5px;
    margin-top: 6px;
}

/* Only applied to items that actually have a bar (just Block, per-entry,
   now that Day-of-Year moved to .day-divider) — Moment uses a bare
   .time-cell-bar-row with nothing inside, so it reserves the same height
   for alignment without showing a hollow track.
   NOTE: this is combined on the SAME element as .time-cell-bar-row (which
   sets height: 5px), so this must NOT redeclare `height` — doing so once
   already broke the bars: same specificity + later in the stylesheet meant
   this rule's height won the cascade, and `height: 100%` with no explicit
   parent height collapses to 0, so the track (and the fill inside it)
   silently disappeared. 2026-08-21. */
.bar-track {
    border-radius: 3px;
    background: var(--glass-fill);
    border: 1px solid var(--glass-border);
    overflow: hidden;
}

.bar-fill {
    height: 100%;
    border-radius: 3px;
}

.time-number-hero {
    font-family: var(--mono);
    font-size: 2rem;
    font-weight: 700;
    line-height: 1;
    letter-spacing: 0.02em;
}

.time-number-small {
    font-family: var(--mono);
    font-size: 1.2rem;
    font-weight: 600;
    line-height: 1;
}

.entry-time-label {
    font-size: 0.58rem;
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--text-faint);
    white-space: nowrap;
}

/* Block — hero size, phase-colored (per-entry inline --entry-phase-color).
   Day/Night now reads on the label itself ("Block · Day") instead of a
   separate pill elsewhere in the entry, so the color and the word that
   explains it sit in the same place. */
.block-indicator {
    flex: 1;
    max-width: 170px;
    min-width: 110px;
}

.block-number {
    color: var(--entry-phase-color, var(--accent));
}

.block-bar-fill {
    background: var(--entry-phase-color, var(--accent));
    box-shadow: 0 0 6px var(--entry-phase-color, var(--accent-dim));
}

/* Moment — small/reference size */
.moment-indicator {
    flex: 0 0 auto;
}

.moment-number {
    color: var(--text-secondary);
}

/* Year of life (age) and Day of (birthday-)year both used to be per-entry
   indicators here. Since they only change once a year/day, showing them on
   every single entry was pure repetition — moved out to the inline sticky
   .year-divider/.day-divider above the entry list instead (see above). */

/* Legacy fallback (pre-elasticTime entries with no Block/Moment to show) */
.entry-elastic-time {
    font-family: var(--mono);
    font-size: 1rem;
    font-weight: 600;
    color: var(--accent);
    letter-spacing: 0.05em;
}

.entry-tags-inline {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

/* Energy (slow/steady/excited) moved down to sit with the #hashtags in
   .entry-tags instead of up in .entry-header-row — per Luke, it reads more
   like a tag than like provenance/theme metadata, so it belongs with the
   other tags. Style unchanged, just relocated. */
.entry-energy {
    background: var(--accent-faint);
    color: var(--accent);
    padding: 3px 8px;
    border-radius: var(--r-tile);
    font-size: 0.7rem;
    font-weight: 500;
    text-transform: lowercase;
}

/* Marks a perception entry (system/AI-noticed) apart from the user's own
   expression entries — deliberately quieter than .entry-energy (theme moved
   to .day-divider-theme, no longer per-entry) since it's provenance, not
   content. */
.entry-type-badge {
    color: var(--text-faint);
    font-size: 0.7rem;
    font-weight: 500;
    font-style: italic;
}

/* Date/time on second line, below Block row — "day X of year Y" now lives in
   the .year-divider/.day-divider sticky headers above, promoted out of this
   faint run entirely. */
.entry-datetime {
    font-size: 0.72rem;
    color: var(--text-faint);
    font-family: var(--mono);
    letter-spacing: 0.02em;
}

.entry-content {
    font-size: 1em;
    line-height: 1.65;
    color: var(--text-primary);
    margin: 0 0 10px 0;
}
.entry-content h1, .entry-content h2, .entry-content h3 { margin: 0.75em 0 0.35em; font-size: 1em; font-weight: 600; }
.entry-content h1 { font-size: 1.1em; }
.entry-content ul, .entry-content ol { margin: 0.5em 0; padding-left: 1.25em; }
.entry-content li { margin: 0.2em 0; }
.entry-content a { color: var(--accent); text-decoration: underline; }
.entry-content code { background: var(--glass-fill); padding: 0.15em 0.4em; border-radius: 4px; font-size: 0.9em; }
.entry-content pre { margin: 0.5em 0; overflow-x: auto; }
.entry-content pre code { padding: 10px; display: block; }
.entry-content blockquote { margin: 0.5em 0; padding-left: 1em; border-left: 3px solid var(--accent); color: var(--text-secondary); }
.entry-content p { margin: 0 0 0.5em; }
.entry-content p:last-child { margin-bottom: 0; }

.entry-tags {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 15px;
}

.tag {
    background: var(--accent-faint);
    color: var(--accent);
    padding: 4px 8px;
    border-radius: var(--r-tile);
    font-size: 0.75em;
    font-weight: 500;
}

/* AI Interface — glass/tile (design system) */
.ai-status {
    font-size: 0.9em;
    padding: 12px 15px;
    background: var(--glass-fill);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    margin-bottom: 20px;
    border-left: 4px solid var(--accent);
    flex-shrink: 0;
    color: var(--text-secondary);
}
.ai-status strong { color: var(--accent); }

/* Time Print empty-state summary (vision doc step 3, 2026-08-20) — the primary object
   at the top of Context when today's Time Print exists, ahead of the question box. */
.time-print-summary {
    background: var(--glass-fill);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    padding: 20px;
    margin-bottom: 20px;
    flex-shrink: 0;
}
.time-print-summary-header {
    font-size: 0.8em;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 14px;
}
.time-print-row { margin-bottom: 12px; }
.time-print-row:last-child { margin-bottom: 0; }
.time-print-row-label {
    font-size: 0.75em;
    font-weight: 600;
    color: var(--text-secondary);
    margin-bottom: 2px;
}
.time-print-row-text {
    font-size: 0.9em;
    line-height: 1.5;
    color: var(--text-primary);
}
.time-print-summary-loading {
    font-size: 0.9em;
    color: var(--text-secondary);
    font-style: italic;
    margin: 0;
}

/* Emerging/Season/Long View compressed disclosures (2026-08-22, per Luke) — Today/
   Recent/Possibility stay plain .time-print-row always-open rows above, unchanged.
   Native <details>/<summary>, same pattern as Journey's .filters-disclosure, so
   keyboard operation and aria semantics come for free — not a custom widget. */
.time-print-disclosure {
    margin-bottom: 12px;
}
.time-print-disclosure:last-child { margin-bottom: 0; }

.time-print-disclosure-label {
    cursor: pointer;
    list-style: none;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.75em;
    font-weight: 600;
    color: var(--text-secondary);
}
.time-print-disclosure-label::-webkit-details-marker { display: none; }
.time-print-disclosure-label::before {
    content: '▸';
    font-size: 0.85em;
    transition: transform 0.15s ease;
}
.time-print-disclosure[open] > .time-print-disclosure-label::before { transform: rotate(90deg); }
.time-print-disclosure-label:hover { color: var(--accent); }

.time-print-disclosure > .time-print-row-text { margin-top: 6px; }

/* Nested one level per layer (Season inside Emerging, Long View inside Season) — the
   indent + rule is the visual cue that opening this one is only possible because its
   parent is already open, matching the actual click-through-in-order mechanic. */
.time-print-disclosure .time-print-disclosure {
    margin-top: 10px;
    margin-left: 4px;
    padding-left: 12px;
    border-left: 1px solid var(--glass-border);
}

/* A locked layer — shown instead of a real <details> when the journal doesn't span
   that layer's window yet (see getJournalSpanDays/buildLockedTimePrintRow in
   explore.js). Deliberately non-interactive and quieter than an open disclosure—
   dimmed, not clickable — with an honest reason rather than just disappearing. */
.time-print-locked {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 12px;
    opacity: 0.55;
}
.time-print-locked-label {
    font-size: 0.75em;
    font-weight: 600;
    color: var(--text-secondary);
}
.time-print-locked-note {
    font-size: 0.75em;
    font-style: italic;
    color: var(--text-faint, var(--text-secondary));
}

/* Genuinely fixed to the viewport, 2026-08-20 (Luke's call, direction change same day —
   see docs/time-print-serendipity-vision.md's "Evaluating the built experience" section).
   Was position: sticky within .content-section's overflow-y: auto scroll box; real
   mobile testing found that wrong on first paint, self-correcting only after a scroll —
   a known sticky-in-nested-scroll containing-block bug. Fixed positioning doesn't depend
   on a scrolling ancestor's geometry at all, so there's nothing to get wrong on first
   paint. Horizontal inset matches .app-wrapper + .content-section's combined padding
   (20px + 30px desktop, 10px + 15px mobile — see the @media override below) so the bar
   lines up with the content column above it, not the raw viewport edge. env(
   safe-area-inset-bottom) covers the iOS home-indicator area on notched devices; the
   --ai-input-bar-height variable is set by explore.js's ResizeObserver (see
   setupAiInputHeightObserver) so #contextSection's padding-bottom (below) tracks the
   bar's actual rendered height instead of a guessed constant — the bar's height isn't
   fixed, the textarea has `resize: vertical`.

   2026-08-20, same-day follow-up: --tile-fill is only 7% white — fine for a tile sitting
   on undisturbed background, but scrolled content was visibly showing through the bar
   underneath it. Swapped to a near-opaque bg-base tint + backdrop-filter blur so anything
   scrolling past is actually blocked, not just tinted. Also gave the bar a full border +
   top-rounded corners (previously border-top only) — at wide viewports the 1200px-capped
   bar was reading as an accidental cutoff against the open background beyond it rather
   than a deliberately terminated card; matching .content-section's card treatment fixes
   that. */
.ai-input-section {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* 200, not 100, 2026-08-23: .global-user-bar (main.css's mobile block) is
       position:sticky at z-index:100 on mobile — an exact tie with this element,
       which is position:fixed. Both also use backdrop-filter, so both get promoted to
       their own GPU compositing layer; iOS Safari has known bugs resolving z-order
       between sticky and fixed layers at a tied z-index, and reproduced on Luke's
       phone as Ask Serendipity rendering behind other content — not reproducible on
       desktop, where the header bar is plain position:relative with nothing to
       conflict against. An unambiguous z-index removes the tie entirely rather than
       relying on the browser to resolve it "correctly". */
    z-index: 200;
    max-width: 1200px;
    margin: 0 auto;
    background: var(--tile-fill-solid);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border: 1px solid var(--tile-border);
    border-bottom: none;
    border-radius: var(--r-card) var(--r-card) 0 0;
    padding: 15px 50px calc(15px + env(safe-area-inset-bottom, 0px));
}
@media (max-width: 768px) {
    .ai-input-section {
        padding-left: 25px;
        padding-right: 25px;
    }
}

/* Set by explore.js's ResizeObserver on #aiInputSection — fallback here is only what
   renders before that first observer callback fires (effectively instant, but a real
   value belongs here regardless of first-paint timing). */
#contextSection {
    padding-bottom: var(--ai-input-bar-height, 140px);
}

.ai-input {
    width: 100%;
    padding: 12px;
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    background: var(--glass-fill);
    color: var(--text-primary);
    font-size: 0.9em;
    resize: vertical;
    min-height: 80px;
    font-family: var(--sans);
    margin-bottom: 10px;
}

/* Full-width, 2026-08-23 per Luke — matches the textarea above it instead of sizing to
   its own text, both wider and easier to hit than a content-sized button floating at
   the left edge of the input bar. */
#aiButton {
    display: block;
    width: 100%;
}
.ai-input:focus { outline: none; border-color: var(--accent); }

/* No longer its own internal scroller (flex: 1 + overflow-y: auto) — .content-section
   is the one scroll area for the whole Context section now, so the sticky input above
   has a single, predictable scroll context to stick within, not a nested one. */
.ai-response {
    flex-shrink: 0;
    overflow-x: hidden;
    background: var(--glass-fill);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    padding: 20px;
    font-size: 0.9em;
    line-height: 1.6;
    color: var(--text-primary);
    margin-bottom: 20px;
    min-height: 120px;
}
.ai-response:not(:has(.ai-response-greeting)) {
    white-space: normal;
}
/* .ai-response-intro (a plain sentence) was the pre-2026-08-22 default state, replaced
   by the greeting+buttons below — renamed here rather than left as dead CSS. */
.ai-response-greeting {
    margin: 0;
    color: var(--text-secondary, rgba(255,255,255,0.7));
    font-size: 0.95em;
}

/* Quick-action buttons (2026-08-22, per Luke): a growing row, not inline links in the
   greeting sentence — so a third/fourth "phrase we like" later is just another button,
   not a sentence rewrite. Only ever shown as part of DEFAULT_AI_INTRO_HTML's empty
   state (explore.js) — a real response replaces this whole area, same as the old
   plain-sentence intro did. */
.ai-quick-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 14px;
}
.ai-quick-action {
    font-size: 0.85em;
    padding: 8px 16px;
}

/* Lucky Draw result (2026-08-22) — Serendipity's connecting note, then the two drawn
   cards from context.json. Deliberately simple/text-based, not Focus's flip-card UI
   (focusCardFrontHTML/.focus-card in focus.js) — that's scoped to Focus's own session
   flow; sharing it here would mean exporting it for reuse, an open question not yet
   settled. Revisit if these should visually match later. */
.lucky-draw-why {
    margin: 0 0 16px;
    color: var(--text-primary);
}
.lucky-draw-cards {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
}
.lucky-draw-card {
    flex: 1;
    min-width: 200px;
    background: var(--glass-fill);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    padding: 14px 16px;
}
.lucky-draw-card-title {
    margin: 0 0 6px;
    font-size: 1em;
    font-weight: 600;
    color: var(--accent);
}
.lucky-draw-card-focus {
    margin: 0 0 8px;
    font-weight: 500;
    color: var(--text-primary);
}
.lucky-draw-card-context {
    margin: 0;
    font-size: 0.9em;
    color: var(--text-secondary);
}

/* My Story (2026-08-22) — auto-saved immediately on generation (see tellMyStory/
   saveMyStoryEntry in explore.js) independently of the thread's own Save actions
   (2026-08-23: those now show once the thread has ANY item, My Story included — this
   note is what tells the person their My Story specifically is already persisted, so
   using Save to Journey/Save Last Response on top of it isn't required). */
.ai-response-saved-note {
    margin: 12px 0 0;
    font-size: 0.85em;
    font-style: italic;
    color: var(--text-faint, var(--text-secondary));
}

/* Threaded conversation items, 2026-08-23 — each askAI/Lucky Draw/My Story exchange
   gets its own .ai-thread-item instead of replacing the one before it (see
   exploreState.thread in explore.js). Divider between items only (:not(:first-child)),
   not a border around each one individually — reads as one continuous conversation,
   not a stack of separate cards. */
.ai-thread-item {
    margin-bottom: 1.25em;
}
.ai-thread-item:not(:first-child) {
    padding-top: 1.25em;
    border-top: 1px solid var(--glass-border);
}
.ai-thread-question {
    margin: 0 0 0.5em;
    font-weight: 600;
    color: var(--text-secondary);
}
.ai-response p { margin: 0 0 0.75em; }
.ai-response p:last-child { margin-bottom: 0; }
.ai-response strong { font-weight: 600; color: var(--text-primary); }
.ai-response ul, .ai-response ol { margin: 0.5em 0 1em; padding-left: 1.25em; }

.ai-response-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 15px;
    margin-bottom: 20px;
    padding-top: 15px;
    border-top: 1px solid var(--tile-border);
    flex-shrink: 0;
}

/* flex-basis 140px, not a hard max-width, 2026-08-23: three buttons now (Save to
   Journey / Save Last Response / Clear, up from two) — wrap lets a narrow phone put
   the third on its own row instead of squeezing all three illegibly thin. */
.ai-response-actions .btn {
    flex: 1 1 140px;
}

.system-config-section {
    padding-top: 20px;
    border-top: 1px solid var(--tile-border);
    flex-shrink: 0;
    overflow: visible;
    margin-top: 20px;
}

.config-details {
    background: var(--glass-fill);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    padding: 15px;
}

.config-summary {
    font-weight: 600;
    color: var(--text-primary);
    cursor: pointer;
    padding: 5px 0;
    user-select: none;
    list-style: none;
}

.config-summary::-webkit-details-marker {
    display: none;
}

.config-summary::before {
    content: '▶ ';
    transition: transform 0.2s ease;
}

.config-details[open] .config-summary::before {
    transform: rotate(90deg);
}

.prompt-management {
    background: var(--accent-faint);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    padding: 15px;
    margin: 15px 0;
    border-left: 4px solid var(--accent);
}

.prompt-management h3 {
    color: var(--accent);
    margin-bottom: 10px;
    font-size: 1.1em;
}

.prompt-textarea {
    width: 100%;
    min-height: 120px;
    max-height: 400px;
    padding: 12px;
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    background: var(--glass-fill);
    color: var(--text-primary);
    font-family: var(--mono);
    font-size: 0.85em;
    line-height: 1.5;
    resize: vertical;
    overflow-y: auto;
}
.prompt-textarea:focus { outline: none; border-color: var(--accent); }

/* MOBILE: Prevent zoom on focus */
@media (max-width: 768px) {
    .prompt-textarea {
        font-size: 16px !important;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    }
}

.prompt-actions {
    display: flex;
    gap: 10px;
    margin-top: 10px;
}

/* Debug Toggle Section — design system tokens */
.debug-toggle-section {
    background: var(--accent-faint);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    padding: 15px;
    margin: 15px 0;
    border-left: 4px solid var(--accent);
}

.debug-toggle-section h3 {
    color: var(--accent);
    margin-bottom: 10px;
    font-size: 1.1em;
    display: flex;
    align-items: center;
    gap: 8px;
}

.debug-toggle-section h3::before {
    content: '🐛';
}

.debug-controls {
    display: flex;
    align-items: center;
    gap: 15px;
    flex-wrap: wrap;
}

.debug-toggle-wrapper {
    display: flex;
    align-items: center;
    gap: 8px;
}

.debug-toggle {
    width: 50px;
    height: 24px;
    background: var(--tile-fill);
    border: 1px solid var(--tile-border);
    border-radius: 12px;
    position: relative;
    cursor: pointer;
    transition: background 0.3s ease;
    outline: none;
    appearance: none;
}

.debug-toggle:checked {
    background: var(--accent);
}

.debug-toggle::before {
    content: '';
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    background: var(--text-primary);
    border-radius: 50%;
    transition: transform 0.3s ease;
    box-shadow: 0 2px 4px rgba(0,0,0,0.3);
}

.debug-toggle:checked::before {
    transform: translateX(26px);
}

.debug-toggle-label {
    font-size: 0.9em;
    font-weight: 500;
    color: var(--text-primary);
    user-select: none;
}

.debug-keyboard-hint {
    font-size: 0.75em;
    color: var(--text-faint);
    font-style: italic;
    margin-top: 8px;
}

.debug-status {
    padding: 8px 12px;
    background: rgba(255, 152, 0, 0.1);
    border: 1px solid rgba(255, 152, 0, 0.3);
    border-radius: 6px;
    font-size: 0.8em;
    color: #ef6c00;
    font-weight: 500;
}

/* Admin badge */
.admin-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: linear-gradient(45deg, #9c27b0, #673ab7);
    color: white;
    padding: 4px 8px;
    border-radius: 12px;
    font-size: 0.7em;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.5px;
}

.admin-badge::before {
    content: '👨‍💻';
    font-size: 0.9em;
}

.debug-section {
    background: var(--accent-faint);
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    padding: 15px;
    margin: 15px 0;
    border-left: 4px solid var(--accent);
}

.debug-section h3 {
    color: var(--accent);
    margin-bottom: 10px;
    font-size: 1.1em;
}

.debug-help {
    font-size: 0.8em;
    color: var(--text-secondary);
    margin: 5px 0 0 0;
    font-style: italic;
}

/* Enhanced Debug Button */
.debug-button {
    position: fixed;
    bottom: 20px;
    right: 20px;
    padding: 10px 14px;
    background: var(--accent);
    color: white;
    border: none;
    border-radius: 25px;
    cursor: pointer;
    font-size: 11px;
    font-weight: 600;
    font-family: var(--mono);
    box-shadow: 0 4px 12px var(--accent-dim);
    z-index: 1000;
    touch-action: manipulation;
    transition: all 0.3s ease;
    user-select: none;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    display: none;
}

/* (hover: hover) gate — see the comment on .auth-submit:hover above. */
@media (hover: hover) and (pointer: fine) {
    .debug-button:hover {
        transform: translateY(-2px);
        box-shadow: 0 6px 16px var(--accent-dim);
        background: var(--accent-hover);
    }
}

.debug-button:active {
    transform: translateY(0);
    box-shadow: 0 2px 8px var(--accent-faint);
}

.debug-button::before {
    content: '🐛 ';
    margin-right: 4px;
}

/* Loading and States */
.loading, .no-entries {
    text-align: center;
    padding: 40px;
    color: var(--text-secondary);
}

.no-entries {
    font-style: italic;
    color: var(--text-secondary);
}

/* Auth overlay + modal defined at top of file (design system) */

/* Birthday Setup — tile + accent (design system) */
.birthday-setup {
    background: var(--tile-fill);
    border: 1px solid var(--tile-border);
    border-radius: var(--r-card);
    padding: 2em;
    margin-bottom: 2em;
    text-align: center;
    color: var(--text-primary);
    flex-shrink: 0;
}
.birthday-setup h3 { color: var(--text-primary); }
.birthday-setup p { color: var(--text-secondary); }

.birthday-input {
    padding: 0.8em;
    border: 1px solid var(--glass-border);
    border-radius: var(--r-tile);
    background: var(--glass-fill);
    color: var(--text-primary);
    font-size: 1em;
    margin: 0.5em;
}
.birthday-input:focus { outline: none; border-color: var(--accent); }

.birthday-button {
    background: var(--accent);
    color: #fff;
    border: none;
    padding: 0.8em 1.5em;
    border-radius: var(--r-pill);
    font-weight: 600;
    cursor: pointer;
    margin: 0.5em;
    transition: all 0.2s ease;
}
/* (hover: hover) gate — see the comment on .auth-submit:hover above. */
@media (hover: hover) and (pointer: fine) {
    .birthday-button:hover {
        background: var(--accent-hover);
        transform: translateY(-1px);
    }
}

/* Utility Classes */
.hidden {
    display: none !important;
}

/* Mobile Responsive - CORRECTED for header persistence */
@media (max-width: 768px) {
    html, body {
        height: 100%;
        /* REMOVED: overflow: hidden that was causing header loss */
        -webkit-overflow-scrolling: touch;
        /* FIX: Prevent viewport jumping on form focus */
        -webkit-text-size-adjust: 100%;
    }

    body {
        padding-top: env(safe-area-inset-top);
        padding-bottom: env(safe-area-inset-bottom);
        padding-left: env(safe-area-inset-left);
        padding-right: env(safe-area-inset-right);
        /* FIX: Keep body scrollable for header accessibility */
        overflow-y: auto;
        overflow-x: hidden;
    }
    
    /* Mobile: Adjust wrapper padding */
    .app-wrapper {
        padding: 0 10px; /* Only horizontal padding on mobile */
    }

    /* FIX: Global user bar - always visible with minimal top margin */
    .global-user-bar {
        margin: 5px 0 8px 0;
        border-radius: 0 0 8px 8px;
        /* CRITICAL: Ensure header stays accessible */
        position: sticky;
        top: 0;
        z-index: 100;
        /* Sticky + var(--tile-fill)'s 7% opacity let scrolled content show through —
           same bug as .ai-input-section, see --tile-fill-solid's comment in
           elastic-design.css. 2026-08-20. */
        background: var(--tile-fill-solid);
        backdrop-filter: blur(20px);
        -webkit-backdrop-filter: blur(20px);
    }

    .status-rail {
        padding: 8px 15px;
        font-size: 0.85em;
    }

    .container {
        min-height: calc(100vh - 90px);
        min-height: calc(100dvh - 90px);
        overflow: visible;
        margin: 0;
        padding: 0;
    }

    /* FIX: Navigation - sticky below header */
    .app-nav {
        margin: 0 0 10px 0;
        flex-shrink: 0;
        position: sticky;
        top: calc(env(safe-area-inset-top) + 50px);
        z-index: 90;
        /* Was var(--tile-fill) (7% opacity) — same bleed-through bug as
           .ai-input-section, see --tile-fill-solid's comment in elastic-design.css.
           2026-08-20. */
        background: var(--tile-fill-solid);
        backdrop-filter: blur(20px);
        -webkit-backdrop-filter: blur(20px);
    }

    .nav-pill {
        padding: 8px 10px;
        font-size: 13px;
        min-height: 36px;
    }

    /* FIX: Content section - flexible height */
    .content-section {
        --content-padding: 15px;
        margin: 0;
        padding: var(--content-padding);
        /* REMOVED: Fixed height that caused header loss */
        min-height: calc(100vh - 200px);
        min-height: calc(100dvh - 200px);
        min-height: calc(100svh - 200px);
        /* Was `overflow: visible` here, which silently canceled the overflow-y: scroll
           set above (line ~217) — same @media (max-width: 768px) query, later rule wins.
           That left the fixed-height box (height: calc(100dvh - 130px)) unable to
           actually scroll its content, which is what caused the "not using all the
           space" / mid-content blank-gap bug Luke reported on the Journey page.
           2026-08-21. */
        display: flex;
        flex-direction: column;
    }

    .section-header {
        margin-bottom: 15px;
        flex-shrink: 0;
    }

    .section-header h1 {
        font-size: 1.6em;
        margin-bottom: 10px;
    }

    .elastic-time-display {
        padding: 1.5em 1em;
        margin-bottom: 1.5em;
        flex-shrink: 0;
    }

    .block-gauge-wrap {
        width: 150px;
        height: 116px;
        margin-top: 1.05em;
    }

    .block-gauge {
        width: 150px;
        height: 90px;
    }

    .block-gauge-center {
        top: 66px;
    }

    .block-gauge-num {
        font-size: 2rem;
    }

    .day-theme {
        font-size: 1.2em;
        margin-bottom: 1em;
    }

    .solar-times {
        font-size: 0.75em;
        padding: 6px;
        margin-top: 0.8em;
    }

    .solar-chip-row {
        gap: 0.6em;
    }

    /* Not flex:1/min-height:0 anymore, 2026-08-22: that tied the textarea's height to
       .content-section's live (100dvh-based) height, which has to recompute every time
       the on-screen keyboard opens/closes/animates. On real iOS Safari that live
       reflow is a known trigger for a hit-test desync bug — a tap can land squarely
       inside Save Entry's own getBoundingClientRect() and still be routed to
       #journalSection instead (reproduced on Luke's phone via Web Inspector: touch
       coordinates geometrically inside the button, elementFromPoint said otherwise).
       A fixed textarea height means nothing here needs to reflow when the keyboard
       moves — if content runs long, .content-section's own scroll (the one scroll
       context per section, see memory) handles it same as any other section. */
    .journal-entry {
        display: flex;
        flex-direction: column;
    }

    .journal-entry textarea {
        min-height: 150px;
        font-size: 16px;
        padding: 12px;
        padding-bottom: 2.5rem; /* room for .journal-moment-badge, same as desktop */
        border-radius: 8px;
        resize: none;
    }

    .journal-actions {
        flex-shrink: 0;
        margin-top: 15px;
        gap: 10px;
    }

    .filters-disclosure summary {
        font-size: 0.8em;
        padding: 6px 12px;
    }

    .filters {
        flex-direction: column;
        gap: 10px;
        margin-bottom: 15px;
        flex-shrink: 0;
    }

    .entry-time-primary { gap: 12px; flex-wrap: wrap; }
    .time-number-hero { font-size: 1.7rem; }
    .time-number-small { font-size: 1.05rem; }
    .block-indicator { min-width: 95px; }

    .filter-group {
        flex-direction: row;
        align-items: center;
        gap: 10px;
    }

    .filter-group label {
        min-width: 70px;
        font-size: 0.75em;
        flex-shrink: 0;
    }

    .filter-input {
        flex: 1;
        padding: 8px 10px;
        font-size: 14px;
        border-radius: 6px;
    }

    .timeline-content {
        /* Was flex: 1 + overflow-y: auto + max-height — its own internal scroller,
           nested inside .content-section's own overflow-y: scroll. Two nested scroll
           containers meant flex-basis: 0% only gave this box ~200px of the parent's
           fixed height (whatever the filter fields above it didn't use), so entries
           scrolled in a cramped little window instead of using the screen — the "not
           using all the space" bug Luke reported on the Journey page. .content-section
           is the single scroll context now (matches the Context section's .ai-response,
           see its comment above); this just flows as a normal block within it.
           2026-08-21. */
        flex: none;
        overflow: visible;
        padding-bottom: 20px;
    }

    .timeline-entry {
        padding: 10px 0 10px 12px;
        margin-bottom: 15px;
        border-left: 3px solid var(--accent);
    }

    .timeline-entry::before {
        left: -6px;
        width: 8px;
        height: 8px;
        top: 12px;
    }

    .entry-header {
        gap: 6px;
        margin-bottom: 8px;
    }

    .entry-content {
        font-size: 0.9em;
        line-height: 1.5;
        margin: 8px 0;
    }

    .ai-input {
        font-size: 16px;
        min-height: 80px;
        padding: 12px;
        border-radius: 8px;
        resize: none;
    }

    .ai-response {
        /* CORRECTED: Don't constrain AI response height on mobile */
        flex: 0 0 auto;
        font-size: 0.85em;
        padding: 15px;
        margin-bottom: 15px;
        /* REMOVED: All height constraints for natural flow */
        overflow: visible;
        background: var(--glass-fill);
        border-radius: 8px;
        line-height: 1.6;
        white-space: pre-wrap;
    }
    
    .ai-response-actions {
        gap: 8px;
        margin-top: 10px;
        margin-bottom: 15px;
        padding-top: 10px;
    }
    
    .ai-response-actions .btn {
        font-size: 14px;
        padding: 10px 12px;
        min-height: 44px;
    }

    .debug-controls {
        flex-direction: column;
        align-items: stretch;
        gap: 10px;
    }

    .debug-toggle-wrapper {
        justify-content: space-between;
        width: 100%;
        padding: 10px 0;
    }

    .debug-keyboard-hint {
        font-size: 0.7em;
        text-align: center;
        padding: 10px 0;
    }
    
    /* ADDED: Ensure debug sections scroll naturally within container */
    .debug-toggle-section, .debug-section {
        margin: 15px 0;
        padding: 15px;
        /* ENSURE: Natural flow, no individual scrolling */
        overflow: visible;
        flex-shrink: 0;
    }
    
    .debug-toggle-section h3, .debug-section h3 {
        font-size: 1em;
        margin-bottom: 15px;
    }
    
    /* ENSURE: System config sections flow naturally */
    .system-config-section {
        overflow: visible;
        flex-shrink: 0;
    }
    
    .config-details {
        overflow: visible;
    }

    .debug-button {
        bottom: 15px;
        right: 15px;
        font-size: 10px;
        padding: 8px 12px;
        border-radius: 20px;
    }

    .btn {
        padding: 10px 16px;
        font-size: 14px;
        border-radius: 8px;
        min-height: 44px;
        display: flex;
        align-items: center;
        justify-content: center;
    }

    /* FIX: Form inputs - prevent zoom without viewport changes */
    input, textarea, select {
        font-size: 16px !important;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
        /* PREVENT: Viewport jumping */
        -webkit-text-size-adjust: 100%;
        zoom: 1;
    }
    
    /* CRITICAL: Maintain header visibility on focus */
    input:focus, textarea:focus, select:focus {
        outline: none;
        zoom: 1;
        /* ENSURE: Header remains accessible */
        scroll-margin-top: 120px;
    }
    
    /* Specific overrides for text areas that commonly cause zoom */
    .ai-input, .journal-entry textarea, .filter-input, .prompt-textarea {
        font-size: 16px !important;
        font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
        -webkit-text-size-adjust: 100%;
        zoom: 1;
        transform: none;
    }
}

/* Additional fix for very small screens */
@media (max-width: 480px) {
    .content-section {
        --content-padding: 10px;
        padding: var(--content-padding);
        /* Bottom reach room comes from .content-section::after (the 768px rule above,
           still in effect here — media queries aren't mutually exclusive) rather than
           padding-bottom, which this shorthand would otherwise collapse back to 10px. */
        margin: 0 5px 5px 5px;
    }
    
    .app-nav {
        margin: 5px;
    }
    
    .elastic-time-display {
        padding: 1em;
    }
    
    .block-gauge-wrap {
        width: 130px;
        height: 98px;
    }

    .block-gauge {
        width: 130px;
        height: 78px;
    }

    .block-gauge-center {
        top: 58px;
    }

    .block-gauge-num {
        font-size: 1.75rem;
    }

    /* 2x2 instead of the flex row's 3-then-1 wrap at this width (Luke's
       call, 2026-08-23) — grid instead of flex so the pairing is deliberate
       (Sunrise+Solar Noon / Sunset+Nadir, matching DOM/chronological order)
       rather than whatever wraps. */
    .solar-chip-row {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 0.5em 1.2em;
        justify-items: center;
    }

    .status-rail {
        padding: 6px 10px;
    }
}

/* Landscape mobile orientation */
@media (max-width: 768px) and (orientation: landscape) {
    .content-section {
        height: calc(100vh - 60px);
        height: calc(100dvh - 60px);
        height: calc(100svh - 60px);
    }
    
    .elastic-time-display {
        padding: 1em;
        margin-bottom: 1em;
    }
    
    .block-gauge-wrap {
        margin-top: 0.5em;
    }
}