/* ===========================================================================
   vk-motion.css — OPT-IN motion + material layer for the locked design system
   ---------------------------------------------------------------------------
   Harvested 2026-08-07 from six references (full writeup:
   docs/ops/motion-harvest-20260807.md) and re-expressed as vanilla CSS against
   OUR tokens. Nothing in this file is copy-pasted verbatim from a source —
   every curve, stop and technique was read from the source, then rebuilt here
   with our own selectors, our own palette and our own comments.

   SOURCES AND LICENSING (see the catalog for the full per-effect table)
     - transitions.dev (Jakub Antalik) — cli/free/*.md + skills/transitions-dev/
       SKILL.md, fetched from github.com/Jakubantalik/transitions.dev. NO SPDX
       LICENSE file in that repo (`license: null` via the GitHub API); the
       README states the free-tier snippets are "copy-ready... paste into any
       project". That is a usage grant, not a license grant, so nothing below
       is a literal copy — durations, easing curves and the token GRID
       (duration/scale/blur/distance ladders) are re-derived as our own
       --vkm-* custom properties. Attributed by URL per section.
     - metal-fx (Jakub Antalik) — github.com/Jakubantalik/metal-fx, MIT.
       Real technique is a WebGL plasma shader + canvas reflection compositor;
       far past this file's "no external libs, transform/opacity only" budget.
       What's ported here is the STATIC GRAMMAR around that shader — the
       inset hairline rim (`.metal-fx-root::after`) and the glow layer's
       opacity choreography — rebuilt as a CSS-only brushed-metal + pointer
       glare in .vkm-metal. MIT notice: Copyright (c) 2026 Jakub Antalik.
     - border-beam (Jakub Antalik) — github.com/Jakubantalik/border-beam, MIT.
       .vkm-beam-ring below is a direct re-implementation of its "-r1-"
       rotate variant: a registered `<angle>` custom property animated
       directly, driving a conic-gradient ring carved by the standard
       content-box-exclude mask trick. Captured from LIVE computed styles on
       beam.jakubantalik.com and cross-checked against the MIT source tree.
       MIT notice: Copyright (c) 2026 Jakub Antalik.
     - thinking-orbs (Jakub Antalik & Alex Brinza) — MIT. The real component is
       a 2D-canvas particle system (9 states) — out of budget for this file.
       Referenced only as the concept behind .vkm-orb's CSS-only breathing
       approximation; no source code from that repo appears below.
     - pryzm.design — Next.js/Tailwind product site, no snippet source
       available (minified, no public repo). Used for VOCABULARY only: this
       file's naming of "grain", "aberration", "fluted glass" as distinct
       material ideas follows what Pryzm calls its own background presets.
       No CSS below is copied from its (obfuscated) bundles.
     - designminis.com — a curated tools directory. Its own `.keycap` press
       treatment (layered inset shadows + `translateY(1px)` on hover) is the
       model for .vkm-press below, re-authored against our tokens.
     - Standard public-domain CSS techniques (page-curl-via-shadow-pseudo,
       repeating-linear-gradient rules-on-paper, SVG feTurbulence grain) are
       decades-old, commonly documented patterns (e.g. freefrontend.com/
       css-paper-effects, sitepoint's CSS3 paper-curl writeups) and are
       treated as generic knowledge, re-authored from scratch for .vkm-paper.

   THE CONTRACT
     1. LOCKED PALETTE ONLY. Every colour below resolves through var() against
        tokens already declared in vedika-tokens.css (--vds-*) or the page-
        level tokens vk-locked.css establishes (--ink/--muted/--space/
        --surface/--paper/--display/--body/--mono). Fallback values after the
        comma are the SAME hex the token already resolves to elsewhere in the
        system — they are safety nets for a page that loads this file without
        one of those two stylesheets, not a second palette.
     2. OPT-IN. No bare `.pill`, `.sheet`, `.mg`, `.nv`, `.bot` selector
        appears anywhere below without a `.vkm-*` class or `[data-vkm*]`
        attribute on the SAME compound selector. A page that never adds one
        of those hooks renders byte-identical to today.
     3. REDUCED MOTION. Every entrance-hiding rule and every animation lives
        inside `@media (prefers-reduced-motion: no-preference)`. Under
        `reduce`, opted-in elements render in their SETTLED state (visible,
        untransformed) — never hidden, never spinning, never blurred.
     4. NOT TOUCHING EXISTING ANATOMY. This file never re-declares a base
        property (`background`, `box-shadow`, `transition`) on `.pill` or
        `.sheet` themselves — that would silently replace the multi-layer
        depth stack vk-locked.css already built (box-shadow does not merge
        across rules; a second declaration REPLACES the first). Every visual
        addition below lives on an unclaimed pseudo-element (`.pill::before`
        and `.sheet::before` are unclaimed — verified against the full
        2,931-line file before writing this), on a JS-injected child layer,
        or on a NEW class that behaves exactly like the system's own existing
        modifier classes (`.sheet--dark`, `.pill--brand`) already do.
     5. TRANSFORM/OPACITY BY DEFAULT. Reveal, hover and press motion below is
        transform + opacity only. The two effects that must legitimately
        animate paint — .vkm-metal's brushed sheen and .vkm-beam-ring's
        conic-gradient rotation — are opt-in accents, not defaults, and are
        called out with a cost note at each definition. This codebase has a
        recorded 236.7ms paint-cost incident from animating a similar
        property across 40 cards at once; keep beams/metal to 1–2 per view.
     6. ARMED BOOT, LIKE vedika-scroll-reveal.js. Nothing gets an initial
        hidden state from CSS alone — see §1. A script failure or slow load
        must never leave content invisible. This mirrors, and does not
        replace, the sitewide `[data-r]` reveal contract already shipped in
        js/vedika-scroll-reveal.js; the two systems do not overlap because
        that one auto-tags subjects and this one is opt-in via `data-vkm`.

   Load this file AFTER vk-locked.css and WITHOUT a `layer()` on its <link> or
   @import — unlayered CSS always wins over layered CSS regardless of source
   order, which only matters here because it means this file cannot be
   accidentally shadowed by the `locked` layer as more gets added to it.
   =========================================================================== */

/* ===========================================================================
   1. TOKENS — the harvested vocabulary, re-expressed as --vkm-*
   ---------------------------------------------------------------------------
   Durations and the default ease are transitions.dev's own documented grid
   (skill's "Motion tokens" table / refine/server/motion-tokens.mjs), values
   only. --vkm-ease-locked is not from a source at all — it's this codebase's
   OWN sitewide default (--vds-ease-out), reused so the new layer moves in the
   same register as everything else already on the page.
   =========================================================================== */
:root{
  /* durations — transitions.dev DURATION_TOKENS, ms */
  --vkm-d-micro:  80ms;   /* shake segment, tooltip delay        */
  --vkm-d-quick:  150ms;  /* close/collapse, text swap           */
  --vkm-d-fast:   250ms;  /* open, dropdown, icon swap           */
  --vkm-d-medium: 350ms;  /* panel close, toast close            */
  --vkm-d-slow:   400ms;  /* panel open, skeleton reveal         */
  --vkm-d-slower: 500ms;  /* emphasis: text reveal, badge, count */
  --vkm-stagger:  60ms;   /* per-item delay step, capped at 4    */

  /* easing — transitions.dev's documented curves, byte-for-byte */
  --vkm-ease-out:      cubic-bezier(.22,1,.36,1);   /* "Smooth ease out", the site default   */
  --vkm-ease-spring:   cubic-bezier(.34,1.36,.64,1); /* bouncy overshoot — badge / menu pop   */
  --vkm-ease-spring-lg:cubic-bezier(.34,3.85,.64,1); /* strong overshoot — avatar-row return  */
  /* this codebase's own sitewide default (vedika-tokens.css --vds-ease-out),
     reused rather than replaced so page-native motion and this layer agree */
  --vkm-ease-locked: var(--vds-ease-out, cubic-bezier(.16,1,.3,1));

  /* distances / blur — transitions.dev DISTANCE_TOKENS / BLUR_TOKENS, px */
  --vkm-rise-y:    12px;  /* text/card reveal travel   */
  --vkm-rise-y-sm: 8px;   /* badge / stat travel       */
  --vkm-blur-sm:   2px;   /* panel / skeleton reveal   */
  --vkm-blur-md:   3px;   /* text reveal               */

  /* scale — transitions.dev SCALE_TOKENS */
  --vkm-scale-dropdown: .97;
  --vkm-scale-modal:    .96;

  /* colour — resolved through THIS system's own tokens only. The violet
     used for glare/beam/metal accents is --vds-accent-primary /
     --vds-v-500..700, i.e. the same ramp the brand capsule already uses. */
  --vkm-accent:      var(--vds-accent-primary, #7c3aed);
  --vkm-accent-2:    var(--vds-v-500, #9b7cff);
  --vkm-accent-soft: var(--vds-v-300, #c8b8ff);
  --vkm-ink:         var(--ink, var(--vds-text-primary, #0C0C0E));
  --vkm-muted:       var(--muted, var(--vds-text-secondary, #5C6373));
  --vkm-surface:     var(--surface, var(--vds-surface, #FFFFFF));
  --vkm-mono:        var(--mono, var(--vds-font-mono, 'Fragment Mono', ui-monospace, monospace));
}

/* ===========================================================================
   2. ARMED BOOT — nothing hides until vk-motion.js says the watcher is live.
   ---------------------------------------------------------------------------
   Same failure-mode reasoning as js/vedika-scroll-reveal.js's header comment,
   restated for this file's own attribute so the two stay independent: an
   element is only ever given a pre-hidden state under
   html[data-vkm-armed] — a flag vk-motion.js sets AFTER its observer exists —
   so "script never runs" and "script runs late" both fail open to visible,
   never to blank. See vk-motion.js for the matching deadline-reveal.
   =========================================================================== */
@media (prefers-reduced-motion: no-preference){
  html[data-vkm-armed] [data-vkm~="rise"],
  html[data-vkm-armed] .vkm-rise{
    opacity:0; transform:translateY(var(--vkm-rise-y));
  }
  html[data-vkm-armed] [data-vkm~="stagger"] > *{
    opacity:0; transform:translateY(var(--vkm-rise-y-sm));
  }
  html[data-vkm-armed] [data-vkm~="rise"].vkm-in,
  html[data-vkm-armed] .vkm-rise.vkm-in,
  html[data-vkm-armed] [data-vkm~="stagger"].vkm-in > *{
    opacity:1; transform:none;
  }
}

/* ===========================================================================
   3. SCROLL REVEAL — fade-rise + stagger-children
   ---------------------------------------------------------------------------
   transform + opacity only. Apply `.vkm-rise` or `data-vkm="rise"` to a
   single element, or `data-vkm="stagger"` to a container whose direct
   children should stagger in. vk-motion.js adds `.vkm-in` on intersection.
   =========================================================================== */
.vkm-rise,
[data-vkm~="rise"]{
  transition: opacity var(--vkm-d-slower) var(--vkm-ease-locked),
              transform var(--vkm-d-slower) var(--vkm-ease-locked);
  will-change: transform, opacity;
}

[data-vkm~="stagger"] > *{
  transition: opacity var(--vkm-d-slow) var(--vkm-ease-locked),
              transform var(--vkm-d-slow) var(--vkm-ease-locked);
  will-change: transform, opacity;
}
/* Capped stagger — 4 visible steps, then everything else rides the last
   delay together. Mirrors vedika-scroll-reveal.js's own STAGGER_CAP so a
   50-item grid doesn't take 3 seconds to finish revealing. */
[data-vkm~="stagger"] > *:nth-child(1){ transition-delay: 0ms }
[data-vkm~="stagger"] > *:nth-child(2){ transition-delay: var(--vkm-stagger) }
[data-vkm~="stagger"] > *:nth-child(3){ transition-delay: calc(var(--vkm-stagger) * 2) }
[data-vkm~="stagger"] > *:nth-child(n+4){ transition-delay: calc(var(--vkm-stagger) * 3) }

/* Section-enter choreography: a heading + lede pairing, second line held
   back — ported from transitions.dev "Texts reveal" (--stagger-* family),
   distance/blur values taken from its documented defaults (12px / 3px). */
@media (prefers-reduced-motion: no-preference){
  html[data-vkm-armed] [data-vkm~="rise-blur"]{
    opacity:0; transform:translateY(var(--vkm-rise-y)); filter:blur(var(--vkm-blur-md));
    transition: opacity var(--vkm-d-slower) var(--vkm-ease-out),
                transform var(--vkm-d-slower) var(--vkm-ease-out),
                filter var(--vkm-d-slower) var(--vkm-ease-out);
  }
  html[data-vkm-armed] [data-vkm~="rise-blur"].vkm-in{
    opacity:1; transform:none; filter:blur(0);
  }
  html[data-vkm-armed] [data-vkm~="rise-blur"][data-vkm-delay="1"]{ transition-delay: var(--vkm-stagger) }
  html[data-vkm-armed] [data-vkm~="rise-blur"][data-vkm-delay="2"]{ transition-delay: calc(var(--vkm-stagger) * 2) }
}

/* ===========================================================================
   4. HOVER / PRESS POLISH — opt-in refinements for .pill / .sheet
   ---------------------------------------------------------------------------
   `.vkm-lift` and `.vkm-press` only ADD transform/box-shadow on hover/active
   states that the base component does not already own at rest, so they
   compose without replacing the 8-layer box-shadow .pill already carries.
   =========================================================================== */

/* A slightly stronger hover lift than the base .tilt/.pill transition, for
   a card that should read as more interactive than the default (e.g. a
   pricing tier's recommended card). Adds nothing at rest. */
.pill.vkm-lift:hover, .sheet.vkm-lift:hover{
  transform: translateY(-3px);
}

/* Physical "keycap" press — harvested from designminis.com's `.keycap`
   (layered inset shadows + translateY(1px) on hover/press). Re-authored as
   an ADDITIVE box-shadow on ::after glare timing untouched; only :active
   gets a new transform, and .pill already defines `.pill:active` itself
   (translateY(-1px) scale(.998)) — this modifier SHARPENS that same moment
   with a slightly deeper press for buttons that want to feel mechanical
   (the model-picker / send button class of control, not the hero CTA). */
.vkm-press:active{
  transform: translateY(1px) scale(.985) !important;
  transition-duration: 70ms !important;
}

/* ===========================================================================
   5. MAGNETIC POINTER — ≤4px nudge toward the cursor
   ---------------------------------------------------------------------------
   vk-motion.js writes --vkm-mx / --vkm-my (each clamped to ±4px) on
   pointermove for any [data-vkm-magnetic] element. Idle state is (0,0), so
   an element with no listener attached (JS blocked) simply never moves.
   =========================================================================== */
@media (prefers-reduced-motion: no-preference){
  [data-vkm-magnetic]{
    transform: translate(var(--vkm-mx, 0px), var(--vkm-my, 0px));
    transition: transform 180ms var(--vkm-ease-out);
    will-change: transform;
  }
}

/* ===========================================================================
   6. GLASS / METAL FUSION — priority effect, founder-flagged
   ---------------------------------------------------------------------------
   Composes with .pill / .pill--light / .sheet WITHOUT touching their base
   background, box-shadow or canvas bloom. Both modifiers live entirely on
   the ::before pseudo-element (confirmed unclaimed on both .pill and .sheet
   across the full vk-locked.css file) so the marble bloom canvas
   (.pill__blob / .sheet__field), the sheen (.pill::after) and the label
   z-index stack are all untouched. Pick ONE of .vkm-metal / .vkm-glass per
   element — both target the same pseudo-element slot.

   Pointer tracking: vk-motion.js writes --vkm-gx / --vkm-gy (0–100%) on
   pointermove for any element carrying EITHER class, using the same
   pointer-position math transitions.dev's "Card hover tilt" recipe uses for
   its glare (github.com/Jakubantalik/transitions.dev, cli/free/card-tilt.md)
   — tracked on the element itself since .pill/.sheet are already the flat,
   non-rotating hit area (no separate wrapper needed, unlike that recipe's
   tilt case, because this fusion does not tilt in 3D).

   COST NOTE: the specular ramps below animate `background-position`, which
   repaints. That's why §5's magnetic nudge and §3's reveal stay
   transform/opacity-only and this section is opt-in — cap it to the one or
   two highest-intent surfaces per view (the nav CTA, a hero card), not a
   whole grid of sheets.
   =========================================================================== */

.pill.vkm-metal, .sheet.vkm-metal,
.pill.vkm-glass, .sheet.vkm-glass{
  position: relative; /* .pill/.sheet already are; stated for standalone use */
}
.pill.vkm-metal::before, .sheet.vkm-metal::before,
.pill.vkm-glass::before, .sheet.vkm-glass::before{
  content:'';
  position:absolute; inset:0; z-index:1; /* above the bloom canvas (z:auto/0), below .pill__label (z:2) */
  border-radius:inherit;
  pointer-events:none;
}

/* ---- .vkm-metal — brushed double-frequency ramp + hairline rim ----------
   The ramp is the SAME two-frequency repeating-linear-gradient technique
   already proven in this file's own vk-locked.css (.grid-flow::after,
   22deg / 3–7–13–19–26px + 40–74–130px stops) — reused verbatim rather than
   re-invented, so a metal pill and the page's own grid ground read as the
   same material. The inset hairline is metal-fx's own rim grammar
   (.metal-fx-root::after: inset 0 0 0 1px @ .10 white / .06 black),
   github.com/Jakubantalik/metal-fx, MIT. */
.pill.vkm-metal::before, .sheet.vkm-metal::before{
  background:
    /* pointer-tracked catch-light, transitions.dev card-tilt glare geometry
       (3 concentric radii, opacity .32 at rest → driven live by --vkm-gx/gy) */
    radial-gradient(circle 140px at var(--vkm-gx,50%) var(--vkm-gy,20%),
      rgba(255,255,255,.30), rgba(255,255,255,.05) 55%, transparent 80%),
    repeating-linear-gradient(22deg,
      rgba(255,255,255,.00) 0px, rgba(255,255,255,.38) 3px,
      rgba(226,232,240,.16) 7px, rgba(124,58,237,.00) 13px,
      rgba(167,139,250,.26) 19px, rgba(255,255,255,.00) 26px),
    repeating-linear-gradient(22deg,
      rgba(255,255,255,.00) 0px, rgba(255,255,255,.16) 40px,
      rgba(148,163,184,.08) 74px, rgba(255,255,255,.00) 130px);
  mix-blend-mode: screen;
  opacity:.6;
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,.10),
    inset 0 -10px 16px -12px rgba(0,0,0,.30);
  transition: opacity var(--vkm-d-fast) var(--vkm-ease-out);
}
.pill--light.vkm-metal::before, .sheet:not(.sheet--dark).vkm-metal::before{
  mix-blend-mode:multiply;
  box-shadow:
    inset 0 0 0 1px rgba(16,16,24,.08),
    inset 0 -10px 16px -12px rgba(16,16,24,.10);
}
.vkm-metal:hover::before{ opacity:.85 }

/* ---- .vkm-glass — edge refraction + moving reflection -------------------
   Chromatic hairline (a 1px hue-split rim) + a soft moving highlight that
   rides the pointer, both ideas taken from metal-fx's "chromatic" preset
   register (github.com/Jakubantalik/metal-fx, MIT — palette-driven, not its
   literal shader) and from border-beam's translucent-edge language. Backdrop
   blur is intentionally OMITTED: .pill/.sheet sit on an opaque ground in
   every current layout, so a backdrop-filter would cost paint for no visible
   refraction — add it per-instance only where the host truly sits over
   moving content. */
.pill.vkm-glass::before, .sheet.vkm-glass::before{
  background:
    radial-gradient(circle 160px at var(--vkm-gx,50%) var(--vkm-gy,15%),
      rgba(255,255,255,.42), rgba(255,255,255,.08) 45%, transparent 75%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.55),
    inset 0 0 0 1px rgba(255,255,255,.14),
    inset 1px 0 0 rgba(167,139,250,.20),   /* chromatic hairline, violet edge  */
    inset -1px 0 0 rgba(56,189,248,.16);   /* chromatic hairline, cyan edge    */
  opacity:.7;
  transition: opacity var(--vkm-d-fast) var(--vkm-ease-out);
}
.sheet--dark.vkm-glass::before{
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.16),
    inset 0 0 0 1px rgba(255,255,255,.10),
    inset 1px 0 0 rgba(167,139,250,.30),
    inset -1px 0 0 rgba(56,189,248,.22);
}
.vkm-glass:hover::before{ opacity:1 }

/* Pressed/depth state, shared by both materials — a brief inward dim so the
   surface reads as pushed rather than merely darkened. */
.vkm-metal:active::before, .vkm-glass:active::before{
  opacity:.4; transition-duration: var(--vkm-d-micro);
}

@media (prefers-reduced-motion: reduce){
  .vkm-metal::before, .vkm-glass::before{ transition:none }
}

/* ===========================================================================
   7. BORDER BEAM — .vkm-beam-ring, injected by vk-motion.js
   ---------------------------------------------------------------------------
   Direct re-implementation of border-beam's "-r1-" rotate variant
   (github.com/Jakubantalik/border-beam, MIT), captured from LIVE computed
   styles on beam.jakubantalik.com. Runs as a CHILD element rather than a
   pseudo-element — deliberately, so it can compose with .vkm-metal/.vkm-glass
   (which already own ::before) on the SAME .pill/.sheet, and so vk-motion.js
   can pause it via IntersectionObserver when off-screen (the paint-cost
   guardrail from §0.5). Author markup does not need to add this element by
   hand: give any .pill/.sheet `data-vkm-beam` and vk-motion.js injects
   `<i class="vkm-beam-ring" aria-hidden="true"></i>` once, at boot.

   THE MASK TRICK ("ring, not fill"): `mask: … content-box exclude, …` plus
   `padding: 2px` carves a transparent hole the size of the padding-box out
   of the middle of the layer, leaving only a 2px frame for the
   conic-gradient to paint into. This is the same technique border-beam
   itself uses; .pill/.sheet already clip to their own border-radius via
   `overflow:hidden`, so — unlike the source, which clip-paths itself — this
   ring only needs `border-radius:inherit` and the parent does the rest. */
@property --vkm-beam-angle{
  syntax: '<angle>'; inherits: true; initial-value: 0deg;
}
.vkm-beam-ring{
  /* z-index:1 — SAME layer as .vkm-metal/.vkm-glass's ::before (also z:1).
     Ties resolve by generated/DOM order: a ::before is treated as the
     element's first child, and vk-motion.js appends this ring LAST, so
     within the tie it paints after (on top of) the metal/glass sheen —
     and both stay below .pill__label's explicit z-index:2, so text is
     never covered regardless of which materials are combined. */
  position:absolute; inset:0; z-index:1;
  border-radius:inherit;
  pointer-events:none;
  padding:2px;
  background: conic-gradient(from var(--vkm-beam-angle),
    transparent 0%, transparent 58%,
    rgba(196,181,253,.10) 62%, rgba(196,181,253,.30) 65%,
    rgba(167,139,250,.65) 68%, rgba(124,58,237,.95) 70%,
    rgba(167,139,250,.65) 72%, rgba(196,181,253,.30) 75%,
    rgba(196,181,253,.10) 78%, transparent 82%, transparent 100%);
  -webkit-mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  opacity:0;
}
[data-vkm-beam] .vkm-beam-ring{ opacity:1 }
@media (prefers-reduced-motion: no-preference){
  [data-vkm-beam][data-vkm-beam-on] .vkm-beam-ring{
    animation: vkm-beam-spin 2.1s linear infinite;
  }
  @keyframes vkm-beam-spin{ to{ --vkm-beam-angle: 360deg } }
}
@media (prefers-reduced-motion: reduce){
  /* Designed degradation: a static soft rim instead of a spin — never gone,
     never moving. Browsers that don't support @property (pre-16.4 Safari)
     land in this exact same still frame, because an unregistered custom
     property cannot interpolate and the conic-gradient just renders at its
     initial 0deg — a static highlight, not a broken animation. */
  .vkm-beam-ring{ opacity:.55 }
}
/* Iridescent variant — border-beam's "-r2-" hue-shift, for the one surface
   per page that should read as the marble/glass material rather than a
   plain violet sweep (pairs with .vkm-metal/.vkm-glass on the same host). */
.vkm-beam-ring--iridescent{
  animation: vkm-beam-spin 2.1s linear infinite, vkm-beam-hue 12s ease-in-out infinite;
}
@media (prefers-reduced-motion: no-preference){
  @keyframes vkm-beam-hue{
    0%{ filter:hue-rotate(-24deg) saturate(1.2) }
    50%{ filter:hue-rotate(24deg) saturate(1.2) }
    100%{ filter:hue-rotate(-24deg) saturate(1.2) }
  }
}

/* ===========================================================================
   8. STAT COUNT-UP — styling only; vk-motion.js animates the number
   ---------------------------------------------------------------------------
   Tabular numerals so digits don't shift width mid-count (transitions.dev's
   own "Spinning counter" recipe calls this out explicitly:
   font-variant-numeric: tabular-nums on .t-reel). No layout property is
   touched during the count — only textContent, which repaints text but
   never reflows a fixed-width figure column.
   =========================================================================== */
[data-vkm-count]{
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}
@media (prefers-reduced-motion: no-preference){
  [data-vkm-count]{
    transition: opacity var(--vkm-d-slower) var(--vkm-ease-locked);
  }
  html[data-vkm-armed] [data-vkm-count]:not(.vkm-in){ opacity:.35 }
  html[data-vkm-armed] [data-vkm-count].vkm-in{ opacity:1 }
}

/* ===========================================================================
   9. MEGA MENU OPEN/CLOSE POLISH — additive to .mg, never replacing it
   ---------------------------------------------------------------------------
   vk-locked.css already animates `.mg` on opacity/translate/visibility
   (line ~285). This modifier ADDS a scale-in (a genuinely separate CSS
   property from `translate`, so it cannot clobber the existing rule) plus a
   short, capped stagger on the link list — transitions.dev's "Menu dropdown"
   pre-scale token (0.97) and ease (cubic-bezier(0.22,1,0.36,1)).
   Apply `.vkm-mega-polish` to the SAME element that already has `.mg`.
   =========================================================================== */
.mg.vkm-mega-polish{
  scale: var(--vkm-scale-dropdown);
  /* Restating the base transition list (vk-locked.css ~line 285) because a
     more specific selector on the same property list would otherwise win
     and silently drop opacity/translate/visibility — see §0.4. Values are
     copied from that rule, not altered. */
  transition: opacity .18s ease, translate .18s ease, visibility .18s,
              scale var(--vkm-d-fast) var(--vkm-ease-out);
}
.mg__cb:checked ~ .mg.vkm-mega-polish,
li:hover > .mg.vkm-mega-polish,
li:focus-within > .mg.vkm-mega-polish{
  scale: 1;
}
@media (prefers-reduced-motion: no-preference){
  .mg.vkm-mega-polish li{
    opacity:0; transform:translateY(4px);
    transition: opacity var(--vkm-d-fast) var(--vkm-ease-out),
                transform var(--vkm-d-fast) var(--vkm-ease-out);
  }
  .mg.vkm-mega-polish li:nth-child(1){ transition-delay:0ms }
  .mg.vkm-mega-polish li:nth-child(2){ transition-delay:30ms }
  .mg.vkm-mega-polish li:nth-child(3){ transition-delay:60ms }
  .mg.vkm-mega-polish li:nth-child(n+4){ transition-delay:90ms }
  .mg__cb:checked ~ .mg.vkm-mega-polish li,
  li:hover > .mg.vkm-mega-polish li,
  li:focus-within > .mg.vkm-mega-polish li{ opacity:1; transform:none }
}

/* ===========================================================================
   10. THINKING ORB — CSS-only approximation of thinking-orbs' concept
   ---------------------------------------------------------------------------
   thinking-orbs (github.com/Jakubantalik/thinking-orbs, MIT) is a 2D-canvas
   particle system with nine hand-tuned states — well outside this file's
   "no external libs, ~150-line JS" budget. .vkm-orb is NOT a port of that
   code; it is a small opacity/scale "breathing ring" carrying the same JOB
   (an agent-is-working indicator) built from primitives this file already
   has. Use it where vamademo.html's #cast "Casting the chart…" sequence
   currently has no motion of its own — see the catalog's per-page mapping.
   =========================================================================== */
.vkm-orb{
  position:relative; display:inline-block; width:1.5em; height:1.5em;
}
.vkm-orb::before, .vkm-orb::after{
  content:''; position:absolute; inset:0; border-radius:50%;
  border:1.5px solid var(--vkm-accent);
}
@media (prefers-reduced-motion: no-preference){
  .vkm-orb::before{ animation: vkm-orb-breathe 1.8s var(--vkm-ease-locked) infinite }
  .vkm-orb::after{ animation: vkm-orb-breathe 1.8s var(--vkm-ease-locked) infinite .6s }
  @keyframes vkm-orb-breathe{
    0%{ transform:scale(.7); opacity:.9 }
    70%{ transform:scale(1.25); opacity:0 }
    100%{ transform:scale(1.25); opacity:0 }
  }
}
@media (prefers-reduced-motion: reduce){
  .vkm-orb::before{ opacity:.6 }
  .vkm-orb::after{ display:none }
}

/* ===========================================================================
   11. REALISTIC PAPER — .vkm-paper, for .sheet used as a form panel
   ---------------------------------------------------------------------------
   Founder ask: "realistic paper kinda form and effect kinda texture." Every
   ingredient here is a well-documented, decades-old CSS technique (grain via
   SVG feTurbulence, ruled lines via repeating-linear-gradient, a corner
   shadow suggesting a lift) rebuilt against our tokens — see catalog for the
   standalone demo markup and a screenshot-ready snippet.

   Grain reuses --vds-grain-image / --vds-grain-tile — this system ALREADY
   defines a zero-mean feTurbulence dither in vedika-tokens.css for exactly
   this purpose (its own header calls it "a genuine ZERO-MEAN dither"), so
   .vkm-paper does not introduce a second noise asset.

   All of this lives on .sheet::before (confirmed unclaimed) plus new
   descendant selectors for inputs/labels — .sheet's own background,
   box-shadow and box model are untouched; .vkm-paper only sets `background`
   the same sanctioned way `.sheet--dark` already does. */
.sheet.vkm-paper{
  background: #FDFDFC; /* warm paper-white, still inside the locked neutral family */
}
.sheet.vkm-paper::before{
  content:'';
  position:absolute; inset:0; z-index:0; pointer-events:none;
  border-radius:inherit;
  background-image:
    var(--vds-grain-image),
    radial-gradient(120px 90px at 100% 100%, rgba(16,16,24,.07), transparent 70%);
  background-size: var(--vds-grain-tile, 168px) var(--vds-grain-tile, 168px), 100% 100%;
  opacity:.5; /* grain itself is ~2-3%; the corner shadow is the visible part */
}
/* Deckled top edge — a static zigzag clip-path, not a repeating mask.
   FIRST DRAFT USED A MASK AND IT WAS WRONG: `mask-repeat: repeat-x` repeats
   the pattern only along X — along Y the tile paints once at `mask-position:
   top` and everything outside that single tile is fully transparent, i.e.
   masked OUT. That hid the entire card below the top 7px in testing (caught
   by the smoke test in the harvest session, before this ever reached a real
   page). clip-path has no such failure mode: it only ever cuts the outer
   silhouette, so content further down the box can never be affected by it.
   21 points at 5% steps, alternating 0px/5px, then the two bottom corners —
   opt in separately from the base .vkm-paper grain, since a full deckle can
   read as noisy on a small panel. */
.sheet.vkm-paper.vkm-paper--deckle{
  clip-path: polygon(
    0% 5px, 5% 0px, 10% 5px, 15% 0px, 20% 5px, 25% 0px, 30% 5px, 35% 0px,
    40% 5px, 45% 0px, 50% 5px, 55% 0px, 60% 5px, 65% 0px, 70% 5px, 75% 0px,
    80% 5px, 85% 0px, 90% 5px, 95% 0px, 100% 5px,
    100% 100%, 0% 100%
  );
}
/* Pre-printed field captions: Fragment Mono, small caps, like a form's own
   letterpress label rather than a UI hint. */
.vkm-paper label, .vkm-paper .vkm-paper__label{
  font-family: var(--vkm-mono);
  font-size:10px; letter-spacing:.14em; text-transform:uppercase;
  color: var(--vkm-muted);
}
/* Inputs as a ruled line, not a boxed field — "filling in a printed form". */
.vkm-paper input, .vkm-paper textarea{
  border:0; border-radius:0; padding:6px 2px;
  background: transparent;
  border-bottom: 1px solid rgba(16,16,24,.18);
  font-family: var(--body, var(--vds-font-sans, inherit));
  color: var(--vkm-ink);
}
.vkm-paper textarea{
  /* multi-line "ruled paper": repeating baseline every 1.6em, offset so the
     first rule sits under the first line of text rather than above it */
  line-height:1.6em;
  background-image: repeating-linear-gradient(
    rgba(16,16,24,.12), rgba(16,16,24,.12) 1px, transparent 1px, transparent 1.6em);
  background-position: 0 1.5em;
}
.vkm-paper input:focus, .vkm-paper textarea:focus{
  outline:none;
  border-bottom-color: var(--vkm-accent);
}
/* Stamp-style submit area — a rotated, dashed rubber-stamp treatment for the
   panel's primary action. A NEW element the author adds (`<span
   class="vkm-paper__stamp">`), never a modifier on the shared .pill. */
.vkm-paper__stamp{
  display:inline-flex; align-items:center; justify-content:center;
  font-family: var(--vkm-mono); font-size:11px; letter-spacing:.12em; text-transform:uppercase;
  color: var(--vkm-accent);
  border:1.5px dashed var(--vkm-accent);
  border-radius:6px;
  padding:8px 16px;
  transform: rotate(-3deg);
  opacity:.85;
}

@media (prefers-reduced-motion: reduce){
  /* No motion is defined in this section to begin with; this block exists so
     a future addition here inherits the guard by default. */
}
