/* =============================================================================
   SudaHub — the shell
   RESKIN Phase 2 (docs/RESKIN.md §2.1, §2.2). Replaces the console shell
   (rail + ticker + status strip) with the mockup's phone silhouette:
   blurred sticky header, bottom tab bar, one column at every width.

   THE CONSOLE IS GONE. No rail, no status strip, no ≥1280px master–detail
   split, no library filter rail. On desktop this is a centered ~480px
   column with the lattice filling the margins — decided 2026-07-28. Do not
   reintroduce a wide layout for "desktop users"; there is one silhouette.

   Everything here is logical-property only, so the whole shell mirrors
   under dir="rtl" with no left/right of its own. The four content panels
   keep their internal styling until their own phase (4–6).
   ========================================================================== */

:root {
    --header-h: 74px;
    --tabbar-h: 56px;
    /* Native insets. env() resolves to 0px in a browser and to the real
       notch / home-indicator on iOS, so one rule covers web and Capacitor.
       Launching on both stores makes this load-bearing, not cosmetic. */
    --inset-top: env(safe-area-inset-top, 0px);
    --inset-bottom: env(safe-area-inset-bottom, 0px);
    --shell-max: 480px;
}

/* ================= APP FRAME =================
   The column. Centered at every width; the lattice shows through the
   margins on desktop, which is the whole point of keeping it. */
.app-shell {
    width: 100%;
    max-width: var(--shell-max);
    margin-inline: auto;
    min-height: 100vh;
    min-height: 100dvh;
    position: relative;
}

/* ================= HEADER =================
   Blurred, sticky, hairline bottom border, with a soft lift behind it —
   the mockup's own gradient, rebuilt from tokens. */
.app-header {
    position: sticky;
    inset-block-start: 0;
    z-index: 30;
    padding-block-start: calc(var(--inset-top) + var(--space-4));
    padding-block-end: var(--space-4);
    padding-inline: var(--space-6);
    display: flex;
    flex-direction: column;
    border-block-end: 1px solid var(--color-neutral-900);
    background:
        linear-gradient(180deg,
            color-mix(in srgb, var(--color-accent-900) 55%, var(--color-bg)) 0%,
            var(--color-bg) 100%);
    backdrop-filter: blur(14px) saturate(140%);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
}

.app-header-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.app-brand { display: flex; flex-direction: column; gap: 3px; min-width: 0; }

/* Solid accent, matching the mockup — NOT the full-chroma gradient. The
   gradient version also broke the contrast audit, because
   -webkit-text-fill-color:transparent means the computed `color` is not what
   is painted, so the mark had to be excluded from the check entirely. A
   solid mark is measurable. */
.app-wordmark {
    margin: 0;
    font-family: var(--font-brand);
    font-weight: var(--fw-medium);
    font-size: var(--fs-title-l);
    line-height: 1;
    letter-spacing: var(--tracking-heading);
    color: var(--color-accent);
}

.app-header-spacer { flex: 1; }

/* The LIVE pill and the header language toggle were removed by request
   (2026-07-31). Language is still changeable in You > Settings, which is
   where a once-per-install preference belongs; connection state is carried
   by the offline banner, which is a better signal than a green dot because
   it says what to DO about it. */

.app-header-btn {
    display: grid;
    place-items: center;
    min-width: 32px;
    height: 32px;
    padding-inline: var(--space-2);
    border-radius: var(--radius-md);
    border: none;
    background: transparent;
    color: var(--color-neutral-300);
    cursor: pointer;
}
.app-header-btn:hover { background: var(--color-surface); color: var(--color-accent-200); }
.app-header-btn svg { width: 18px; height: 18px; display: block; }


.app-tagline {
    margin: 0;
    font-family: var(--font-label);
    font-size: var(--fs-micro);
    letter-spacing: var(--tracking-wide);
    color: var(--color-neutral-500);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---------- Tokens button ---------- */
.app-tokens-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    min-height: 30px;
    padding-inline: var(--space-3);
    border-radius: var(--radius-pill);
    border: 1px solid var(--color-accent-700);
    background: transparent;
    color: var(--color-accent-200);
    font-family: var(--font-mono);
    font-size: var(--fs-meta);
    font-weight: var(--fw-medium);
    cursor: pointer;
    white-space: nowrap;
}
.app-tokens-btn:hover { background: color-mix(in srgb, var(--color-accent) 14%, transparent); }
.app-tokens-btn svg { width: 15px; height: 15px; flex: none; }

/* ================= OFFLINE BANNER ================= */
.app-offline-banner {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-6);
    background: var(--color-accent-900);
    border-block-end: 1px solid var(--color-accent-800);
    font-size: var(--fs-caption);
    color: var(--color-accent-200);
}
.app-offline-banner svg { width: 15px; height: 15px; flex: none; }

/* ================= CONTENT =================
   Bottom padding clears the fixed tab bar plus the home indicator. */
.app-content-region {
    box-sizing: border-box;
    width: 100%;
    padding-inline: var(--space-4);
    padding-block-start: var(--space-4);
    padding-block-end: calc(var(--tabbar-h) + var(--inset-bottom) + var(--space-8));
}

/* ================= PULL TO REFRESH =================
   Height is set inline by the gesture (gestures.js) and is 0 at rest, so
   this occupies nothing until pulled. */
.ptr-gutter {
    height: 0;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}
.ptr-inner {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    opacity: 0;
    transition: opacity var(--dur-micro) var(--ease-out);
}
.ptr-gutter[data-visible="true"] .ptr-inner { opacity: 1; }
.ptr-spinner {
    width: 14px;
    height: 14px;
    border-radius: var(--radius-pill);
    border: 1.6px solid var(--color-accent-700);
    border-block-start-color: var(--color-accent);
    animation: sh-spin 0.9s linear infinite;
    animation-play-state: paused;
}
.ptr-gutter[data-refreshing="true"] .ptr-spinner { animation-play-state: running; }
@keyframes sh-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
    /* The spinner is the only signal that a refresh is in flight, so it is
       not removed under reduced motion — just slowed to a non-vestibular
       rate rather than stopped. */
    .ptr-spinner { animation-duration: 2.4s; }
}
.ptr-label {
    font-family: var(--font-label);
    font-size: var(--fs-micro);
    letter-spacing: var(--tracking-label);
    color: var(--color-neutral-400);
}
[lang="en"] .ptr-label { text-transform: uppercase; }

/* ================= TAB BAR =================
   Fixed to the viewport bottom, but constrained to the column's width so
   the header and the tab bar share one silhouette. A full-bleed bar under a
   480px column reads as two unrelated surfaces on desktop; on a phone the
   two are identical anyway, since the column IS the viewport. */
.app-tabbar {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    z-index: 40;
    width: 100%;
    max-width: var(--shell-max);
    margin-inline: auto;
    border-block-start: 1px solid var(--color-neutral-900);
    background: var(--glass-bg);
    backdrop-filter: blur(14px) saturate(140%);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    padding-block-end: var(--inset-bottom);
}
@supports not (backdrop-filter: blur(1px)) {
    .app-tabbar { background: var(--color-surface); }
}

.app-tabbar-items {
    max-width: var(--shell-max);
    margin-inline: auto;
    display: flex;
    align-items: stretch;
    gap: var(--space-1);
    padding: var(--space-2) var(--space-3);
}

.app-tab {
    flex: 1;
    /* 50px per the mockup, but the tap target must stay ≥44px (§9) — the
       padding here is what guarantees it, not the height. */
    min-height: 50px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    border: none;
    background: transparent;
    cursor: pointer;
    /* §5's fix: the mockup ran these at neutral-600 (4.3:1). The floor for
       a 9.5px tracked label is neutral-400. */
    color: var(--color-neutral-400);
    font-family: var(--font-ui);
    font-weight: var(--fw-medium);
    font-size: var(--fs-tab);
    letter-spacing: var(--tracking-tight);
    line-height: 1;
}
[lang="en"] .app-tab { text-transform: uppercase; }
.app-tab svg { width: 21px; height: 21px; display: block; }
.app-tab.is-active { color: var(--color-accent); }
.app-tab:active { opacity: 0.7; }

/* ================= TOAST =================
   Above the tab bar, 6px accent dot, short slide-up (§2.2). */
.toast-host {
    position: fixed;
    inset-inline: 0;
    inset-block-end: calc(var(--tabbar-h) + var(--inset-bottom) + var(--space-8));
    /* TOP OF THE STACK, above every surface in the app — room 150, book page
       155, reader 160, screenshot overlay 200, splash 300.
       It used to be 70, which put it UNDER all of them: buying a Fast Pass
       inside the discussion room fired a toast that rendered behind the room,
       so the confirmation was only discovered later, sitting on the Club
       screen. A message the user cannot see is the same as no message, and
       these carry things like "you need 8 more tokens". */
    z-index: 400;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    pointer-events: none;
    padding-inline: var(--space-6);
}
.toast {
    width: 100%;
    max-width: calc(var(--shell-max) - var(--space-8));
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-4);
    border-radius: var(--radius-md);
    background: var(--color-neutral-900);
    box-shadow: var(--shadow-md);
    font-size: var(--fs-label);
    color: var(--color-neutral-200);
    pointer-events: auto;
}
.toast-dot {
    width: 6px;
    height: 6px;
    flex: none;
    border-radius: var(--radius-pill);
    background: var(--color-accent);
}
.toast-body { flex: 1; min-width: 0; }
.toast-undo {
    flex: none;
    border: none;
    background: transparent;
    color: var(--color-accent-200);
    font-family: var(--font-ui);
    font-weight: var(--fw-medium);
    font-size: var(--fs-caption);
    letter-spacing: var(--tracking-tight);
    cursor: pointer;
}
[lang="en"] .toast-undo { text-transform: uppercase; }

@keyframes sh-up { from { transform: translateY(14px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@media (prefers-reduced-motion: no-preference) {
    .toast { animation: sh-up var(--dur-enter) var(--ease-out) both; }
}

/* ================= ONBOARDING =================
   Full-cover sequence, opening on the language choice (§2.2). Sits under
   toasts and sheets so a toast raised during onboarding is still visible. */
.onboarding {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    flex-direction: column;
    padding-block-start: calc(var(--inset-top) + 74px);
    padding-block-end: calc(var(--inset-bottom) + 44px);
    padding-inline: var(--space-8);
    background:
        radial-gradient(700px 420px at 15% 0%, var(--color-accent-900) 0%, var(--color-bg) 62%);
}
.onboarding-inner {
    width: 100%;
    max-width: var(--shell-max);
    margin-inline: auto;
    display: flex;
    flex-direction: column;
    flex: 1;
}
.onboarding-brand { display: flex; align-items: baseline; gap: var(--space-3); }
.onboarding-brand-mark {
    font-family: var(--font-brand);
    font-weight: var(--fw-medium);
    font-size: var(--fs-display);
    line-height: 1;
    color: var(--color-accent);
}
.onboarding-brand-tag {
    font-family: var(--font-label);
    font-size: var(--fs-micro);
    letter-spacing: var(--tracking-wide);
    color: var(--color-neutral-500);
}

.onboarding-step { display: flex; flex-direction: column; flex: 1; }
.onboarding-step[hidden] { display: none; }
@media (prefers-reduced-motion: no-preference) {
    .onboarding-step { animation: sh-up var(--dur-enter) var(--ease-out) both; }
}

/* Step 1 sits its content at the bottom of the screen, steps 2-3 at the top.
   This is justify-content, not `margin-block-start:auto` — the step is
   already `flex:1`, so an auto margin has nothing left to push against. */
.onboarding-step-lead { justify-content: flex-end; gap: var(--space-6); }
.onboarding-step-top { justify-content: flex-start; gap: var(--space-4); padding-block-start: var(--space-8); }

.onboarding-headline {
    font-family: var(--font-ui);
    font-weight: var(--fw-medium);
    font-size: var(--fs-hero);
    line-height: var(--lh-tight);
    letter-spacing: var(--tracking-heading);
    text-wrap: pretty;
    color: var(--color-neutral-100);
}
.onboarding-headline-sm { font-size: var(--fs-display); }
.onboarding-sub {
    font-size: var(--fs-body);
    line-height: var(--lh-body);
    color: var(--color-neutral-400);
    text-wrap: pretty;
}
.onboarding-note { font-size: var(--fs-body-s); color: var(--color-neutral-500); }

.onboarding-actions { display: flex; gap: var(--space-4); margin-block-start: var(--space-2); }

.onboarding-btn {
    flex: 1;
    min-height: 52px;
    border-radius: var(--radius-md);
    border: 1px solid var(--color-accent);
    background: transparent;
    color: var(--color-accent-200);
    font-family: var(--font-ui);
    font-weight: var(--fw-medium);
    font-size: var(--fs-body-l);
    cursor: pointer;
}
.onboarding-btn:hover { background: color-mix(in srgb, var(--color-accent) 14%, transparent); }
.onboarding-btn-quiet {
    border-color: var(--color-neutral-700);
    color: var(--color-neutral-300);
}
.onboarding-btn-quiet:hover { border-color: var(--color-accent); color: var(--color-accent-200); background: transparent; }
.onboarding-btn-text {
    min-height: 44px;
    border: none;
    background: transparent;
    color: var(--color-neutral-500);
    font-size: var(--fs-body-s);
    font-weight: var(--fw-regular);
    cursor: pointer;
}
.onboarding-btn-text:hover { color: var(--color-accent-200); }

.onboarding-chips { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.onboarding-chip {
    min-height: 38px;
    padding-inline: var(--space-6);
    border-radius: var(--radius-pill);
    border: 1px solid var(--color-neutral-800);
    background: transparent;
    color: var(--color-neutral-400);
    font-family: var(--font-ui);
    font-weight: var(--fw-medium);
    font-size: var(--fs-label);
    letter-spacing: var(--tracking-tight);
    cursor: pointer;
}
.onboarding-chip[aria-pressed="true"] {
    border-color: var(--color-accent);
    background: color-mix(in srgb, var(--color-accent) 16%, transparent);
    color: var(--color-accent-200);
}

.onboarding-field { display: flex; flex-direction: column; gap: var(--space-2); }
.onboarding-label {
    font-family: var(--font-label);
    font-size: var(--fs-micro);
    letter-spacing: var(--tracking-label);
    color: var(--color-neutral-500);
}
[lang="en"] .onboarding-label { text-transform: uppercase; }
.onboarding-input {
    min-height: 48px;
    border-radius: var(--radius-md);
    border: 1px solid var(--color-neutral-800);
    background: var(--color-surface);
    color: var(--color-text);
    padding-inline: var(--space-4);
    font-family: var(--font-ui);
    font-size: var(--fs-body);
    outline: none;
}
.onboarding-input:focus-visible { border-color: var(--color-accent); }

.onboarding-foot { margin-block-start: auto; display: flex; gap: var(--space-2); padding-block-start: var(--space-6); }
.onboarding-pip { height: 2px; flex: 1; background: var(--color-accent-800); border-radius: var(--radius-pill); }
.onboarding-pip.is-active { background: var(--color-accent); }

/* ================= RETIRED =================
   The console shell's classes are gone from the markup. These no-op rules
   stay only long enough to catch a stale reference during Phases 3–6; if
   one of these ever paints, something still emits console markup. */
.app-rail, .app-statusbar, .app-ticker, .app-topbar { display: none !important; }

.app-offline-retry {
    flex: none;
    min-height: 26px;
    padding-inline: var(--space-3);
    border-radius: var(--radius-pill);
    border: 1px solid var(--color-accent-600);
    background: transparent;
    color: var(--color-accent-200);
    font-family: var(--font-label);
    font-size: var(--fs-micro);
    letter-spacing: var(--tracking-tight);
    cursor: pointer;
}
[lang="en"] .app-offline-retry { text-transform: uppercase; }
.app-offline-retry:hover { background: color-mix(in srgb, var(--color-accent) 16%, transparent); }

/* ---------- Tokens centre ---------- */
.tokens-balance {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: var(--space-2);
    padding-block: var(--space-6);
}
.tokens-balance-n {
    font-family: var(--font-mono);
    font-size: var(--fs-hero);
    font-weight: var(--fw-medium);
    color: var(--color-accent-200);
}
.tokens-balance-label {
    font-family: var(--font-label);
    font-size: var(--fs-caption);
    letter-spacing: var(--tracking-label);
    color: var(--color-neutral-500);
}
[lang="en"] .tokens-balance-label { text-transform: uppercase; }
.tokens-explainer {
    margin: 0 0 var(--space-6);
    font-size: var(--fs-body-s);
    line-height: var(--lh-body);
    color: var(--color-neutral-400);
    text-align: center;
}
.tokens-watch { width: 100%; }
.tokens-note {
    margin: var(--space-4) 0 0;
    font-size: var(--fs-caption);
    line-height: var(--lh-body);
    color: var(--color-neutral-500);
    text-align: center;
}

/* ================= SPLASH (RESKIN) =================
   Replaces the console-era splash: a gradient-filled "S" tile on
   --brand-navy, drawn from the full-chroma palette CLAUDE.md §4 marks as
   superseded. No icon now — the wordmark and the slogan are the identity,
   exactly as in the header, on the same flat --color-bg the rest of the app
   uses.

   Written entirely in local CSS, with no Tailwind utility classes. That is
   deliberate and it is the point of the exercise: `flex` and `items-center`
   are defined ONLY by the CDN stylesheet, so the previous splash — the very
   first thing painted — sat unstyled until a network round-trip to
   cdn.tailwindcss.com came back. On the connections this app is built for,
   that is precisely backwards. */
.splash {
    position: fixed;
    inset: 0;
    z-index: 300;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    overflow: hidden;
    background: var(--color-bg);
    transition: opacity 380ms var(--ease-out);
}

/* Same face, weight and colour as .app-wordmark in the header, just larger —
   so the mark does not shift character between the splash and the app. */
.splash-wordmark {
    margin: 0;
    font-family: var(--font-brand);
    font-weight: var(--fw-medium);
    font-size: calc(var(--fs-hero) * 1.35);
    line-height: 1;
    letter-spacing: var(--tracking-heading);
    color: var(--color-accent);
}

/* A hairline, the app's own structural device, drawn in rather than decorated
   with. It is the only moving element that is not text. */
.splash-rule {
    width: 68px;
    height: 1px;
    background: var(--color-accent);
    opacity: 0.45;
    transform-origin: center;
}

.splash-tagline {
    margin: 0;
    font-family: var(--font-label);
    font-size: var(--fs-micro);
    letter-spacing: var(--tracking-wide);
    color: var(--color-neutral-500);
    text-align: center;
}
[lang="en"] .splash-tagline { text-transform: uppercase; }
/* Never letter-space or uppercase Arabic (§3) — the script is connected and
   tracking breaks it. */
[lang="ar"] .splash-tagline { letter-spacing: 0; text-transform: none; }

/* ---- Entrance: staggered, and over before it draws attention to itself ---- */
@keyframes splash-rise {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes splash-draw {
    from { transform: scaleX(0); opacity: 0; }
    to   { transform: scaleX(1); opacity: 0.45; }
}
.splash-wordmark { animation: splash-rise 560ms var(--ease-out) both; }
.splash-rule     { animation: splash-draw 520ms var(--ease-out) 220ms both; }
.splash-tagline  { animation: splash-rise 560ms var(--ease-out) 340ms both; }

/* Motion is a courtesy, never a gate: with it suppressed the splash must still
   be fully legible, not invisible waiting for an animation that never runs. */
@media (prefers-reduced-motion: reduce) {
    .splash-wordmark, .splash-rule, .splash-tagline { animation: none; }
    .splash-rule { opacity: 0.45; }
}
[data-reduce-effects="true"] .splash-wordmark,
[data-reduce-effects="true"] .splash-rule,
[data-reduce-effects="true"] .splash-tagline { animation: none; }
[data-reduce-effects="true"] .splash-rule { opacity: 0.45; }
