/* ===========================================================================
   base.css — shared primitives
   ---------------------------------------------------------------------------
   The vocabulary every section is built from. Two rules govern this file:

   1. Nothing here declares a literal colour, font, size, radius or duration.
      Every value is a var(--vb-*). This file is the bridge between tokens
      and sections, and it must stay as agnostic as the sections do.
   2. It is small on purpose. Primitives cover type, rhythm, containers and
      buttons — the things every section needs and that must stay consistent
      across the library. Layout is each section's own business.

   Naming:
      .vb-*    shared primitive (this file)
      .vbs-*   section-scoped class, prefixed with the section slug
               e.g. .vbs-hero-split, .vbs-hero-split__copy
   ========================================================================= */

/* --- reset ---------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

body {
	margin: 0;
	/* The page's own ground, not the paper role — so a preview or a whole site
	   driven onto another surface carries the body with it. */
	background-color: var(--vb-surface);
	/* And the brand's ground image over it, `none` unless a brand declares one.
	   Deliberately not background-attachment: fixed — a page-length gradient
	   that scrolls with the document is the effect these brands actually want,
	   and fixed attachment is the one that stutters on mobile. */
	background-image: var(--vb-surface-image);
	background-blend-mode: var(--vb-surface-blend);
	color: var(--vb-color-ink);
	font-family: var(--vb-font-sans);
	font-size: var(--vb-fs-body);
	font-weight: var(--vb-weight-body);
	line-height: var(--vb-lh-body);
	-webkit-font-smoothing: antialiased;
	text-rendering: optimizeLegibility;
}

img, svg, video, canvas { display: block; max-width: 100%; height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }

:where(a) { color: inherit; text-decoration: none; }

:where(h1, h2, h3, h4, h5, h6, p, figure, blockquote, ul, ol, dl) { margin: 0; }
:where(ul, ol) { padding: 0; list-style: none; }

:focus-visible {
	outline: 2px solid var(--vb-color-accent);
	outline-offset: 3px;
	border-radius: var(--vb-radius-sm);
}

/* --- rhythm ---------------------------------------------------------------
   Every section root gets .vb-section. It owns the vertical rhythm so a brand
   can make the whole site breathe more by moving one token. */
.vb-section        { padding-block: var(--vb-space-section); position: relative; }
.vb-section--sm    { padding-block: var(--vb-space-section-sm); }
.vb-section--lg    { padding-block: var(--vb-space-section-lg); }
.vb-section--flush { padding-block: 0; }
.vb-section--top-flush    { padding-block-start: 0; }
.vb-section--bottom-flush { padding-block-end: 0; }

/* A section's ground is not a class. It is data-vb-surface on the section
   root — one control, four values — and the panel tokens that follow from it.
   The contract lives in tokens.css, next to the roles it is built from. */

/* --- containers ----------------------------------------------------------- */
.vb-container {
	width: 100%;
	max-width: calc(var(--vb-max-content) + var(--vb-gutter) * 2);
	margin-inline: auto;
	padding-inline: var(--vb-gutter);
}
.vb-container--wide   { max-width: calc(var(--vb-max-wide) + var(--vb-gutter) * 2); }
.vb-container--narrow { max-width: calc(var(--vb-max-narrow) + var(--vb-gutter) * 2); }
.vb-container--full   { max-width: none; }
.vb-container--bleed  { max-width: none; padding-inline: 0; }

/* --- type ----------------------------------------------------------------- */
.vb-eyebrow {
	font-family: var(--vb-font-sans);
	font-size: var(--vb-fs-eyebrow);
	line-height: var(--vb-lh-eyebrow);
	letter-spacing: var(--vb-ls-eyebrow);
	font-weight: var(--vb-weight-eyebrow);
	text-transform: var(--vb-case-eyebrow);
	color: var(--vb-color-mute);
	display: block;
}

.vb-display-1, .vb-display-2 {
	font-family: var(--vb-font-display);
	font-weight: var(--vb-weight-display);
	text-transform: var(--vb-case-display);
	text-wrap: balance;
	color: var(--vb-color-ink);
}
.vb-display-1 {
	font-size: var(--vb-fs-display-1);
	line-height: var(--vb-lh-display-1);
	letter-spacing: var(--vb-ls-display-1);
}
.vb-display-2 {
	font-size: var(--vb-fs-display-2);
	line-height: var(--vb-lh-display-2);
	letter-spacing: var(--vb-ls-display-2);
}

.vb-h1, .vb-h2, .vb-h3 {
	font-family: var(--vb-font-display);
	font-weight: var(--vb-weight-heading);
	color: var(--vb-color-ink);
	text-wrap: balance;
}
.vb-h1 { font-size: var(--vb-fs-h1); line-height: var(--vb-lh-h1); letter-spacing: var(--vb-ls-h1); }
.vb-h2 { font-size: var(--vb-fs-h2); line-height: var(--vb-lh-h2); letter-spacing: var(--vb-ls-h2); }
.vb-h3 { font-size: var(--vb-fs-h3); line-height: var(--vb-lh-h3); letter-spacing: var(--vb-ls-h3); }

.vb-lead  { font-size: var(--vb-fs-lead); line-height: var(--vb-lh-lead); color: var(--vb-color-ink-soft); text-wrap: pretty; }
.vb-body  { font-size: var(--vb-fs-body); line-height: var(--vb-lh-body); color: var(--vb-color-ink-soft); text-wrap: pretty; }
.vb-small { font-size: var(--vb-fs-small); line-height: var(--vb-lh-small); color: var(--vb-color-mute); }
.vb-prose { max-width: var(--vb-max-prose); }

/* --- flow ----------------------------------------------------------------
   Owl selector. Set --vb-flow locally to change the gap for one block. */
.vb-stack > * + * { margin-block-start: var(--vb-flow, 1.25rem); }

/* --- buttons -------------------------------------------------------------- */
.vb-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 0.5em;
	padding: 0.9em 1.6em;
	border: var(--vb-border-hair) solid transparent;
	border-radius: var(--vb-radius-btn);
	font-size: var(--vb-fs-small);
	font-weight: var(--vb-weight-strong);
	line-height: 1;
	letter-spacing: 0.01em;
	cursor: pointer;
	text-align: center;
	transition: background-color var(--vb-dur-base) var(--vb-ease-out-soft),
	            color var(--vb-dur-base) var(--vb-ease-out-soft),
	            border-color var(--vb-dur-base) var(--vb-ease-out-soft),
	            transform var(--vb-dur-fast) var(--vb-ease-out-soft),
	            box-shadow var(--vb-dur-base) var(--vb-ease-out-soft);
}
.vb-btn:active { transform: translateY(1px); }

.vb-btn--primary {
	background-color: var(--vb-color-accent);
	color: var(--vb-color-accent-contrast);
	border-color: var(--vb-color-accent);
}
.vb-btn--primary:hover {
	background-color: var(--vb-color-accent-hover);
	border-color: var(--vb-color-accent-hover);
}

.vb-btn--outline {
	background-color: transparent;
	color: var(--vb-color-ink);
	border-color: var(--vb-color-rule);
}
.vb-btn--outline:hover {
	border-color: var(--vb-color-ink);
	background-color: var(--vb-color-ink);
	color: var(--vb-color-paper);
}

.vb-btn--ghost { background-color: transparent; color: var(--vb-color-ink); }
.vb-btn--ghost:hover { background-color: var(--vb-color-paper-muted); }

/* Text link with a travelling arrow — the workhorse tertiary action. */
.vb-link {
	display: inline-flex;
	align-items: center;
	gap: 0.5em;
	font-size: var(--vb-fs-small);
	font-weight: var(--vb-weight-strong);
	color: var(--vb-color-accent);
	padding-block-end: 0.15em;
	border-block-end: var(--vb-border-hair) solid transparent;
	transition: border-color var(--vb-dur-base) var(--vb-ease-out-soft),
	            color var(--vb-dur-base) var(--vb-ease-out-soft);
}
.vb-link:hover { border-block-end-color: currentColor; }
.vb-link__arrow { transition: transform var(--vb-dur-base) var(--vb-ease-out-soft); }
.vb-link:hover .vb-link__arrow { transform: translateX(0.25em); }

/* --- rules & chips -------------------------------------------------------- */
.vb-rule { height: var(--vb-border-hair); background-color: var(--vb-color-rule); border: 0; width: 100%; }

.vb-chip {
	display: inline-flex;
	align-items: center;
	gap: 0.4em;
	padding: 0.4em 0.85em;
	border-radius: var(--vb-radius-pill);
	border: var(--vb-border-hair) solid var(--vb-color-rule);
	background-color: var(--vb-color-paper-muted);
	font-size: var(--vb-fs-small);
	color: var(--vb-color-ink-soft);
	white-space: nowrap;
}

/* --- media ---------------------------------------------------------------
   Aspect-ratio box. Set --vb-ratio locally (e.g. 4/5, 16/9). */
.vb-media {
	position: relative;
	/* Explicit, because the box is not always a div — several sections hang it on
	   a <span> inside a link or a track. An inline box ignores aspect-ratio, so
	   the frame would collapse to the image's own size. */
	display: block;
	overflow: hidden;
	aspect-ratio: var(--vb-ratio, 16 / 9);
	background-color: var(--vb-color-paper-sunk);
	border-radius: var(--vb-radius-card);
}
.vb-media > img,
.vb-media > video { width: 100%; height: 100%; object-fit: cover; }

/* --- reveal on scroll -----------------------------------------------------
   Progressive enhancement. js/reveal.js adds .is-in when the element enters
   the viewport. With JS off, or with reduced motion, content is simply
   visible — never hidden behind an animation that may not run. */
[data-vb-reveal] {
	opacity: 0;
	transform: translateY(var(--vb-reveal-y, 16px));
	transition: opacity var(--vb-dur-slow) var(--vb-ease-out-soft),
	            transform var(--vb-dur-slow) var(--vb-ease-out-soft);
	transition-delay: var(--vb-reveal-delay, 0ms);
}
[data-vb-reveal].is-in { opacity: 1; transform: none; }
html:not(.vb-js) [data-vb-reveal] { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
	*, *::before, *::after {
		animation-duration: 0.01ms !important;
		animation-iteration-count: 1 !important;
		transition-duration: 0.01ms !important;
		scroll-behavior: auto !important;
	}
	[data-vb-reveal] { opacity: 1; transform: none; }
}

/* --- a11y ----------------------------------------------------------------- */
.vb-visually-hidden {
	position: absolute; width: 1px; height: 1px;
	padding: 0; margin: -1px; overflow: hidden;
	clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --- placeholder art ------------------------------------------------------
   The library ships no photography — a section's job is to demonstrate
   structure, and a stock photo mostly demonstrates the stock photo. Add
   .vb-media--ph to any .vb-media for a token-coloured stand-in that follows
   every preset. Replace with a real <img> when porting into a project. */
.vb-media--ph {
	background-image:
		repeating-linear-gradient(
			45deg,
			transparent 0 10px,
			color-mix(in srgb, var(--vb-color-mute) 18%, transparent) 10px 11px
		);
	background-color: var(--vb-color-paper-sunk);
	display: grid;
	place-items: center;
}
.vb-media--ph::after {
	content: attr(data-ph-label);
	font-family: var(--vb-font-mono);
	font-size: var(--vb-fs-small);
	letter-spacing: 0.06em;
	color: var(--vb-color-mute);
	padding: 0.4em 0.9em;
	background-color: var(--vb-color-paper);
	border-radius: var(--vb-radius-pill);
}

/* Backdrop variant — keeps the hatch, drops the label. For full-bleed
   placeholders where a floating ratio pill would read as an artefact. */
.vb-media--ph-bare::after { content: none; }

/* Portrait / square / wide ratio shorthands, so sections don't invent values. */
.vb-ratio-square    { --vb-ratio: 1 / 1; }
.vb-ratio-portrait  { --vb-ratio: 4 / 5; }
.vb-ratio-tall      { --vb-ratio: 3 / 4; }
.vb-ratio-wide      { --vb-ratio: 16 / 9; }
.vb-ratio-cinema    { --vb-ratio: 21 / 9; }

/* Page-level dark, for a site that is dark end to end. Applies the same role
   swap as .vb-invert but to the document, so every default section inside it
   follows without being set to `dark` one by one. */
html.vb-invert-page,
html.vb-invert-page body { background-color: var(--vb-surface); }

/* --- chromatics ----------------------------------------------------------
   Two ways to reach the brand's chromatic sequence, and a section uses one or
   the other — never a --vb-color-chroma-N directly. Reaching for a numbered
   stop by hand is the same mistake as hardcoding a hex: it decides that THIS
   card is stop 2, when the whole point is that the brand decides.

   Sections paint from the four --vb-chroma-* aliases, which always resolve,
   cycle or no cycle: --vb-chroma is the field, --vb-chroma-ink the mark,
   --vb-chroma-soft the tint, --vb-chroma-contrast what sits on the field.
   Which one you want depends on how much of the colour is on screen — see the
   note in tokens.css. Drawing a thin glyph in the field colour is exactly the
   mistake that split exists to prevent.

     data-vb-chroma-cycle  on a container — its children take stops 1, 2, 3,
                           1, 2, 3 down the list. This is the Bauhaus case:
                           a grid of six tiles in three colours, and the
                           section never learns what the colours are.
     data-vb-chroma="2"    on one element — pin it to a stop. For a layout
                           whose colour is composed rather than sequential.

   Under every brand that declares no sequence, all three stops resolve to the
   accent and both forms are invisible. That is the intended default: nothing
   in the library changes appearance until a brand opts in. */
:root {
	--vb-chroma:          var(--vb-color-chroma-1);
	--vb-chroma-ink:      var(--vb-color-chroma-1-ink);
	--vb-chroma-soft:     var(--vb-color-chroma-1-soft);
	--vb-chroma-contrast: var(--vb-color-chroma-1-contrast);
}

[data-vb-chroma="1"],
[data-vb-chroma-cycle] > :nth-child(3n + 1) {
	--vb-chroma:          var(--vb-color-chroma-1);
	--vb-chroma-ink:      var(--vb-color-chroma-1-ink);
	--vb-chroma-soft:     var(--vb-color-chroma-1-soft);
	--vb-chroma-contrast: var(--vb-color-chroma-1-contrast);
}
[data-vb-chroma="2"],
[data-vb-chroma-cycle] > :nth-child(3n + 2) {
	--vb-chroma:          var(--vb-color-chroma-2);
	--vb-chroma-ink:      var(--vb-color-chroma-2-ink);
	--vb-chroma-soft:     var(--vb-color-chroma-2-soft);
	--vb-chroma-contrast: var(--vb-color-chroma-2-contrast);
}
[data-vb-chroma="3"],
[data-vb-chroma-cycle] > :nth-child(3n + 3) {
	--vb-chroma:          var(--vb-color-chroma-3);
	--vb-chroma-ink:      var(--vb-color-chroma-3-ink);
	--vb-chroma-soft:     var(--vb-color-chroma-3-soft);
	--vb-chroma-contrast: var(--vb-color-chroma-3-contrast);
}

/* --- the ramp ------------------------------------------------------------
   Type filled with the brand's gradient. Safe everywhere: --vb-color-ramp
   defaults to the accent restated as a gradient, so under a brand with no
   ramp this is simply accent-coloured text rather than invisible text.

   The colour declaration is not dead code behind the clip — it is what
   Windows High Contrast and any engine that drops background-clip fall back
   to, and it keeps the text readable instead of transparent. */
/* --- overprint -----------------------------------------------------------
   Two inks crossing. Put this on an element that deliberately overlaps another
   — an offset duplicate, a tinted plate over a photo, a colour bar running
   under a headline — and under a brand that declares --vb-blend-ink it
   multiplies into what is behind it instead of covering it.

   Inert unless a brand opts in: --vb-blend-ink is `normal` at the root, which
   is mix-blend-mode's own default, so a section can carry this class safely
   and only a Risograph-like brand does anything with it.

   isolation is deliberately NOT set here. A blended element composites against
   its nearest stacking context, and which one that should be is the section's
   decision rather than this file's — a plate meant to overprint the page needs
   a different boundary from one meant to overprint only its own card. */
.vb-overprint { mix-blend-mode: var(--vb-blend-ink); }

.vb-ramp-text {
	background-image: var(--vb-color-ramp);
	color: var(--vb-color-accent);
	-webkit-background-clip: text;
	        background-clip: text;
	-webkit-text-fill-color: transparent;
	/* The gradient is sized to the text box, so a ramp reads across the
	   headline rather than across whatever ancestor happens to be painting. */
	background-size: 100% 100%;
	background-repeat: no-repeat;
}
@media (forced-colors: active) {
	.vb-ramp-text { -webkit-text-fill-color: currentColor; background-image: none; }
}

/* Delayed observers or stalled entrance transitions must not strand content.
   Recovery requires real viewport visibility and respects transition grace. */
html.vb-js [data-vb-reveal][data-vb-reveal-fallback] {
  opacity: 1;
  transform: none;
  transition: none;
}
