/*
 * tokens.css. Layer 1 (structure) and layer 2 (semantic colour) from design.md
 * section 3. Never edited per app. The only per app file is themes/<slug>.css, which
 * as of entry [135] may set ONE variable, --brand-accent, and nothing else.
 *
 * EVERY value here comes from the purchased template's compiled stylesheet, and every
 * one is traceable: docs/template-tokens.md lists the app.css line it was read from.
 *
 * An earlier version of this file was built from mockup/hub-admin.html: a dark, 11.5px,
 * 2px radius instrument panel. That was wrong. hub-admin.html says which screens exist,
 * what data they show and what gates them. It says nothing about how anything looks.
 * See CLAUDE.md house rule 9.
 *
 * If a component needs a value that is not here, that is a gap in this file. Add it
 * here, deliberately, and know it changes every component at once.
 */

:root {
  /*
   * Inter, which is what the template uses (app.css 8731 and fourteen more).
   * --font-cond has no counterpart in the template: it ships one family. The name is
   * kept because components reference it, and it resolves to Inter, so a condensed
   * face can be introduced later in one place if the suite ever wants one.
   */
  --font-sans: Inter, system-ui, -apple-system, sans-serif;
  --font-cond: Inter, system-ui, -apple-system, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;

  /* app.css 6897 to 6924. The template's scale, not the prototype's. */
  /*
   * 8px, and the count badge on the header bell is the only thing that may use it.
   * app.css 6940. It is below every legibility floor this project otherwise keeps to,
   * and it is here because the badge is a two character number inside a 16px circle,
   * read as a shape rather than as text, with the real figure in the label beside it.
   * Never use it for anything a person has to read as words.
   */
  --text-2xs:  0.5rem;
  --text-xs:   0.75rem;
  --text-sm:   0.875rem;
  --text-base: 0.875rem;
  --text-md:   1rem;
  --text-lg:   1.125rem;
  --text-xl:   1.25rem;
  --text-2xl:  1.5rem;
  /*
   * The approved Sitemapify file's three figure sizes, entry [239]: its h1 (26px), its fact
   * figure (30px) and its plan price (34px). Structural, identical everywhere, by the owner's
   * ruling that a structural token re-renders every app rather than re-skinning a customer's
   * site, since every app is fixture only. --text-display (40px) stays above them.
   */
  --text-3xl:  1.625rem;
  --text-4xl:  1.875rem;
  --text-5xl:  2.125rem;

  --leading-tight: 1.3333;
  --leading-base:  1.4286;
  --leading-loose: 1.5;

  /*
   * The template's control line box: 1.5rem on 0.875rem text, which is a ratio of
   * 1.7143 and is what .btn (app.css 8731) and .form-label (app.css 9503) use. It is
   * not on the general leading scale because it is a control metric, not prose.
   * Added when a rebuild agent stopped and asked for it rather than baking 24px.
   */
  --leading-control: 1.7143;

  --weight-normal: 400;
  --weight-medium: 500;
  --weight-semi:   600;
  --weight-bold:   700;
  /* The approved file's 800, entry [239]: its h1, figures, prices and wordmark. */
  --weight-heavy:  800;

  --tracking-wide: 0.025em;
  --tracking-caps: 0.05em;

  /* Tailwind's default scale, as compiled into app.css. */
  --space-0:  0;
  --space-1:  0.125rem;
  --space-2:  0.25rem;
  --space-3:  0.5rem;
  --space-4:  0.75rem;
  --space-5:  1rem;
  --space-6:  1.25rem;
  --space-7:  1.5rem;
  --space-8:  2rem;
  --space-9:  2.5rem;
  --space-10: 3rem;
  --space-11: 4rem;

  /*
   * 8, 12 and 16px, the approved Sitemapify file's own, entry [239]. They were the template's
   * 2, 4 and 6 (app.css 5629 to 5662) from Phase 2 until the owner reversed the [238] ruling
   * that kept them: consistency was never at risk, because a structural token is identical
   * everywhere whichever value it holds, and every app is fixture only, so one change here
   * re-renders the suite and re-skins nobody's site. It was most of the visual gap.
   */
  --radius-xs:   0.125rem;   /* a swatch or a bar end: the 2px the file draws on its severity dot */
  --radius-sm:   0.5rem;
  --radius-md:   0.75rem;
  --radius-lg:   1rem;
  --radius-pill: 9999px;
  --radius-full: 50%;

  --border-width: 1px;

  /*
   * Control metrics, read from the template's own components rather than derived from
   * the spacing scale. docs/component-specs.md carries the app.css line for each.
   *
   * These exist because the template's button padding is 10px, which is not a step on
   * Tailwind's scale and so is not on ours. Phase 2 reached for the nearest spacing
   * token instead, which is how a 44px control became a 29px one.
   *
   * --control-h and --touch-min are the same number on purpose. The template's button
   * already meets the 44px touch target; ours missed it only by being built to a
   * denser mockup.
   */
  --control-h:      2.75rem;   /* .btn, app.css 8731, computes to 44px */
  --control-h-sm:   2.125rem;  /* 34px, the approved Sitemapify file's small button, entry [241]; 2rem (app.css 8776) before */
  --control-h-lg:   2.875rem;  /* 46px, the file's large button, entry [241]; 3rem (app.css 8759) before */
  --control-pad-y:  0.625rem;  /* 10px */
  --control-pad-x:  1rem;

  /*
   * 44px, and NOT the template's 38px. Ruled 2026-08-27, entry [064], and this is the one
   * place in this file where house rule 9 is deliberately not followed.
   *
   * .form-control at app.css 9534 computes to 38px. House rule 10 says a touch target is at
   * least 44px. Every text input, select, search box and secret readout in the suite sits on
   * this token, so the two rules cannot both hold and the conflict had been rediscovered
   * three times before it was written down.
   *
   * It resolves in opposite directions for the two kinds of control, and the reason is
   * mechanical rather than aesthetic:
   *
   *   a BUTTON keeps the template's size and gains an invisible hit area. pagination.css
   *     lays a --touch-min ::after over a 34px chip; --icon-face does the same for the
   *     header's circle. Clicks on the overlay still reach the control, so the paint and
   *     the target can differ. --control-h-sm is 34px since entry [241], the approved file's.
   *
   *   an INPUT cannot. A pseudo element over a text field swallows the caret, and there is
   *     no way to enlarge the hit area of a replaced element without covering it. So the
   *     box itself has to be 44px, and house rule 9 yields.
   *
   * Forms are also where a phone user actually taps: a list screen is mostly reading, and
   * every write in this panel ends in a field.
   *
   * The knock on effects are both correct without further edits. layout.css aligns a
   * checkbox against a labelled control with (--touch-min - --input-h) / 2, which is now
   * zero, which is right: a 44px row beside a 44px input needs no offset. secret.css set
   * --touch-min on its root to stand taller than the 38px inputs beside it, and now agrees
   * with them by arithmetic rather than by exception.
   */
  --input-h:        2.75rem;   /* 44px. House rule 10 over app.css 9534, entry [064] */
  --input-pad-y:    0.5rem;
  --input-pad-x:    0.75rem;

  --cell-pad-y:     1.25rem;   /* .table-th and .table-td, app.css 10772 and 10798 */
  --cell-pad-x:     1.5rem;

  /*
   * The dense table's horizontal cell padding. DERIVED, not measured: the template has no
   * dense table because it has no table wider than 9 columns and averages 5.7, where ours
   * average 7.7 with seven at ten. docs/ui-patterns.md section 9 carries the argument.
   * It is a step on the spacing scale rather than an invented number.
   */
  --cell-pad-x-dense: 1rem;

  --card-pad:       1.5rem;    /* card-body p-6, the shape used 194 times */
  --alert-pad-y:    1.125rem;  /* .alert, app.css 8540, an 18px step not on the scale */

  /* Plot heights for chart. Not on any template control: a chart is ours. */
  --plot-h:         10rem;
  --plot-h-sm:      8rem;

  --touch-min:      2.75rem;   /* CLAUDE.md house rule 10 */

  /*
   * The header's circular icon button and the count badge that sits on it.
   * app.css 15861 and 15876 for the 32px face, 4476 and 4713 for the 16px badge.
   *
   * --icon-face is the painted circle and NOT the hit area. The template's button is
   * 32px all the way out, which is below the 44px floor in house rule 10, so ours
   * keeps the 32px circle and puts it inside a --touch-min box. Same picture, legal
   * target. The avatar in the profile block is the same 32px, which is why one token
   * serves both: they stand next to each other and have to agree.
   */
  --icon-face:      2rem;
  --badge-size:     1rem;

  /*
   * The signed out page frame. Every one of these is a template measurement that is not
   * a step on the spacing or type scale, in the same way --alert-pad-y is not, so they
   * are recorded here rather than baked as literals into login.css.
   * docs/component-specs.md, "The auth page frame", carries the reasoning per line.
   */
  --auth-width:      524px;    /* .auth-box max-width, app.css 11105 */
  --auth-pad:        1.75rem;  /* .auth-box padding, app.css 11105 */
  --auth-pad-y:      2.75rem;  /* 44px, .auth-box at md, app.css 11111 */
  --auth-pad-x:      2.625rem; /* 42px, .auth-box at md, app.css 11111 */
  --auth-panel-max:  520px;    /* the brand block, signin-one.html:34 */
  --auth-inset:      5rem;     /* pt-20 and ltr:pl-20 on it, signin-one.html:34 */
  --auth-art-drop:   -130px;   /* bottom-[-130px] on the art, signin-one.html:44 */
  --auth-art-drop-xl: -160px;  /* 2xl:bottom-[-160px], same line */

  /* .left-column h4, app.css 11085. 40px on a 48px line box, a ratio of 1.2. */
  --text-display:    2.5rem;
  --leading-display: 1.2;

  /*
   * The identity mark. Three sizes, all of them template measurements.
   * docs/ui-patterns.md section 2.2. --icon-face (2rem) is the middle one and already
   * exists, because the profile trigger and the header icon button stand in one row.
   */
  --tag-min:   5.625rem;  /* 90px, min-w-[90px] on the status pill, 206 uses */
  --avatar-sm: 1.75rem;   /* 28px, w-7 h-7, advance-table.html list rows */
  --avatar-lg: 2.5rem;    /* 40px, h-10 w-10, project.html:2257 */

  /* app.css 7418 and 7408. shadow-base is the template's own surface shadow. */
  --shadow-sm: 0px 0px 1px rgba(40, 41, 61, 0.08), 0px 0.5px 2px rgba(96, 97, 112, 0.16);
  --shadow-md: 0px 2px 4px rgba(40, 41, 61, 0.04), 0px 8px 16px rgba(96, 97, 112, 0.16);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);

  /*
   * THE STACKING ORDER, bottom to top. Entry [058] wrote this down because the sticky table
   * header and the sticky top bar were both on --z-sticky, the table comes later in the DOM,
   * and so the header sliced through the top bar on the way past while scrolling. Two things
   * on one token is not a collision anybody sees until it happens.
   *
   *   --z-base     0     page content
   *   --z-raised   50    a sticky element INSIDE the page. The table header, and nothing else
   *                      yet. It must stay below the top bar it scrolls past.
   *   --z-sticky   100   the top bar
   *   --z-nav      200   the sidebar
   *   --z-overlay  800   the drawer scrim, and the menu and help panels
   *   --z-drawer   900   the off canvas sidebar on a narrow screen
   *   --z-toast    1000  the tooltip surface, which is a child of body
   *
   * TWO THINGS THAT ARE NOT ON THIS LADDER AND MUST NOT BE ADDED TO IT.
   *
   * The confirm dialog is a native <dialog> and renders in the TOP LAYER, which is above every
   * z-index on the page by definition. It has no token and needs none.
   *
   * A value used inside a stacking context only orders that context's own children. The menu
   * and help panels sit inside .l-bar, which is itself --z-sticky, so their 800 orders them
   * against their siblings in the bar and NOT against the page. That is why fixing the table
   * header fixed the profile menu being cut in half: the menu was never able to beat a page
   * element, because .l-bar could not.
   */
  --z-base:    0;
  --z-raised:  50;
  --z-sticky:  100;
  --z-nav:     200;
  --z-overlay: 800;
  --z-drawer:  900;
  --z-toast:   1000;

  --bp-sm: 640px;
  --bp-md: 900px;   /* 820 until [239], 768 in [239], 900 since [240]: the approved Sitemapify file stacks its sidebar under 900 */
  --bp-lg: 1180px;
  --bp-xl: 1440px;

  --nav-width: 230px;     /* the approved Sitemapify file's rail, entry [241]; 248 (app.css 12911) until then */
  /* The collapsed rail. Not a template measurement: the template has no rail. It is
     the touch minimum (44px) plus --space-3 either side, which centres a 20px glyph
     and keeps the row a legal target. Entry [039]. */
  --nav-rail:  64px;
  --bar-height: 64px;     /* app.css 12671, 0.75rem padding around a 40px control */

  --duration-fast: 150ms;
  --duration-base: 200ms;

  /*
   * For attention, not interaction. The only use is the critical status pulse.
   * Deliberately slow: under about 350ms per cycle crosses three flashes a second,
   * which is the seizure threshold in WCAG 2.3.1.
   */
  --duration-slow: 1800ms;

  --ease: cubic-bezier(0.4, 0, 0.2, 1);

  /*
   * Surfaces. The template is a LIGHT interface. slate-50 ground, white cards.
   * app.css 6025, 5997, 6033, 6013.
   */
  --ground:    rgb(248 250 252);
  --surface:   rgb(255 255 255);
  --surface-2: rgb(248 250 252);
  --surface-3: rgb(241 245 249);

  --line:      rgb(226 232 240);   /* slate-200, control borders, app.css 6013 */
  --line-soft: rgb(241 245 249);   /* slate-100, table rules, app.css 6033 */

  /* Ink, most to least prominent. app.css 6017 and 6067 for the first two. */
  --ink:   rgb(15 23 42);
  --ink-2: rgb(71 85 105);
  /*
   * --ink-3 is NOT the template's, since entry [244]. It was slate-400, rgb(148 163 184), app.css
   * 6059, which the template uses as its PLACEHOLDER colour; this suite had made it the faintest
   * TEXT ink in 86 declarations (hints, deltas, notes, labels), where it measures 2.56:1 on white
   * and 2.45 on --surface-2 against the 4.5 floor for text under 18px. The owner's ruling: a
   * repaint costs a render on a fixture-only suite, so the token moves rather than the 86 uses.
   * rgb(96 110 132) measures 5.17 on white, 4.94 on --surface-2 and 4.72 on --surface-3; slate-500
   * (the approved file's muted, #64748B) was ruled out at 4.34 on --surface-3. Against --ink-2 it
   * is L* 46.0 to 35.7, a third of the step the old value had, still a clear rank below. Where it
   * is not text (the idle lamp dot, the mute meter fill, three hover borders, the search glyph)
   * it darkens visibly, in every case towards legible.
   */
  --ink-3: rgb(96 110 132);

  /*
   * Semantic colour. Identical in every app, forever. A person learns what warning
   * looks like once. Each has a solid value, a dim value for borders and rules, and a
   * subtle value for tinted backgrounds. app.css 6001 to 6251.
   */
  --success:        rgb(80 199 147);
  --success-dim:    rgb(63 154 122);
  --success-subtle: rgb(231 253 241);

  --warning:        rgb(250 145 107);
  --warning-dim:    rgb(223 130 96);
  --warning-subtle: rgb(255 244 241);

  --danger:         rgb(241 89 92);
  --danger-dim:     rgb(215 80 82);
  --danger-subtle:  rgb(254 239 239);

  --info:           rgb(12 231 250);
  --info-dim:       rgb(0 184 212);
  --info-subtle:    rgb(231 254 255);

  --muted:          rgb(160 174 192);   /* the template's secondary, app.css 6255 */
  --muted-dim:      rgb(203 213 225);
  --muted-subtle:   rgb(248 250 252);

  /*
   * The semantic hues as TEXT. Entry [237], the owner's ruling on the Sitemapify
   * reconciliation: the approved screens print red, amber and green as 25px figures and
   * mono gap words, and the three solids above measure 2.1 to 3.3:1 on white, because
   * they were drawn from the template as badge fills. These clear 4.5:1 on white, on
   * --ground AND on their own subtle tint, because entry [238] moved the tag's text onto them
   * and a tag is ink on its tint (measured on the tint: 5.79, 4.65, 4.71). The danger ink is
   * one step darker than the approved file's red for that reason: #DC2626 measures 4.33 on
   * --danger-subtle. The tints stay as fills; a fill never takes an ink and an ink never
   * fills. An addition, so nothing already drawn changes except what is pointed at it.
   */
  --danger-ink:  rgb(185 28 28);
  --warn-ink:    rgb(180 83 9);
  --success-ink: rgb(21 128 61);

  /*
   * THE ACTION COLOUR IS A BRAND VARIABLE. Entry [243], reversing [236 ruling 1] and [237].
   *
   * What a person clicks: the filled button, the current nav row, a fill that means "done".
   * [237] made it structural, one blue for the suite, so that a seventh brand variable would
   * not end the six-variable rule. The owner reversed that on 2026-09-12: an action colour
   * derived from the brand IS a brand colour, and making it structural forced every app to
   * share one. So these three are DEFAULTS, the suite blue, and a theme file may set them:
   * Sitemapify sets --action to its logo orange and --action-ink and --action-text to the
   * navy. Every other app is unchanged by the defaults holding. tools/check-themes.php
   * measures each theme's ink on its action and its action text on the page.
   *
   *   --action        the fill
   *   --action-ink    the words ON the fill. White on the blue 5.17:1; white on the orange
   *                   measured 2.30:1 and FAILS, so Sitemapify's is the navy at 6.22:1.
   *   --action-text   the action colour AS TEXT or glyph on a light ground: outline button
   *                   labels, the current nav row, eyebrows, the chip, the icon discs. The
   *                   blue's is its own darker step; the orange has no readable step on white
   *                   (2.30:1 at the brand value) and design.md's collision rule keeps orange
   *                   out of text, so Sitemapify's is the navy.
   */
  --action:        rgb(37 99 235);
  --action-ink:    rgb(255 255 255);

  /*
   * Reserved across the whole suite: indigo means AI derived. Never a brand colour,
   * never decorative. This is our rule, from design.md section 3, and is deliberately
   * NOT taken from the template.
   */
  --ai:        rgb(99 91 255);
  --ai-dim:    rgb(79 70 229);
  --ai-subtle: rgb(238 237 255);

  /*
   * THE SUITE BRAND. Five values, identical in all thirteen apps, entry [135].
   *
   * These lived in themes/<slug>.css until the owner ruled one navy for the whole suite on
   * 2026-09-04, entry [134]. That left twelve of thirteen theme files byte identical except
   * for one line, so a change to the navy was thirteen edits: the duplication house rule 7
   * exists to prevent, created by the rule that was meant to prevent it. A theme file now
   * holds --brand-accent alone.
   *
   * The HUB is the one mount that is not this navy. themes/admin.css overrides the four that
   * differ, and it can, because a theme class sits on <body> and wins over :root here.
   *
   * Measured, not chosen. tools/check-themes.php asserts the floor and the derivations:
   *
   *   white on --brand              14.28:1   primary button label, panel ground
   *   --brand on --ground           13.79:1   links, active nav, focus ring
   *   white on --brand-hover        12.40:1
   *   --ink on --brand-subtle       14.92:1   avatar initials
   *   --brand on --brand-subtle     11.94:1   stat disc glyph
   *   --brand-border on white        3.07:1   tag edge, floor is 3
   */
  --brand:        rgb(15 43 76);

  /* LIGHTER on hover, 47 per cent brighter in luminance, the figure the suite has used since
     entry [067] so every app answers a pointer at the same rate. */
  --brand-hover:  rgb(18 53 93);

  --brand-ink:    rgb(255 255 255);

  /* The tint behind the stat disc and the avatar. The navy's hue, S 72, L 93. */
  --brand-subtle: rgb(224 236 250);

  /* A boundary a person is meant to see, held to the 3:1 floor rather than taken as a naive
     tint. It does NOT track the brand's darkness. */
  --brand-border: rgb(94 150 217);

  --focus-width: 2px;
  --focus-offset: 1px;
}

/*
 * DERIVED FROM --action, entry [243], and declared on body rather than :root for one reason: a
 * custom property resolves its var() references where it is DECLARED. On :root these would
 * resolve against the suite blue before a theme class on <body> had set --action, and every app
 * would derive its hover and tints from the wrong fill. On body they resolve against the theme's.
 *
 *   --action-hover   the fill answering a pointer, 15 per cent towards black. The blue's was
 *                    blue-700 rgb(29 78 216) by hand; the mix gives rgb(31 84 200).
 *   --action-subtle  the tint behind the current nav row, the icon discs, the chip, the gain
 *                    line: 7 per cent of the action on the surface. Blue-50 rgb(239 246 255) by
 *                    hand; the mix gives rgb(240 244 254).
 *   --action-border  16 per cent: the agent strip's and the gain line's edge. Blue-100
 *                    rgb(219 234 254) by hand; the mix gives rgb(220 230 252).
 *   --action-text    defaults to the hover step. A theme states its own where that step does
 *                    not read on white, which is Sitemapify's case.
 *
 * The blue's three drift by one to six units from the hand values [241] set, which is under a
 * perceptible step; the orange's three come out of the same arithmetic and are what makes item
 * 7 of the ruling true: they follow the fill without a theme naming them.
 */
body {
  --action-hover:  color-mix(in srgb, var(--action) 85%, rgb(0 0 0));
  --action-subtle: color-mix(in srgb, var(--action) 7%, var(--surface));
  --action-border: color-mix(in srgb, var(--action) 16%, var(--surface));
  --action-text:   var(--action-hover);
}
