/* =========================================================================
   Circly — shared design tokens. SINGLE SOURCE OF TRUTH for the --d-*
   palette (16 names, values lifted verbatim from public/css/app.css:8-12)
   and the four Vazirmatn font faces (copied verbatim from
   public/css/style.css:3-26).

   Extended 2026-08-23 with the scales this file originally declared out of
   scope: type, spacing, radius, elevation, stacking and motion. Every scale
   below was derived by MEASURING the existing stylesheets, not by importing
   a grid — the step values are the values already in use, so adopting them
   is close to a no-op visually. That is the point: this layer exists to stop
   the drift (38 font sizes and 202 raw hexes in app.css alone), not to
   restyle a shipped product.

   RULE: component CSS references these names only. A raw px, rem or hex in
   app.css / landing.css / style.css / admin.css is now a defect.
   ========================================================================= */

:root {
  --d-rail: #0b0d11;
  --d-side: #14171f;
  --d-main: #0f1216;
  --d-hover: #1b1f29;
  --d-active: #232838;
  --d-float: #080a0e;
  --d-head: #14171f;
  --d-text: #eef1f6;
  --d-dim: #9aa3b2;
  --d-faint: #656d7d;
  --d-mute: #4c5260;
  --d-accent: #7c5cff;
  --d-accent-hover: #6a49f2;
  --d-green: #3ddc84;
  --d-red: #ff5c6c;
  --d-divider: rgba(0, 0, 0, .32);
}

/* Redesign (v1.4, owner decision 2026-10-07): the v2 glass + aurora look, dark only. Active
   only when the inline flag in app.html sets data-ui=2. This block is the sole
   declaration site for the new design. Full colours only, never HSL triplets: the
   app uses var() and color-mix() directly.

   REVISION 2026-10-07 ("exact v2 everywhere", owner): the palette below is the dark
   theme of server-preview-v2.html (preview/shadcn-server, e53d5a5) converted from its
   HSL triplets to 8-bit sRGB, replacing the earlier calmer Slate + Sky values. The one
   value that does NOT equal the v2 conversion is --d-red (v2 #f05151, here #f15f5f): v2's
   own red fails 4.5:1 as text on the hover and selected surfaces (4.24 / 4.23). Spec and
   before/after: .planning/redesign/V2-DESIGN-LANGUAGE.md. */
:root[data-ui="2"] {
  /* Surfaces. v2 panels are translucent glass over the aurora; each token below is the
     OPAQUE twin the text gate measures against (the glass fill composited over --d-main).
     Every text token below measures >= 4.5:1 on every one of these
     (test-tokens-static.mjs, DES-06). The rail has no panel in v2: it floats on the page. */
  --d-rail: #05080f;
  --d-main: #05080f;
  --d-side: #090e18;
  --d-head: #090e18;
  --d-float: #0d1322;
  --d-hover: #1d283a;
  --d-active: #112a3c;

  /* Text. v2 has two text tiers (foreground, muted-foreground) plus a hint at 80% alpha.
     --d-faint and --d-mute stay ONE AA grey for the hint tier: they carry 134 text sites
     today (research Pitfall 3), so under the flag they must be legible.
     TEXT x SURFACES: measured 4.5 and up */
  --d-text: #f8fafc;
  --d-dim: #94a3b8;
  --d-faint: #8f9db2;
  --d-mute: #8f9db2;

  /* Accent: v2 "primary" sky (198 93% 60%) with its cyan partner (187 92% 69%) for the
     gradient end, and DARK foreground on it (white on sky is 2.8:1 and is forbidden).
     --d-accent-text is the same sky: v2 sets accent text directly on glass.
     CONTROLS: measured 6.8 to 13 */
  --d-accent: #3abff8;
  --d-accent-2: #67e8f9;
  --d-accent-hover: var(--d-accent-2);
  --d-accent-press: #0ea5e9;
  --d-accent-text: #3abff8;
  --d-accent-fg: #0b111e;

  /* Status. Text colours, so they are in the TEXT matrix. --d-red is v2's destructive and
     do-not-disturb red lightened 3 points of HSL lightness to pass on --d-hover and --d-active. */
  --d-green: #30d96e;
  --d-red: #f15f5f;
  --d-gold: #f6aa28;
  --d-offline: #5a687c;

  /* Lines. --d-divider and --d-border are decorative (v2 glass-line, 12% of a steel blue)
     and never the only cue for a control. --d-border-input and --d-ring are the
     non-text pairs (WCAG 1.4.11). NON_TEXT: measured 3.1 to 9.5. Inputs never sit on
     --d-active. */
  --d-divider: rgba(112, 146, 194, .12);
  --d-border: rgba(112, 146, 194, .12);
  --d-border-input: #64748b;
  --d-ring: #3abff8;

  /* Tints. v2 builds every other tint with color-mix(in srgb, var(--d-accent) N%, transparent). */
  --d-accent-soft: rgba(58, 191, 248, .16);
  --d-accent-faint: rgba(58, 191, 248, .10);
  --d-glow: #3abff8;
  --d-scrim: rgba(2, 6, 23, .55);

  /* Glass. Opaque twin first: every fallback resolves to --d-glass-solid (panels) or
     --d-float (menus, composer, docks). v2 values: panel fill 62%, raised fill 86%,
     blur 22px saturate 1.4. The blur steps are v2's own, one per surface family.
     TRANSLUCENT: measured 5.0 and up even over a white page. */
  --d-glass: rgba(11, 17, 30, .62);
  --d-glass-2: rgba(14, 21, 37, .86);
  --d-glass-solid: #090e18;
  --d-glass-line: rgba(112, 146, 194, .12);
  --d-glass-blur: 22px;
  --d-glass-blur-lg: 24px;
  --d-glass-blur-md: 20px;
  --d-glass-blur-sm: 16px;
  --d-glass-blur-xs: 12px;
  --d-glass-blur-chip: 8px;
  --d-glass-saturate: 1.4;

  /* Aurora backdrop (three blurred blobs behind everything) and the v2 shadow family. */
  --d-aurora-a: rgba(13, 162, 231, .28);
  --d-aurora-b: rgba(9, 196, 220, .14);
  --d-aurora-c: rgba(67, 70, 239, .16);
  --d-aurora-blur: 80px;
  --d-shadow-panel: 0 20px 50px -20px rgba(0, 0, 0, .7);
  --d-shadow-menu: 0 24px 60px -20px rgba(0, 0, 0, .6);
  --d-shadow-dock: 0 20px 50px -20px rgba(0, 0, 0, .6);
  --d-shadow-profile: 0 30px 80px -24px rgba(0, 0, 0, .7);
  --d-shadow-palette: 0 40px 100px -30px rgba(0, 0, 0, .8);
  --d-shadow-poster: 0 6px 14px -6px rgba(0, 0, 0, .6);

  /* User-data colours (avatars, server tiles, role names). These are what people chose or were
     assigned, not brand chrome; --d-av-6 is violet and is allowlisted for that reason in
     test-tokens-static.mjs (PURPLE_ALLOW). Index order = the v2 sample people. */
  --d-av-1: #0ea5e9;
  --d-av-2: #ec4899;
  --d-av-3: #6366f1;
  --d-av-4: #f59e0b;
  --d-av-5: #10b981;
  --d-av-6: #8b5cf6;
  --d-av-7: #14b8a6;
  --d-av-8: #f97316;
  --d-av-9: #e11d48;
  --d-av-10: #64748b;
  --d-av-11: #0284c7;
  --d-av-12: #475569;
  /* Server tile gradient: 140deg from --d-av-N to --d-srv-N (v2: friends 1, gamers 3, books 4, english 5, lofi 2). */
  --d-srv-1: #0e7490;
  --d-srv-2: #831843;
  --d-srv-3: #312e81;
  --d-srv-4: #b45309;
  --d-srv-5: #065f46;
  --d-role-mod: #38bdf8;
  --d-role-dj: #f472b6;

  /* Artwork (server banner, poster, event art, watch stage). Illustration, not chrome. */
  --d-art-1: #38bdf8;
  --d-art-2: #0e7490;
  --d-art-3: #0c4a6e;
  --d-art-4: #172554;
  --d-art-5: #7dd3fc;
  --d-art-6: #020617;
  --d-art-7: #082f49;
  --d-art-8: #0b1220;
  --d-art-9: #f472b6;
  --d-art-10: #1e1b4b;
  --d-art-11: #e0f2fe;

  /* Radius steps v2 uses that the shared ladder lacks (the ladder already has 6/8/10/14/20). */
  --r-12: 12px;
  --r-16: 16px;
  --r-18: 18px;

  /* Spacing steps for the new design only. They extend the ladder: because
     --sp-11 is already 52px, the numbers 12-15 are NOT size ordered against it. */
  --sp-12: 24px;
  --sp-13: 32px;
  --sp-14: 48px;
  --sp-15: 64px;
}

@font-face {
  font-family: 'Vazirmatn';
  src: url('../fonts/Vazirmatn-Regular.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Vazirmatn';
  src: url('../fonts/Vazirmatn-Medium.woff2') format('woff2');
  font-weight: 500;
  font-display: swap;
}
@font-face {
  font-family: 'Vazirmatn';
  src: url('../fonts/Vazirmatn-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: 'Vazirmatn';
  src: url('../fonts/Vazirmatn-Bold.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}

/* =========================================================================
   Baseline keyboard focus + motion preference, for EVERY surface.

   app.css restored the focus ring for the shell (app.css:1064) and landing.css
   did the same for the landing page, but style.css — which dresses login,
   reset-password, invite, room, index and the 404 — had no :focus-visible rule
   at all. Login and password reset are the two highest-stakes keyboard forms in
   the product and neither showed where focus was.

   This is the floor, not the law: the selector is wrapped in :where() so it
   carries ZERO specificity and every existing app.css / landing.css rule keeps
   winning unchanged. Surfaces that already answer focus their own way are
   unaffected; surfaces that answered not at all now do.
   ========================================================================= */
:where(button, [href], input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--d-accent);
  outline-offset: 2px;
  border-radius: 6px;
}

/* Motion preference, likewise applied everywhere rather than per-stylesheet.
   app.css carries an identical block for the shell; this one is what reaches
   the six style.css pages, which had none. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* =========================================================================
   TIER 1 — additional global colour. Every value below already appears as a
   literal in app.css; naming it here is the whole change. The counts are the
   number of raw occurrences each token retires.
   ========================================================================= */
:root {
  --d-white: #fff;                        /* 57× */
  --d-black: #000;                        /* 10× */
  --d-gold: #f0b232;                      /* 16× — boost / warning amber */
  --d-cyan: #3ec9ff;                      /* 6× — SECOND BRAND HUE. Partner in
                                             --lp-grad and the "changed" kind.
                                             Introduced by the 2026-08-10 landing
                                             redesign and never tokenised. */
  --d-accent-text: #a48cff;               /* accent AS TEXT on --d-main — 6.95:1.
                                             --d-accent itself is 3.4:1 and must
                                             never be used for body-size text. */
  --d-amber: #ffc65c;                     /* changelog "fixed" — distinct from
                                             --d-gold, which means boost. */

  /* --- TIER 2: aliases that RESOLVE PHANTOM NAMES ---------------------------
     app.css referenced --d-warn and --d-bg2 through var(--name, #hex). Neither
     was ever declared anywhere, so the inline fallback was doing 100% of the
     work and the token name was decoration. Declaring them here makes the name
     real and folds two near-miss literals into the palette:
       --d-warn  fallback was #faa61a — 10 units off --d-gold  #f0b232
       --d-bg2   fallback was #0f1218 —  2 units off --d-main  #0f1216
     The --d-warn fold is the only visible change (one em-badge, slightly less
     orange); --d-bg2 is imperceptible. */
  --d-warn: var(--d-gold);
  --d-bg2: var(--d-main);

  /* Accent + status tints. Always the accent at low alpha, never a new hue. */
  --d-accent-soft: rgba(124, 92, 255, .18);   /* 6× — pressed / mine */
  --d-accent-faint: rgba(124, 92, 255, .12);  /* 3× — hover wash */
  --d-red-soft: rgba(255, 92, 108, .16);      /* 2× — destructive hover */
  --d-gold-soft: rgba(240, 178, 50, .1);      /* 2× */
  --d-gold-edge: rgba(240, 178, 50, .35);     /* 2× */

  /* Scrims — behind modals and over media. */
  --d-scrim: rgba(0, 0, 0, .5);           /* 12× */
  --d-scrim-soft: rgba(0, 0, 0, .35);
  --d-scrim-heavy: rgba(0, 0, 0, .6);     /* 4× */

  /* White tints — the app's hairlines and hover washes on dark surfaces.
     Five steps because five are genuinely in use; do not add a sixth. */
  --d-tint-1: rgba(255, 255, 255, .06);   /* 6× */
  --d-tint-2: rgba(255, 255, 255, .08);   /* 8× — .07 and .09 fold in here */
  --d-tint-3: rgba(255, 255, 255, .12);
  --d-tint-4: rgba(255, 255, 255, .18);   /* 3× — .16 folds in here */
  --d-tint-5: rgba(255, 255, 255, .25);

  /* Wordle board. Domain colours, deliberately NOT the product palette — the
     game is a quotation of a known board and reads wrong recoloured. Named so
     they stop looking like stray hexes during a palette audit. */
  --w-hit: #6aaa64;
  --w-near: #c9b458;
  --w-miss: #3a3a3c;
  --w-key: #818384;
  --w-key-dim: #565758;
  --w-dim: #787c7e;
}

/* =========================================================================
   TIER 1 — type scale. app.css alone carried 38 distinct literal font sizes
   (48 across all stylesheets), ~200 of them packed into the 11px–15px band.
   Eleven steps replace all of them. Steps 100–600 ARE that dense band: each
   is load-bearing today, and each snap moves text by at most ~0.5px.
   Body copy is --fs-700. Nothing outside this ladder.
   ========================================================================= */
:root {
  --fs-100: .66rem;    /* 10.6px — badge counts, micro timestamps */
  --fs-200: .72rem;    /* 11.5px — hints, captions, edit hints */
  --fs-300: .78rem;    /* 12.5px — chips, secondary labels */
  --fs-400: .82rem;    /* 13.1px — dense lists, member rows, nav */
  --fs-500: .88rem;    /* 14.1px — buttons, inputs, form labels */
  --fs-600: .94rem;    /* 15.0px — dense body */
  --fs-700: 1rem;      /* 16px   — body: message text */
  --fs-800: 1.15rem;   /* 18.4px — dialog + section titles */
  --fs-900: 1.5rem;    /* 24px   — headings */
  --fs-1000: 2rem;     /* 32px   — display */
  --fs-1100: 3rem;     /* 48px   — hero (landing only) */

  /* Weights. Only these four have a real Vazirmatn face loaded above —
     anything else is synthesised by the browser and looks it.
     app.css previously used font-weight:800 at six sites with no 800 face
     loaded, so the browser synthesised the weight on top of the 700 face —
     heavier and softer-edged than real bold. Those six now use --fw-bold.
     Adding a weight here without shipping its .woff2 re-creates that bug. */
  --fw-regular: 400;
  --fw-medium: 500;
  --fw-semibold: 600;
  --fw-bold: 700;

  /* Line height. Persian sets taller than Latin — Vazirmatn's ascenders and
     the diacritics both want the extra leading, so --lh-normal is 1.55 and
     not the 1.4 a Latin-first system would pick. */
  --lh-tight: 1.2;     /* display + headings */
  --lh-snug: 1.35;     /* dense UI rows */
  --lh-normal: 1.55;   /* body / message text */
  --lh-relaxed: 1.75;  /* long-form prose: legal, changelog */
}

/* =========================================================================
   TIER 1 — spacing. Measured, not invented: 8/6/10/12/4 account for ~470 of
   the ~600 spacing literals in app.css. This ladder keeps every one of those
   and absorbs only the strays (1,3,5,7,9,11,18,22,24,32), so adopting it is
   a near-zero visual change. A textbook 4px grid would be more principled
   and would move ~250 declarations — deliberately not done here.
   ========================================================================= */
:root {
  --sp-1: 2px;    /* 44× — icon nudges, chip insets */
  --sp-2: 4px;    /* 71× — absorbs 3px, 5px */
  --sp-3: 6px;    /* 98× — absorbs 7px */
  --sp-4: 8px;    /* 130× — the default gap */
  --sp-5: 10px;   /* 94× — absorbs 9px, 11px */
  --sp-6: 12px;   /* 72× */
  --sp-7: 14px;   /* 31× */
  --sp-8: 16px;   /* 28× */
  --sp-9: 20px;   /* 39× — absorbs 18px, 22px */
  --sp-10: 28px;  /* absorbs 24px, 32px */
  --sp-11: 52px;  /* section rhythm */
}

/* =========================================================================
   TIER 1 — radius. 20 distinct literals become 8 steps. --r-pill and
   --r-circle are separate on purpose: a pill is a rectangle whose ends are
   round, a circle is an avatar. They are not interchangeable and 50% on a
   non-square element is a bug, not a pill.
   ========================================================================= */
:root {
  --r-1: 4px;      /* absorbs 1,2,3,5 — inline code, tiny chips */
  --r-2: 6px;      /* absorbs 7 — inputs, small buttons */
  --r-3: 8px;      /* absorbs 9 — the default: cards, chips, menus */
  --r-4: 10px;     /* absorbs 11,12 — popovers, panels */
  --r-5: 14px;     /* absorbs 16 — dialogs */
  --r-6: 20px;     /* absorbs 18,24 — sheets, large surfaces */
  --r-pill: 999px; /* absorbs 99px */
  --r-circle: 50%; /* avatars and status dots ONLY */
}

/* =========================================================================
   TIER 1 — elevation + stacking. Five shadows, and a z ladder to replace the
   ad-hoc 0/2/60/69/70/80/100/130/150/180/190/200/260/300/400 in app.css.
   `z-index: 69` sitting next to `z-index: 70` is the reason this exists.
   Nothing may invent a value between two rungs.

   The order, lowest to highest:
     sticky < dropdown   floats that live inside one panel's own stacking context
                         (a header, a composer, a menu that never leaves its panel)
     < overlay           drawers (rail, channel list, member list) and their scrim
     < modal             dialogs: the dialog scrim and its card
     < popover           menus, popovers, pickers and the quick profile card: they
                         open from drawers AND from dialogs, so they sit above both
     < toast             toasts clear everything that can be open
     < tooltip           nothing above this
   quick 261023-d9q (2026-10-23) added the popover rung: on a phone the drawers
   (300) swallowed every popup that stayed on dropdown (200); still nothing
   between two rungs, and no existing rung's value changed.
   ========================================================================= */
:root {
  --sh-1: 0 2px 8px rgba(0, 0, 0, .3);      /* resting menus */
  --sh-2: 0 4px 14px rgba(0, 0, 0, .45);    /* popovers */
  --sh-3: 0 8px 24px rgba(0, 0, 0, .5);     /* dialogs */
  --sh-4: 0 10px 30px rgba(0, 0, 0, .5);    /* mention picker, drawers */
  --sh-5: 0 20px 60px rgba(0, 0, 0, .6);    /* full-screen surfaces */
  --sh-divider: 0 1px 0 var(--d-divider);   /* header hairline */

  --z-base: 0;
  --z-raised: 2;        /* in-flow lift: hover cards, sticky rows */
  --z-sticky: 100;      /* channel header, composer bar */
  --z-dropdown: 200;    /* menus, mention picker, emoji tray */
  --z-overlay: 300;     /* drawers, side sheets, scrims */
  --z-modal: 400;       /* dialogs */
  --z-popover: 450;     /* menus, popovers, pickers, quick profile card: above drawers (300) AND dialogs (400), because they open from both */
  --z-toast: 500;       /* toasts — must clear dialogs */
  --z-tooltip: 600;     /* nothing above this */
}

/* =========================================================================
   TIER 1 — motion. .12s already dominates (42×) and one easing curve is
   already doing all the real work (10×); this just names them. Every value
   here is still overridden to ~0 by the prefers-reduced-motion block below.
   ========================================================================= */
:root {
  --dur-1: 80ms;    /* press / active feedback */
  --dur-2: 120ms;   /* the default: hover, colour, border */
  --dur-3: 160ms;   /* small enter/exit */
  --dur-4: 240ms;   /* panels, drawers */
  --dur-5: 400ms;   /* full-surface transitions */

  --ease-standard: cubic-bezier(.2, .8, .2, 1);  /* the house curve */
  --ease-out: ease-out;
  --ease-in-out: ease-in-out;
}

/* =========================================================================
   SCROLLBARS — one styled treatment, everywhere.
   =========================================================================
   The app styled three scrollable regions by hand (.channel-list, .msg-list,
   .menu) with a raw hex, and left everything else — the whole admin panel,
   every settings sheet, every dialog body — on the operating system's default.
   The result was a dark product with a pale grey Windows scrollbar cutting
   through half its surfaces, which is exactly where a UI stops feeling like
   one thing.

   Declared here because tokens.css is the only stylesheet EVERY page loads, so
   this reaches app, admin, landing, login and pay from one place.

   Regions that deliberately hide their scrollbar (.server-rail, .ctrl-row) keep
   doing so: a class selector outranks the universal one below, in both the
   standard property and the -webkit pseudo-element. */
:root {
  --d-sb-thumb:       rgba(255, 255, 255, .14);
  --d-sb-thumb-hover: rgba(255, 255, 255, .26);
  --d-sb-track:       transparent;
  --d-sb-size:        10px;
}

/* Firefox and the standard property. `thin` rather than `auto` — a full-width
   scrollbar inside a 240px sidebar is a quarter of the content width. */
* { scrollbar-width: thin; scrollbar-color: var(--d-sb-thumb) var(--d-sb-track); }

/* Chromium and WebKit. Track stays transparent so the bar reads as part of the
   surface it sits on rather than as a channel cut into it. */
*::-webkit-scrollbar { inline-size: var(--d-sb-size); block-size: var(--d-sb-size); }
*::-webkit-scrollbar-track { background: var(--d-sb-track); }
*::-webkit-scrollbar-thumb {
  background: var(--d-sb-thumb);
  border-radius: 999px;
  /* A transparent border with background-clip is the only way to inset a
     -webkit thumb; padding and margin do nothing to it. */
  border: 2px solid transparent;
  background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--d-sb-thumb-hover); background-clip: content-box; }
*::-webkit-scrollbar-corner { background: transparent; }

/* A pointer sitting on a scrollbar is not hovering the page behind it, so the
   thumb must not depend on hover alone to be findable — it is visible at rest. */
@media (prefers-reduced-motion: reduce) {
  *::-webkit-scrollbar-thumb { transition: none; }
}
