/**
 * GENERATED — run pnpm sync — do not edit.
 *
 * Every generated stylesheet this template needs, concatenated in cascade
 * order so the page pays for one request rather than 10.
 *
 * Sources, in the order they appear below:
 *   packages/tokens/src/tokens.css
 *   packages/tokens/src/themes/noir.css
 *   packages/tokens/src/reset.css
 *   templates/_shared/fonts/fonts.css
 *   packages/registry/intro-sequence/intro-sequence.css
 *   packages/registry/liquid-headline/liquid-headline.css
 *   packages/registry/card-stack/card-stack.css
 *   packages/registry/horizontal-handoff/horizontal-handoff.css
 *   packages/registry/drip-type/drip-type.css
 *   packages/registry/blend-cursor/blend-cursor.css
 *
 * Your own styles are base.css and sections/*.css. Edit those.
 */

/* ── packages/tokens/src/tokens.css ────────────────────────────────────── */

/**
 * vcraft design tokens.
 *
 * The single place in the repo where a raw colour, duration or pixel value is
 * allowed to exist. Components read these and never define them.
 *
 * Naming: --vc-<category>-<name>
 * Light values live on :root. Dark overrides only the values that change.
 */

:root {
  /* ── Colour ───────────────────────────────────────────────────────────── */
  --vc-bg: #ffffff;
  --vc-surface: #f4f4f5;
  --vc-ink: #09090b;
  --vc-ink-muted: #71717a;
  --vc-accent: #2563eb;
  --vc-border: #e4e4e7;

  /* ── Space ────────────────────────────────────────────────────────────── */
  --vc-space-1: 0.25rem;
  --vc-space-2: 0.5rem;
  --vc-space-3: 0.75rem;
  --vc-space-4: 1rem;
  --vc-space-5: 1.25rem;
  --vc-space-6: 1.5rem;
  --vc-space-7: 2rem;
  --vc-space-8: 2.5rem;
  --vc-space-9: 3rem;
  --vc-space-10: 4rem;
  --vc-space-11: 6rem;
  --vc-space-12: 8rem;

  /* ── Radius ───────────────────────────────────────────────────────────── */
  --vc-radius-sm: 0.25rem;
  --vc-radius-md: 0.5rem;
  --vc-radius-lg: 1rem;
  --vc-radius-full: 9999px;

  /* ── Type ─────────────────────────────────────────────────────────────── */
  --vc-font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --vc-font-mono: ui-monospace, "SF Mono", "Cascadia Mono", Menlo, monospace;

  /* Headlines only. Defaults to the body face so a template that ships no
     display font still resolves this to something real — point it at a face
     in the template, the way base.css does for --vc-font-sans. */
  --vc-font-display: var(--vc-font-sans);

  --vc-text-xs: 0.75rem;
  --vc-text-sm: 0.875rem;
  --vc-text-base: 1rem;
  --vc-text-lg: 1.125rem;
  --vc-text-xl: 1.25rem;
  --vc-text-2xl: 1.5rem;
  --vc-text-3xl: 1.875rem;
  --vc-text-4xl: 2.25rem;
  --vc-text-5xl: 3rem;

  --vc-leading-tight: 1.15;
  --vc-leading-base: 1.5;

  /* ── Motion ───────────────────────────────────────────────────────────── */
  --vc-dur-fast: 120ms;
  --vc-dur-base: 240ms;
  --vc-dur-slow: 480ms;
  --vc-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
  --vc-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* ── Elevation ────────────────────────────────────────────────────────── */
  --vc-shadow-sm: 0 1px 2px rgb(9 9 11 / 0.06);
  --vc-shadow-md: 0 4px 12px rgb(9 9 11 / 0.08);
  --vc-shadow-lg: 0 16px 40px rgb(9 9 11 / 0.12);

  /* ── Z-index ──────────────────────────────────────────────────────────── */
  --vc-z-overlay: 100;
  --vc-z-tooltip: 200;
}

[data-theme="dark"] {
  --vc-bg: #09090b;
  --vc-surface: #18181b;
  --vc-ink: #fafafa;
  --vc-ink-muted: #a1a1aa;
  --vc-accent: #60a5fa;
  --vc-border: #27272a;

  --vc-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.5);
  --vc-shadow-md: 0 4px 12px rgb(0 0 0 / 0.55);
  --vc-shadow-lg: 0 16px 40px rgb(0 0 0 / 0.65);
}

/**
 * Section surfaces.
 *
 * A page that alternates between a light and a dark band needs to say so per
 * section, not per property. `data-vc-surface` re-points the palette tokens
 * for one subtree, and every component inside it follows without knowing the
 * section exists — they already read --vc-ink and --vc-bg.
 *
 * Absolute, not relative: "dark" is dark in either theme. A section that
 * flipped with the theme would turn an alternating page into a uniform one
 * the moment the theme changed, which is the opposite of the point.
 *
 * Declared here in the default palette so the mechanism exists for every
 * theme; a theme that wants its own two surfaces overrides these, as noir.css
 * does.
 */

[data-vc-surface="light"] {
  --vc-bg: #ffffff;
  --vc-surface: #f4f4f5;
  --vc-ink: #09090b;
  --vc-ink-muted: #71717a;
  --vc-accent: #2563eb;
  --vc-border: #e4e4e7;

  --vc-shadow-sm: 0 1px 2px rgb(9 9 11 / 0.06);
  --vc-shadow-md: 0 4px 12px rgb(9 9 11 / 0.08);
  --vc-shadow-lg: 0 16px 40px rgb(9 9 11 / 0.12);
}

[data-vc-surface="dark"] {
  --vc-bg: #09090b;
  --vc-surface: #18181b;
  --vc-ink: #fafafa;
  --vc-ink-muted: #a1a1aa;
  --vc-accent: #60a5fa;
  --vc-border: #27272a;

  --vc-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.5);
  --vc-shadow-md: 0 4px 12px rgb(0 0 0 / 0.55);
  --vc-shadow-lg: 0 16px 40px rgb(0 0 0 / 0.65);
}

/* ── packages/tokens/src/themes/noir.css ───────────────────────────────── */

/**
 * Theme: noir
 *
 * Warm off-white paper against near-black ink, with a single amber accent.
 * The palette the Noir Portfolio template ships with.
 *
 * Load tokens.css first; this only overrides colour and elevation.
 */

[data-vc-theme="noir"] {
  --vc-bg: #f7f5f1;
  --vc-surface: #ede9e2;
  --vc-ink: #121110;
  --vc-ink-muted: #6b665e;
  --vc-accent: #c2410c;
  --vc-border: #ddd8cf;

  --vc-shadow-sm: 0 1px 2px rgb(18 17 16 / 0.07);
  --vc-shadow-md: 0 4px 12px rgb(18 17 16 / 0.1);
  --vc-shadow-lg: 0 16px 40px rgb(18 17 16 / 0.14);
}

[data-vc-theme="noir"][data-theme="dark"],
[data-theme="dark"] [data-vc-theme="noir"] {
  --vc-bg: #0c0b0a;
  --vc-surface: #1a1816;
  --vc-ink: #f3efe8;
  --vc-ink-muted: #9b948a;
  --vc-accent: #fb923c;
  --vc-border: #2a2724;

  --vc-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.55);
  --vc-shadow-md: 0 4px 12px rgb(0 0 0 / 0.6);
  --vc-shadow-lg: 0 16px 40px rgb(0 0 0 / 0.7);
}

/**
 * Noir's two surfaces.
 *
 * The template alternates bands: warm off-white, then near-black, and back.
 * These are the same two palettes the light and dark themes already use — a
 * dark section is not a third colour scheme, it is the dark theme applied to
 * one subtree.
 *
 * The accent shifts with the surface for the same reason it shifts with the
 * theme: #c2410c is 4.76:1 on the off-white and 1.9:1 on the near-black, which
 * is not a colour, it is a smudge. #fb923c reads 8.69:1 there.
 *
 * Two selectors each because the attribute can sit on the themed element
 * itself — `<html data-vc-theme="noir" data-vc-surface="dark">` — or on a
 * section inside it, and a descendant combinator alone would miss the first.
 */

[data-vc-theme="noir"][data-vc-surface="light"],
[data-vc-theme="noir"] [data-vc-surface="light"] {
  --vc-bg: #f7f5f1;
  --vc-surface: #ede9e2;
  --vc-ink: #121110;
  --vc-ink-muted: #6b665e;
  --vc-accent: #c2410c;
  --vc-border: #ddd8cf;

  --vc-shadow-sm: 0 1px 2px rgb(18 17 16 / 0.07);
  --vc-shadow-md: 0 4px 12px rgb(18 17 16 / 0.1);
  --vc-shadow-lg: 0 16px 40px rgb(18 17 16 / 0.14);
}

[data-vc-theme="noir"][data-vc-surface="dark"],
[data-vc-theme="noir"] [data-vc-surface="dark"] {
  --vc-bg: #0c0b0a;
  --vc-surface: #1a1816;
  --vc-ink: #f3efe8;
  --vc-ink-muted: #9b948a;
  --vc-accent: #fb923c;
  --vc-border: #2a2724;

  --vc-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.55);
  --vc-shadow-md: 0 4px 12px rgb(0 0 0 / 0.6);
  --vc-shadow-lg: 0 16px 40px rgb(0 0 0 / 0.7);
}

/* ── packages/tokens/src/reset.css ─────────────────────────────────────── */

/**
 * Opt-in reset for templates.
 *
 * Components never import this — they ship no global rules. Templates load it
 * once, before their own base layer.
 */

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

* {
  margin: 0;
}

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  tab-size: 4;
}

body {
  min-height: 100vh;
  background: var(--vc-bg);
  color: var(--vc-ink);
  font-family: var(--vc-font-sans);
  font-size: var(--vc-text-base);
  line-height: var(--vc-leading-base);
  -webkit-font-smoothing: antialiased;
}

img,
picture,
video,
canvas,
svg {
  display: block;
  max-width: 100%;
}

input,
button,
textarea,
select {
  font: inherit;
  color: inherit;
}

button {
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
}

p,
h1,
h2,
h3,
h4,
h5,
h6 {
  overflow-wrap: break-word;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: inherit;
  font-weight: inherit;
  line-height: var(--vc-leading-tight);
}

ul[role="list"],
ol[role="list"] {
  list-style: none;
  padding: 0;
}

a {
  color: inherit;
  text-decoration-thickness: from-font;
  text-underline-offset: 0.15em;
}

:focus-visible {
  outline: 2px solid var(--vc-accent);
  outline-offset: 2px;
}

:where(#root, #__next) {
  isolation: isolate;
}

@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;
  }
}

/* ── templates/_shared/fonts/fonts.css ─────────────────────────────────── */

/**
 * Self-hosted Geist, shared by every template that asks for it.
 *
 * Synced into a template's `src/styles/fonts.css` by `pnpm sync`, which also
 * copies the `.woff2` files and their OFL licence into `public/fonts/`. Which
 * files a template gets is the `fonts` array in its template.json.
 *
 * Two variable faces, one file each. A variable font is the whole weight range
 * in a single request — the alternative is six static files and six requests,
 * five of which paint nothing.
 *
 * Paths are relative, and have to stay that way: a leading slash resolves
 * against the filesystem root when the page is opened from `file://`, which is
 * the one thing this tier must survive.
 *
 * `font-display: swap` is not optional. A blocking webfont is the easiest way
 * to lose the performance score the template is sold on, and the fallback
 * stack in `--vc-font-sans` is close enough in metrics that the swap is not a
 * jolt.
 *
 * The subset is the Google Fonts `latin` range plus U+2190–2193, because the
 * arrows are page furniture here and a missing glyph would silently fall back
 * to the system font mid-sentence. `unicode-range` repeats it exactly: a
 * browser that needs a codepoint outside it should not fetch these at all.
 *
 * Licence: SIL OFL 1.1 — see `public/fonts/geist-OFL.txt`, which has to ship
 * beside the fonts in every copy. `sync-template.js` refuses to vendor a font
 * whose licence is missing.
 */

@font-face {
  font-family: "Geist";
  src: url("../../public/fonts/geist-sans-latin-var.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2190-2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Geist Mono";
  src: url("../../public/fonts/geist-mono-latin-var.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2190-2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/**
 * Anton, display only.
 *
 * One static weight — Anton has no variable cut and no second weight to
 * interpolate towards, so the 11KB here is the whole family. It is never used
 * below a headline: `--vc-font-display` is the only token that points at it,
 * and nothing but the hero, the section titles and the stats read that token.
 *
 * Subset tighter than Geist on purpose. Geist sets running copy, so it needs
 * the full latin range; Anton sets four or five headlines, so it carries
 * printable ASCII plus the punctuation an English headline actually uses —
 * curly quotes, en and em dashes, the ellipsis, the middot. 18.6KB down to
 * 11.3KB.
 *
 * That is a real ceiling, and it is the one thing to know before editing
 * `hero.headline` in site.config.js: a headline with an accented character in
 * it falls back to Geist for that glyph and nothing else. Re-run the
 * pyftsubset command in README.md with a wider `--unicodes` if you need more.
 *
 * No `font-weight` range, because there is no range. Asking for 700 gets the
 * one weight synthesised-bold by the browser, which on a face this heavy is a
 * smear — the display rules set `font-weight: 400` explicitly to stop it.
 *
 * Licence: SIL OFL 1.1 — see `public/fonts/anton-OFL.txt`.
 */

@font-face {
  font-family: "Anton";
  src: url("../../public/fonts/anton-display-latin.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range:
    U+0020-007E, U+00A0, U+00B7, U+2010-2011, U+2013-2014, U+2018-2019, U+201C-201D,
    U+2022, U+2026;
}

/* ── packages/registry/intro-sequence/intro-sequence.css ───────────────── */

/**
 * Intro Sequence
 *
 * Reads tokens, never defines them. No global rules — every selector is scoped
 * under .vc-intro-sequence.
 *
 * The hidden state is applied by JavaScript, never by this stylesheet. If it
 * lived here, a script that failed to load would leave the page permanently
 * blank. A flash of the finished page is a far better failure than no page.
 */

.vc-intro-sequence__item {
  transition:
    opacity var(--vc-dur-slow) var(--vc-ease-out) var(--vc-is-delay, 0s),
    transform var(--vc-dur-slow) var(--vc-ease-out) var(--vc-is-delay, 0s);
}

/**
 * translate3d, not translateY: it promotes each item to its own layer for the
 * duration of the reveal, which keeps a long stagger off the main thread. The
 * class is removed when the sequence ends, so the layers go with it.
 */
.vc-intro-sequence__item--pending {
  opacity: 0;
  transform: translate3d(0, var(--vc-is-distance, 0), 0);
}

/**
 * Belt and braces. The factory already declines to hide anything under reduced
 * motion; this covers a preference that changes mid-reveal, before the factory
 * has stripped the classes.
 */
@media (prefers-reduced-motion: reduce) {
  .vc-intro-sequence__item {
    transition: none;
  }

  .vc-intro-sequence__item--pending {
    opacity: 1;
    transform: none;
  }
}

/* ── packages/registry/liquid-headline/liquid-headline.css ─────────────── */

/**
 * Liquid Headline
 *
 * Reads tokens, never defines them. No global rules — every selector is scoped
 * under .vc-liquid-headline. Colour, size and weight all inherit, so the
 * headline looks like the page's headline and not like a component.
 */

.vc-liquid-headline {
  display: block;
}

/**
 * The text, for assistive tech only. The split layer beside it is aria-hidden,
 * because per-character elements get announced one letter at a time.
 *
 * user-select: none keeps it out of a copied selection, so the headline is
 * copied once rather than twice.
 */
.vc-liquid-headline__label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
  user-select: none;
  -webkit-user-select: none;
}

.vc-liquid-headline__visual {
  display: block;
}

/**
 * Words are atomic, so a line never breaks mid-word. max-width is the exception
 * that matters: one word longer than the line wraps inside its own box instead
 * of overflowing the container.
 */
.vc-liquid-headline__word {
  display: inline-block;
  max-width: 100%;
}

/**
 * inline-block is load-bearing: transforms do not apply to a non-replaced
 * inline box. The custom properties are written by the spring loop.
 */
.vc-liquid-headline__char {
  display: inline-block;
  transform: translate(0, var(--vc-lh-y, 0)) scale(var(--vc-lh-s, 1));
}

/**
 * Belt and braces. The factory already declines to bind a pointer or take a
 * frame under reduced motion; this covers a stale inline value left behind by
 * a preference change mid-animation.
 */
@media (prefers-reduced-motion: reduce) {
  .vc-liquid-headline__char {
    transform: none;
  }
}

/* ── packages/registry/card-stack/card-stack.css ───────────────────────── */

/**
 * Card Stack
 *
 * Reads tokens, never defines them. No global rules — every selector is scoped
 * under .vc-card-stack. Nothing here paints: no background, no border, no
 * radius. A card looks like the buyer's card, and the stack only decides where
 * it sits and how far back it is.
 *
 * The pinning is declared here but switched on from JavaScript, which adds
 * .vc-card-stack__card once it has written that card's offset. A page whose
 * script never ran is then a plain readable column of cards rather than a pile
 * with no depth to it.
 */

/**
 * transform-origin is load-bearing twice over. It makes the card recede toward
 * the sliver of itself still on show, which is what reads as depth — and it
 * leaves the top edge exactly where it was, so the factory can measure a
 * scaled card without measuring its own output.
 *
 * z-index is explicit rather than left to source order. `position: sticky`
 * makes every card a stacking context whatever the value, so this costs
 * nothing and stops a stray z-index inside a card reordering the deck.
 *
 * scroll-margin-top keeps an anchor link — #work, #project-3 — from landing
 * under the pinned card above it.
 */
.vc-card-stack__card {
  position: sticky;
  top: var(--vc-cs-pin, 0px);
  z-index: var(--vc-cs-index, auto);
  transform: scale(var(--vc-cs-scale, 1));
  transform-origin: top center;
  opacity: var(--vc-cs-fade, 1);
  scroll-margin-top: var(--vc-cs-pin, 0px);
}

/**
 * Belt and braces. The factory already declines to pin anything under reduced
 * motion; this covers a preference that flips after a frame is scheduled and
 * before it runs.
 *
 * `relative`, not `static`: both lay out identically in flow, but a card that
 * was relying on being a containing block for something absolutely positioned
 * inside it keeps that either way. Which is the same reason the pinned state
 * can be dropped without the card's own contents moving.
 */
@media (prefers-reduced-motion: reduce) {
  .vc-card-stack__card {
    position: relative;
    top: auto;
    transform: none;
    opacity: 1;
    scroll-margin-top: 0;
  }
}

/* ── packages/registry/horizontal-handoff/horizontal-handoff.css ───────── */

/**
 * Horizontal Handoff
 *
 * Reads tokens, never defines them. No global rules — every selector is scoped
 * under .vc-horizontal-handoff. Nothing here paints: no background, no border,
 * no radius. The panels are the buyer's, and this only decides where they sit.
 *
 * The unpinned rules below are the real fallback, not a degraded one. Without
 * the --pinned class this is an ordinary horizontal scroll container: every
 * panel reachable, the layout intact, and the scrolling the visitor's own.
 * That is the reduced-motion mode and the nothing-to-travel mode both.
 */

.vc-horizontal-handoff__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  /* Momentum on iOS, and no vertical rubber-banding fighting the page. */
  overscroll-behavior-x: contain;
}

/**
 * max-content, so the track is as wide as the panels put together rather than
 * as wide as its parent. Without it a flex row inside a clipped box would
 * squeeze every panel down to fit, and there would be nothing to travel.
 */
.vc-horizontal-handoff__track {
  display: flex;
  width: max-content;
  gap: var(--vc-hh-gap, 0);
}

/**
 * Pinned. The viewport sticks to the top of the scrollport for exactly as long
 * as the section is passing through it, and the track moves sideways by the
 * amount of that passage the visitor has covered.
 *
 * The height is a custom property so a buyer can hand it 100svh — which does
 * not resize when mobile browser chrome slides away, and so does not jump
 * mid-handoff — or a fixed height, without touching this file.
 */
.vc-horizontal-handoff--pinned .vc-horizontal-handoff__viewport {
  position: sticky;
  top: 0;
  height: var(--vc-hh-height, 100vh);
  overflow: hidden;
}

.vc-horizontal-handoff--pinned .vc-horizontal-handoff__track {
  height: 100%;
  transform: translate3d(var(--vc-hh-x, 0px), 0, 0);
}

/**
 * Belt and braces. The factory already declines to pin anything under reduced
 * motion; this covers the frame between a preference changing and the factory
 * hearing about it. Returning to `static` is safe here because the viewport is
 * an element the component created and nothing else styles.
 */
@media (prefers-reduced-motion: reduce) {
  .vc-horizontal-handoff--pinned .vc-horizontal-handoff__viewport {
    position: static;
    height: auto;
    overflow-x: auto;
    overflow-y: hidden;
  }

  .vc-horizontal-handoff--pinned .vc-horizontal-handoff__track {
    height: auto;
    transform: none;
  }
}

/* ── packages/registry/drip-type/drip-type.css ─────────────────────────── */

/**
 * Drip Type
 *
 * Reads tokens, never defines them. No global rules — every selector is scoped
 * under .vc-drip-type. Colour, size and weight all inherit, so the headline
 * looks like the page's headline and not like a component. The ink colour is
 * resolved from the cascade by the factory, because a canvas cannot read a
 * custom property.
 */

/**
 * The positioning context for the canvas. A headline is a block anyway; this
 * only makes it explicit, and makes the overlay's coordinates the element's
 * own padding box.
 */
.vc-drip-type {
  position: relative;
  display: block;
}

/**
 * The text, for assistive tech only. The split layer beside it is aria-hidden,
 * because per-character elements get announced one letter at a time.
 *
 * user-select: none keeps it out of a copied selection, so the headline is
 * copied once rather than twice.
 */
.vc-drip-type__label {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
  user-select: none;
  -webkit-user-select: none;
}

/**
 * Above the canvas. Both are painted in the same colour, so the only thing
 * this ordering decides is that a drip passing a letter runs behind it rather
 * than over it.
 */
.vc-drip-type__visual {
  position: relative;
  z-index: 1;
  display: block;
}

/**
 * Words are atomic, so a line never breaks mid-word — splitting into
 * characters would otherwise let the browser break between any two of them.
 * max-width is the exception that matters: one word longer than the line wraps
 * inside its own box instead of overflowing the container.
 */
.vc-drip-type__word {
  display: inline-block;
  max-width: 100%;
}

/**
 * Left inline on purpose. An inline box's rect is font-metric height rather
 * than line height, which is what the ink measurement is built on, and inline
 * keeps the baseline alignment the browser already worked out.
 */
.vc-drip-type__char {
  display: inline;
}

/**
 * Sized to the text plus the reach the drips are given below it, which the
 * factory writes as --vc-dt-reach. Takes no pointer events: everything under
 * it stays clickable and selectable.
 *
 * width is stated explicitly, and that is not a style choice. A canvas is a
 * replaced element, so anchoring it with `left: 0; right: 0` does not stretch
 * it the way it would a div — it keeps `width: auto`, which for a replaced
 * element means its intrinsic size. With only a height given it then takes the
 * width from its intrinsic 2:1 ratio and lands nowhere near the text. Worse,
 * the factory sizes the backing store from the rendered box, so the box would
 * be deriving its size from the backing store that was derived from the box.
 * A percentage width resolves against the containing block and breaks the loop.
 */
.vc-drip-type__canvas {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 0;
  display: block;
  width: 100%;
  height: calc(100% + var(--vc-dt-reach, 0px));
  pointer-events: none;
}

/**
 * Applied by the factory under reduced motion. Hidden rather than removed, so
 * turning the preference back off needs no remount.
 */
.vc-drip-type--still .vc-drip-type__canvas {
  display: none;
}

/**
 * Belt and braces. The factory already declines to bind a pointer or take a
 * frame under reduced motion; this covers the frame between a preference
 * changing and the factory hearing about it.
 */
@media (prefers-reduced-motion: reduce) {
  .vc-drip-type__canvas {
    display: none;
  }
}

/* ── packages/registry/blend-cursor/blend-cursor.css ───────────────────── */

/**
 * Blend Cursor
 *
 * Reads tokens, never defines them. No global rules — every selector is scoped
 * under .vc-blend-cursor, and the one rule that reaches outside it is scoped to
 * .vc-blend-cursor-host, a class the factory adds and removes.
 *
 * Nothing here hides the native cursor except that host class, and the factory
 * only ever adds it while the disc is actually on screen. A stylesheet that
 * hid the cursor on its own would leave a page whose script failed to load
 * with no cursor at all.
 */

.vc-blend-cursor-host {
  cursor: none;
}

/**
 * The outer element carries position only, rewritten every frame from
 * JavaScript, and must never have a transition on transform — a transition
 * here would be a second animation fighting the easing.
 *
 * Centred by margin rather than by a translate(-50%, -50%) baked into the
 * transform, so the per-frame write stays a plain translate3d.
 */
.vc-blend-cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: var(--vc-z-tooltip);
  width: var(--vc-bc-size, 24px);
  height: var(--vc-bc-size, 24px);
  margin: calc(var(--vc-bc-size, 24px) / -2);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--vc-dur-fast) var(--vc-ease-out);
}

.vc-blend-cursor--visible {
  opacity: 1;
}

/* Over a text field the real cursor is back, so this one gets out of the way. */
.vc-blend-cursor--yielded {
  opacity: 0;
}

/**
 * The inner element carries size and appearance, and swells on hover through a
 * transition. Separating the two transforms is what lets a CSS transition and
 * a per-frame write coexist on the same cursor.
 */
.vc-blend-cursor__disc {
  width: 100%;
  height: 100%;
  border-radius: var(--vc-radius-full);
  transform: scale(1);
  transition: transform var(--vc-dur-base) var(--vc-ease-spring);
}

.vc-blend-cursor--hover .vc-blend-cursor__disc {
  transform: scale(var(--vc-bc-swell, 2.5));
}

/**
 * Inverting the backdrop needs no colour at all, which is the whole reason it
 * is the default. Every fill that could be named instead collapses in one
 * theme or the other: measured against the token set, --vc-bg is invisible in
 * dark and --vc-ink is invisible in light, because `difference` against a
 * backdrop equal to the fill is black.
 */
.vc-blend-cursor__disc--invert {
  backdrop-filter: invert(1);
  -webkit-backdrop-filter: invert(1);
}

/**
 * The named-colour alternative. --vc-accent is the default because it is the
 * one colour token that is mid-tone in both themes by construction, so the
 * difference against the page is large either way.
 */
.vc-blend-cursor__disc--blend {
  background: var(--vc-bc-color, var(--vc-accent));
  mix-blend-mode: var(--vc-bc-blend, difference);
}

/**
 * Belt and braces. The factory already stops easing and writes the disc
 * straight to the pointer under reduced motion; this covers the swell, which
 * is a CSS transition the factory has no hand in.
 */
@media (prefers-reduced-motion: reduce) {
  .vc-blend-cursor,
  .vc-blend-cursor__disc {
    transition: none;
  }
}
