/* ===========================================================================
   tokens.css — the design contract
   ---------------------------------------------------------------------------
   Every section in this library styles itself EXCLUSIVELY from these variables.
   No section may hardcode a colour, font, radius or shadow. That rule is the
   whole point: swap the token block, re-skin the entire library, touch no
   section CSS.

   Namespace is --vb-* on purpose. The vibe-bricks plugin already emits these
   same names into :root on every Bricks site we build, so a section pasted
   into a Bricks Code element inherits that client's brand with no edits.
   Standalone (this catalog), the defaults below apply.
   ========================================================================= */

:root {
	/* --- colour roles -----------------------------------------------------
	   Roles, not hues. "ink" is whatever text is, "paper" is whatever the
	   page is. A dark brand swaps the values, never the role names. */
	--vb-color-ink:            #111827;  /* primary text */
	--vb-color-ink-soft:       #4b5563;  /* secondary text, body copy */
	--vb-color-mute:           #6b7280;  /* tertiary text, captions, meta */
	--vb-color-paper:          #ffffff;  /* page background */
	--vb-color-paper-muted:    #f6f6f7;  /* alternating band background */
	--vb-color-paper-sunk:     #ecedef;  /* deepest neutral surface */
	--vb-color-rule:           #e5e7eb;  /* hairlines, borders, dividers */
	--vb-color-accent:         #4b26a6;  /* brand action colour */
	--vb-color-accent-hover:   #3c1e85;
	--vb-color-accent-contrast:#ffffff;  /* text that sits ON accent */
	--vb-color-accent-soft:    #efeaf9;  /* accent tint background */

	/* --- the dark surface, restated as roles ------------------------------
	   Every role above has a counterpart here, and .vb-invert does nothing but
	   swap one set for the other. Nothing about the inverted surface is a
	   literal in a rule any more: a brand that wants its own dark world
	   declares these eleven and gets it, and a brand that declares none gets
	   the shared variation below. The accent needs the restatement most — a
	   light-mode accent is picked to sit on paper and goes near-invisible once
	   the surface flips — but the same is true of every hairline and tint. */
	--vb-color-ink-on-dark:         #ffffff;
	--vb-color-ink-soft-on-dark:    #c9cbd1;
	--vb-color-mute-on-dark:        #8b8f99;
	--vb-color-paper-on-dark:       #0d0e12;
	--vb-color-paper-muted-on-dark: #16181e;
	--vb-color-paper-sunk-on-dark:  #1e212a;
	--vb-color-rule-on-dark:        #2a2d37;
	--vb-color-accent-soft-on-dark: #1d1733;
	--vb-color-accent-on-dark:          #a78bfa;
	--vb-color-accent-on-dark-hover:    #c4b5fd;
	--vb-color-accent-on-dark-contrast: #14121f;

	/* --- the chromatic sequence -------------------------------------------
	   The accent above is a ROLE: "the colour an action is". Some brands need
	   colour that is not a role at all — Bauhaus red, blue and yellow share a
	   canvas and none of them means "click me". A page like that assigns colour
	   by POSITION, not by meaning, and the accent slot cannot express it.

	   So: an ordered set of chromatics a brand owns, and that sections cycle
	   across repeated items without ever knowing what the hues are. Each stop
	   carries its own -soft (the tint) and -contrast (what stays legible ON it),
	   because a hue you cannot put text on is not usable, it is decoration.

	   Every stop falls back down the chain to the accent, so a brand that
	   declares none of this — which is every brand today — renders exactly as
	   before: the cycle runs through three identical colours and is invisible.
	   Declaring stop 2 alone gives a two-colour alternation, since 3 follows 2.

	   The dark side does NOT fall back to the light hue. It falls back to the
	   dark accent, for the same reason the accent has an -on-dark at all: a hue
	   picked to sit on paper goes muddy the moment the ground flips. A brand
	   that wants its chromatics on both worlds declares both, exactly as it
	   already does for the accent. (It is also the only arrangement that works:
	   pointing the dark stop at the light one, while the invert rule points the
	   light one back at the dark, is a var() cycle and computes to nothing.)

	   Naming here is regular — `<stop>-on-dark` always goes last — where the
	   accent's own dark tokens are not (`accent-soft-on-dark` beside
	   `accent-on-dark-contrast`). Those are load-bearing in brand files already;
	   this set gets the rule the accent should have had.

	   Each stop is FOUR values, not one, and that is the part worth
	   understanding rather than copying:

	     <stop>            the FIELD. A shape filled with the hue.
	     <stop>-contrast   what stays legible ON that field.
	     <stop>-soft       the tint, for a plate the hue sits on.
	     <stop>-ink        the MARK. The hue at drawing strength — a glyph, a
	                       rule, a word — for use on paper or on the tint.

	   Field and mark are separate because for half the useful hues they cannot
	   be the same value. Bauhaus yellow is the clearest case: as a field with
	   black on it, it is unmissable and scores about 10:1; as a 1.5px glyph on
	   cream it scores 1.6:1 and is effectively invisible. Both are the same
	   yellow and both are correct — the difference is how much of it is on
	   screen. A system with one slot per hue has to pick, and picking the
	   drawable one throws away the colour that made it Bauhaus.

	   -ink defaults to the field, so a brand whose hues are already dark enough
	   to draw with declares nothing extra and nothing changes. */
	--vb-color-chroma-1:          var(--vb-color-accent);
	--vb-color-chroma-1-ink:      var(--vb-color-chroma-1);
	--vb-color-chroma-1-soft:     var(--vb-color-accent-soft);
	--vb-color-chroma-1-contrast: var(--vb-color-accent-contrast);

	--vb-color-chroma-2:          var(--vb-color-chroma-1);
	--vb-color-chroma-2-ink:      var(--vb-color-chroma-2);
	--vb-color-chroma-2-soft:     var(--vb-color-chroma-1-soft);
	--vb-color-chroma-2-contrast: var(--vb-color-chroma-1-contrast);

	--vb-color-chroma-3:          var(--vb-color-chroma-2);
	--vb-color-chroma-3-ink:      var(--vb-color-chroma-3);
	--vb-color-chroma-3-soft:     var(--vb-color-chroma-2-soft);
	--vb-color-chroma-3-contrast: var(--vb-color-chroma-2-contrast);

	--vb-color-chroma-1-on-dark:          var(--vb-color-accent-on-dark);
	--vb-color-chroma-1-ink-on-dark:      var(--vb-color-chroma-1-on-dark);
	--vb-color-chroma-1-soft-on-dark:     var(--vb-color-accent-soft-on-dark);
	--vb-color-chroma-1-contrast-on-dark: var(--vb-color-accent-on-dark-contrast);

	--vb-color-chroma-2-on-dark:          var(--vb-color-chroma-1-on-dark);
	--vb-color-chroma-2-ink-on-dark:      var(--vb-color-chroma-2-on-dark);
	--vb-color-chroma-2-soft-on-dark:     var(--vb-color-chroma-1-soft-on-dark);
	--vb-color-chroma-2-contrast-on-dark: var(--vb-color-chroma-1-contrast-on-dark);

	--vb-color-chroma-3-on-dark:          var(--vb-color-chroma-2-on-dark);
	--vb-color-chroma-3-ink-on-dark:      var(--vb-color-chroma-3-on-dark);
	--vb-color-chroma-3-soft-on-dark:     var(--vb-color-chroma-2-soft-on-dark);
	--vb-color-chroma-3-contrast-on-dark: var(--vb-color-chroma-2-contrast-on-dark);

	/* --- the ramp ---------------------------------------------------------
	   A gradient is not a colour, so it cannot live in a colour role — but a
	   whole family of brands treats one as their primary surface. This token
	   holds a full CSS <image>: a gradient, a conic sweep, layered stops.

	   It defaults to the flat accent restated as a two-stop gradient rather
	   than to `none`, so anything painting with it is safe under every brand
	   that has never heard of it — a ramp of one colour is that colour. */
	--vb-color-ramp: linear-gradient(180deg, var(--vb-color-accent), var(--vb-color-accent));

	/* --- type families ---------------------------------------------------- */
	--vb-font-sans:    'Inter', system-ui, -apple-system, sans-serif;
	--vb-font-display: 'Inter', system-ui, -apple-system, sans-serif;
	--vb-font-mono:    ui-monospace, 'SF Mono', Menlo, Consolas, monospace;

	/* --- type scale -------------------------------------------------------
	   Fluid by default. Each step carries its own line-height + tracking so a
	   brand can make display type tight and body type airy independently. */
	--vb-fs-display-1: clamp(2.75rem, 5vw + 0.5rem, 5.5rem);
	--vb-lh-display-1: 1.02;
	--vb-ls-display-1: -0.03em;

	--vb-fs-display-2: clamp(2rem, 3vw + 0.75rem, 3.5rem);
	--vb-lh-display-2: 1.08;
	--vb-ls-display-2: -0.02em;

	--vb-fs-h1:        clamp(1.875rem, 2vw + 1rem, 2.75rem);
	--vb-lh-h1:        1.15;
	--vb-ls-h1:        -0.02em;

	--vb-fs-h2:        clamp(1.5rem, 1.2vw + 1rem, 2rem);
	--vb-lh-h2:        1.2;
	--vb-ls-h2:        -0.01em;

	--vb-fs-h3:        1.25rem;
	--vb-lh-h3:        1.35;
	--vb-ls-h3:        -0.005em;

	--vb-fs-lead:      clamp(1.125rem, 0.5vw + 1rem, 1.375rem);
	--vb-lh-lead:      1.55;

	--vb-fs-body:      1rem;
	--vb-lh-body:      1.7;

	--vb-fs-small:     0.875rem;
	--vb-lh-small:     1.6;

	--vb-fs-eyebrow:   0.6875rem;
	--vb-lh-eyebrow:   1;
	--vb-ls-eyebrow:   0.18em;

	/* --- weights ----------------------------------------------------------
	   Exposed as tokens so a brand on a variable display face can go 300 or
	   800 without any section knowing about it. */
	--vb-weight-display: 700;
	--vb-weight-heading: 650;
	--vb-weight-body:    400;
	--vb-weight-strong:  600;
	--vb-weight-eyebrow: 600;

	/* --- case / decoration ------------------------------------------------
	   A brand toggles ALL eyebrows to lowercase, or all display type to
	   uppercase, from one line. Sections must never set text-transform. */
	--vb-case-eyebrow: uppercase;
	--vb-case-display: none;

	/* --- rhythm ----------------------------------------------------------- */
	--vb-space-section:    clamp(4rem, 8vw, 7.5rem);
	--vb-space-section-sm: clamp(2.5rem, 5vw, 4.5rem);
	--vb-space-section-lg: clamp(6rem, 12vw, 11rem);
	--vb-gutter:           clamp(1.25rem, 4vw, 2.5rem);
	/* Host sites with a persistent header set this to the header's occupied
	   block size so section-level sticky controls never disappear beneath it. */
	--vb-sticky-header-offset: 0px;

	/* --- measure ---------------------------------------------------------- */
	--vb-max-content: 85rem;  /* default container: 1440px box once the 2.5rem gutters are in */
	--vb-max-wide:    96rem;  /* wide container — galleries, marquees, GSAP compositions only */
	--vb-max-narrow:  48rem;  /* centred text column */
	--vb-max-prose:   68ch;   /* readable paragraph measure */

	/* Opt-in composition roles. Sections declare consumption in artDirection
	   metadata; declaring a value alone never invents layout or decorations.
	   Dimensions are inherited, and a composed section may override a role. */
	--vb-section-gap: clamp(1.5rem, 3vw, 3rem);
	--vb-card-padding: clamp(1.25rem, 2.5vw, 2rem);
	--vb-heading-measure: 19ch;
	--vb-ornament-size: 2rem; /* repeat size for opt-in ground patterns */
	--vb-display-stretch: 100%; /* variable display face width, where available */

	/* --- shape ------------------------------------------------------------ */
	--vb-radius-sm:   0.375rem;
	--vb-radius-card: 0.75rem;
	--vb-radius-lg:   1.5rem;
	--vb-radius-pill: 999px;
	--vb-border-hair: 1px;

	/* Buttons and the controls that sit beside them get their own radius,
	   pointed at the pill by default so nothing changes for a brand that
	   doesn't care. Button shape and tag shape are separate brand decisions —
	   plenty of brands want round chips above square buttons, and while these
	   shared one token that combination was unreachable. Set this alone to
	   square the buttons and leave every pill in the library round. */
	--vb-radius-btn:  var(--vb-radius-pill);

	/* --- depth ------------------------------------------------------------ */
	--vb-shadow-card:       0 1px 2px rgb(15 23 42 / 0.05), 0 8px 24px -12px rgb(15 23 42 / 0.18);
	--vb-shadow-card-hover: 0 1px 2px rgb(15 23 42 / 0.08), 0 18px 40px -16px rgb(15 23 42 / 0.26);
	--vb-shadow-pop:        0 24px 64px -24px rgb(15 23 42 / 0.35);

	/* --- motion -----------------------------------------------------------
	   Travel and cadence are tokens like any other, so "how far a thing moves
	   before it settles" is a brand decision rather than a constant. They live
	   here rather than in motion.css because a declaration on the animating
	   element beats an inherited one whatever its specificity — while these sat
	   in a :where() block on the section itself, a preset could set them and
	   nothing would listen. A section may still retune them on its own root. */
	--vb-ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
	--vb-ease-in-out:   cubic-bezier(0.65, 0, 0.35, 1);
	--vb-dur-fast:      160ms;
	--vb-dur-base:      280ms;
	--vb-dur-slow:      600ms;
	--vb-motion-distance: 24px;  /* how far a reveal travels */
	--vb-motion-step:     70ms;  /* delay between staggered children */
}

/* ===========================================================================
   The dark role swap
   ---------------------------------------------------------------------------
   Anything carrying .vb-invert — or a section set to one of the two dark
   surfaces below — flips to the brand's dark world. Because it only reassigns
   the SAME role tokens, every child keeps working untouched. This is how a
   section offers a light and dark treatment without a second stylesheet.

   Not one value lives in this rule. Every line points at an *-on-dark token,
   so the inverted surface is as brand-owned as the light one — override the
   tokens and this rule follows. It used to hold eight literal hexes, which is
   why a preset could repaint the whole library except the moment a section
   went dark, where every brand collapsed onto the same slate.

   No section is dark at rest. Sections are authored on paper; dark is the
   variation a builder opts into per section with data-vb-surface, or for a
   whole page with .vb-invert-page. .vb-invert itself is now for INNER blocks
   only — one element meant as a counterweight to the light section around it:
   a featured card, a verdict, a marquee band. A whole section goes dark
   through its own control, not by wearing the class.
   ========================================================================= */
/* The html-qualified pair is not redundant. presets.css loads after this file
   and its [data-vb-preset="x"] block declares --vb-color-paper directly — same
   0,1,0 specificity, later in the cascade. When the page itself is driven dark
   (the catalog's Surface picker, shot.sh, compose --invert) BOTH attributes land
   on <html>, and the preset would win: every preset rendered `dark` identical to
   `default`. Qualifying with `html` takes it to 0,2,0, which is exactly what the
   old .vb-invert-page class was doing for free. On a section root the plain
   selector is enough — the preset is an ancestor there, and a declaration on the
   element always beats an inherited one. */
.vb-invert,
html.vb-invert-page,
[data-vb-surface="dark"],
[data-vb-surface="dark-surface"],
html[data-vb-surface="dark"],
html[data-vb-surface="dark-surface"] {
	--vb-color-ink:         var(--vb-color-ink-on-dark);
	--vb-color-ink-soft:    var(--vb-color-ink-soft-on-dark);
	--vb-color-mute:        var(--vb-color-mute-on-dark);
	--vb-color-paper:       var(--vb-color-paper-on-dark);
	--vb-color-paper-muted: var(--vb-color-paper-muted-on-dark);
	--vb-color-paper-sunk:  var(--vb-color-paper-sunk-on-dark);
	--vb-color-rule:        var(--vb-color-rule-on-dark);
	--vb-color-accent-soft: var(--vb-color-accent-soft-on-dark);
	--vb-color-accent:          var(--vb-color-accent-on-dark);
	--vb-color-accent-hover:    var(--vb-color-accent-on-dark-hover);
	--vb-color-accent-contrast: var(--vb-color-accent-on-dark-contrast);

	/* The chromatic sequence flips with everything else. Stop 1 would follow
	   the accent on its own, but all three are restated so the set behaves as
	   one thing rather than one that is magic and two that are not. */
	--vb-color-chroma-1:          var(--vb-color-chroma-1-on-dark);
	--vb-color-chroma-1-ink:      var(--vb-color-chroma-1-ink-on-dark);
	--vb-color-chroma-1-soft:     var(--vb-color-chroma-1-soft-on-dark);
	--vb-color-chroma-1-contrast: var(--vb-color-chroma-1-contrast-on-dark);
	--vb-color-chroma-2:          var(--vb-color-chroma-2-on-dark);
	--vb-color-chroma-2-ink:      var(--vb-color-chroma-2-ink-on-dark);
	--vb-color-chroma-2-soft:     var(--vb-color-chroma-2-soft-on-dark);
	--vb-color-chroma-2-contrast: var(--vb-color-chroma-2-contrast-on-dark);
	--vb-color-chroma-3:          var(--vb-color-chroma-3-on-dark);
	--vb-color-chroma-3-ink:      var(--vb-color-chroma-3-ink-on-dark);
	--vb-color-chroma-3-soft:     var(--vb-color-chroma-3-soft-on-dark);
	--vb-color-chroma-3-contrast: var(--vb-color-chroma-3-contrast-on-dark);

	/* The ramp has to be restated, and the reason is a var() rule worth knowing:
	   a var() INSIDE a custom property is substituted where that property is
	   DECLARED, not where it is used. --vb-color-ramp is declared on :root, so
	   its default resolves against the root accent once and then inherits as a
	   finished value — a descendant that changes --vb-color-accent does not
	   change the ramp it inherited. Restating it here re-derives it against the
	   accent as this rule leaves it, which is what makes a nested .vb-invert
	   block carry the dark ramp rather than the light one.
	   (Verified, not assumed: without this line an inner .vb-invert computes
	   --vb-color-ramp to the light accent while --vb-color-accent is already
	   dark.) A brand that hardcodes literal stops in its own ramp — Spectrum
	   does — is outside this and restates it in its own dark block. */
	--vb-color-ramp: linear-gradient(180deg, var(--vb-color-accent), var(--vb-color-accent));


	color: var(--vb-color-ink);
	background-color: var(--vb-color-paper);
	background-image: var(--vb-surface-image);
	background-blend-mode: var(--vb-surface-blend);
}

/* ===========================================================================
   data-vb-surface — the section's own ground, and the one control that sets it
   ---------------------------------------------------------------------------
   One attribute on the section root, four values, exactly one at a time:

     default        the page itself
     surface        an offshoot of the page, one step off
     dark           the brand's OTHER world
     dark-surface   an offshoot of that, one step off

   The last two are labelled "Inverse" and "Inverse surface" in the catalog and
   in Bricks, and the label is the honest one: `dark` means "flip to the other
   set of roles", which on a light brand is dark and on a dark-native brand
   (Terminal, Tech, where --vb-color-paper is already near-black) is light. The
   attribute values stay `dark` / `dark-surface` because that is what the role
   tokens behind them are called — renaming those would rename eleven
   *-on-dark tokens that a brand already declares.

   "Surface" is what builds rhythm down a page: alternate default and surface
   and the sections read as bands without any of them being a colour decision.
   It is deliberately a *step*, not a second palette — in the light world it
   darkens, in the dark world it lightens, and both come off the paper and
   paper-muted roles a brand already owns.

   `default` declares nothing at all, on purpose. A default section IS the
   page: it paints no ground of its own and inherits every token below, so the
   same markup is white on a white page and dark inside .vb-invert-page, with
   no second setting to keep in sync. Only the other three paint a band.

   --- the panel tokens ------------------------------------------------------
   A section that paints a card, a well or a plate must paint it from
   --vb-panel, never from a role token. --vb-panel means "one legible step off
   whatever this section's ground is", so the same card reads on all four
   surfaces: it is the tint on white, white on the tint, and a lift on either
   dark. --vb-panel-2 is the second step, for a well inside a panel.

   Reaching past these for --vb-color-paper is the bug this replaced. A white
   card is only a card for as long as the section behind it is not white, and
   half the library was one surface change away from finding that out.
   ========================================================================= */
:root,
.vb-invert,
html.vb-invert-page,
[data-vb-surface="dark"] {
	--vb-surface: var(--vb-color-paper);
	--vb-panel:   var(--vb-color-paper-muted);
	--vb-panel-2: var(--vb-color-paper-sunk);

	/* A ground that is an image rather than a flat colour. Both default to
	   `none`, so every brand that has never declared them paints exactly as it
	   did. A gradient brand sets --vb-surface-image once and every ground in
	   the library — the body, and every section that opts into a band — carries
	   it, because these two properties are always painted together.

	   background-color stays underneath on purpose: it is what the text was
	   contrast-checked against, and it is what shows if the image fails or the
	   brand only wanted a wash on part of the page. */
	--vb-surface-image: none;
	--vb-panel-image:   none;

	/* How that image meets the colour underneath it. `normal` is plain alpha
	   compositing — the image sits ON the ground. `multiply` is what ink does:
	   it cannot make paper lighter, only darker, so a texture composited this
	   way prints INTO the ground rather than over it. `screen` is the inverse
	   and is what a dark ground needs, because multiplying a near-black paper
	   by anything leaves it near-black and the texture disappears (measured on
	   rendered PNGs: stdev 1.56 under multiply against 8.3 on light).

	   This is background-blend-mode, which blends an element's own background
	   layers against each other and needs no extra markup — which is the only
	   reason it can live in the token contract. The OTHER overprint, one
	   element multiplying into the one behind it, is mix-blend-mode and cannot
	   be reached from a brand alone: see --vb-blend-ink. */
	--vb-surface-blend: normal;
	--vb-panel-blend:   normal;

	/* Overprint BETWEEN elements, for sections that deliberately layer one
	   thing over another. A brand sets this and anything wearing .vb-overprint
	   multiplies into what is behind it; a brand that declares nothing gets
	   `normal`, the property's own default, so the class is inert everywhere
	   else. Sections have to opt in, because there is nothing to blend until a
	   section decides two things overlap — a token cannot invent the overlap. */
	--vb-blend-ink: normal;
}

[data-vb-surface="surface"] {
	--vb-surface: var(--vb-color-paper-muted);
	--vb-panel:   var(--vb-color-paper);
	--vb-panel-2: var(--vb-color-paper-sunk);
}

/* On dark the steps only go one way — up. A panel that went darker than an
   already-dark band reads as a hole rather than a card, so the second dark
   surface lifts to sunk and keeps paper for the deepest well.

   The descendant selectors are the page-level cases: a section authored
   `surface` sitting inside a dark page IS the dark-surface case, and has to
   step the same way as one authored `dark-surface`. */
[data-vb-surface="dark-surface"],
html.vb-invert-page                  [data-vb-surface="surface"],
html[data-vb-surface="dark"]         [data-vb-surface="surface"],
html[data-vb-surface="dark-surface"] [data-vb-surface="surface"] {
	--vb-surface: var(--vb-color-paper-muted);
	--vb-panel:   var(--vb-color-paper-sunk);
	--vb-panel-2: var(--vb-color-paper);
}

/* Only a section that opted into a band paints one. */
[data-vb-surface="surface"],
[data-vb-surface="dark"],
[data-vb-surface="dark-surface"] {
	background-color: var(--vb-surface);
	background-image: var(--vb-surface-image);
	background-blend-mode: var(--vb-surface-blend);
}
