/* ===========================================================================
   PRIMITIVES — the design system, ported 1:1 from the COFACTOR storefront's
   `globals.css` utilities and `ui/button.tsx` variants.

   Everything downstream composes from THIS ladder. If a page needs a size,
   weight or radius that isn't here, the spec is wrong — don't invent a value
   inline. Names match the source utilities so a component can be translated
   class-for-class.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   THE MISSING HALF OF THE TOKEN SYSTEM.

   tokens.css (generated) carries COLOUR and ELEVATION only. The radius scale,
   the easing curves, the brand-tinted shadows and the chrome offsets lived in
   the Next app's `globals.css` @theme block — so porting tokens.css alone left
   every `var(--radius-*)` and `var(--ease-*)` UNDEFINED. Consequences, both
   silent: every radius computed to 0 (square corners sitewide), and every
   `transition: … var(--ease-out-quart)` was an invalid declaration, which
   drops the whole shorthand — so nothing animated at all.

   Values are copied verbatim from the reference. --radius is THE personality
   knob: 0.375rem reads as a spec panel, 0.75rem as friendly-consumer SaaS.
--------------------------------------------------------------------------- */
:root {
  /* A BRAND OWNS ITS RADII — and as of the token pipeline it finally does.
     This block used to DEFINE the scale, which meant the opposite: primitives.css
     loads AFTER the brand's tokens.css, so whatever a brand set here was
     overwritten, and a vertical had to re-override it from the layer that loads
     last (see verticals/skincare/assets/components.css, which did exactly that).
     Now the engine CONSUMES --p-radius-* from tokens.css; the fallback is only
     for a brand still on the legacy generator with no tokens/ directory.
     Same for motion. Change these in brands/<b>/tokens/primitives.tokens.json. */
  --radius: var(--p-radius-base, 0.375rem);
  --radius-sm: var(--p-radius-sm, calc(var(--radius) * 0.6));
  --radius-md: var(--p-radius-md, calc(var(--radius) * 0.8));
  --radius-lg: var(--p-radius-lg, var(--radius));
  --radius-xl: var(--p-radius-xl, calc(var(--radius) * 1.4));
  --radius-2xl: var(--p-radius-2xl, calc(var(--radius) * 1.8));
  --radius-pill: var(--p-radius-pill, 999px);
  /* fixed micro-radii (chips, pips, ticks) — deliberately NOT scaled by --radius */
  --radius-2: var(--p-radius-2, 2px);
  --radius-3: var(--p-radius-3, 3px);
  --radius-4: var(--p-radius-4, 4px);
  --radius-5: var(--p-radius-5, 5px);
  --radius-circle: var(--p-radius-circle, 100%);
  --radius-3xl-plus: 1.75rem;

  /* motion — the built-in CSS easings are too weak to read as intentional */
  --ease-out-quart: var(--p-ease-out-quart, cubic-bezier(0.25, 1, 0.5, 1));
  --ease-out-expo: var(--p-ease-out-expo, cubic-bezier(0.16, 1, 0.3, 1));
  --ease-in-out-quart: var(--p-ease-in-out-quart, cubic-bezier(0.77, 0, 0.175, 1));
  --ease-drawer: var(--p-ease-drawer, cubic-bezier(0.32, 0.72, 0, 1));
  --ease-out-back: var(--p-ease-out-back, cubic-bezier(0.34, 1.3, 0.64, 1));
  --ease-brand: var(--p-ease-out-quart, cubic-bezier(0.25, 1, 0.5, 1));

  /* elevation aliases — the reference names shadows `--shadow-*` and maps them
     onto the generated `--elevation-*` set */
  --shadow-key: var(--elevation-key);
  --shadow-whisper: var(--elevation-whisper);
  --shadow-card: var(--elevation-card);
  --shadow-pop: var(--elevation-pop);
  --shadow-lift: var(--elevation-lift);
  /* brand-tinted glow — CTA hover only, never decoration */
  --shadow-brand:
    0 8px 24px -8px color-mix(in oklab, var(--brand) 40%, transparent),
    0 2px 8px -2px color-mix(in oklab, var(--brand) 20%, transparent);
  --shadow-ring-brand: inset 0 0 0 1px var(--brand);
  --shadow-glass-top: inset 0 1px 0 color-mix(in oklab, white 12%, transparent);
  --shadow-glass-faint: inset 0 1px 0 color-mix(in oklab, white 8%, transparent);
  --shadow-stage: 0 16px 20px color-mix(in oklab, black 55%, transparent);

  /* Sticky-chrome offset — the one number every sticky bar docks against.
     Consumes the raw space steps rather than a semantic token because the
     value is BREAKPOINT-dependent and generated tokens.css is flat: it has
     no media queries, so a semantic --site-header-h could only ever carry
     the mobile value and would silently lose the desktop one. */
  --site-header-h: var(--p-space-16, 4rem);
  --site-chrome-h: var(--site-header-h);
}
@media (min-width: 1024px) {
  :root { --site-header-h: var(--p-space-20, 5rem); }
}

/* Border-box everywhere. TT5 does NOT set this globally, so any element with
   width:100% plus padding/border rendered wider than its slot — it made every
   `1fr` grid button overflow its column by exactly (padding + border). Fixing
   it here rather than per-component, because the bug is systemic. */
*, *::before, *::after { box-sizing: border-box; }

/* ---------------------------------------------------------------------------
   TYPE SCALE
   headings: title-2xl 40 · title-xl 28 · title-lg 24 · title-md 20 · title-sm 18
   body:     body-lg 18 · body-base 16 · body-sm 15 · caption 13
   All headings carry 600. General Sans ships 400/500/600/700 and Raleway is
   cut 400–700 here, so off-grid weights silently round up and break the ladder.

   THE FACE BOUNDARY IS SEMANTIC NOW, NOT A SIZE. 2026-08-21: every heading is
   --font-display, from display-xl down to title-sm. Everything that RUNS a
   view — body, UI, controls, nav — is --font-sans.

   It used to be a size. The boundary sat at ~30px, between title-2xl and
   title-xl, because the display face was a chunky old-style serif that stopped
   reading as a voice and started reading as a texture below that. Raleway is a
   humanist sans; it has no such floor, so the size line bought nothing and cost
   a rule everyone had to remember. "Is it a heading?" is the whole test.

   WHAT THAT COSTS, RECORDED HERE RATHER THAN DISCOVERED LATER. The old boundary
   was also what gave headings and body two different VOICES. With one sans
   family running both, the separation now rests entirely on weight (600 vs 400)
   and tracking. That is a thinner signal than a face change, and it is thinnest
   exactly at title-sm/title-md, where the size gap to body-lg is small.

   Tracking is doing more work than it used to. The display steps run −0.02em
   and title-2xl −0.014em, both existing rungs. They were tuned for a serif that
   carried its own horizontal glue; Raleway does not, but the rungs stay because
   Raleway ships NO optical-size axis — a 90px hero is the same drawing as a
   20px product name, and negative tracking at the top of the ladder is what
   keeps the hero from reading as a scaled-up label.
--------------------------------------------------------------------------- */
.display-xl   { font-family: var(--font-display); font-size: clamp(3rem, 6.4vw, 5.75rem); line-height: 1;    letter-spacing: var(--p-tracking-n20); font-weight: 600; }
.display-hero { font-family: var(--font-display); font-size: clamp(2.75rem, 5vw, 5rem);   line-height: 1.02; letter-spacing: var(--p-tracking-n20); font-weight: 600; }
.display-lg   { font-family: var(--font-display); font-size: clamp(2.25rem, 4.2vw, 3.75rem); line-height: 1.04; letter-spacing: var(--p-tracking-n20); font-weight: 600; }
.display-md   { font-family: var(--font-display); font-size: clamp(1.75rem, 2.8vw, 2.5rem); line-height: 1.08; letter-spacing: var(--p-tracking-n20); font-weight: 600; }

.title-2xl { font-family: var(--font-display); font-size: clamp(1.875rem, 3vw, 2.5rem); line-height: 1.14; letter-spacing: var(--p-tracking-n14); font-weight: 600; text-wrap: balance; }
.title-xl  { font-family: var(--font-display); font-size: clamp(1.5rem, 2vw, 1.75rem);  line-height: 1.18; letter-spacing: var(--p-tracking-n20);  font-weight: 600; text-wrap: balance; }
.title-lg  { font-family: var(--font-display); font-size: var(--p-text-24);   line-height: 1.25; letter-spacing: -0.018em; font-weight: 600; text-wrap: balance; }
.title-md  { font-family: var(--font-display); font-size: var(--p-text-20);  line-height: 1.3;  letter-spacing: var(--p-tracking-n14); font-weight: 600; }
.title-sm  { font-family: var(--font-display); font-size: var(--p-text-18); line-height: 1.35; letter-spacing: var(--p-tracking-n10);  font-weight: 600; }

.body-lg   { font-size: var(--p-text-18);  line-height: 1.6; letter-spacing: -0.005em; }
.body-base { font-size: var(--p-text-16);      line-height: 1.6; }
.body-sm   { font-size: var(--p-text-15); line-height: 1.55; }
.caption   { font-size: var(--p-text-13); line-height: 1.5; }

/* micro steps used by chips/labels (named for their px value).
   Each carries its paired line-height: Tailwind's font-size utilities bundle
   one, so porting the sizes alone drifts vertical rhythm on every surface. */
.text-10 { font-size: var(--p-text-10);      line-height: 14px; }
.text-11 { font-size: var(--p-text-11);      line-height: 16px; }
.text-13 { font-size: var(--p-text-13); line-height: 1.125rem; }
.text-15 { font-size: var(--p-text-15); line-height: 1.375rem; }
.text-17 { font-size: var(--p-text-17); line-height: 1.625rem; }

/* the Tailwind size/leading pairs the ported components rely on */
.t-xs   { font-size: var(--p-text-12);  line-height: 1rem; }
.t-sm   { font-size: var(--p-text-14); line-height: 1.25rem; }
.t-base { font-size: var(--p-text-16);     line-height: 1.5rem; }
.t-lg   { font-size: var(--p-text-18); line-height: 1.75rem; }
.t-xl   { font-size: var(--p-text-20);  line-height: 1.75rem; }
.t-2xl  { font-size: var(--p-text-24);   line-height: 2rem; }

/* tracking scale (em × 100) */
.tracking-4  { letter-spacing: var(--p-tracking-4); }
.tracking-6  { letter-spacing: var(--p-tracking-6); }
.tracking-8  { letter-spacing: var(--p-tracking-8); }
.tracking-10 { letter-spacing: var(--p-tracking-10); }
.tracking-12 { letter-spacing: var(--p-tracking-12); }

/* THE WIT — one display-italic line per view, and never more. It was a bare
   `font-style: italic` on --font-serif at three separate call sites, which is
   how a rationed device quietly stops being rationed: nothing named it, so
   nothing could count it. Naming it makes the budget greppable. */
.wit {
  font-family: var(--font-display); font-style: italic; font-weight: 400;
  letter-spacing: var(--p-tracking-n10);
}

/* ONE tracking for every uppercase mono label: 0.06em. 2026-08-20, with the
   mono swap. There used to be two rungs — 0.12em for eyebrows, 0.08em for
   micro-labels — drawn against Fragment Mono, a narrow Helvetica-mono. Martian
   Mono is wide by design and the two effects compound: at 0.12em/12px "COPPER
   PEPTIDE" stops reading as a word and becomes fourteen separate letters.
   Rendered against both faces at 11px and 12px, 0.06–0.08em is the band that
   holds; 0.06em is inside it at BOTH sizes, so the second rung bought nothing
   except a value to get wrong. The component labels that hand-set their own
   tracking were swept onto it — mono blocks only, since General Sans labels
   are unaffected by a mono swap. A label is a label, not a texture. */

/* mono is the house signal for DATA — always tabular so figures never shift */
.mono { font-family: var(--font-mono), ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; }
/* ONE tracking for every uppercase mono label: 0.06em. 2026-08-20, with the
   mono swap. There used to be two rungs — 0.12em for eyebrows, 0.08em for
   micro-labels — drawn against Fragment Mono, a narrow Helvetica-mono. Martian
   Mono is wide by design and the two effects compound: at 0.12em/12px "COPPER
   PEPTIDE" stops reading as a word and becomes fourteen separate letters.
   Rendered against both faces at 11px and 12px, 0.06–0.08em is the band that
   holds; 0.06em is inside it at BOTH sizes, so the second rung bought nothing
   except a value to get wrong. The 28 component labels that hand-set their own
   tracking were swept onto it — mono blocks only, since General Sans labels
   are unaffected by a mono swap. A label is a label, not a texture. */
.eyebrow {
  font-family: var(--font-mono), ui-monospace, monospace;
  font-size: var(--p-text-12); letter-spacing: var(--p-tracking-6); text-transform: uppercase;
  color: var(--ink-3); margin: 0;
}
.mono-label {
  font-family: var(--font-mono), ui-monospace, monospace;
  font-size: var(--p-text-11); font-weight: 500; letter-spacing: var(--p-tracking-6); text-transform: uppercase;
}

/* ink ladder */
.text-ink-2 { color: var(--ink-2); }
.text-ink-3 { color: var(--ink-3); }
.text-ink-4 { color: var(--ink-4); }
.text-brand { color: var(--brand); }
.text-muted { color: var(--muted-foreground); }

/* STATE CLASSES FOR THE /design SURFACE. Every :hover / :focus-visible /
   :active rule below also matches .is-hover / .is-focus / .is-active, so the
   design page can show a hovered button WITHOUT restating its declarations.
   Restating them is how a specimen sheet drifts from the component it claims
   to document — which is the same class of bug as a hand-maintained theme.json.
   These classes are inert on the storefront; nothing applies them there.
--------------------------------------------------------------------------- */

/* ---------------------------------------------------------------------------
   BUTTONS — base + variants + sizes, matching ui/button.tsx exactly.
   Base is rounded-lg (= --radius), a transparent 1px border so outlined and
   filled variants share identical box metrics, and font-weight 500.
--------------------------------------------------------------------------- */
.btn {
  display: inline-flex; flex-shrink: 0; align-items: center; justify-content: center;
  gap: var(--p-space-2); white-space: nowrap; text-decoration: none; cursor: pointer;
  border: 1px solid transparent; border-radius: var(--radius-lg);
  background-clip: padding-box;
  font-family: inherit; font-size: var(--p-text-14); font-weight: 500;
  user-select: none;
  transition: transform 150ms var(--ease-out-quart),
              background-color 150ms var(--ease-out-quart),
              border-color 150ms var(--ease-out-quart),
              box-shadow 200ms var(--ease-out-quart),
              color 150ms var(--ease-out-quart),
              opacity 150ms var(--ease-out-quart);
}
.btn:focus-visible, .btn.is-focus { outline: none; border-color: var(--ring); box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 30%, transparent); }
.btn:disabled, .btn[aria-disabled="true"] { pointer-events: none; opacity: 0.5; }
.btn svg { flex-shrink: 0; pointer-events: none; width: 1rem; height: 1rem; }
/* every pressable control confirms the press — 150ms, scale 0.98 */
.btn:active, .btn.is-active { transform: scale(0.98); }

.btn--cta       { background: var(--brand); color: var(--brand-foreground); box-shadow: var(--elevation-key); }
.btn--cta:hover, .btn--cta.is-hover { background: var(--brand-hover); color: var(--brand-foreground); }
.btn--ink       { background: var(--foreground); color: var(--background); box-shadow: var(--elevation-key); }
.btn--ink:hover, .btn--ink.is-hover { background: color-mix(in oklab, var(--foreground) 90%, transparent); color: var(--background); }
.btn--outline   { border-color: var(--border-strong); background: var(--background); color: var(--foreground); }
.btn--outline:hover, .btn--outline.is-hover { background: var(--muted); color: var(--foreground); }
.btn--secondary { background: var(--secondary); color: var(--secondary-foreground); }
.btn--secondary:hover, .btn--secondary.is-hover { background: color-mix(in oklab, var(--secondary), var(--foreground) 5%); }
.btn--soft      { background: var(--brand-muted); color: var(--brand); }
.btn--soft:hover, .btn--soft.is-hover { background: color-mix(in oklab, var(--brand-muted), var(--brand) 10%); }
.btn--ghost     { background: transparent; color: var(--foreground); }
.btn--ghost:hover, .btn--ghost.is-hover { background: var(--muted); }
.btn--glass {
  border-color: color-mix(in oklab, white 15%, transparent);
  background: color-mix(in oklab, white 10%, transparent);
  color: #fff; backdrop-filter: blur(12px); box-shadow: var(--shadow-glass-top);
}
.btn--glass:hover, .btn--glass.is-hover { background: color-mix(in oklab, white 15%, transparent); }

/* sizes — `default` is the 44px WCAG touch floor; `sm` (36px) is for
   desktop-dense contexts only. */
.btn--xs  { height: 1.75rem; gap: var(--p-space-1-5); padding-inline: var(--p-space-2-5); font-size: var(--p-text-13); border-radius: var(--radius-md); }
.btn--sm  { height: 2.25rem; gap: var(--p-space-1-5); padding-inline: var(--p-space-3);  font-size: var(--p-text-13); }
.btn--md  { height: 2.75rem; padding-inline: var(--p-space-4); }
.btn--lg  { height: 2.75rem; gap: var(--p-space-2); padding-inline: var(--p-space-5); font-size: var(--p-text-15); }
.btn--xl  { height: 3rem;    gap: var(--p-space-2); padding-inline: var(--p-space-7); font-size: var(--p-text-16);      font-weight: 600; border-radius: var(--radius-xl); }
.btn--2xl { height: 3.5rem;  gap: var(--p-space-2-5); padding-inline: var(--p-space-8);  font-size: var(--p-text-17); font-weight: 600; border-radius: var(--radius-xl); }
.btn--full { width: 100%; }

/* ---------------------------------------------------------------------------
   CONTROLS
--------------------------------------------------------------------------- */
.control {
  height: 2.75rem; width: 100%; min-width: 0;
  border: 1px solid var(--border); border-radius: var(--radius-xl);
  background: var(--control); color: var(--foreground);
  font-family: inherit; font-size: var(--p-text-15);
  padding-inline: var(--p-space-3);
  outline: none;
  transition: border-color 150ms var(--ease-out-quart), background-color 150ms var(--ease-out-quart), box-shadow 150ms var(--ease-out-quart);
}
.control::placeholder { color: var(--muted-foreground); }
.control:hover, .control.is-hover { border-color: color-mix(in oklab, var(--foreground) 20%, transparent); }
.control:focus-visible, .control.is-focus {
  border-color: var(--ring); background: var(--background);
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 30%, transparent);
}

/* ---------------------------------------------------------------------------
   PILLS / CHIPS / STAGES
--------------------------------------------------------------------------- */
.pill {
  display: inline-flex; flex-shrink: 0; align-items: center; gap: var(--p-space-1-5);
  border-radius: var(--p-radius-pill); padding: var(--p-space-2) var(--p-space-3-5);
  font-size: var(--p-text-13); font-weight: 500; white-space: nowrap; cursor: pointer;
  border: 1px solid var(--border); background: var(--background); color: var(--ink-3);
  transition: background-color 150ms var(--ease-out-quart), color 150ms var(--ease-out-quart),
              border-color 150ms var(--ease-out-quart), transform 150ms var(--ease-out-quart);
}
.pill:hover, .pill.is-hover { border-color: color-mix(in oklab, var(--foreground) 20%, transparent); color: var(--foreground); }
.pill:focus-visible, .pill.is-focus {
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in oklab, var(--ring) 30%, transparent);
}
.pill:active, .pill.is-active { transform: scale(0.98); }
.pill[aria-selected="true"] { background: var(--foreground); color: var(--background); border-color: transparent; }
/* `.pill svg` is (0,0,2) and `.ms-i` — what ms_icon() emits — is (0,1,0), so
   the icon slot documented here NEVER won the size fight and every pill icon
   drew at the 1.25rem section size. The class selector is what fixes it; the
   element selector stays for hand-written SVG. */
.pill svg, .pill .ms-i {
  width: var(--p-space-3-5); height: var(--p-space-3-5); flex-shrink: 0; color: var(--brand);
}
.pill[aria-selected="true"] svg { color: var(--background); }
/* A filter for a category with nothing in it still renders — the shop draws the
   whole taxonomy so the row does not reflow as stock moves. Undefined until
   2026-08-20, which left a dead filter looking live: full contrast, pointer
   cursor, and a hover response to a click that does nothing. Same treatment as
   `.btn:disabled` above; one convention for "you cannot press this". */
.pill:disabled, .pill[aria-disabled="true"] { pointer-events: none; opacity: 0.5; }
.pill__count { font-family: var(--font-mono), ui-monospace, monospace; font-size: var(--p-text-11); font-variant-numeric: tabular-nums; color: var(--ink-4); }
.pill[aria-selected="true"] .pill__count { color: color-mix(in oklab, var(--background) 60%, transparent); }

/* the lit product stage — light and dark variants */
.stage-light {
  background: radial-gradient(120% 100% at 50% 0%,
    var(--p-neutral-white) 0%, var(--p-neutral-white) 60%, var(--p-neutral-cream) 100%);
}
.stage-dark {
  background: radial-gradient(130% 100% at 50% 0%,
    var(--p-brand-700) 0%, var(--p-brand-800) 55%, var(--p-brand-900) 100%);
}

.chip-cat {
  display: inline-flex; align-items: center; flex-shrink: 0;
  border-radius: var(--radius-md); background: var(--secondary);
  padding: var(--p-space-1) var(--p-space-2);
  font-family: var(--font-mono), ui-monospace, monospace;
  font-size: var(--p-text-11); font-weight: 500; letter-spacing: var(--p-tracking-6); text-transform: uppercase;
  color: var(--ink-3);
}

/* page axis */
.axis { box-sizing: border-box; width: 100%; max-width: calc(80rem + 2rem); margin-inline: auto; padding-inline: var(--p-space-4); }
@media (min-width: 640px) { .axis { max-width: calc(80rem + 3rem); padding-inline: var(--p-space-6); } }
.axis-tight { box-sizing: border-box; width: 100%; max-width: calc(80rem + 1.5rem); margin-inline: auto; padding-inline: var(--p-space-3); }
@media (min-width: 640px) { .axis-tight { max-width: calc(80rem + 3rem); padding-inline: var(--p-space-6); } }

/* ---------------------------------------------------------------------------
   FOCUS
   A click was leaving a hard rectangular ring behind on links and buttons.
   The fix is NOT to delete focus styling — that strands keyboard users, and a
   visible focus indicator is a WCAG 2.4.7 requirement. `:focus-visible` is
   exactly this split: the browser shows it for keyboard/assistive navigation
   and withholds it for pointer activation. So suppress the ring on :focus and
   define a deliberate one on :focus-visible.
   `outline` (not box-shadow) is used so it follows each element's own
   border-radius instead of drawing a square around a rounded control.
--------------------------------------------------------------------------- */
a:focus, button:focus, summary:focus, [tabindex]:focus,
input:focus, select:focus, textarea:focus { outline: none; }

a:focus-visible, button:focus-visible, summary:focus-visible, [tabindex]:focus-visible,
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}
/* Components that already draw their own focus treatment opt out of the
   generic outline so the two never stack. */
.btn:focus-visible, .control:focus-visible, .pill:focus-visible, .ms-slice:focus-visible, .btn.is-focus, .control.is-focus, .pill.is-focus, .ms-slice.is-focus { outline: none; }
