/* ═══════════════════════════════════════════════════════════════════════════
   NEOBOT DESIGN TOKENS  (--nb-*)

   The single source of truth for the dashboard's geometry, surfaces, colour
   semantics, spacing and motion.

   ─── WHAT THIS FILE IS NOT ───────────────────────────────────────────────
   It declares NO selectors, NO rules and NO `!important`. It is a values
   document. Adding it changes nothing on screen; that is the point, and it is
   the acceptance test for the commit that introduced it.

   ─── LOAD POSITION ───────────────────────────────────────────────────────
   Linked SECOND, immediately after z-index.css and BEFORE style.css, on every
   page that loads dashboard.css.

   It goes first, not last, deliberately. Custom properties resolve at
   computed-value time, so position is irrelevant to correctness — and
   irrelevant is exactly what a token file should be. A token file that a later
   sheet can silently redefine is a token file that lies. Putting it LAST would
   be actively wrong: dashboard-mobile.css:940 declares
   `@media (max-width: 380px) :root { --mob-gutter: 12px }`, and a token file
   loading after it at equal specificity would kill that narrow-phone retune.

   The `--nb-` prefix was verified against all 114 stylesheets, HTML files and
   JS modules: zero prior occurrences, so nothing can collide.

   ─── WHY THE VALUES LOOK FAMILIAR ────────────────────────────────────────
   Almost every value here already exists in the codebase, usually several
   times under several names (the ink ramp appears under 17+ names; the panel
   gradient is retyped verbatim in 15 files; #3b82f6 is written raw 317 times
   across 32 files). These are not new decisions — they are the values that
   already won, given one name each. That is what makes adoption a rename
   rather than a restyle.

   ─── WHAT IS DELIBERATELY ABSENT ─────────────────────────────────────────
   font-size. There are 76 distinct sizes in dashboard.css and 113 more across
   the modules; the top 20 cover only 79% / 67%, and 35% of module sizes are
   used exactly once. Collapsing that is ~189 individual judgement calls, not a
   find-and-replace, so type gets its own hand-audited phase. Only the three
   mechanically-safe typographic axes (mono stack, numerals) are here.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {

    /* ── SHAPE ────────────────────────────────────────────────────────────
       Shape carries meaning. It says what KIND of thing an element is, and it
       should not be spent on decoration.

           A person            →  circle          (--nb-r-avatar)
           A server            →  squircle        (--nb-r-squircle)
           A bot / app / plugin→  squircle        (--nb-r-squircle)
           A status or filter  →  pill            (--nb-r-pill)
           Everything else     →  the ladder below, by surface size

       The ladder is NeoBot's own. Four modules independently converged on
       16/14/12/10/9/7/6 (role-colors, invite-tracker, bot-personalizer,
       automation-builder) before this file existed. It is kept rather than
       replaced with a borrowed scale: consistency with what the product
       already is beats matching another product's exact numbers. Discord's
       LOGIC is adopted — semantic, size-ordered, fewer distinct steps — not
       its literal values.

       ⚠️ A "squircle" here is a rounded rectangle at a consistent
       radius-to-size ratio, not a true superellipse. Nothing in this codebase
       uses `corner-shape` or a clip-path mask, and 66 icon tiles already sit
       in a 0.18–0.32 ratio band with a mode of 0.25. The token encodes that
       convention so it stops being re-guessed per tile. */

    --nb-r-xs:        4px;   /* chips inside text, tiny inline marks          */
    --nb-r-tab:       6px;   /* tiny details, tab strips                      */
    --nb-r-btn:       8px;   /* compact controls: buttons, inputs, selects    */
    --nb-r-inner:    10px;   /* inner tiles, compact catalogue cards          */
    --nb-r-menu:     12px;   /* standard interactive cards, dropdown surfaces */
    --nb-r-card:     14px;   /* comfortable feature cards                     */
    --nb-r-panel:    16px;   /* featured cards, major section groups          */
    --nb-r-dialog:   24px;   /* desktop dialogs                               */
    --nb-r-sheet:    32px;   /* mobile bottom sheets — TOP corners only       */
    --nb-r-pill:    999px;   /* badges, filters, statuses                     */
    --nb-r-avatar:    50%;   /* people. only people                           */
    --nb-r-squircle: 25%;    /* servers / bots / apps — ratio, not a length   */

    /* Compatibility, not scale. The first cut of this ladder had 7 and 9 and
       no 8 — so when Stage A tried to alias --avb-r-control and --it-r-tile,
       both 8px, there was nothing to alias them to. Two modules independently
       wanting a step the scale did not offer is the scale being wrong, not the
       modules. 8px is also what the 4px spacing rhythm implies for a control.
       These two remain so existing 7px/9px declarations have a name during
       migration; new work should reach for the semantic steps above. */
    --nb-r-7:         7px;
    --nb-r-9:         9px;

    /* ── SPACING ──────────────────────────────────────────────────────────
       A 4px rhythm. 6,326 length tokens currently resolve to 102 distinct
       pixel values and only 56% land on any multiple of 4; these seven steps
       already cover 65% of dashboard.css's spacing.

       --nb-sp-icon-gap is off the grid on purpose. `gap: 7px` appears 47 times
       across 18 files as the icon-to-label gap inside buttons and chips. It is
       a real convention rather than drift, and naming it is cheaper than
       fighting 47 sites to save one pixel. */

    --nb-sp-1:        4px;
    --nb-sp-2:        8px;
    --nb-sp-3:       12px;
    --nb-sp-4:       16px;
    --nb-sp-5:       20px;
    --nb-sp-6:       24px;
    --nb-sp-8:       32px;
    --nb-sp-icon-gap: 7px;

    /* ── CONTROL HEIGHTS ──────────────────────────────────────────────────
       609 interactive rules currently measure to 47 distinct heights on a
       near-continuous 18–72px ramp with no peak, because 72.7% derive their
       height from padding + line-height and so land on fractional pixels.
       Declaring a height is the only fix that holds.

       --nb-h-tap is the WCAG 2.5.5 target-size floor and mirrors --mob-tap. */

    --nb-h-sm:       32px;
    --nb-h-md:       36px;
    --nb-h-lg:       40px;
    --nb-h-tap:      44px;

    /* ── SURFACE HIERARCHY ────────────────────────────────────────────────
       Depth should be carried by the SURFACE, not by drawing a line around
       every container. Today 77% of surface rules paint a 1px border, and on
       the same panel the hairline is 3–5x more visible than the fill step it
       delimits — so the border is doing all the work and the hierarchy reads
       as one flat level with boxes on it.

       The ladder, outermost to innermost:

           --nb-surface-page      the ground the app sits on
           --nb-surface-raised    a section lifted off the ground
           --nb-surface-card      a card within a section
           --nb-surface-hover     an interactive surface under the cursor
           --nb-surface-float     menus, popovers, modals (above everything)

       plus two recessed treatments:

           --nb-surface-well      inputs and code blocks (pressed into a card)
           --nb-surface-inset     subtle recess for grouping inside a card

       Borders are for FOCUS, SELECTION, form affordances and genuinely strong
       separation. Not for "this is a box". */

    --nb-surface-page:   #0a0a10;
    --nb-surface-raised: linear-gradient(180deg, rgba(26, 28, 33, 0.82) 0%, rgba(18, 20, 24, 0.78) 100%);
    --nb-surface-card:   #14161b;
    --nb-surface-hover:  rgba(255, 255, 255, 0.04);
    --nb-surface-active: rgba(255, 255, 255, 0.07);
    --nb-surface-float:  #161a27;
    --nb-surface-well:   rgba(10, 12, 16, 0.6);
    --nb-surface-inset:  rgba(0, 0, 0, 0.22);

    /* ── HAIRLINES ────────────────────────────────────────────────────────
       52 token names currently cover 7 alpha steps between 0.045 and 0.10 with
       no ladder, and 268 declarations sit on six values whose contrast against
       a panel differs by less than the eye resolves. Four steps is enough. */

    --nb-line-faint:  rgba(255, 255, 255, 0.045);
    --nb-line:        rgba(255, 255, 255, 0.07);
    --nb-line-strong: rgba(255, 255, 255, 0.10);
    --nb-line-heavy:  rgba(255, 255, 255, 0.14);

    /* ── INK ──────────────────────────────────────────────────────────────
       The four-step ramp already exists 17+ times under different names with
       byte-identical values, so naming it is a pure rename.

       ⚠️ --nb-ink-faint is a deliberate FIFTH step, not a synonym for ink-4.
       invite-tracker.css:53-56 documents that #5b5f67 measures 2.93:1 on the
       glass panel — below WCAG AA — and substitutes #7d828b at 4.86:1. Reusing
       ink-4 for genuinely small print would standardise that failure across
       every module. Use ink-4 for decorative/structural greys, ink-faint for
       text a human is expected to read. */

    --nb-ink-1:     #f2f3f5;
    --nb-ink-2:     #c8cbd1;
    --nb-ink-3:     #8b8f97;
    --nb-ink-4:     #5b5f67;
    --nb-ink-faint: #7d828b;

    /* ── ELEVATION ────────────────────────────────────────────────────────
       516 box-shadow declarations resolve to 383 distinct strings, 315 of them
       used exactly once. --nb-elev-action is the worst offender given a name:
       that exact three-part recipe is repeated verbatim in 13 rules across 9
       files with no variable behind it. */

    --nb-elev-panel:  0 30px 60px -30px rgba(0, 0, 0, 0.6),
                      0 1px 0 rgba(255, 255, 255, 0.03) inset;
    --nb-elev-raised: 0 1px 2px rgba(0, 0, 0, 0.5),
                      0 12px 28px -14px rgba(0, 0, 0, 0.7),
                      inset 0 1px 0 rgba(255, 255, 255, 0.04);
    --nb-elev-float:  0 12px 32px -8px rgba(0, 0, 0, 0.65),
                      0 2px 8px rgba(0, 0, 0, 0.4);
    --nb-elev-action: 0 1px 2px rgba(0, 0, 0, 0.45),
                      0 6px 14px -6px rgba(59, 130, 246, 0.55),
                      inset 0 1px 0 rgba(255, 255, 255, 0.18);

    /* ── COLOUR SEMANTICS ─────────────────────────────────────────────────
       Colour states PURPOSE. It is not decoration and it is not identity
       paint on a panel.

           blue    an action you can take                --nb-action
           purple  NeoBot intelligence, premium, recommended  --nb-brand
           green   active / succeeded                    --nb-ok
           amber   needs attention                       --nb-warn
           red     destructive / failed                  --nb-danger

       ⚠️ --nb-brand is CHROME. It is never a button fill. Buttons are blue;
       purple marks the assistant, the premium tier and branded moments.

       ⚠️ Matching hex is NOT matching meaning. Do not alias a token onto one
       of these merely because the values are equal today. Known exceptions
       that must stay independent even though they currently collide:
         · Overview's data series (--ov-msg is #3b82f6, --ov-xp is #a78bfa) —
           that surface's rule is "colour only inside data", so folding them in
           means retuning a button repaints the chart;
         · the Automations canvas node kinds (--avb-action #2dd4bf names a node
           KIND, not a clickable thing);
         · Discord facsimile/preview surfaces, which must mirror Discord;
         · anything a server admin can configure. */

    --nb-action:       #3b82f6;
    --nb-action-hover: #2563eb;
    --nb-action-soft:  rgba(59, 130, 246, 0.14);
    --nb-action-ring:  rgba(59, 130, 246, 0.24);
    --nb-action-ink:   #93c5fd;

    --nb-brand:        #a78bfa;
    --nb-brand-ink:    #c4b5fd;
    --nb-brand-soft:   rgba(167, 139, 250, 0.15);
    --nb-brand-line:   rgba(167, 139, 250, 0.34);

    --nb-ok:           #34d399;
    --nb-ok-soft:      rgba(52, 211, 153, 0.14);
    --nb-warn:         #fbbf24;
    --nb-warn-soft:    rgba(251, 191, 36, 0.14);
    --nb-danger:       #ef4444;
    --nb-danger-ink:   #f87171;
    --nb-danger-soft:  rgba(239, 68, 68, 0.14);

    /* ── FOCUS ────────────────────────────────────────────────────────────
       One focus treatment for the whole product. 100 focus rules currently
       resolve to 55 distinct visual treatments in 44 different ring colours. */

    --nb-focus-ring:   2px solid var(--nb-brand-ink);
    --nb-focus-offset: 2px;

    /* ── MOTION ───────────────────────────────────────────────────────────
       12 motion tokens already exist, six of them holding the identical
       material curve, and two competing "defaults" — so a component changes
       feel when it moves between tabs. Hover motion is fast and small; the
       product's standing rules forbid spring, glow and hover-lift on
       informational surfaces. */

    --nb-ease:      cubic-bezier(0.4, 0, 0.2, 1);
    --nb-ease-out:  cubic-bezier(0.32, 0.72, 0, 1);
    --nb-dur-fast:  0.15s;
    --nb-dur:       0.2s;
    --nb-dur-slow:  0.3s;

    /* ── INTERACTION MOTION ───────────────────────────────────────────────
       The three generic durations above say how long; these say WHAT, so a
       switch and a modal stop sharing one number by accident.

       The target is Discord's newer interface: motion is FEEDBACK, not
       choreography. A control should feel like it responded before you
       finished the gesture, and nothing should travel far enough to watch.

           --nb-t-hover    100-140ms   colour, surface, border
           --nb-t-press     90ms       the press itself, faster than the release
           --nb-t-state    160ms       switch throw, segmented selection
           --nb-t-pop      160ms       dropdown / popover entrance
           --nb-t-modal    200ms       modal / sheet entrance
           --nb-t-tip      120ms       tooltip

       ⚠️ Standing prohibitions, all of them previously violated somewhere in
       this product: no spring/overshoot easing, no scale above 1, no
       hover-lift on informational surfaces, no glow pulse, no looping
       ambient animation in chrome. A popover moves 4px and 2% of its size —
       enough to read as "this arrived", not enough to watch.

       The distances live here too so an entrance cannot drift per component. */
    --nb-t-hover:   0.12s;
    --nb-t-press:   0.09s;
    --nb-t-state:   0.16s;
    --nb-t-pop:     0.16s;
    --nb-t-modal:   0.2s;
    --nb-t-tip:     0.12s;

    --nb-pop-scale: 0.98;
    --nb-pop-slide: 4px;
    --nb-modal-scale: 0.975;

    /* ── FEEDBACK MOTION ──────────────────────────────────────────────────
       Added by INTERACTION_SPEC.md. The six durations above answer "how long
       does a control take to respond"; these three answer "how long does a
       REPORT stay on screen", which is a different question and was previously
       answered with a different literal in each module (1400ms in xp-levels'
       reward flash, 1500ms in its token toast, 1600ms in role-colors' saved
       flash, 2500ms in welcome's test button, 900ms in settings-modal's saving
       flag).

           --nb-t-settle   a derived VALUE has just changed; tint and fade
           --nb-t-echo     a ROW has just changed; tint and fade. Longer,
                           because the eye has to travel to it
           --nb-t-walk     one step of a sequence being replayed

       ⚠️ A hold is not a lock. Nothing may disable a control for the length of
       one of these — the report runs while the control is already live. */
    --nb-t-settle:  0.6s;
    --nb-t-echo:    0.9s;
    --nb-t-walk:    0.18s;

    /* ⚠️ THE ONE SANCTIONED OVERSHOOT, and it is scoped rather than general.
       The prohibition above ("no spring/overshoot easing") governs levels 1 and
       2 — a control responding to you must not bounce. A PAYOFF at level 3 or
       above may, once, on one element in the sequence.

       This is the value already in welcome.css as `.wel-anim-pop`; naming it is
       what lets the prohibition stay true as written instead of being quietly
       contradicted by whichever module needed a payoff first.
       feature-feedback.css's `--ff-spring` (…0.2, 1.4) points here now: two
       springs 0.1 apart is two vocabularies for one idea. */
    --nb-spring:    cubic-bezier(0.3, 0.8, 0.2, 1.3);

    /* ── TYPE (seeded, not swept) ─────────────────────────────────────────
       Only the axes that are mechanically safe. --nb-font-mono ends 16
       competing stacks for one role — three of which name fonts the page never
       loads, so they would silently break alignment if they ever resolved.
       --nb-num names a keyword written literally 81 times.

       There is deliberately no font-size or font-weight token here. Note also
       that NO webfont is loaded anywhere in the dashboard, so the 15 declared
       weights currently collapse to five rendered ones; resolving that belongs
       to the typography phase, not to this file. */

    --nb-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
    --nb-num:       tabular-nums;
}
