From 2200b04978c75e3660b99be49f49a88971e88d7d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 21 Aug 2026 11:18:48 +0000 Subject: [PATCH 1/3] feat(motion): add Conditional Motion Engine (vl-motion) with CSS if() progressive enhancement Co-authored-by: Erik Assis Cardoso --- apps/docs/src/layouts/Base.astro | 5 + apps/docs/src/pages/motion-conditions.astro | 149 +++++++ .../showcase/pages/motion/zero-js-motion.html | 46 ++ apps/showcase/public/css/01-tokens.css | 15 + apps/showcase/public/css/03-motion.css | 19 +- .../public/css/03a-motion-conditions.css | 178 ++++++++ apps/showcase/public/css/motion-core.css | 1 + apps/showcase/public/css/velora.css | 1 + .../scripts/validate-showcase-contract.mjs | 408 +++++++++--------- docs/project/CONTRACT.md | 27 ++ packages/compiler/src/grammar.mjs | 2 + packages/css/src/01-tokens.css | 15 + packages/css/src/03-motion.css | 19 +- packages/css/src/03a-motion-conditions.css | 178 ++++++++ packages/css/src/motion-core.css | 1 + packages/css/src/velora.css | 1 + 16 files changed, 848 insertions(+), 217 deletions(-) create mode 100644 apps/docs/src/pages/motion-conditions.astro create mode 100644 apps/showcase/public/css/03a-motion-conditions.css create mode 100644 packages/css/src/03a-motion-conditions.css diff --git a/apps/docs/src/layouts/Base.astro b/apps/docs/src/layouts/Base.astro index 56e1b3d..468d3d4 100644 --- a/apps/docs/src/layouts/Base.astro +++ b/apps/docs/src/layouts/Base.astro @@ -423,6 +423,11 @@ function isActive(href: string): boolean { Motion Effects + Conditional Motion Scenes Page Transitions + Motion + +

Conditional Motion Engine

+

+ A single declarative attribute — vl-motion — selects a motion mode for an + element and its subtree. It does not name a preset; instead it re-scales the shared engine tokens + that every preset already reads, so one attribute adapts timing, travel, depth, blur and easing + across the whole subtree. Zero JavaScript. +

+ +

The Modes

+

+ Add vl-motion to any element. Descendant vl-effect / vl-enter + reveals inherit the mode automatically. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ValueIntentEngine effect
standardBalanced default feelNeutral scale (1×), cinematic easing
subtleQuiet, fast, short travelFaster duration, reduced travel & depth, soft easing
cinematicGrand, slow, deep, blurred entrancesSlower duration, larger travel & depth, entrance blur
stillAuthor-driven reduced motionResting/composed state, no movement (independent of OS setting)
+ +
<!-- One attribute governs the whole subtree -->
+<section vl-motion="cinematic">
+  <h2 vl-enter="reveal-cinematic" vl-timeline="view">Grand & deep</h2>
+  <p vl-effect="fade-up" vl-timeline="view">Slower, deeper, blurred entrance.</p>
+</section>
+
+<section vl-motion="subtle">...quieter, faster motion...</section>
+<section vl-motion="still">...reduced-motion-safe resting state...</section>
+ +

Engine Tokens

+

+ Modes work by assigning central --vl-* knobs (declared in 01-tokens.css). + Presets consume these instead of hardcoding per-selector values, which keeps adaptation in one place. +

+ + + + + + + + + + + + + +
TokenRoleNeutral default
--vl-motion-modeActive mode (queried by if(style()))standard
--vl-motion-speed-scaleDuration multiplier1
--vl-motion-intensityTravel-distance multiplier1
--vl-motion-depthParallax / z-depth multiplier1
--vl-motion-blurAdditive entrance blur0px
--vl-motion-preferenceReflects reduced-motion intentauto
--vl-motion-mode-easeEasing selected by the mode--vl-ease-cinematic
+

+ Because the defaults are neutral (scale 1, offset 0), motion behaves exactly + as before when no vl-motion mode is present. Explicit per-element modifiers such as + vl-speed and vl-depth still win over the mode. +

+ +

Progressive Enhancement with CSS if()

+

+ The engine is CSS-first progressive enhancement. There are two layers: +

+ +
@supports (width: if(style(--vl-probe: 1): 1px; else: 2px)) {
+  [vl-motion] {
+    --vl-motion-intensity: if(
+      media(prefers-reduced-motion: reduce): 0;
+      style(--vl-motion-mode: cinematic): if(media(pointer: coarse): 1.2; else: 1.6);
+      style(--vl-motion-mode: subtle): 0.45;
+      else: 1
+    );
+    --vl-motion-depth: if(
+      supports(animation-timeline: view()): 1.8;   /* only where scroll timelines exist */
+      else: 0
+    );
+  }
+}
+ +

Fallback for browsers without if()

+

+ If a browser does not support CSS if(), the entire @supports block is + ignored and the baseline [vl-motion="…"] values remain in effect. Modes still work — + they simply do not receive the extra live pointer/support-aware refinements. Nothing breaks, and no + JavaScript fallback is introduced. +

+ +

Accessibility

+

+ Reduced motion is always respected: +

+ + +

Browser Support

+

+ The vl-motion modes themselves work in all modern browsers. The if()-based + refinement requires a browser with CSS if() support; everywhere else the baseline modes + are used. See the live comparison on the + Zero-JS Motion showcase page. +

+ diff --git a/apps/showcase/pages/motion/zero-js-motion.html b/apps/showcase/pages/motion/zero-js-motion.html index 8996277..3e93cd2 100644 --- a/apps/showcase/pages/motion/zero-js-motion.html +++ b/apps/showcase/pages/motion/zero-js-motion.html @@ -332,6 +332,52 @@

+
+
+Conditional Motion Engine +
+

+ One attribute. Adaptive motion. +

+

+ The vl-motion attribute selects a motion mode that centrally re-scales Velora's shared engine tokens — timing, travel, depth, blur and easing — without touching a single line of JavaScript. Baseline modes work everywhere; CSS if() is layered on only as progressive enhancement. Scroll each panel into view to compare the same reveal under different modes. +

+
+
+ +
+vl-motion="cinematic" +

Grand & deep

+

Slower timing, longer travel, an entrance blur and deeper parallax. Ideal for hero moments and full-bleed storytelling.

+Replay reveal +
+ +
+vl-motion="subtle" +

Quiet & quick

+

Faster timing, short travel and soft easing. Ideal for dense dashboards and content-heavy interfaces where restraint matters.

+Replay reveal +
+ +
+vl-motion="still" +

Calm & static

+

Author-driven reduced motion: the composed resting state renders with no movement, independent of the OS setting. The engine also honors prefers-reduced-motion automatically for every mode.

+No motion +
+
+
<!-- Zero-JS conditional motion. One attribute governs the whole subtree. -->
+<section vl-motion="cinematic">
+  <h2 vl-enter="reveal-cinematic" vl-timeline="view">Grand & deep</h2>
+  <p vl-effect="fade-up" vl-timeline="view">Slower, deeper, blurred entrance.</p>
+</section>
+
+<section vl-motion="subtle">...quieter, faster motion...</section>
+<section vl-motion="still">...reduced-motion-safe resting state...</section>
+

Modes re-scale central --vl-motion-* tokens. CSS if() adds live media/pointer/support adaptation as progressive enhancement — never required.

+
diff --git a/apps/showcase/public/css/01-tokens.css b/apps/showcase/public/css/01-tokens.css index bedae58..74f0aca 100644 --- a/apps/showcase/public/css/01-tokens.css +++ b/apps/showcase/public/css/01-tokens.css @@ -151,6 +151,21 @@ --vl-motion-distance: 1.25rem; --vl-motion-ease: var(--vl-ease-cinematic); + /* --- Conditional Motion Engine (see 03a-motion-conditions.css) --- + * Central knobs consumed by the vl-motion="standard|subtle|cinematic|still" + * grammar. Presets read these instead of hardcoding per-selector values, so + * a single mode declaration adapts timing, travel, depth and blur globally. + * Defaults below are the neutral "standard" baseline (scale 1 / offset 0), + * so motion behaves identically when no vl-motion mode is applied. + */ + --vl-motion-mode: standard; /* active engine mode (queried via if(style())) */ + --vl-motion-preference: auto; /* auto | reduce — reflects reduced-motion intent */ + --vl-motion-speed-scale: 1; /* duration multiplier */ + --vl-motion-intensity: 1; /* travel-distance / displacement multiplier */ + --vl-motion-depth: 1; /* parallax / z-depth multiplier */ + --vl-motion-blur: 0px; /* additive entrance blur for cinematic mode */ + --vl-motion-mode-ease: var(--vl-ease-cinematic); /* easing selected by mode */ + /* View Transitions (MPA) — 05-transitions.css */ --vl-vt-root-duration: 0.55s; --vl-vt-shared-duration: 0.58s; diff --git a/apps/showcase/public/css/03-motion.css b/apps/showcase/public/css/03-motion.css index 32b64a1..04b60d4 100644 --- a/apps/showcase/public/css/03-motion.css +++ b/apps/showcase/public/css/03-motion.css @@ -273,7 +273,7 @@ @keyframes vl-flow-in { from { opacity: 0; - filter: blur(6px); + filter: blur(calc(6px + var(--vl-motion-blur, 0px))); transform: translate3d(0, var(--vl-motion-distance, 1.5rem), 0) scale(0.97); } to { @@ -506,10 +506,15 @@ Each preset sets tokenized variables per handbook ========================================================================= */ - /* --- Base: any element with vl-effect gets composable defaults --- */ + /* --- Base: any element with vl-effect gets composable defaults --- + * Duration and easing route through the Conditional Motion Engine knobs + * (--vl-motion-speed-scale / --vl-motion-mode-ease). Both default to the + * neutral baseline (scale 1 / cinematic), so behavior is unchanged unless a + * vl-motion mode is present on the element or an ancestor. See + * 03a-motion-conditions.css. */ [vl-effect] { - --vl-motion-duration: var(--vl-duration-slow); - --vl-motion-ease: var(--vl-ease-cinematic); + --vl-motion-duration: calc(var(--vl-duration-slow) * var(--vl-motion-speed-scale, 1)); + --vl-motion-ease: var(--vl-motion-mode-ease, var(--vl-ease-cinematic)); animation-fill-mode: both; } @@ -521,8 +526,8 @@ [vl-hover], [vl-state], [vl-exit] { - --vl-motion-duration: var(--vl-duration-slow); - --vl-motion-ease: var(--vl-ease-cinematic); + --vl-motion-duration: calc(var(--vl-duration-slow) * var(--vl-motion-speed-scale, 1)); + --vl-motion-ease: var(--vl-motion-mode-ease, var(--vl-ease-cinematic)); } [vl-enter] { @@ -2050,7 +2055,7 @@ from { opacity: 0; transform: perspective(1400px) translate3d(0, 3rem, -80px) scale(0.94); - filter: blur(8px) brightness(0.72); + filter: blur(calc(8px + var(--vl-motion-blur, 0px))) brightness(0.72); } to { opacity: 1; diff --git a/apps/showcase/public/css/03a-motion-conditions.css b/apps/showcase/public/css/03a-motion-conditions.css new file mode 100644 index 0000000..ae6bb0c --- /dev/null +++ b/apps/showcase/public/css/03a-motion-conditions.css @@ -0,0 +1,178 @@ +@layer velora.motion { + /* + * Velora — Conditional Motion Engine (v1) + * ========================================================================= + * Zero JavaScript. A single declarative attribute — vl-motion — selects a + * motion *mode* that centrally re-scales the shared engine knobs defined in + * 01-tokens.css. Existing presets already consume those knobs + * (--vl-motion-duration, --vl-motion-distance, --vl-motion-ease, ...), so + * one attribute adapts timing, travel, depth, blur and easing across the + * whole subtree without scattering hardcoded values into every selector. + * + * Public grammar: + * vl-motion="standard" Balanced, default cinematic feel. + * vl-motion="subtle" Quieter, faster, shorter travel. + * vl-motion="cinematic" Grander, slower, deeper, with entrance blur. + * vl-motion="still" Author-driven reduced motion — resting state, no movement. + * + * Progressive enhancement: + * - BASELINE (works everywhere): plain [vl-motion="…"] attribute selectors + * assign the engine knobs. No CSS if() required. + * - ENHANCEMENT (@supports if()): a single [vl-motion] rule re-derives the + * same knobs with CSS if() using media(), style() and supports() so the + * mode additionally adapts to prefers-reduced-motion, pointer type and + * scroll-timeline availability. If if() is unsupported the whole block is + * ignored and the baseline values above remain in effect. + * + * Accessibility: prefers-reduced-motion is honored here (knobs collapse) and + * globally in 03-motion.css (animations are fully disabled). + */ + + /* ========================================================================= + BASELINE — mode → engine knobs (no if() required) + ========================================================================= */ + [vl-motion="standard"] { + --vl-motion-mode: standard; + --vl-motion-speed-scale: 1; + --vl-motion-intensity: 1; + --vl-motion-depth: 1; + --vl-motion-blur: 0px; + --vl-motion-mode-ease: var(--vl-ease-cinematic); + --vl-motion-distance: calc(1.25rem * var(--vl-motion-intensity)); + } + + [vl-motion="subtle"] { + --vl-motion-mode: subtle; + --vl-motion-speed-scale: 0.62; + --vl-motion-intensity: 0.45; + --vl-motion-depth: 0.4; + --vl-motion-blur: 0px; + --vl-motion-mode-ease: var(--vl-ease-out-soft); + --vl-motion-distance: calc(1.25rem * var(--vl-motion-intensity)); + } + + [vl-motion="cinematic"] { + --vl-motion-mode: cinematic; + --vl-motion-speed-scale: 1.5; + --vl-motion-intensity: 1.6; + --vl-motion-depth: 1.8; + --vl-motion-blur: 6px; + --vl-motion-mode-ease: var(--vl-ease-cinematic); + --vl-motion-distance: calc(1.25rem * var(--vl-motion-intensity)); + } + + [vl-motion="still"] { + --vl-motion-mode: still; + --vl-motion-speed-scale: 1; + --vl-motion-intensity: 0; + --vl-motion-depth: 0; + --vl-motion-blur: 0px; + --vl-motion-mode-ease: var(--vl-ease-out-soft); + --vl-motion-distance: 0rem; + } + + /* + * "still" presents the composed resting state with no movement, regardless of + * the OS reduced-motion setting. The descendant combinator raises specificity + * above the (0,1,0) per-preset animation rules so nested effects settle without + * a runtime. Content stays visible (fill state) — only motion is removed. + */ + [vl-motion="still"], + [vl-motion="still"] :is([vl-effect], [vl-enter], [vl-exit], [vl-scroll], [vl-loop], [vl-loop-effect]), + [vl-motion="still"] :is([vl-children="stagger"], [vl-children="cascade"], [vl-children="sequence"], [vl-children="orchestrate"]) > * { + animation: none; + transition-duration: var(--vl-duration-instant); + opacity: 1; + transform: none; + filter: none; + } + + /* ========================================================================= + DEPTH ADAPTATION — scroll / parallax presets scale by --vl-motion-depth + Kept central: the depth multiplier flows into the existing parallax and + depth-drift custom properties rather than being redefined per page. + ========================================================================= */ + [vl-motion] :is([vl-scroll~="parallax"], [vl-effect~="parallax"]) { + --vl-parallax-from: calc(-12% * var(--vl-motion-depth, 1)); + --vl-parallax-to: calc(12% * var(--vl-motion-depth, 1)); + } + + [vl-motion] :is([vl-scroll~="depth-drift"], [vl-effect~="depth-drift"]) { + --vl-depth-from: calc(6% * var(--vl-motion-depth, 1)); + --vl-depth-to: calc(-6% * var(--vl-motion-depth, 1)); + } + + /* ========================================================================= + BASELINE device refinement — soften heavy depth on touch/coarse pointers + ========================================================================= */ + @media (pointer: coarse) { + [vl-motion="cinematic"] { + --vl-motion-depth: 1.15; + } + } + + /* ========================================================================= + ACCESSIBILITY — reduced motion collapses the engine knobs (baseline) + Global 03-motion.css additionally disables animations outright; here we + neutralize travel/depth/blur so even transition-based motion stays calm. + ========================================================================= */ + @media (prefers-reduced-motion: reduce) { + [vl-motion] { + --vl-motion-preference: reduce; + --vl-motion-speed-scale: 1; + --vl-motion-intensity: 0; + --vl-motion-depth: 0; + --vl-motion-blur: 0px; + --vl-motion-distance: 0rem; + } + } + + /* ========================================================================= + PROGRESSIVE ENHANCEMENT — CSS if() (Baseline 2025+) + Everything above is the guaranteed fallback. When if() is supported, one + rule re-derives the knobs, folding in live media()/style()/supports() + conditions. Same or later cascade position than the baseline attribute + selectors, so this wins where available and is dropped where it is not. + ========================================================================= */ + @supports (width: if(style(--vl-probe: 1): 1px; else: 2px)) { + [vl-motion] { + /* Duration multiplier: mode via style(), untouched under reduced motion. */ + --vl-motion-speed-scale: if( + media(prefers-reduced-motion: reduce): 1; + style(--vl-motion-mode: cinematic): 1.5; + style(--vl-motion-mode: subtle): 0.62; + else: 1 + ); + + /* Travel: reduced motion → 0; cinematic eases off on coarse pointers. */ + --vl-motion-intensity: if( + media(prefers-reduced-motion: reduce): 0; + style(--vl-motion-mode: still): 0; + style(--vl-motion-mode: cinematic): if(media(pointer: coarse): 1.2; else: 1.6); + style(--vl-motion-mode: subtle): 0.45; + else: 1 + ); + + /* Depth: only when scroll-driven timelines exist, else flat (0). */ + --vl-motion-depth: if( + media(prefers-reduced-motion: reduce): 0; + supports(animation-timeline: view()): if( + style(--vl-motion-mode: cinematic): if(media(pointer: coarse): 1.15; else: 1.8); + style(--vl-motion-mode: subtle): 0.4; + style(--vl-motion-mode: still): 0; + else: 1 + ); + else: 0 + ); + + /* Entrance blur: cinematic only, never under reduced motion. */ + --vl-motion-blur: if( + media(prefers-reduced-motion: reduce): 0px; + style(--vl-motion-mode: cinematic): 6px; + else: 0px + ); + + --vl-motion-distance: calc(1.25rem * var(--vl-motion-intensity)); + } + } +} diff --git a/apps/showcase/public/css/motion-core.css b/apps/showcase/public/css/motion-core.css index 2f56136..858adf8 100644 --- a/apps/showcase/public/css/motion-core.css +++ b/apps/showcase/public/css/motion-core.css @@ -5,3 +5,4 @@ @layer velora.reset, velora.tokens, velora.layout, velora.motion, velora.components, velora.transitions, velora.utilities, velora.overrides; @import "./03-motion.css"; +@import "./03a-motion-conditions.css"; diff --git a/apps/showcase/public/css/velora.css b/apps/showcase/public/css/velora.css index e66f73a..6f7ded6 100644 --- a/apps/showcase/public/css/velora.css +++ b/apps/showcase/public/css/velora.css @@ -10,6 +10,7 @@ @import "./02-layout.css"; @import "./03-motion.css"; @import "./03b-motion-extended.css"; +@import "./03a-motion-conditions.css"; @import "./04-components.css"; @import "./04a-forms.css"; @import "./04b-dialogs.css"; diff --git a/apps/showcase/scripts/validate-showcase-contract.mjs b/apps/showcase/scripts/validate-showcase-contract.mjs index 26c87bc..b56411f 100644 --- a/apps/showcase/scripts/validate-showcase-contract.mjs +++ b/apps/showcase/scripts/validate-showcase-contract.mjs @@ -1,44 +1,45 @@ -import { fileURLToPath } from "node:url"; -import { promises as fs } from "node:fs"; -import path from "node:path"; -import { - extractIds, - extractInPageAnchors, - extractVlAttributes, -} from "../../../packages/compiler/src/extract.mjs"; +import { fileURLToPath } from "node:url"; +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { + extractIds, + extractInPageAnchors, + extractVlAttributes, +} from "../../../packages/compiler/src/extract.mjs"; const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url)); -const ROOT = path.resolve(SCRIPT_DIR, ".."); -const INDEX_PATH = path.join(ROOT, "index.html"); -const PAGES_DIR = path.join(ROOT, "pages"); -const REPORT_PATH = path.join(ROOT, "output", "contract-checklist.md"); - -const HOME_ANCHOR_MOTION_ATTRS = new Set([ - "vl-effect", - "vl-enter", - "vl-exit", - "vl-scroll", - "vl-hover", - "vl-loop", - "vl-loop-effect", - "vl-timeline", - "vl-range", - "vl-duration", - "vl-speed", - "vl-direction", - "vl-children", - "vl-stagger", - "vl-pin", - "vl-scrub", - "vl-once", - "vl-state", -]); - -const HOME_SCENE_FRAME_CLASSES = new Set([ - "showcase-home-hero-root", - "showcase-home-display-grid", - "showcase-home-scenes-shell", -]); +const ROOT = path.resolve(SCRIPT_DIR, ".."); +const INDEX_PATH = path.join(ROOT, "index.html"); +const PAGES_DIR = path.join(ROOT, "pages"); +const REPORT_PATH = path.join(ROOT, "output", "contract-checklist.md"); + +const HOME_ANCHOR_MOTION_ATTRS = new Set([ + "vl-effect", + "vl-enter", + "vl-exit", + "vl-scroll", + "vl-hover", + "vl-loop", + "vl-loop-effect", + "vl-timeline", + "vl-range", + "vl-duration", + "vl-speed", + "vl-motion", + "vl-direction", + "vl-children", + "vl-stagger", + "vl-pin", + "vl-scrub", + "vl-once", + "vl-state", +]); + +const HOME_SCENE_FRAME_CLASSES = new Set([ + "showcase-home-hero-root", + "showcase-home-display-grid", + "showcase-home-scenes-shell", +]); const ALLOWED_VL_ATTRS = new Set([ "vl-effect", @@ -49,6 +50,7 @@ const ALLOWED_VL_ATTRS = new Set([ "vl-range", "vl-duration", "vl-speed", + "vl-motion", "vl-direction", "vl-loop", "vl-loop-effect", @@ -85,7 +87,7 @@ function rel(filePath) { return path.relative(ROOT, filePath).replaceAll(path.sep, "/"); } -async function listHtmlFiles(dir) { +async function listHtmlFiles(dir) { const entries = await fs.readdir(dir, { withFileTypes: true }); const out = []; for (const entry of entries) { @@ -98,123 +100,123 @@ async function listHtmlFiles(dir) { out.push(full); } } - return out; -} - -async function collectHtmlFiles() { - const files = []; - - try { - await fs.access(INDEX_PATH); - files.push(INDEX_PATH); - } catch { - // Optional for template-only runs. - } - - files.push(...(await listHtmlFiles(PAGES_DIR))); - return files; -} - -function formatList(values) { - return values.length ? values.map((v) => `\`${v}\``).join(", ") : "none"; -} - -function lineNumberForIndex(content, index) { - return content.slice(0, index).split("\n").length; -} - -function readAttrValue(tag, attrName) { - const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const attrRe = new RegExp(`\\b${escaped}\\s*=\\s*(?:"([^"]+)"|'([^']+)'|([^\\s"'=<>` + "`" + `]+))`, "i"); - const match = attrRe.exec(tag); - return match ? (match[1] ?? match[2] ?? match[3]) : ""; -} - -function hasAnyClass(className, classSet) { - return className.split(/\s+/).some((token) => classSet.has(token)); -} - -function findHomeAnchorMotionAttrs(content) { - const violations = []; - const sectionRe = /]*\bid\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s"'=<>`]+))[^>]*>/gi; - let sectionMatch; - - while ((sectionMatch = sectionRe.exec(content))) { - const tag = sectionMatch[0]; - const id = sectionMatch[1] ?? sectionMatch[2] ?? sectionMatch[3]; - const attrs = extractVlAttributes(tag) - .filter((attr) => HOME_ANCHOR_MOTION_ATTRS.has(attr.name)) - .map((attr) => attr.name); - - if (!attrs.length) continue; - - violations.push({ - id, - line: lineNumberForIndex(content, sectionMatch.index), - attrs: [...new Set(attrs)], - className: readAttrValue(tag, "class"), - }); - } - - return violations; -} - -function findHomeSceneFrameMotionAttrs(content) { - const violations = []; - const tagRe = /<([a-zA-Z][a-zA-Z0-9-]*)(\s[^>]*)?\/?>/gs; - let tagMatch; - - while ((tagMatch = tagRe.exec(content))) { - const tag = tagMatch[0]; - const className = readAttrValue(tag, "class"); - - if (!className || !hasAnyClass(className, HOME_SCENE_FRAME_CLASSES)) continue; - - const attrs = extractVlAttributes(tag) - .filter((attr) => HOME_ANCHOR_MOTION_ATTRS.has(attr.name)) - .map((attr) => attr.name); - - if (!attrs.length) continue; - - violations.push({ - tag: tagMatch[1], - className, - line: lineNumberForIndex(content, tagMatch.index), - attrs: [...new Set(attrs)], - }); - } - - return violations; -} - -async function writeReport(results, totals) { - const lines = []; - lines.push("# Showcase Contract Checklist"); - lines.push(""); - lines.push(`- Pages scanned: **${totals.pages}**`); - lines.push(`- Unknown vl-* attrs: **${totals.unknownCount}**`); - lines.push(`- Deprecated vl-* attrs: **${totals.deprecatedCount}**`); - lines.push(`- Broken #anchors: **${totals.anchorCount}**`); - lines.push(`- Home anchor motion violations: **${totals.homeAnchorMotionCount}**`); - lines.push(`- Home scene frame motion violations: **${totals.homeSceneFrameMotionCount}**`); - lines.push(""); - lines.push("## Per-page status"); - lines.push(""); - - for (const page of results) { - const status = - page.unknown.length || page.deprecated.length || page.brokenAnchors.length || page.homeAnchorMotion.length - || page.homeSceneFrameMotion.length - ? "FAIL" - : "OK"; - lines.push(`### ${status} - \`${page.file}\``); - lines.push(`- Unknown vl-* attrs: ${formatList(page.unknown)}`); - lines.push(`- Deprecated vl-* attrs: ${formatList(page.deprecated.map((d) => `${d.attr} (${d.reason})`))}`); - lines.push(`- Broken #anchors: ${formatList(page.brokenAnchors.map((a) => `#${a}`))}`); - lines.push(`- Home anchor motion: ${formatList(page.homeAnchorMotion.map((v) => `#${v.id} line ${v.line}: ${v.attrs.join(", ")}`))}`); - lines.push(`- Home scene frame motion: ${formatList(page.homeSceneFrameMotion.map((v) => `${v.className} line ${v.line}: ${v.attrs.join(", ")}`))}`); - lines.push(""); - } + return out; +} + +async function collectHtmlFiles() { + const files = []; + + try { + await fs.access(INDEX_PATH); + files.push(INDEX_PATH); + } catch { + // Optional for template-only runs. + } + + files.push(...(await listHtmlFiles(PAGES_DIR))); + return files; +} + +function formatList(values) { + return values.length ? values.map((v) => `\`${v}\``).join(", ") : "none"; +} + +function lineNumberForIndex(content, index) { + return content.slice(0, index).split("\n").length; +} + +function readAttrValue(tag, attrName) { + const escaped = attrName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const attrRe = new RegExp(`\\b${escaped}\\s*=\\s*(?:"([^"]+)"|'([^']+)'|([^\\s"'=<>` + "`" + `]+))`, "i"); + const match = attrRe.exec(tag); + return match ? (match[1] ?? match[2] ?? match[3]) : ""; +} + +function hasAnyClass(className, classSet) { + return className.split(/\s+/).some((token) => classSet.has(token)); +} + +function findHomeAnchorMotionAttrs(content) { + const violations = []; + const sectionRe = /]*\bid\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s"'=<>`]+))[^>]*>/gi; + let sectionMatch; + + while ((sectionMatch = sectionRe.exec(content))) { + const tag = sectionMatch[0]; + const id = sectionMatch[1] ?? sectionMatch[2] ?? sectionMatch[3]; + const attrs = extractVlAttributes(tag) + .filter((attr) => HOME_ANCHOR_MOTION_ATTRS.has(attr.name)) + .map((attr) => attr.name); + + if (!attrs.length) continue; + + violations.push({ + id, + line: lineNumberForIndex(content, sectionMatch.index), + attrs: [...new Set(attrs)], + className: readAttrValue(tag, "class"), + }); + } + + return violations; +} + +function findHomeSceneFrameMotionAttrs(content) { + const violations = []; + const tagRe = /<([a-zA-Z][a-zA-Z0-9-]*)(\s[^>]*)?\/?>/gs; + let tagMatch; + + while ((tagMatch = tagRe.exec(content))) { + const tag = tagMatch[0]; + const className = readAttrValue(tag, "class"); + + if (!className || !hasAnyClass(className, HOME_SCENE_FRAME_CLASSES)) continue; + + const attrs = extractVlAttributes(tag) + .filter((attr) => HOME_ANCHOR_MOTION_ATTRS.has(attr.name)) + .map((attr) => attr.name); + + if (!attrs.length) continue; + + violations.push({ + tag: tagMatch[1], + className, + line: lineNumberForIndex(content, tagMatch.index), + attrs: [...new Set(attrs)], + }); + } + + return violations; +} + +async function writeReport(results, totals) { + const lines = []; + lines.push("# Showcase Contract Checklist"); + lines.push(""); + lines.push(`- Pages scanned: **${totals.pages}**`); + lines.push(`- Unknown vl-* attrs: **${totals.unknownCount}**`); + lines.push(`- Deprecated vl-* attrs: **${totals.deprecatedCount}**`); + lines.push(`- Broken #anchors: **${totals.anchorCount}**`); + lines.push(`- Home anchor motion violations: **${totals.homeAnchorMotionCount}**`); + lines.push(`- Home scene frame motion violations: **${totals.homeSceneFrameMotionCount}**`); + lines.push(""); + lines.push("## Per-page status"); + lines.push(""); + + for (const page of results) { + const status = + page.unknown.length || page.deprecated.length || page.brokenAnchors.length || page.homeAnchorMotion.length + || page.homeSceneFrameMotion.length + ? "FAIL" + : "OK"; + lines.push(`### ${status} - \`${page.file}\``); + lines.push(`- Unknown vl-* attrs: ${formatList(page.unknown)}`); + lines.push(`- Deprecated vl-* attrs: ${formatList(page.deprecated.map((d) => `${d.attr} (${d.reason})`))}`); + lines.push(`- Broken #anchors: ${formatList(page.brokenAnchors.map((a) => `#${a}`))}`); + lines.push(`- Home anchor motion: ${formatList(page.homeAnchorMotion.map((v) => `#${v.id} line ${v.line}: ${v.attrs.join(", ")}`))}`); + lines.push(`- Home scene frame motion: ${formatList(page.homeSceneFrameMotion.map((v) => `${v.className} line ${v.line}: ${v.attrs.join(", ")}`))}`); + lines.push(""); + } await fs.mkdir(path.dirname(REPORT_PATH), { recursive: true }); await fs.writeFile(REPORT_PATH, `${lines.join("\n")}\n`, "utf8"); @@ -222,62 +224,62 @@ async function writeReport(results, totals) { async function main() { const shouldWriteReport = process.argv.includes("--write-report"); - const htmlFiles = await collectHtmlFiles(); + const htmlFiles = await collectHtmlFiles(); const results = []; const totals = { - pages: htmlFiles.length, - unknownCount: 0, - deprecatedCount: 0, - anchorCount: 0, - homeAnchorMotionCount: 0, - homeSceneFrameMotionCount: 0, - }; - - for (const filePath of htmlFiles) { - const content = await fs.readFile(filePath, "utf8"); - const attrs = [...new Set(extractVlAttributes(content).map((attr) => attr.name))]; - const ids = new Set(extractIds(content)); - const anchors = extractInPageAnchors(content); + pages: htmlFiles.length, + unknownCount: 0, + deprecatedCount: 0, + anchorCount: 0, + homeAnchorMotionCount: 0, + homeSceneFrameMotionCount: 0, + }; + + for (const filePath of htmlFiles) { + const content = await fs.readFile(filePath, "utf8"); + const attrs = [...new Set(extractVlAttributes(content).map((attr) => attr.name))]; + const ids = new Set(extractIds(content)); + const anchors = extractInPageAnchors(content); const unknown = attrs.filter((a) => !ALLOWED_VL_ATTRS.has(a) && !DEPRECATED_VL_ATTRS.has(a)); - const deprecated = attrs - .filter((a) => DEPRECATED_VL_ATTRS.has(a)) - .map((attr) => ({ attr, reason: DEPRECATED_VL_ATTRS.get(attr) })); - const brokenAnchors = [...new Set(anchors.filter((a) => !ids.has(a)))]; - const homeAnchorMotion = path.resolve(filePath) === INDEX_PATH ? findHomeAnchorMotionAttrs(content) : []; - const homeSceneFrameMotion = path.resolve(filePath) === INDEX_PATH ? findHomeSceneFrameMotionAttrs(content) : []; - - totals.unknownCount += unknown.length; - totals.deprecatedCount += deprecated.length; - totals.anchorCount += brokenAnchors.length; - totals.homeAnchorMotionCount += homeAnchorMotion.length; - totals.homeSceneFrameMotionCount += homeSceneFrameMotion.length; - - results.push({ - file: rel(filePath), - unknown, - deprecated, - brokenAnchors, - homeAnchorMotion, - homeSceneFrameMotion, - }); - } + const deprecated = attrs + .filter((a) => DEPRECATED_VL_ATTRS.has(a)) + .map((attr) => ({ attr, reason: DEPRECATED_VL_ATTRS.get(attr) })); + const brokenAnchors = [...new Set(anchors.filter((a) => !ids.has(a)))]; + const homeAnchorMotion = path.resolve(filePath) === INDEX_PATH ? findHomeAnchorMotionAttrs(content) : []; + const homeSceneFrameMotion = path.resolve(filePath) === INDEX_PATH ? findHomeSceneFrameMotionAttrs(content) : []; + + totals.unknownCount += unknown.length; + totals.deprecatedCount += deprecated.length; + totals.anchorCount += brokenAnchors.length; + totals.homeAnchorMotionCount += homeAnchorMotion.length; + totals.homeSceneFrameMotionCount += homeSceneFrameMotion.length; + + results.push({ + file: rel(filePath), + unknown, + deprecated, + brokenAnchors, + homeAnchorMotion, + homeSceneFrameMotion, + }); + } if (shouldWriteReport) { await writeReport(results, totals); } - if ( - totals.unknownCount - || totals.deprecatedCount - || totals.anchorCount - || totals.homeAnchorMotionCount - || totals.homeSceneFrameMotionCount - ) { - console.error( - `Contract check failed: unknown=${totals.unknownCount}, deprecated=${totals.deprecatedCount}, brokenAnchors=${totals.anchorCount}, homeAnchorMotion=${totals.homeAnchorMotionCount}, homeSceneFrameMotion=${totals.homeSceneFrameMotionCount}`, - ); + if ( + totals.unknownCount + || totals.deprecatedCount + || totals.anchorCount + || totals.homeAnchorMotionCount + || totals.homeSceneFrameMotionCount + ) { + console.error( + `Contract check failed: unknown=${totals.unknownCount}, deprecated=${totals.deprecatedCount}, brokenAnchors=${totals.anchorCount}, homeAnchorMotion=${totals.homeAnchorMotionCount}, homeSceneFrameMotion=${totals.homeSceneFrameMotionCount}`, + ); process.exitCode = 1; return; } diff --git a/docs/project/CONTRACT.md b/docs/project/CONTRACT.md index 89c2f59..78dbc11 100644 --- a/docs/project/CONTRACT.md +++ b/docs/project/CONTRACT.md @@ -23,6 +23,7 @@ This is the operational contract for Motion API, Design API, and showcase page s - `vl-range` - `vl-duration` - `vl-speed` +- `vl-motion` - `vl-direction` - `vl-loop` - `vl-loop-effect` @@ -61,6 +62,32 @@ This is the operational contract for Motion API, Design API, and showcase page s - `vl-loop` / `vl-loop-effect`: `aurora-drift` - `vl-hover`: `gradient-sweep`, `border-trace` - `vl-scene`: `cinematic-hero`, `sticky-story`, `glass-bento`, `product-reveal`, `editorial-cinema` +- `vl-motion`: `standard`, `subtle`, `cinematic`, `still` + +### 2.3.1 Conditional Motion Engine — `vl-motion` + +`vl-motion` selects a motion *mode* for an element and its subtree. It does not +name a preset; instead it re-scales the central engine knobs (defined in +`01-tokens.css`) that existing presets already consume, so one attribute adapts +timing, travel distance, depth, blur and easing without per-selector overrides. +Implemented in `packages/css/src/03a-motion-conditions.css`. + +| Value | Intent | Engine effect | +| ----- | ------ | ------------- | +| `standard` | Default balanced feel | Neutral scale (1×), cinematic easing | +| `subtle` | Quiet, fast, short travel | Faster duration, reduced travel/depth, soft easing | +| `cinematic` | Grand, slow, deep, blurred entrances | Slower duration, larger travel/depth, entrance blur | +| `still` | Author-driven reduced motion | Resting/composed state, no movement (regardless of OS setting) | + +Progressive enhancement contract: + +- Baseline behavior uses plain `[vl-motion="…"]` attribute selectors and works in + every browser. +- Advanced conditional refinement uses CSS `if()` with `media()`, `style()` and + `supports()`, wrapped in `@supports (width: if(...))`. Browsers without `if()` + ignore that block and keep the baseline values. +- `prefers-reduced-motion: reduce` is always honored (engine knobs collapse here + and animations are disabled globally in `03-motion.css`). ### 2.4 Authoring profile (v2 target: practical and flexible) diff --git a/packages/compiler/src/grammar.mjs b/packages/compiler/src/grammar.mjs index 9530a4d..ef3fa75 100644 --- a/packages/compiler/src/grammar.mjs +++ b/packages/compiler/src/grammar.mjs @@ -7,6 +7,7 @@ export const ALLOWED_VL_ATTRS = new Set([ "vl-range", "vl-duration", "vl-speed", + "vl-motion", "vl-direction", "vl-loop", "vl-loop-effect", @@ -181,6 +182,7 @@ export const VALUE_RULES = { "vl-duration": /^(?:\d*\.?\d+m?s|var\(--[a-z0-9-]+\))$/i, "vl-stagger": /^(?:\d*\.?\d+m?s|var\(--[a-z0-9-]+\))$/i, "vl-speed": /^(slow|normal|fast|cinema|slower|fastest|turbo|ultra-slow)$/, + "vl-motion": /^(standard|subtle|cinematic|still)$/, "vl-direction": /^(normal|reverse|alternate|alternate-reverse)$/, "vl-loop": /^(?:-1|infinite|\d+)$/, }; diff --git a/packages/css/src/01-tokens.css b/packages/css/src/01-tokens.css index bedae58..74f0aca 100644 --- a/packages/css/src/01-tokens.css +++ b/packages/css/src/01-tokens.css @@ -151,6 +151,21 @@ --vl-motion-distance: 1.25rem; --vl-motion-ease: var(--vl-ease-cinematic); + /* --- Conditional Motion Engine (see 03a-motion-conditions.css) --- + * Central knobs consumed by the vl-motion="standard|subtle|cinematic|still" + * grammar. Presets read these instead of hardcoding per-selector values, so + * a single mode declaration adapts timing, travel, depth and blur globally. + * Defaults below are the neutral "standard" baseline (scale 1 / offset 0), + * so motion behaves identically when no vl-motion mode is applied. + */ + --vl-motion-mode: standard; /* active engine mode (queried via if(style())) */ + --vl-motion-preference: auto; /* auto | reduce — reflects reduced-motion intent */ + --vl-motion-speed-scale: 1; /* duration multiplier */ + --vl-motion-intensity: 1; /* travel-distance / displacement multiplier */ + --vl-motion-depth: 1; /* parallax / z-depth multiplier */ + --vl-motion-blur: 0px; /* additive entrance blur for cinematic mode */ + --vl-motion-mode-ease: var(--vl-ease-cinematic); /* easing selected by mode */ + /* View Transitions (MPA) — 05-transitions.css */ --vl-vt-root-duration: 0.55s; --vl-vt-shared-duration: 0.58s; diff --git a/packages/css/src/03-motion.css b/packages/css/src/03-motion.css index 32b64a1..04b60d4 100644 --- a/packages/css/src/03-motion.css +++ b/packages/css/src/03-motion.css @@ -273,7 +273,7 @@ @keyframes vl-flow-in { from { opacity: 0; - filter: blur(6px); + filter: blur(calc(6px + var(--vl-motion-blur, 0px))); transform: translate3d(0, var(--vl-motion-distance, 1.5rem), 0) scale(0.97); } to { @@ -506,10 +506,15 @@ Each preset sets tokenized variables per handbook ========================================================================= */ - /* --- Base: any element with vl-effect gets composable defaults --- */ + /* --- Base: any element with vl-effect gets composable defaults --- + * Duration and easing route through the Conditional Motion Engine knobs + * (--vl-motion-speed-scale / --vl-motion-mode-ease). Both default to the + * neutral baseline (scale 1 / cinematic), so behavior is unchanged unless a + * vl-motion mode is present on the element or an ancestor. See + * 03a-motion-conditions.css. */ [vl-effect] { - --vl-motion-duration: var(--vl-duration-slow); - --vl-motion-ease: var(--vl-ease-cinematic); + --vl-motion-duration: calc(var(--vl-duration-slow) * var(--vl-motion-speed-scale, 1)); + --vl-motion-ease: var(--vl-motion-mode-ease, var(--vl-ease-cinematic)); animation-fill-mode: both; } @@ -521,8 +526,8 @@ [vl-hover], [vl-state], [vl-exit] { - --vl-motion-duration: var(--vl-duration-slow); - --vl-motion-ease: var(--vl-ease-cinematic); + --vl-motion-duration: calc(var(--vl-duration-slow) * var(--vl-motion-speed-scale, 1)); + --vl-motion-ease: var(--vl-motion-mode-ease, var(--vl-ease-cinematic)); } [vl-enter] { @@ -2050,7 +2055,7 @@ from { opacity: 0; transform: perspective(1400px) translate3d(0, 3rem, -80px) scale(0.94); - filter: blur(8px) brightness(0.72); + filter: blur(calc(8px + var(--vl-motion-blur, 0px))) brightness(0.72); } to { opacity: 1; diff --git a/packages/css/src/03a-motion-conditions.css b/packages/css/src/03a-motion-conditions.css new file mode 100644 index 0000000..ae6bb0c --- /dev/null +++ b/packages/css/src/03a-motion-conditions.css @@ -0,0 +1,178 @@ +@layer velora.motion { + /* + * Velora — Conditional Motion Engine (v1) + * ========================================================================= + * Zero JavaScript. A single declarative attribute — vl-motion — selects a + * motion *mode* that centrally re-scales the shared engine knobs defined in + * 01-tokens.css. Existing presets already consume those knobs + * (--vl-motion-duration, --vl-motion-distance, --vl-motion-ease, ...), so + * one attribute adapts timing, travel, depth, blur and easing across the + * whole subtree without scattering hardcoded values into every selector. + * + * Public grammar: + * vl-motion="standard" Balanced, default cinematic feel. + * vl-motion="subtle" Quieter, faster, shorter travel. + * vl-motion="cinematic" Grander, slower, deeper, with entrance blur. + * vl-motion="still" Author-driven reduced motion — resting state, no movement. + * + * Progressive enhancement: + * - BASELINE (works everywhere): plain [vl-motion="…"] attribute selectors + * assign the engine knobs. No CSS if() required. + * - ENHANCEMENT (@supports if()): a single [vl-motion] rule re-derives the + * same knobs with CSS if() using media(), style() and supports() so the + * mode additionally adapts to prefers-reduced-motion, pointer type and + * scroll-timeline availability. If if() is unsupported the whole block is + * ignored and the baseline values above remain in effect. + * + * Accessibility: prefers-reduced-motion is honored here (knobs collapse) and + * globally in 03-motion.css (animations are fully disabled). + */ + + /* ========================================================================= + BASELINE — mode → engine knobs (no if() required) + ========================================================================= */ + [vl-motion="standard"] { + --vl-motion-mode: standard; + --vl-motion-speed-scale: 1; + --vl-motion-intensity: 1; + --vl-motion-depth: 1; + --vl-motion-blur: 0px; + --vl-motion-mode-ease: var(--vl-ease-cinematic); + --vl-motion-distance: calc(1.25rem * var(--vl-motion-intensity)); + } + + [vl-motion="subtle"] { + --vl-motion-mode: subtle; + --vl-motion-speed-scale: 0.62; + --vl-motion-intensity: 0.45; + --vl-motion-depth: 0.4; + --vl-motion-blur: 0px; + --vl-motion-mode-ease: var(--vl-ease-out-soft); + --vl-motion-distance: calc(1.25rem * var(--vl-motion-intensity)); + } + + [vl-motion="cinematic"] { + --vl-motion-mode: cinematic; + --vl-motion-speed-scale: 1.5; + --vl-motion-intensity: 1.6; + --vl-motion-depth: 1.8; + --vl-motion-blur: 6px; + --vl-motion-mode-ease: var(--vl-ease-cinematic); + --vl-motion-distance: calc(1.25rem * var(--vl-motion-intensity)); + } + + [vl-motion="still"] { + --vl-motion-mode: still; + --vl-motion-speed-scale: 1; + --vl-motion-intensity: 0; + --vl-motion-depth: 0; + --vl-motion-blur: 0px; + --vl-motion-mode-ease: var(--vl-ease-out-soft); + --vl-motion-distance: 0rem; + } + + /* + * "still" presents the composed resting state with no movement, regardless of + * the OS reduced-motion setting. The descendant combinator raises specificity + * above the (0,1,0) per-preset animation rules so nested effects settle without + * a runtime. Content stays visible (fill state) — only motion is removed. + */ + [vl-motion="still"], + [vl-motion="still"] :is([vl-effect], [vl-enter], [vl-exit], [vl-scroll], [vl-loop], [vl-loop-effect]), + [vl-motion="still"] :is([vl-children="stagger"], [vl-children="cascade"], [vl-children="sequence"], [vl-children="orchestrate"]) > * { + animation: none; + transition-duration: var(--vl-duration-instant); + opacity: 1; + transform: none; + filter: none; + } + + /* ========================================================================= + DEPTH ADAPTATION — scroll / parallax presets scale by --vl-motion-depth + Kept central: the depth multiplier flows into the existing parallax and + depth-drift custom properties rather than being redefined per page. + ========================================================================= */ + [vl-motion] :is([vl-scroll~="parallax"], [vl-effect~="parallax"]) { + --vl-parallax-from: calc(-12% * var(--vl-motion-depth, 1)); + --vl-parallax-to: calc(12% * var(--vl-motion-depth, 1)); + } + + [vl-motion] :is([vl-scroll~="depth-drift"], [vl-effect~="depth-drift"]) { + --vl-depth-from: calc(6% * var(--vl-motion-depth, 1)); + --vl-depth-to: calc(-6% * var(--vl-motion-depth, 1)); + } + + /* ========================================================================= + BASELINE device refinement — soften heavy depth on touch/coarse pointers + ========================================================================= */ + @media (pointer: coarse) { + [vl-motion="cinematic"] { + --vl-motion-depth: 1.15; + } + } + + /* ========================================================================= + ACCESSIBILITY — reduced motion collapses the engine knobs (baseline) + Global 03-motion.css additionally disables animations outright; here we + neutralize travel/depth/blur so even transition-based motion stays calm. + ========================================================================= */ + @media (prefers-reduced-motion: reduce) { + [vl-motion] { + --vl-motion-preference: reduce; + --vl-motion-speed-scale: 1; + --vl-motion-intensity: 0; + --vl-motion-depth: 0; + --vl-motion-blur: 0px; + --vl-motion-distance: 0rem; + } + } + + /* ========================================================================= + PROGRESSIVE ENHANCEMENT — CSS if() (Baseline 2025+) + Everything above is the guaranteed fallback. When if() is supported, one + rule re-derives the knobs, folding in live media()/style()/supports() + conditions. Same or later cascade position than the baseline attribute + selectors, so this wins where available and is dropped where it is not. + ========================================================================= */ + @supports (width: if(style(--vl-probe: 1): 1px; else: 2px)) { + [vl-motion] { + /* Duration multiplier: mode via style(), untouched under reduced motion. */ + --vl-motion-speed-scale: if( + media(prefers-reduced-motion: reduce): 1; + style(--vl-motion-mode: cinematic): 1.5; + style(--vl-motion-mode: subtle): 0.62; + else: 1 + ); + + /* Travel: reduced motion → 0; cinematic eases off on coarse pointers. */ + --vl-motion-intensity: if( + media(prefers-reduced-motion: reduce): 0; + style(--vl-motion-mode: still): 0; + style(--vl-motion-mode: cinematic): if(media(pointer: coarse): 1.2; else: 1.6); + style(--vl-motion-mode: subtle): 0.45; + else: 1 + ); + + /* Depth: only when scroll-driven timelines exist, else flat (0). */ + --vl-motion-depth: if( + media(prefers-reduced-motion: reduce): 0; + supports(animation-timeline: view()): if( + style(--vl-motion-mode: cinematic): if(media(pointer: coarse): 1.15; else: 1.8); + style(--vl-motion-mode: subtle): 0.4; + style(--vl-motion-mode: still): 0; + else: 1 + ); + else: 0 + ); + + /* Entrance blur: cinematic only, never under reduced motion. */ + --vl-motion-blur: if( + media(prefers-reduced-motion: reduce): 0px; + style(--vl-motion-mode: cinematic): 6px; + else: 0px + ); + + --vl-motion-distance: calc(1.25rem * var(--vl-motion-intensity)); + } + } +} diff --git a/packages/css/src/motion-core.css b/packages/css/src/motion-core.css index 2f56136..858adf8 100644 --- a/packages/css/src/motion-core.css +++ b/packages/css/src/motion-core.css @@ -5,3 +5,4 @@ @layer velora.reset, velora.tokens, velora.layout, velora.motion, velora.components, velora.transitions, velora.utilities, velora.overrides; @import "./03-motion.css"; +@import "./03a-motion-conditions.css"; diff --git a/packages/css/src/velora.css b/packages/css/src/velora.css index e66f73a..6f7ded6 100644 --- a/packages/css/src/velora.css +++ b/packages/css/src/velora.css @@ -10,6 +10,7 @@ @import "./02-layout.css"; @import "./03-motion.css"; @import "./03b-motion-extended.css"; +@import "./03a-motion-conditions.css"; @import "./04-components.css"; @import "./04a-forms.css"; @import "./04b-dialogs.css"; From 32d0bfa0036e39c43f7437bf7e82f4e90d4785d2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 21 Aug 2026 11:33:06 +0000 Subject: [PATCH 2/3] fix(showcase): use vl-range=entry so conditional-motion demos complete near page bottom Co-authored-by: Erik Assis Cardoso --- apps/showcase/pages/motion/zero-js-motion.html | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/showcase/pages/motion/zero-js-motion.html b/apps/showcase/pages/motion/zero-js-motion.html index 3e93cd2..8f4f02c 100644 --- a/apps/showcase/pages/motion/zero-js-motion.html +++ b/apps/showcase/pages/motion/zero-js-motion.html @@ -349,23 +349,23 @@

vl-motion="cinematic" -

Grand & deep

-

Slower timing, longer travel, an entrance blur and deeper parallax. Ideal for hero moments and full-bleed storytelling.

-Replay reveal +

Grand & deep

+

Slower timing, longer travel, an entrance blur and deeper parallax. Ideal for hero moments and full-bleed storytelling.

+Replay reveal
vl-motion="subtle" -

Quiet & quick

-

Faster timing, short travel and soft easing. Ideal for dense dashboards and content-heavy interfaces where restraint matters.

-Replay reveal +

Quiet & quick

+

Faster timing, short travel and soft easing. Ideal for dense dashboards and content-heavy interfaces where restraint matters.

+Replay reveal
vl-motion="still" -

Calm & static

-

Author-driven reduced motion: the composed resting state renders with no movement, independent of the OS setting. The engine also honors prefers-reduced-motion automatically for every mode.

-No motion +

Calm & static

+

Author-driven reduced motion: the composed resting state renders with no movement, independent of the OS setting. The engine also honors prefers-reduced-motion automatically for every mode.

+No motion
<!-- Zero-JS conditional motion. One attribute governs the whole subtree. -->

From 8ca60587b7f5b3b43120635ae3b8e361166cf2b3 Mon Sep 17 00:00:00 2001
From: Erik Cardoso 
Date: Mon, 24 Aug 2026 17:11:00 -0300
Subject: [PATCH 3/3] chore: update documentation and showcase structure

- Refined the public Showcase to include only six routes: Home, Timeline, Skins, Catalog, Hosts, and Archive.
- Updated README to clarify Velora as a declarative CSS motion engine and the role of Skins as optional design-system models.
- Enhanced documentation for scene management, timelines, and modular entry points.
- Removed outdated pages and streamlined navigation in the showcase.
- Adjusted the contract checklist to reflect the new structure and ensure compliance with motion standards.
---
 .../hooks/state/continual-learning-index.json |   93 +
 .cursor/hooks/state/continual-learning.json   |    8 +
 AGENTS.md                                     |   25 +
 CHANGELOG.md                                  |    7 +
 README.md                                     |   43 +-
 apps/docs/src/layouts/Base.astro              |   28 +-
 apps/docs/src/pages/hosts.astro               |   43 +
 apps/docs/src/pages/index.astro               |  166 +-
 apps/docs/src/pages/scenes.astro              |  254 +-
 apps/docs/src/pages/timelines.astro           |   82 +-
 apps/showcase/config/template-registry.mjs    |   74 +-
 apps/showcase/index.html                      |  238 +-
 apps/showcase/output/contract-checklist.md    |  239 +-
 apps/showcase/pages/core/archive.html         |  258 +
 apps/showcase/pages/core/hosts.html           |  252 +
 apps/showcase/pages/core/skins.html           |  274 +
 .../pages/motion/api-motion-catalog.html      |  572 +-
 .../showcase/pages/scenes/scene-timeline.html |  311 +
 apps/showcase/public/css/01-motion-tokens.css |   52 +
 apps/showcase/public/css/01-tokens.css        |  425 +-
 apps/showcase/public/css/01-visual-tokens.css |  335 +
 apps/showcase/public/css/02-layout.css        |   86 +-
 apps/showcase/public/css/03-motion.css        |   97 +-
 apps/showcase/public/css/03c-scene-engine.css |  159 +
 apps/showcase/public/css/base.css             |    4 +-
 apps/showcase/public/css/motion-core.css      |    5 +-
 apps/showcase/public/css/scene-recipes.css    |  197 +
 .../css/showcase-api-motion-catalog.css       | 4904 ++++++++------
 .../showcase/public/css/showcase-tw-demos.css |    2 +-
 apps/showcase/public/css/theme.css            |   10 +
 apps/showcase/public/css/velora.css           |    6 +-
 apps/showcase/public/img/catalog-glyphs.svg   |   37 +
 .../public/js/showcase-api-motion-catalog.js  |  518 ++
 apps/showcase/scripts/apply-lean-nav.mjs      |   83 +
 .../scripts/check-framework-css-sync.mjs      |   21 +
 apps/showcase/scripts/fill-stub-hub-pages.mjs |    5 +-
 apps/showcase/scripts/sync-framework-css.mjs  |   24 +
 .../scripts/validate-showcase-contract.mjs    |    3 +
 apps/showcase/templates/default/README.md     |    2 +-
 archive/showcase-2026-08/MANIFEST.md          |   56 +
 archive/showcase-2026-08/README.md            |   22 +
 .../config/template-registry.mjs.bak          |  114 +
 .../css}/04e-showcase-elevation-patterns.css  |    0
 .../css}/08-showcase-home.css                 |    0
 .../css/showcase-api-motion-catalog.css       | 2235 ++++++
 .../showcase-2026-08/css/showcase-home.css    | 4519 +++++++++++++
 archive/showcase-2026-08/css/showcase-hub.css | 1114 +++
 .../css/showcase-reference-pages.css          | 6016 +++++++++++++++++
 archive/showcase-2026-08/css/showcase.css     | 2015 ++++++
 archive/showcase-2026-08/index.html           | 1134 ++++
 .../pages/color/ambient-shadows.html          |    0
 .../pages/color/color-palette.html            |    0
 .../pages/color/color-system.html             |    0
 .../pages/color/design-tokens.html            |    0
 .../pages/color/token-spotlight.html          |    0
 .../pages/color/tonal-stacking.html           |    0
 .../pages/color/tonal-tiers.html              |    0
 .../pages/components/api-design-catalog.html  |    0
 .../pages/components/buttons.html             |    0
 .../pages/components/component-lab.html       |    0
 .../pages/components/component-wizard.html    |    0
 .../pages/components/design-playground.html   |    0
 .../pages/components/forms.html               |    0
 .../pages/components/icons.html               |    0
 .../showcase-2026-08}/pages/core/about.html   |    0
 .../showcase-2026-08}/pages/core/landing.html |    0
 .../pages/demo/impossible.html                |    0
 .../pages/integrations/gsap-anime.html        |    0
 .../pages/library/gallery.html                |    0
 .../pages/library/index.html                  |    0
 .../pages/motion/3d-explorer.html             |    0
 .../pages/motion/3d-rotation.html             |    0
 .../pages/motion/api-motion-catalog.html      | 1211 ++++
 .../pages/motion/interactions.html            |    0
 .../pages/motion/kinetic-cards.html           |    0
 .../pages/motion/kinetic-motion.html          |    0
 .../pages/motion/motion-compiler-demo.html    |    0
 .../pages/motion/motion-extended.html         |    0
 .../pages/motion/motion-playground.html       |    0
 .../pages/motion/motion-principles.html       |    0
 .../pages/motion/scale-shift.html             |    0
 .../pages/motion/scroll-driven-demos.html     |    0
 .../pages/motion/zero-js-motion.html          |    0
 .../pages/scenes/scene-creator.html           |    0
 .../pages/scenes/scene-features.html          |    0
 .../pages/scenes/scene-hero.html              |    5 +-
 .../pages/scenes/scene-story.html             |    5 +-
 .../pages/scenes/scene-timeline.html          |  348 +
 .../pages/scenes/scroll-reveal.html           |    0
 .../pages/tools/accessibility.html            |    0
 .../pages/tools/architecture.html             |    0
 .../pages/tools/brand-voice.html              |    0
 .../pages/tools/contrast-tool.html            |    0
 .../pages/tools/converter.html                |    0
 .../showcase-2026-08}/pages/tools/index.html  |    0
 .../pages/tools/system-modules.html           |    0
 .../typography/typography-composition.html    |    0
 .../pages/typography/typography-spec.html     |    0
 .../pages/typography/typography.html          |    0
 docs/agents/handbook.md                       |   17 +-
 docs/project/CONTRACT.md                      |   54 +-
 docs/project/MOTION_LANGUAGE.md               |   26 +-
 docs/project/PRODUCT_SURFACES_PLAN.md         |    7 +-
 docs/project/SCENE_SYSTEM.md                  |   34 +-
 docs/project/WORKSPACE.md                     |    9 +-
 .../2026-08-24-catalog-core-hardening.md      |  210 +
 .../2026-08-24-catalog-audit-findings.md      |   78 +
 ...026-08-24-catalog-core-hardening-design.md |   91 +
 ...08-24-host-agnostic-scene-engine-design.md |  220 +
 ...2026-08-24-lean-showcase-restart-design.md |  160 +
 .../2026-08-24-motion-core-gap-inventory.md   |   65 +
 ...08-24-showcase-home-catalog-acts-design.md |  114 +
 examples/README.md                            |    6 +-
 examples/tailwind-host/README.md              |   18 +
 examples/tailwind-host/index.html             |   56 +
 examples/tailwind-host/velora-motion.css      |    6 +
 .../compiler/output/motion-compiler-report.md | 2861 +-------
 packages/compiler/src/grammar.mjs             |    7 +
 packages/css/package.json                     |    3 +-
 packages/css/src/01-motion-tokens.css         |   52 +
 packages/css/src/01-tokens.css                |  425 +-
 packages/css/src/01-visual-tokens.css         |  335 +
 packages/css/src/02-layout.css                |   86 +-
 packages/css/src/03-motion.css                |   97 +-
 packages/css/src/03c-scene-engine.css         |  159 +
 packages/css/src/base.css                     |    4 +-
 packages/css/src/motion-core.css              |    5 +-
 packages/css/src/scene-recipes.css            |  197 +
 packages/css/src/theme.css                    |   10 +
 packages/css/src/velora.css                   |    6 +-
 starters/html-css-minimal/index.html          |   29 +-
 starters/html-css-minimal/styles.css          |    5 +
 132 files changed, 26853 insertions(+), 7335 deletions(-)
 create mode 100644 .cursor/hooks/state/continual-learning-index.json
 create mode 100644 .cursor/hooks/state/continual-learning.json
 create mode 100644 AGENTS.md
 create mode 100644 apps/docs/src/pages/hosts.astro
 create mode 100644 apps/showcase/pages/core/archive.html
 create mode 100644 apps/showcase/pages/core/hosts.html
 create mode 100644 apps/showcase/pages/core/skins.html
 create mode 100644 apps/showcase/pages/scenes/scene-timeline.html
 create mode 100644 apps/showcase/public/css/01-motion-tokens.css
 create mode 100644 apps/showcase/public/css/01-visual-tokens.css
 create mode 100644 apps/showcase/public/css/03c-scene-engine.css
 create mode 100644 apps/showcase/public/css/scene-recipes.css
 create mode 100644 apps/showcase/public/css/theme.css
 create mode 100644 apps/showcase/public/img/catalog-glyphs.svg
 create mode 100644 apps/showcase/public/js/showcase-api-motion-catalog.js
 create mode 100644 apps/showcase/scripts/apply-lean-nav.mjs
 create mode 100644 archive/showcase-2026-08/MANIFEST.md
 create mode 100644 archive/showcase-2026-08/README.md
 create mode 100644 archive/showcase-2026-08/config/template-registry.mjs.bak
 rename {packages/css/src => archive/showcase-2026-08/css}/04e-showcase-elevation-patterns.css (100%)
 rename {packages/css/src => archive/showcase-2026-08/css}/08-showcase-home.css (100%)
 create mode 100644 archive/showcase-2026-08/css/showcase-api-motion-catalog.css
 create mode 100644 archive/showcase-2026-08/css/showcase-home.css
 create mode 100644 archive/showcase-2026-08/css/showcase-hub.css
 create mode 100644 archive/showcase-2026-08/css/showcase-reference-pages.css
 create mode 100644 archive/showcase-2026-08/css/showcase.css
 create mode 100644 archive/showcase-2026-08/index.html
 rename {apps/showcase => archive/showcase-2026-08}/pages/color/ambient-shadows.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/color/color-palette.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/color/color-system.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/color/design-tokens.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/color/token-spotlight.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/color/tonal-stacking.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/color/tonal-tiers.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/components/api-design-catalog.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/components/buttons.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/components/component-lab.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/components/component-wizard.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/components/design-playground.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/components/forms.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/components/icons.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/core/about.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/core/landing.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/demo/impossible.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/integrations/gsap-anime.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/library/gallery.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/library/index.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/3d-explorer.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/3d-rotation.html (100%)
 create mode 100644 archive/showcase-2026-08/pages/motion/api-motion-catalog.html
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/interactions.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/kinetic-cards.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/kinetic-motion.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/motion-compiler-demo.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/motion-extended.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/motion-playground.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/motion-principles.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/scale-shift.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/scroll-driven-demos.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/motion/zero-js-motion.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/scenes/scene-creator.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/scenes/scene-features.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/scenes/scene-hero.html (99%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/scenes/scene-story.html (99%)
 create mode 100644 archive/showcase-2026-08/pages/scenes/scene-timeline.html
 rename {apps/showcase => archive/showcase-2026-08}/pages/scenes/scroll-reveal.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/tools/accessibility.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/tools/architecture.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/tools/brand-voice.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/tools/contrast-tool.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/tools/converter.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/tools/index.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/tools/system-modules.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/typography/typography-composition.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/typography/typography-spec.html (100%)
 rename {apps/showcase => archive/showcase-2026-08}/pages/typography/typography.html (100%)
 create mode 100644 docs/superpowers/plans/2026-08-24-catalog-core-hardening.md
 create mode 100644 docs/superpowers/specs/2026-08-24-catalog-audit-findings.md
 create mode 100644 docs/superpowers/specs/2026-08-24-catalog-core-hardening-design.md
 create mode 100644 docs/superpowers/specs/2026-08-24-host-agnostic-scene-engine-design.md
 create mode 100644 docs/superpowers/specs/2026-08-24-lean-showcase-restart-design.md
 create mode 100644 docs/superpowers/specs/2026-08-24-motion-core-gap-inventory.md
 create mode 100644 docs/superpowers/specs/2026-08-24-showcase-home-catalog-acts-design.md
 create mode 100644 examples/tailwind-host/README.md
 create mode 100644 examples/tailwind-host/index.html
 create mode 100644 examples/tailwind-host/velora-motion.css
 create mode 100644 packages/css/src/01-motion-tokens.css
 create mode 100644 packages/css/src/01-visual-tokens.css
 create mode 100644 packages/css/src/03c-scene-engine.css
 create mode 100644 packages/css/src/scene-recipes.css
 create mode 100644 packages/css/src/theme.css

diff --git a/.cursor/hooks/state/continual-learning-index.json b/.cursor/hooks/state/continual-learning-index.json
new file mode 100644
index 0000000..34b2117
--- /dev/null
+++ b/.cursor/hooks/state/continual-learning-index.json
@@ -0,0 +1,93 @@
+{
+  "version": 1,
+  "transcripts": {
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/18618295-bdf2-46e6-b561-c3a2775071d4/18618295-bdf2-46e6-b561-c3a2775071d4.jsonl": {
+      "mtimeMs": 1778184725699,
+      "mtimeSec": 1778184725
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/18618295-bdf2-46e6-b561-c3a2775071d4/subagents/0f44cf87-7461-4845-9f03-338f5dc902fa.jsonl": {
+      "mtimeMs": 1778184539161,
+      "mtimeSec": 1778184539
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/18618295-bdf2-46e6-b561-c3a2775071d4/subagents/3a538d77-2c55-4b14-9d9e-7cc1bc4a3e78.jsonl": {
+      "mtimeMs": 1778184550203,
+      "mtimeSec": 1778184550
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/18618295-bdf2-46e6-b561-c3a2775071d4/subagents/e7c111ca-606d-4a7c-b6ed-6f15fd594ad7.jsonl": {
+      "mtimeMs": 1778184530873,
+      "mtimeSec": 1778184530
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/1a0acb75-2678-41d9-a434-61d34aa5db6e/1a0acb75-2678-41d9-a434-61d34aa5db6e.jsonl": {
+      "mtimeMs": 1777455389418,
+      "mtimeSec": 1777455389
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/4784ec19-7f3e-4ce7-84bb-485c708340f8/4784ec19-7f3e-4ce7-84bb-485c708340f8.jsonl": {
+      "mtimeMs": 1778189613797,
+      "mtimeSec": 1778189613
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/558abbbe-b072-48ec-82e2-2b80941fbe18/558abbbe-b072-48ec-82e2-2b80941fbe18.jsonl": {
+      "mtimeMs": 1777456066747,
+      "mtimeSec": 1777456066
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/6607f714-8ca0-4f9c-8438-37e472087f00/6607f714-8ca0-4f9c-8438-37e472087f00.jsonl": {
+      "mtimeMs": 1778186899932,
+      "mtimeSec": 1778186899
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/6607f714-8ca0-4f9c-8438-37e472087f00/subagents/3421c42d-30dc-4c0a-8862-3e7bef09293c.jsonl": {
+      "mtimeMs": 1778185139365,
+      "mtimeSec": 1778185139
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/6607f714-8ca0-4f9c-8438-37e472087f00/subagents/5257044c-1ee3-4a8a-8bcc-e832c1098b75.jsonl": {
+      "mtimeMs": 1778186072210,
+      "mtimeSec": 1778186072
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/6607f714-8ca0-4f9c-8438-37e472087f00/subagents/5f1fbb68-c2a6-4653-bc18-39fd65714456.jsonl": {
+      "mtimeMs": 1778185807372,
+      "mtimeSec": 1778185807
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/6607f714-8ca0-4f9c-8438-37e472087f00/subagents/71601be8-1824-4ff6-bff6-10979b3b9d1c.jsonl": {
+      "mtimeMs": 1778185707953,
+      "mtimeSec": 1778185707
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/6607f714-8ca0-4f9c-8438-37e472087f00/subagents/8cdb1bbe-17af-4e99-99d4-753cd306a954.jsonl": {
+      "mtimeMs": 1778185806433,
+      "mtimeSec": 1778185806
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/6a780cd3-579a-44fb-8723-104e3be1c936/6a780cd3-579a-44fb-8723-104e3be1c936.jsonl": {
+      "mtimeMs": 1777826388114,
+      "mtimeSec": 1777826388
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/70831a21-356c-4321-aab2-c64a327f61b0/70831a21-356c-4321-aab2-c64a327f61b0.jsonl": {
+      "mtimeMs": 1787600611440,
+      "mtimeSec": 1787600611
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/70831a21-356c-4321-aab2-c64a327f61b0/subagents/4392098b-6f8c-46d5-8890-ba2c6014af90.jsonl": {
+      "mtimeMs": 1787600620087,
+      "mtimeSec": 1787600620
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/70831a21-356c-4321-aab2-c64a327f61b0/subagents/43e4de2c-1f21-4226-92bf-d7a8a53125ed.jsonl": {
+      "mtimeMs": 1787582184721,
+      "mtimeSec": 1787582184
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/70831a21-356c-4321-aab2-c64a327f61b0/subagents/cd35309d-843b-4b29-b69d-cd2ac29eee1e.jsonl": {
+      "mtimeMs": 1787572666994,
+      "mtimeSec": 1787572666
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/9dbf47df-d1ff-4804-94ec-bcd6cc7a408f/9dbf47df-d1ff-4804-94ec-bcd6cc7a408f.jsonl": {
+      "mtimeMs": 1778167194799,
+      "mtimeSec": 1778167194
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/e4897ba2-a6c9-4962-a6eb-182c160d18da/e4897ba2-a6c9-4962-a6eb-182c160d18da.jsonl": {
+      "mtimeMs": 1787316784737,
+      "mtimeSec": 1787316784
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/ed1cd72f-1fd4-49a1-92ef-18325437cf3e/ed1cd72f-1fd4-49a1-92ef-18325437cf3e.jsonl": {
+      "mtimeMs": 1777753610368,
+      "mtimeSec": 1777753610
+    },
+    "/Users/erikassiscardoso/.cursor/projects/Users-erikassiscardoso-Local-Sites-velora/agent-transcripts/f903c4dc-dba1-4bb0-bbc0-2916acc9cfb3/f903c4dc-dba1-4bb0-bbc0-2916acc9cfb3.jsonl": {
+      "mtimeMs": 1777633041954,
+      "mtimeSec": 1777633041
+    }
+  }
+}
diff --git a/.cursor/hooks/state/continual-learning.json b/.cursor/hooks/state/continual-learning.json
new file mode 100644
index 0000000..b53db30
--- /dev/null
+++ b/.cursor/hooks/state/continual-learning.json
@@ -0,0 +1,8 @@
+{
+  "version": 1,
+  "lastRunAtMs": 1787600605625,
+  "turnsSinceLastRun": 1,
+  "lastTranscriptMtimeMs": 1787600605478.6738,
+  "lastProcessedGenerationId": "0851e63a-0490-413f-8bf7-d36a295dfdfc",
+  "trialStartedAtMs": null
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..20adbb5
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,25 @@
+## Learned User Preferences
+
+- Prefer communication in Portuguese.
+- Zero-JS motion is non-negotiable: never introduce JS animation runtimes; keep the CSS-only / HTML+CSS path.
+- Treat Velora as a declarative CSS motion runtime and modern reference—not a utility framework; prioritize positioning, consistency, technical demos, and perceived value over feature volume.
+- Prefer evaluate-then-plan against the execution roadmap before large changes; often wants page-by-page reformulation using Velora CSS and the Showcase template.
+- Showcase pages should feel cinematic and exercise the design system plus motion catalog resources, not generic or simplistic layouts.
+- Prefer complex, coordinated scenes (3D stages, large type, enter/exit choreography, horizontal/stacked sections) over sparse section layouts.
+- Keep showcase UI patterns consistent across pages; avoid exaggerated glow and one-off chrome that breaks the shared look.
+- Stage 3D should be a reusable CSS-only stage contract (perspective/preserve-3d on the container; positional transforms via CSS vars on items; motion on inner content), not cube-triad-specific demos; preserve text-ring-orbit and circle-text-scroll.
+- Aim for GSAP-like scene authorship in HTML/CSS (shared scene clock, relative timing, pin+scrub)—preferred product direction for the scene engine.
+- Core motion must stay host-agnostic and work with any UI (Tailwind and others); **Skins** is the product name for the Velora design-system layer (named themes via `data-editorial-theme`); Showcase is the cinematic reference UI, not the motion contract.
+- Prefer modern CSS capabilities (e.g. `if()`, typed `attr()`, `sibling-index()`) as progressive enhancement for scene choreography.
+- Prefer fixing contract/consistency before expanding demos; prefer a lean public Showcase that archives surplus pages in-repo rather than deleting Skins/DS work; reuse existing motion examples rather than inventing parallel systems.
+
+## Learned Workspace Facts
+
+- Monorepo layout: canonical CSS in `packages/css/src/`; Vite playground in `apps/showcase/`; Astro docs app in `apps/docs/`; markdown source of truth in repo-root `docs/`; plus `design-system/`, `examples/`, `experiments/`, and `starters/html-css-minimal/`.
+- Always edit CSS in `packages/css/src/` and sync to showcase (`pnpm sync:showcase-css`); `apps/showcase/public/css/` is derived and must not be edited as source.
+- Motion is attribute-driven (`vl-effect`, `vl-timeline`, `vl-range`, `vl-scene`, related `vl-*`); rules live in ordered `@layer velora.*` (reset → tokens → layout → motion → components → transitions → utilities → overrides).
+- Product surfaces split: Velora core = scene/motion engine; Showcase = cinematic reference UI/DS; docs site documents the framework once the API is stable.
+- `@velora/css` ships separable entrypoints: `motion-core` (host-agnostic engine) vs `theme` / full bundle (visual Skins); editorial skins use `html[data-editorial-theme]` (e.g. noir, earth, aethel, meridian).
+- Lean Showcase restarts should snapshot retired pages under a dated in-repo `archive/` (outside live Vite registry and contract checks), not delete recoverable DS/skin work.
+- Workspace tooling is pnpm + Turborepo; common checks include `pnpm verify:contract` and showcase CSS drift checks.
+- Root `AGENTS.md` holds learned memory only; agent operating instructions live under `docs/agents/AGENTS.md` and must not be mixed into the learned-memory file.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f806d6..62c9273 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased]
 
+### Changed
+- Public Showcase reduced to six routes (Home, Timeline, Skins, Catalog, Hosts, Archive). Pre-lean pages snapshotted in `archive/showcase-2026-08/` (not Vite/CI).
+- **Skins** is the product name for named editorial models (`data-editorial-theme`). Home is didactic: engine vs Skins.
+- Motion Catalog: `#pin` teaching replaced by `#scene-engine` (numeric pin + stage/acts live demo); Engine vs Skins lanes; DX toolbar framed as optional CSS-only helper.
+- Catalog Phase C (P1): channel-first cards for CONTRACT `vl-enter` / `vl-scroll` / `vl-hover` extensions; scene-engine overlap + auto-clock demos.
+- Catalog DX runtime: toolbar covers full CONTRACT attr set, scene-engine targets, CSS var duration/speed, stagger-aware replay.
+
 ---
 
 ## [1.0.0] — 2025-01-01
diff --git a/README.md b/README.md
index 420e9ce..93504ae 100644
--- a/README.md
+++ b/README.md
@@ -4,9 +4,9 @@
 
 ![License: ISC](https://img.shields.io/badge/license-ISC-blue.svg)
 
-Velora is a CSS-first design system for building premium, cinematic web interfaces using nothing but modern browser APIs. No JavaScript frameworks. No runtime animation libraries. Just HTML and CSS that move.
+Velora is a **declarative CSS motion engine**. HTML attributes describe intent; the browser executes it. No JavaScript animation runtime. Works with Tailwind, Relume, or any UI.
 
-Built for developers and agencies who want production-ready motion without the weight.
+The cinematic **Showcase** is the optional Velora design-system skin — not a requirement to turn motion on.
 
 ## Features
 
@@ -32,26 +32,28 @@ pnpm add @velora/css
 
 ### 2. Import
 
+Host-agnostic (any UI — Tailwind, etc.):
+
 ```css
-@import "@velora/css";
+@import "@velora/css/motion-core";
+@import "@velora/css/transitions"; /* optional */
 ```
 
-Or consume only the bundles you need:
+Full Velora look (theme + components + motion):
 
 ```css
-@import "@velora/css/base";
-@import "@velora/css/motion-core";
-@import "@velora/css/components-core";
-@import "@velora/css/transitions";
+@import "@velora/css";
 ```
 
 ### 3. Use
 
 ```html
-
-

Zero-JS motion

-

Pure CSS. Pure performance.

-
+
+
+

Zero-JS motion

+

Same attributes on Tailwind or Velora UI.

+
+
``` That's it. No build config. No framework bindings. Works with any stack. @@ -71,18 +73,19 @@ It includes: ## Modular Entry Points -Velora now ships stable modular entry points in addition to the full bundle: - -- `@velora/css` or `@velora/css/full` — full framework bundle (default) -- `@velora/css/base` — reset, tokens, layout, utilities -- `@velora/css/motion-core` — core declarative motion grammar -- `@velora/css/motion-extended` — extended motion effects -- `@velora/css/components-core` — components, forms, dialogs, structures +- `@velora/css` or `@velora/css/full` — convenience bundle (theme + components + motion) +- `@velora/css/motion-core` — host-agnostic motion grammar + scene engine +- `@velora/css/motion-extended` — extra effect presets +- `@velora/css/theme` — visual tokens + editorial + scene look recipes +- `@velora/css/base` — reset, motion tokens, structural layout, utilities +- `@velora/css/components-core` — optional UI primitives - `@velora/css/transitions` — view transition presets - `@velora/css/premium` — premium components - `@velora/css/overrides` — last-mile override layer -All bundles keep the same `@layer` contract to avoid cascade drift between pages. +Proofs: `examples/tailwind-host/` (motion-core + Tailwind) · `apps/showcase` (six-page reference UI: Home, Timeline, Skins, Catalog, Hosts, Archive). Older Showcase pages: `archive/showcase-2026-08/`. + +**Skins** are named design-system models (`html[data-editorial-theme]`). They are optional; motion works without them. ## Motion Channels (v2) diff --git a/apps/docs/src/layouts/Base.astro b/apps/docs/src/layouts/Base.astro index 468d3d4..47f17f5 100644 --- a/apps/docs/src/layouts/Base.astro +++ b/apps/docs/src/layouts/Base.astro @@ -417,29 +417,17 @@ function isActive(href: string): boolean {
diff --git a/apps/docs/src/pages/hosts.astro b/apps/docs/src/pages/hosts.astro new file mode 100644 index 0000000..20d0125 --- /dev/null +++ b/apps/docs/src/pages/hosts.astro @@ -0,0 +1,43 @@ +--- +import Base from "../layouts/Base.astro"; +--- + + + Interoperability + +

Hosts (Tailwind & others)

+

+ Velora is a motion engine. Your host owns layout and visual styling. +

+ +

Minimal import

+
@import "@velora/css/motion-core";
+/* optional page transitions */
+@import "@velora/css/transitions";
+ +

Do not require @velora/css/theme or component classes for scenes to work.

+ +

Tailwind example

+
<section vl-scene vl-timeline="view" vl-pin="3" vl-scrub>
+  <div vl-stage class="min-h-svh grid place-items-center gap-6 px-6">
+    <p class="text-sm uppercase tracking-widest" vl-enter="fade-up" vl-act="1">Kicker</p>
+    <h1 class="text-5xl font-semibold" vl-enter="clip-rise" vl-act="1">Title</h1>
+    <img class="w-full max-w-3xl rounded-2xl" vl-scroll="media-zoom" vl-act="2" vl-span="2" alt="" />
+  </div>
+</section>
+ +

+ Repo proof: examples/tailwind-host/. Showcase proves the Velora cinematic UI; + examples prove the host-agnostic path. +

+ +

Layers

+

+ Velora uses @layer velora.*. Keep host utilities outside or after those layers so + Tailwind wins on appearance while vl-* attributes still drive animation. +

+ +

Optional skin

+
@import "@velora/css/motion-core";
+@import "@velora/css/theme"; /* brand tokens + scene recipes */
+ diff --git a/apps/docs/src/pages/index.astro b/apps/docs/src/pages/index.astro index 5059dd9..661a47a 100644 --- a/apps/docs/src/pages/index.astro +++ b/apps/docs/src/pages/index.astro @@ -2,12 +2,16 @@ import Base from "../layouts/Base.astro"; --- - + Documentation

Getting Started

- A motion-native design system. Declarative HTML attributes. Zero JavaScript for animation. Modern CSS only. + A declarative motion language for HTML. Attributes describe intent; CSS executes it. + Works with Tailwind, Relume, or any UI. Showcase is the optional Velora look + (Skins — named models via data-editorial-theme). + The public Showcase is six pages; the previous demos live in + archive/showcase-2026-08/.

Installation

@@ -16,133 +20,53 @@ import Base from "../layouts/Base.astro"; # or npm install @velora/css
-

Then import it once at the root of your app:

-
@import "@velora/css";
- -

Or in a bundler entrypoint:

-
import "@velora/css";
- -

Quick Start

-

Add motion to any element with two attributes:

-
<!-- Fade up when scrolled into view -->
-<div vl-effect="fade-up" vl-timeline="view">
-  Reveals on scroll
-</div>
-
-<!-- Staggered children entrance -->
-<section vl-children="stagger" vl-stagger="100ms">
-  <div>Child 1</div>
-  <div>Child 2</div>
-  <div>Child 3</div>
-</section>
-
-<!-- Full scene orchestration -->
-<section
-  vl-scene="hero"
-  vl-effect="scene-hero-reveal"
-  vl-timeline="view"
-  vl-range="entry 0% cover 70%"
-  vl-pin>
-  <h1>Heading fades up</h1>
-  <img src="hero.jpg" alt="" />
-  <p>Copy blurs in</p>
-  <a href="#" class="vl-cta">Get started</a>
-</section>
- -

Page Transitions

-

Enable MPA cross-document view transitions with one attribute:

-
<!-- In your root layout -->
-<html vl-page-transition="cinema">
-

- Requires @view-transition { navigation: auto; } in your CSS (already included with Velora). - Supported in Chrome 111+, Edge 111+. -

- -

Philosophy

-

- Velora is a declarative motion system for HTML — not a UI kit, not a JavaScript animation library. -

-

The core idea is a three-step contract:

-
    -
  1. Write semantic HTML
  2. -
  3. Add vl-* attributes to declare intent
  4. -
  5. Velora maps those to CSS-based motion, layout, and transitions
  6. -
-

- The framework proves that modern HTML and CSS are sufficient to build premium motion systems when the - architecture is designed correctly. -

- -

CSS Layer Stack

-

Velora organizes CSS into 8 cascade layers, making it safe to override at any point:

-
@layer velora.reset,
-       velora.tokens,
-       velora.layout,
-       velora.motion,
-       velora.components,
-       velora.transitions,
-       velora.utilities,
-       velora.overrides;
- +

Choose an import

- - - - + - - - - - - - - + + + + + + + + + + + +
LayerContents
ImportUse when
velora.resetBrowser normalization and base adjustments
velora.tokensColors, spacing, typography, motion tokens
velora.layoutContainer, stack, grid, and scene wrappers
velora.motionEffect presets, timelines, stagger, reveals
velora.componentsCards, buttons, navbars, accordions, badges
velora.transitionsPage transitions and shared element rules
velora.utilitiesLow-level opt-in utility classes
velora.overridesLast-mile overrides; highest specificity
@velora/css/motion-coreHost UI already provides look (Tailwind, custom DS). Motion + scene engine only.
@velora/css/themeOptional Velora visual tokens + named scene recipes.
@velora/css / fullConvenience: theme + components + motion (Showcase / docs style).
-

Accessibility

+
/* Host-agnostic (recommended for Tailwind projects) */
+@import "@velora/css/motion-core";
+@import "@velora/css/transitions"; /* optional */
+
+/* Or full Velora skin */
+@import "@velora/css";
+ +

Quick Start — scene engine

+

Track + stage + acts: one shared clock, pin, scrub — no JavaScript.

+
<section vl-scene vl-timeline="view" vl-pin="3" vl-scrub>
+  <div vl-stage>
+    <p vl-enter="fade-up" vl-act="1">Kicker</p>
+    <h1 vl-enter="clip-rise" vl-act="1">Title</h1>
+    <img vl-scroll="media-zoom" vl-act="2" vl-span="2" alt="" />
+  </div>
+</section>
+

- All Velora motion is prefers-reduced-motion aware. Users who have requested reduced motion - see instant transitions with no animation. + Style with your own classes. Motion does not require .vl-card or brand tokens. + See Hosts (Tailwind) and Scenes.

-
/* Built into every Velora effect */
-@media (prefers-reduced-motion: reduce) {
-  [vl-effect],
-  [vl-scene],
-  [vl-page-transition] {
-    animation: none !important;
-    transition-duration: 0ms !important;
-  }
-}
-

Explore the Docs

- +

What Velora is

+
    +
  • Motion language — not a utility framework requirement
  • +
  • Zero JS for animation — scroll timelines, view transitions, CSS only
  • +
  • Showcase — cinematic reference UI / design system skin
  • +
diff --git a/apps/docs/src/pages/scenes.astro b/apps/docs/src/pages/scenes.astro index 1ac1d7f..7f1bfca 100644 --- a/apps/docs/src/pages/scenes.astro +++ b/apps/docs/src/pages/scenes.astro @@ -2,253 +2,57 @@ import Base from "../layouts/Base.astro"; --- - + Orchestration

Scenes

- Scenes coordinate the motion of an element and all its descendants as a single - choreographed unit. This is the difference between a primitive effect and a scene. + A scene is a shared clock for children — not a look. Layout and chrome come from the host UI + (or optional Velora theme recipes).

-

Primitive vs Scene

-

- A primitive effect animates one element: -

-
<div vl-effect="fade-up" vl-timeline="view">
-  Just this element fades up
-</div>
-

- A scene preset coordinates the element and its children with distinct timings - per child slot: -

-
<section
-  vl-scene="hero"
-  vl-effect="scene-hero-reveal"
-  vl-timeline="view">
-  <h1>1st child — fades up (slowest)</h1>
-  <img src="hero.jpg" alt="" />  <!-- 2nd child — scales in after 120ms -->
-  <p>3rd child — blurs in after 240ms</p>
-  <a href="#" class="vl-cta">4th+ children — staggered fade</a>
-</section>
- -

The vl-scene Attribute

-

- Add vl-scene to a container to mark it as a scene host. This applies CSS containment - (contain: layout style) so the scene is self-contained. -

-
<section vl-scene="hero">...</section>
-<section vl-scene="story">...</section>
-<section vl-scene="features">...</section>
-

- The value is a semantic label — it doesn't change behavior today but documents intent and allows - future CSS hooks via [vl-scene="hero"] selectors. -

- -

Scene Presets

-

- Scene presets are set via vl-effect on the scene host. They define child-specific - animation sequences: -

- - - - - - - - - - - - - - - - - - - - - - -
ValueChildren behavior
scene-hero-reveal - 1st → fade-up (800ms, cinematic)
- 2nd → scale-in (800ms, spring, 120ms delay)
- 3rd → blur-in (800ms, soft, 240ms delay)
- 4th+ → fade-up (staggered) -
scene-feature-flow - Each child gets a flow-in animation tied to view(block) scroll timeline, - with cascading animation ranges (1st enters earliest, later children enter progressively later). - Requires vl-timeline="view". -
scene-story-pin - Container becomes sticky (position: sticky; min-height: 100vh). Children use - fade-up tied to the block scroll timeline. -
scene-layer-stack - Container gets perspective: 1200px. Children use 3d-entry with - increasing translation depth per child (1rem → 4rem). -
- -

Hero Reveal — Full Example

-
<section
-  vl-scene="hero"
-  vl-effect="scene-hero-reveal"
-  vl-timeline="view"
-  vl-range="entry 0% cover 70%"
-  vl-pin>
-
-  <h1 class="vl-vt-shared-hero">
-    Build motion-rich interfaces
-  </h1>
-
-  <img
-    src="/hero.jpg"
-    alt="Motion design system preview"
-    class="vl-vt-shared-media"
-    width="1200"
-    height="675"
-  />
-
-  <p>
-    Velora proves that HTML and CSS are enough to build premium motion.
-  </p>
-
-  <a href="/docs" class="vl-cta" vl-effect="icon-shift">
-    Get started
-    <svg aria-hidden="true" width="16" height="16">...</svg>
-  </a>
-</section>
- -

Feature Flow — Full Example

-
<section
-  vl-scene="features"
-  vl-effect="scene-feature-flow"
-  vl-timeline="view">
-
-  <!-- Each direct child flows in with cascading scroll-driven ranges -->
-  <div class="vl-card">
-    <div class="vl-card__title">Feature One</div>
-    <p class="vl-card__body">Description</p>
+  

Track + stage + acts

+
<section vl-scene vl-timeline="view" vl-pin="3" vl-scrub>
+  <div vl-stage>
+    <p vl-enter="fade-up" vl-act="1">Kicker</p>
+    <h1 vl-enter="clip-rise" vl-act="1">Title</h1>
+    <img vl-scroll="media-zoom" vl-act="2" vl-span="2" alt="" />
+    <a vl-enter="fade-up" vl-act="5">CTA</a>
   </div>
-  <div class="vl-card">...</div>
-  <div class="vl-card">...</div>
-  <div class="vl-card">...</div>
 </section>
-

Story Pin — Full Example

-
<!-- Wrap in a tall outer container to create scroll space -->
-<div style="height: 300vh;">
-  <section
-    vl-scene="story"
-    vl-effect="scene-story-pin">
-    <h2>Chapter One</h2>
-    <p>This section stays fixed while you scroll.</p>
-  </section>
-</div>
- -

Child Choreography — vl-children

-

- For custom compositions that don't use a named scene preset, use vl-children to - choreograph direct children: -

- + - - - - - - - - - - - - - - - + + + + + + + +
ValueEffect on childrenEasing
AttributeRole
staggerfade-up with incremental delays (n × stagger-step)--vl-ease-cinematic
cascadeflow-in (blur + translate + scale) with incremental delays--vl-ease-out-soft
sequencefade-up with delays based on full animation duration (one-after-another)--vl-ease-cinematic
vl-sceneTrack — owns --vl-scene view-timeline and optional pin height
vl-stageSticky viewport stage (required for pin+scrub)
vl-timelineview (scroll film) or auto (time film)
vl-pinTrack height in viewport multiples (e.g. 3)
vl-scrubLinear fill — progress glued to scroll
vl-actBeat index (same act = overlap / GSAP <)
vl-spanHow many beats the tween spans
vl-rangeEscape hatch — overrides act-derived ranges
-

Supports up to 12 children for stagger and cascade, 6 for sequence.

-
<!-- Staggered grid of cards -->
-<div
-  vl-children="stagger"
-  vl-stagger="80ms"
-  vl-timeline="view"
-  style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem;">
-  <div class="vl-card">Card 1</div>
-  <div class="vl-card">Card 2</div>
-  <div class="vl-card">Card 3</div>
-</div>
-
-<!-- Sequential one-after-another reveal -->
-<ol vl-children="sequence" vl-timeline="view">
-  <li>Step 1 — plays first</li>
-  <li>Step 2 — plays after step 1 finishes</li>
-  <li>Step 3 — plays after step 2 finishes</li>
-</ol>
- -

Stagger Interval — vl-stagger

+

Why track ≠ stage

- Override the default --vl-stagger-step (45ms) with one of the preset values: + A sticky element freezes its own anonymous view() progress. Children bind to the + named track timeline (--vl-scene) while the stage stays pinned.

- - - - - - - - - - - - -
ValueDelay per childFeel
60ms60msQuick, snappy
80ms80msBalanced
100ms100msComfortable
120ms120msSlightly cinematic
150ms150msDeliberate pacing
200ms200msDramatic, slow cascade
-
<div vl-children="stagger" vl-stagger="120ms">
-  <div>Plays at 0ms</div>
-  <div>Plays at 120ms</div>
-  <div>Plays at 240ms</div>
-  <div>Plays at 360ms</div>
-</div>
- -

Live Demo

-
-
-
Child 1
-

Fades up immediately.

-
-
-
Child 2
-

100ms after child 1.

-
-
-
Child 3
-

200ms after child 1.

-
-
-

Accessibility

+

Named presets (skin recipes)

- Scene presets and vl-children choreography all disable under - prefers-reduced-motion: reduce — children appear in their final state instantly. - Semantic structure (heading hierarchy, landmark roles) should be maintained regardless of motion. + Values like vl-scene="cinematic-hero" remain for compatibility. They ship in + scene-recipes.css via @velora/css/theme / full — they mix look + motion. + Prefer track/stage/acts for portable hosts.

-

Browser Support

+

Channels on children

- scene-hero-reveal, scene-layer-stack, and vl-children - orchestrations use time-based delays and work in all modern browsers. - scene-feature-flow and scene-story-pin use scroll-driven timelines and - require Chrome 115+ / Edge 115+. Unsupported browsers see a static layout. + Use existing channels on stage children: vl-enter, vl-scroll, + vl-exit, etc. The scene clock drives when they play.

diff --git a/apps/docs/src/pages/timelines.astro b/apps/docs/src/pages/timelines.astro index 7973c1d..c789373 100644 --- a/apps/docs/src/pages/timelines.astro +++ b/apps/docs/src/pages/timelines.astro @@ -2,67 +2,49 @@ import Base from "../layouts/Base.astro"; --- - - Motion + + Progress model

Timelines

-

- Control when and how animations progress via vl-timeline. - See also: Attribute Reference → vl-timeline. +

+ Timelines decide what drives progress. Effects decide what moves. + The scene engine shares one clock across stage children.

-

Timeline Modes

+

vl-timeline values

- - - - + - - - - + + + +
ValueBehavior
ValueBehavior
viewScroll-driven via view(block) — element's viewport entry controls progress
scrollScroll-driven via scroll(root) — page scroll position controls progress
autoStandard time-based (document timeline)
hoverAnimation paused until hover
viewProgress from element / scene visibility (view() or named --vl-scene)
scrollProgress from root scroll
autoTime-based CSS animation
hoverPaused until hover
-

Range Presets

-

Pair vl-timeline="view" with vl-range to control the animation window:

- - - - - - - - - - - - - - - -
ValueRange
entryentry 0% entry 100%
entry-shortentry 15% cover 35%
entry-longentry 0% cover 70%
covercover 0% cover 100%
containcontain 0% contain 100%
customUses --vl-range custom property
+

Scene clock

+

+ On [vl-scene][vl-timeline="view"], stage children use + animation-timeline: --vl-scene and ranges derived from + vl-act / vl-span / --vl-beats (default 8). +

+

+ With vl-timeline="auto", the same acts map to + animation-delay: (act - 1) * --vl-beat. Pin/scrub are no-ops. +

-

Boolean Modifiers

- - - - - - - - - - - - -
AttributeBehavior
vl-pinSticky positioning for scroll-driven scenes
vl-scrubContinuous linked motion (linear timing)
vl-oncePlay animation only once (forwards fill)
+

Ranges

+

+ Presets via vl-range (entry, entry-short, cover, …) + still apply to standalone elements. Inside a pinned scene, prefer acts; use + vl-range only as an escape hatch. +

-

Example

-
<div vl-effect="fade-up" vl-timeline="view" vl-range="entry-short" vl-once>
-  Appears quickly as it enters the viewport, then stays visible.
-</div>
+

Scrub

+
<section vl-scene vl-timeline="view" vl-pin="3" vl-scrub>
+  …
+</section>
+

vl-scrub forces linear timing and fill both on scene-driven children.

diff --git a/apps/showcase/config/template-registry.mjs b/apps/showcase/config/template-registry.mjs index 19412d1..dc90bf4 100644 --- a/apps/showcase/config/template-registry.mjs +++ b/apps/showcase/config/template-registry.mjs @@ -1,4 +1,3 @@ -import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -10,68 +9,16 @@ export const DEFAULT_TEMPLATE_NAME = "default"; export const TEMPLATE_REGISTRY = { [DEFAULT_TEMPLATE_NAME]: { name: "Velora Default Template", - description: "Baseline multi-page template for Velora showcase surfaces.", + description: "Lean public Showcase: Home, Timeline, Skins, Catalog, Hosts, Archive.", rootFile: "index.html", sections: { - core: [ + live: [ "index.html", - "pages/core/about.html", - "pages/core/landing.html", - ], - demo: [ - "pages/demo/impossible.html", - ], - scenes: [ - "pages/scenes/scene-hero.html", - "pages/scenes/scene-features.html", - "pages/scenes/scene-story.html", - "pages/scenes/scene-creator.html", - "pages/scenes/scroll-reveal.html", - ], - components: [ - "pages/components/buttons.html", - "pages/components/forms.html", - "pages/components/component-lab.html", - "pages/components/component-wizard.html", - "pages/components/icons.html", - ], - motion: [ - "pages/motion/zero-js-motion.html", - "pages/motion/motion-extended.html", - "pages/motion/motion-compiler-demo.html", - "pages/motion/motion-principles.html", - "pages/motion/kinetic-motion.html", - "pages/motion/kinetic-cards.html", - "pages/motion/scale-shift.html", - "pages/motion/interactions.html", + "pages/scenes/scene-timeline.html", + "pages/core/skins.html", "pages/motion/api-motion-catalog.html", - "pages/motion/3d-explorer.html", - "pages/motion/3d-rotation.html", - ], - color: [ - "pages/color/design-tokens.html", - "pages/color/color-system.html", - "pages/color/color-palette.html", - "pages/color/tonal-stacking.html", - "pages/color/tonal-tiers.html", - "pages/color/token-spotlight.html", - "pages/color/ambient-shadows.html", - ], - typography: [ - "pages/typography/typography.html", - "pages/typography/typography-composition.html", - "pages/typography/typography-spec.html", - ], - tools: [ - "pages/tools/system-modules.html", - "pages/tools/architecture.html", - "pages/tools/brand-voice.html", - "pages/tools/accessibility.html", - "pages/tools/contrast-tool.html", - "pages/tools/converter.html", - ], - integrations: [ - "pages/integrations/gsap-anime.html", + "pages/core/hosts.html", + "pages/core/archive.html", ], }, }, @@ -94,15 +41,6 @@ export function resolveTemplateInputs(templateName = DEFAULT_TEMPLATE_NAME) { inputs[toRollupKey(relPath)] = path.resolve(APP_ROOT, relPath); } - const libraryDir = path.join(APP_ROOT, "pages", "library"); - if (fs.existsSync(libraryDir)) { - for (const fileName of fs.readdirSync(libraryDir)) { - if (!fileName.endsWith(".html")) continue; - const key = `lib-${fileName.replace(/\.html$/i, "").replace(/[^a-z0-9]+/gi, "-")}`; - inputs[key] = path.resolve(libraryDir, fileName); - } - } - return inputs; } diff --git a/apps/showcase/index.html b/apps/showcase/index.html index e7be810..d5cf3d2 100644 --- a/apps/showcase/index.html +++ b/apps/showcase/index.html @@ -5,9 +5,9 @@ - Velora CSS - Motion Design System for the Web + Velora — Motion engine + Skins @@ -74,13 +74,13 @@ -

+
+
+

Lesson · vl-scene + vl-stage + vl-act

+

One track, one clock, pin + scrub. Copy this markup; look is a Skin (cards below).

+
+
+
+

Act 1

+

Shared beat

+

Act 2 — Skin look on a card, engine on attributes. Timeline

+
+
+
+
Frame. Cue. Cut.
-

Act II · Display

+

Lesson · vl-enter / clip-rise

Typography becomes a motion surface.

- Type is not decoration here. It carries hierarchy, pacing, emphasis, and brand character through CSS-driven scale, timing, and reveal behaviors. + Channel: enter. Type scale and reveal are engine; the font stack is a Skin. Copy from Catalog. + See catalog.

@@ -518,9 +537,9 @@

- Pair with type spec, - composition, and - design tokens. + Pair with Skins + for type tokens, or the Archive + for the old type spec pages.

@@ -554,12 +573,12 @@

-

Act III · Lanes

+

Lesson · horizontal pin

Walk the system, not a static demo.

- Each lane shows how motion, color, typography, and architecture connect into one production-ready system for launches, product surfaces, and editorial experiences. + Scroll-scrubbed lane: engine. Panel chrome is Skin look. Authorship model: Timeline.

- Open zero-JS motion + Open catalog
@@ -611,7 +630,7 @@

Color system

primary
- Explore color + Explore Skins
@@ -631,7 +650,7 @@

Typography

- Typography hub + Typography in Skins
@@ -651,7 +670,7 @@

Architecture

tokens
- Read architecture + Read architecture @@ -686,12 +705,14 @@

Architecture

-

Act IV · Scenes

+

Lesson · scenes vs Skins

Reusable motion chapters for real pages.

- Scene wrappers package layout, timing, density, and interaction into repeatable sections teams can ship across launches, docs, and product storytelling. + Track / stage / acts is the engine. Named scene recipes and cards are Skin look. + Timeline · Skins. +

@@ -721,7 +742,7 @@

One scene language across every page.

- +

Scene 01

Hero Reveal

@@ -744,7 +765,7 @@

Hero Reveal

- +

Scene 02

Feature Flow

@@ -762,7 +783,7 @@

Feature Flow

- +

Scene 03

Story Pin

@@ -785,7 +806,7 @@

Story Pin

- +

Engine

Scroll Reveal

@@ -886,10 +907,10 @@

Scroll Reveal

-

Act V · Signature

+

Lesson · vl-pin + depth

Hold the frame. Bend the depth.

- A pinned shot, a moving light field, and a few CSS layers turn scroll into a camera cue. + Engine: pin + scroll layers. The cinematic chrome is a Skin. Timeline.

@@ -922,10 +943,10 @@

Hold the frame. Bend the depth.

>
-

Act VI · Map

-

Choose the part of the sequence you want to inspect.

+

Lesson · where to go next

+

Six live pages. Everything else is archived.

- The homepage is the trailer; each destination below opens the mechanics behind a frame, cue, transition, or scene. + Trailer below; mechanics on Timeline, Skins, Catalog, Hosts. Older demos: Archive.

@@ -972,61 +993,61 @@

Choose the part of the sequence you want to Depth Spatial staging, pinning, and handoffs. - + 06 - Library - Complete showcase and reference pages. + Archive + Pre-lean pages kept in the repo, not in Vite.

@@ -1059,50 +1080,15 @@

Choose the part of the sequence you want to

- - - - diff --git a/apps/showcase/output/contract-checklist.md b/apps/showcase/output/contract-checklist.md index bedbf31..8352148 100644 --- a/apps/showcase/output/contract-checklist.md +++ b/apps/showcase/output/contract-checklist.md @@ -1,9 +1,11 @@ # Showcase Contract Checklist -- Pages scanned: **49** +- Pages scanned: **6** - Unknown vl-* attrs: **0** - Deprecated vl-* attrs: **0** - Broken #anchors: **0** +- Home anchor motion violations: **0** +- Home scene frame motion violations: **0** ## Per-page status @@ -11,244 +13,41 @@ - Unknown vl-* attrs: none - Deprecated vl-* attrs: none - Broken #anchors: none +- Home anchor motion: none +- Home scene frame motion: none -### OK - `pages/color/ambient-shadows.html` +### OK - `pages/core/archive.html` - Unknown vl-* attrs: none - Deprecated vl-* attrs: none - Broken #anchors: none +- Home anchor motion: none +- Home scene frame motion: none -### OK - `pages/color/color-palette.html` +### OK - `pages/core/hosts.html` - Unknown vl-* attrs: none - Deprecated vl-* attrs: none - Broken #anchors: none +- Home anchor motion: none +- Home scene frame motion: none -### OK - `pages/color/color-system.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/color/design-tokens.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/color/token-spotlight.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/color/tonal-stacking.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/color/tonal-tiers.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/components/api-design-catalog.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/components/buttons.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/components/component-lab.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/components/component-wizard.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/components/design-playground.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/components/forms.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/components/icons.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/core/about.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/core/landing.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/demo/impossible.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/integrations/gsap-anime.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/library/gallery.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/library/index.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/3d-explorer.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/3d-rotation.html` +### OK - `pages/core/skins.html` - Unknown vl-* attrs: none - Deprecated vl-* attrs: none - Broken #anchors: none +- Home anchor motion: none +- Home scene frame motion: none ### OK - `pages/motion/api-motion-catalog.html` - Unknown vl-* attrs: none - Deprecated vl-* attrs: none - Broken #anchors: none +- Home anchor motion: none +- Home scene frame motion: none -### OK - `pages/motion/interactions.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/kinetic-cards.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/kinetic-motion.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/motion-compiler-demo.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/motion-extended.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/motion-playground.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/motion-principles.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/scale-shift.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/scroll-driven-demos.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/motion/zero-js-motion.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/scenes/scene-creator.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/scenes/scene-features.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/scenes/scene-hero.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/scenes/scene-story.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/scenes/scroll-reveal.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/tools/accessibility.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/tools/architecture.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/tools/brand-voice.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/tools/contrast-tool.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/tools/converter.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/tools/index.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/tools/system-modules.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/typography/typography-composition.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/typography/typography-spec.html` -- Unknown vl-* attrs: none -- Deprecated vl-* attrs: none -- Broken #anchors: none - -### OK - `pages/typography/typography.html` +### OK - `pages/scenes/scene-timeline.html` - Unknown vl-* attrs: none - Deprecated vl-* attrs: none - Broken #anchors: none +- Home anchor motion: none +- Home scene frame motion: none diff --git a/apps/showcase/pages/core/archive.html b/apps/showcase/pages/core/archive.html new file mode 100644 index 0000000..8da6000 --- /dev/null +++ b/apps/showcase/pages/core/archive.html @@ -0,0 +1,258 @@ + + + + + + Archive | Velora + + + + + + + + + + + + + + Skip to content + + +
+
+ + + + +
+ + +
+ + + +
+ + + + Get Started +
+ +
+ +
+
+
+
+
+

Archive // frozen Showcase

+

The previous ~50 pages are kept, not served.

+

+ Snapshot: archive/showcase-2026-08/ in the repo (outside Vite and verify:contract). + Restore a page by copying it into apps/showcase/pages/ and adding it to the template registry. +

+

Paths below are archive-relative. They are not live routes.

+
    +
  • index.html — cinematic home (pre-lean)
  • +
  • pages/scenes/scene-hero.html — hero scene recipe
  • +
  • pages/scenes/scene-story.html — pinned story
  • +
  • pages/scenes/scene-features.html — feature flow
  • +
  • pages/scenes/scene-creator.html — scene creator
  • +
  • pages/components/buttons.html — button kit
  • +
  • pages/components/forms.html — forms
  • +
  • pages/color/design-tokens.html — token reference
  • +
  • pages/typography/typography.html — type samples
  • +
  • pages/tools/architecture.html — architecture
  • +
  • pages/library/gallery.html — library gallery
  • +
  • pages/motion/3d-explorer.html — 3D explorer
  • +
  • Full list: archive/showcase-2026-08/MANIFEST.md
  • +
+
+
+
+
+ + + + + + + + diff --git a/apps/showcase/pages/core/hosts.html b/apps/showcase/pages/core/hosts.html new file mode 100644 index 0000000..5a74e50 --- /dev/null +++ b/apps/showcase/pages/core/hosts.html @@ -0,0 +1,252 @@ + + + + + + Hosts | Velora + + + + + + + + + + + + + + Skip to content + + +
+
+ + + + +
+ + +
+ + + +
+ + + + Get Started +
+ +
+ +
+
+
+
+
+

Hosts // motion without Skins

+

Same vl-*. Your UI.

+

+ Import @velora/css/motion-core (optional transitions). Style with Tailwind or any host. + Do not require .vl-card or Skins tokens for scenes to run. +

+ +

Repo example: examples/tailwind-host/ (serve from repo root). Vite Showcase does not mount that folder.

+
<section vl-scene vl-timeline="view" vl-pin="3" vl-scrub>
+  <div vl-stage class="min-h-svh grid place-items-center">
+    <h1 vl-enter="clip-rise" vl-act="1">Host classes + Velora clock</h1>
+  </div>
+</section>
+
+
+
+
+ + + + + + + + diff --git a/apps/showcase/pages/core/skins.html b/apps/showcase/pages/core/skins.html new file mode 100644 index 0000000..2937210 --- /dev/null +++ b/apps/showcase/pages/core/skins.html @@ -0,0 +1,274 @@ + + + + + + Skins | Velora + + + + + + + + + + + + + + Skip to content + + +
+
+ + + + +
+ + +
+ + + +
+ + + + Get Started +
+ +
+ +
+
+
+
+
+

Skins // named design-system models

+

One kit. Four models. Change the whole project.

+

+ Skins are the Velora visual system — not the motion grammar. + Set html[data-editorial-theme] (Noir, Earth, Aethel, Meridian) and + data-theme (dark / light). Tokens restyle header, type, cards, and controls together. +

+

Use the editorial select in the header. Import @velora/css/theme (or full). Motion does not require Skins — see Hosts.

+
+
+
+
+

Named models

+
+

noir

Noir

Dark kinetic base. Default Showcase skin.

+

earth

Earth

Warm light laboratory. Light theme pack.

+

aethel

Aethel

Architectural concepts. Dark geometry.

+

meridian

Meridian

Terrain intelligence. Cartographic dark.

+
+
+
+
+
+

Replicable kit

+

Same components; the skin retokens them.

+
+ Primary + Ghost + +
+
+

Look of a Skin

+

.vl-card is visual chrome — not a motion requirement.

+
+
<html data-editorial-theme="noir" data-theme="dark">
+@import "@velora/css/theme";
+
+
+
+
+ + + + + + + + diff --git a/apps/showcase/pages/motion/api-motion-catalog.html b/apps/showcase/pages/motion/api-motion-catalog.html index 0f93428..71b7de9 100644 --- a/apps/showcase/pages/motion/api-motion-catalog.html +++ b/apps/showcase/pages/motion/api-motion-catalog.html @@ -23,6 +23,7 @@ Stage 3D Interaction Scroll + Scene Transitions Runtime @@ -50,13 +51,13 @@ -
-
+
+ Nenhum card
- Selected: none -
+
+

Clique num card para ver o que está em motion.

+ +
+
@@ -201,22 +206,30 @@

Motion API as composed a

-
- - - 0ms -
-
- - - 1400ms -
-
- - - 1.00x +
+
+ + + 0ms +
+
+ + + 1400ms +
+
+ + + 1.00x +
-
+ +
+
@@ -241,6 +254,21 @@

Motion API as composed a

+
+
+
+

Engine

+

motion-core

+

Host-agnostic scene + channel motion: vl-scene, vl-stage, vl-act, vl-enter / vl-scroll / vl-hover. Works with any UI host.

+
+
+

Skins

+

theme + recipes

+

Named look via data-editorial-theme and optional vl-scene="…" recipes. Not required for motion. See Skins.

+
+
+
+

Channel Architecture

@@ -320,9 +348,17 @@

Timeline Modes

Entrances and 3D

-

View-timeline entry effects — from basic fade-in to extended 3D behaviours.

+

View-timeline entry effects — prefer vl-enter for channel-first authorship; vl-effect remains a supported alias.

+
+ Channel-first · vl-enter +

Stable CONTRACT extensions — same presets as vl-effect, bound to the enter channel.

+
reveal-cinematicdepth-entermask-sweep
+
+
vl-enter · reveal-cinematic
Reveal Cinematic
+
vl-enter · depth-enter
Depth Enter
+
vl-enter · mask-sweep
Mask Sweep
fade-in
Fade In
fade-up
Fade Up
slide-left
Slide Left
@@ -341,43 +377,36 @@

Entrances and 3D

Stage 3D

-

Bloco dedicado para efeitos e cenas 3D da API. Esta seção centraliza os casos que já estão estáveis e mantém espaço para a evolução do Stage 3D em paralelo.

+

Contrato reutilizável de palco 3D (perspective, preserve-3d, triggers). Demos de cubos ficam em Cube Triad (experimental).

- Roadmap Stage 3D + Stage 3D baseline
<!-- canonical 3D stage baseline -->
 <section vl-scene vl-scene-trigger="zone">
   <div vl-scene-trigger-zone tabindex="0"></div>
   <article vl-effect="spring-3d" vl-timeline="view">...</article>
 </section>
-

Contrato base já canônico e funcional. Expansões de Stage 3D podem evoluir neste bloco sem quebrar os demos atuais.

+

Positional stage contract — not tied to cube-triad choreography.

spring-3d
Entrada 3D com timeline view
hover-tilt-3d
Interação 3D por hover/focus
-
cube-triad-stage
-
-
-
-
-
- -
-
-
-
-
-
-

Hover and Interaction

-

Pointer, link and surface feedback, with a dedicated orb-grid mouse-tracking stage. Card-based hover components live in a dedicated section below.

+

Pointer feedback — prefer vl-hover for channel-first presets. Extended interaction cards below may still use vl-effect.

+
+ Channel-first · vl-hover +

Stable hover channel extensions — gradient sweep and border trace.

+
+
vl-hover · gradient-sweep
Gradient Sweep
+
vl-hover · border-trace
Border Trace
+
hover-lift
Hover me
hover-glow
Hover me
@@ -411,8 +440,8 @@

Hover Card Components

Card-first interaction components grouped in their own section for API and component testing.

-
hover-card-fan

Group hover fan behavior for card components.

-
hover-card-stack
ABC
+
hover-card-fan

Hover a card — siblings fan on the Y axis. Group container uses preserve-3d.

+
hover-card-stack
Layer ALayer BLayer C

Hover the stack — parent tilts in 3D and layers lift with staggered depth.

hover-cross-swap
Corehover any edge
duotone
Velora logo duotone test

Token-driven duotone blend for image-based components.

@@ -593,7 +622,7 @@

Ambient and Continuous

Cube Triad

-

Dedicated section for the cascading 3D cube choreography effect.

+

Experimental extended effect — not the reusable stage-3d contract. Prefer positional stage-3d vars for production stages.

@@ -626,9 +655,17 @@

Cube Triad

Scroll and Timeline

-

Effects driven by view and scroll timelines — parallax, marquee, wipe and depth.

+

Scroll-linked motion — use vl-scroll for channel-first presets; legacy cards below still use vl-effect.

+
+ Channel-first · vl-scroll +

Stable scroll channel values from CONTRACT — scrub ranges are preset-defined on the channel.

+
+
vl-scroll · reveal
Scroll Reveal
+
vl-scroll · media-zoom
Media Zoom
+
vl-scroll · crossfade
Crossfade Scrub
+
vl-scroll · text-highlight
Highlight sweeps as you scroll
cinema-zoom
Cinema Zoom
parallax
Parallax
scroll-marquee
APIMOTIONSCROLLMARQUEE
@@ -641,79 +678,86 @@

Scroll and Timeline

-
+
-

Pin Stories

-

Extended pin showcase with a large stage and safe header offset — long-form narrative scrolling with fixed anchor points.

+

Scene engine

+

Host-agnostic track / stage / acts — shared clock, numeric pin, scrub. Canonical authorship for scroll stories. Full page: Scene Timeline.

- Pin recipe -
<section
- vl-timeline="scroll"
- vl-pin="top"
- vl-range="entry 0% cover 78%">
- <aside vl-pin="top">pinned anchor</aside>
- <article>narrative scroll steps</article>
+      Baseline recipe
+      
<section vl-scene vl-timeline="view" vl-pin="3" vl-scrub>
+  <div vl-stage>
+    <h1 vl-enter="fade-up" vl-act="1">Title</h1>
+    <p vl-enter="fade-up" vl-act="2">Later act</p>
+  </div>
 </section>
-

Use vl-pin to keep a fixed anchor in view while the surrounding content advances through narrative steps.

+

vl-pin is numeric viewport heights on the track (vl-scene). Sticky subject is vl-stage. Values like top/center are not the scene-engine API (boolean vl-pin alone remains a legacy sticky helper).

-
- Pin Story - Large Stage -
- -
-
- Step 01 - Context Frame -

The first block opens the narrative while the pinned anchor maintains the visual chapter reference.

-
-
- Step 02 - Range Tuning -

Tune vl-range to control when each part enters. entry-short keeps the transition tight and precise.

-
-
- Step 03 - Scroll Scrub -

With vl-scrub, the animation tracks scroll in real time — easy to calibrate rhythm and pacing.

-
-
- Step 04 - Cover Phase -

Final phase — prepares the scene exit and hands off cleanly to the next block on the page.

-
-
- Step 05 - Exit Hand-off -

Closes with vl-once to prevent replay and preserve reading state when scrolling back up.

-
+
+ Live · pin + scrub + acts +
+
+

Act 1 · shared clock

+

Track · Stage · Acts

+

Act 2 enters on the same scene timeline.

+

Act 3–4 span · holds while the track scrubs.

+

Act 5 · hand-off

+

Scroll this card’s track. Motion is CSS-only (03c-scene-engine.css + channels). Kicker + title share vl-act="1" — same beat, overlapping range.

-
pin + scrub
Pinned Scrub
-
pin + range
Pin Center
+
+ Act overlap · same beat +
+
+

Shared act 1

+

Title + kicker overlap

+

Act 2 only after act 1 window

+
+
+

Assign the same vl-act to stage children to choreograph overlap on one beat.

+
-
- Session Overlap Scroll Demo -
-
-
- Section A - Base Layer -

Section A holds as a sticky base layer while the scroll continues beneath it.

-
-
- Section B - Overlap -

On scroll, Section B rises over A — layered reading maintained throughout.

-
-
- Section C - Top Layer -

Finally, Section C enters over B and completes the overlap sequence.

-
+
+ Auto clock · beat delays +
+
+

Beat 1 · immediate

+

Beat 2 · +1 beat delay

+

Beat 3 · +2 beat delay

-
+

vl-timeline="auto" maps acts to time delays (no scroll pin). Use for load-in sequences outside scroll stories.

+
+ +
+ Engine attrs +
+ vl-scene + vl-stage + vl-act + vl-span + vl-pin=1…6 + vl-scrub + overlap acts + auto clock +
+

vl-span extends an act across multiple beats (see live demo act 3–4).

+
+ +
+ Skin recipes (optional) +
+ cinematic-hero + sticky-story + glass-bento + product-reveal + editorial-cinema +
+

Named vl-scene="…" values need theme / full bundle. Prefer track/stage/acts for host-agnostic work. Skins

@@ -837,7 +881,8 @@

Ranges, Speed and Runtime Params

new vl-enter values
reveal-cinematicdepth-entermask-sweep
new vl-scroll values
revealmedia-zoomcrossfadetext-highlight
new vl-hover values
gradient-sweepborder-trace
-
new vl-scene values
cinematic-herosticky-storyglass-bentoproduct-revealeditorial-cinema
+
new vl-scene values
cinematic-herosticky-storyglass-bentoproduct-revealeditorial-cinema

Skin recipes (theme). Prefer track/stage/acts for host-agnostic scenes — see Scene Timeline.

+
scene engine
vl-stagevl-actvl-spanvl-pin=Nvl-scrub
loop aurora-drift
Aurora Drift Loop
vl-duration examples
120ms300ms800ms1200ms2s3.6s
vl-direction values
normalreversealternatealternate-reverse
@@ -878,50 +923,15 @@

Ranges, Speed and Runtime Params

- - - @@ -948,243 +958,7 @@
- + + + + Skip to content + + +
+
+ + + + +
+ + +
+ + + +
+ + + + Get Started +
+ +
+ +
+
+
+ +
+
+
+

Scene engine // Track · Stage · Acts

+

One clock. Many acts. Zero JS.

+

+ Write pin + scrub timelines like GSAP — with vl-scene, vl-stage, and vl-act. + Look comes from the host UI (Showcase skin here; Tailwind in examples). +

+ +
+
+
+ +
+
+

Act 1 · overlap

+

Shared beat

+

Act 2 — same track clock, later range.

+
+ Acts 3–4 span — media scrub while the stage stays pinned. +
+

Act 5 · CTA lands last

+
+
+ +
+
+
+

Host-agnostic

+

Same vl-*. Any UI.

+

+ Import @velora/css/motion-core only. Style with Tailwind (or anything). + Showcase / @velora/css/theme is the optional Velora look — not required for motion. +

+
<section vl-scene vl-timeline="view" vl-pin="3" vl-scrub>
+  <div vl-stage class="min-h-svh grid place-items-center gap-6">
+    <h1 class="text-5xl" vl-enter="clip-rise" vl-act="1">Title</h1>
+  </div>
+</section>
+ +
+
+
+ +
+
+
+

Recipes vs engine

+

Named presets stay as skin recipes.

+

+ vl-scene="cinematic-hero" and friends still work via scene-recipes.css (theme). + New work should prefer track / stage / acts for host portability. +

+
+
+
+ +
+
+ + + + + + + + diff --git a/apps/showcase/public/css/01-motion-tokens.css b/apps/showcase/public/css/01-motion-tokens.css new file mode 100644 index 0000000..4f19d34 --- /dev/null +++ b/apps/showcase/public/css/01-motion-tokens.css @@ -0,0 +1,52 @@ +@layer velora.tokens { + /* + * Motion engine knobs — host-agnostic. + * Safe to import with motion-core without Velora brand/visual tokens. + */ + :root { + --vl-ease-cinematic: cubic-bezier(0.2, 0.8, 0.2, 1); + --vl-ease-out-soft: cubic-bezier(0.16, 1, 0.3, 1); + --vl-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + --vl-ease-in-out-smooth: cubic-bezier(0.45, 0, 0.55, 1); + --vl-ease-fluid: cubic-bezier(0.25, 0.8, 0.25, 1); + --vl-ease-impact: cubic-bezier(0.0, 0.0, 0.2, 1); + --vl-ease-silk: cubic-bezier(0.4, 0.0, 0.2, 1); + --vl-ease-elastic: cubic-bezier(0.68, -0.55, 0.265, 1.55); + --vl-ease-smooth-out: cubic-bezier(0, 0.55, 0.45, 1); + + --vl-duration-instant: 80ms; + --vl-duration-fast: 150ms; + --vl-duration-normal: 300ms; + --vl-duration-slow: 500ms; + --vl-duration-slower: 800ms; + + --vl-stagger-step: 45ms; + --vl-beats: 8; + --vl-beat: 120ms; + + --vl-motion-duration: var(--vl-duration-normal); + --vl-motion-distance: 1.25rem; + --vl-motion-ease: var(--vl-ease-cinematic); + + --vl-motion-mode: standard; + --vl-motion-preference: auto; + --vl-motion-speed-scale: 1; + --vl-motion-intensity: 1; + --vl-motion-depth: 1; + --vl-motion-blur: 0px; + --vl-motion-mode-ease: var(--vl-ease-cinematic); + + --vl-vt-root-duration: 0.55s; + --vl-vt-shared-duration: 0.58s; + + --vl-transition-fast: var(--vl-duration-fast) var(--vl-ease-out-soft); + --vl-transition-normal: var(--vl-duration-normal) var(--vl-ease-cinematic); + --vl-transition-slow: var(--vl-duration-slow) var(--vl-ease-cinematic); + + --vl-z-base: 0; + --vl-z-dropdown: 100; + --vl-z-sticky: 200; + --vl-z-modal: 500; + --vl-z-toast: 800; + } +} diff --git a/apps/showcase/public/css/01-tokens.css b/apps/showcase/public/css/01-tokens.css index 74f0aca..f105676 100644 --- a/apps/showcase/public/css/01-tokens.css +++ b/apps/showcase/public/css/01-tokens.css @@ -1,419 +1,6 @@ -@layer velora.tokens { - /* - * Velora tokens — oklch(), prefixo --vl- - * Temas: sistema (prefers-color-scheme) + html[data-theme="light"|"dark"] - * Layout: html[data-layout="default"|"compact"|"presentation"] - */ - - :root { - /* --- Âncoras de cor (ajuste fino global) --- */ - --vl-hue-neutral: 110; - --vl-hue-accent: 128; - --vl-chroma-surface: 0.02; - - /* --- Brand v2 official palette --- */ - --vl-brand-stone: #c8c9b0; - --vl-brand-olive: #a9b08b; - --vl-brand-moss: #7f8668; - --vl-brand-deep: #5c604c; - - --vl-color-primary: var(--vl-brand-moss); - --vl-color-primary-soft: color-mix(in oklch, var(--vl-color-primary) 18%, transparent); - --vl-color-secondary: var(--vl-brand-olive); - --vl-color-accent: var(--vl-brand-deep); - --vl-color-success: #6f8661; - --vl-color-warning: #c1ab72; - --vl-color-danger: #8a5f50; - - /* --- Surfaces — light default --- */ - --vl-bg-main: #e3e3cd; - --vl-bg-surface: #f3f2e6; - --vl-bg-surface-elevated: #ecebd9; - --vl-bg-inset: #d7d8c0; - --vl-bg-overlay: oklch(96% 0.01 var(--vl-hue-neutral) / 0.8); - - /* --- Bordas --- */ - --vl-border-subtle: #bfc1aa; - --vl-border-strong: #a9b08b; - --vl-border-focus: color-mix(in oklch, var(--vl-color-primary) 55%, transparent); - - /* --- Texto --- */ - --vl-text-primary: #0a0a0a; - --vl-text-secondary: #232321; - --vl-text-muted: #6f705f; - --vl-text-inverse: #f3f2e6; - - /* --- Tipografia (UI clean + display futurista; carregar fontes no HTML do app) --- */ - --vl-font-family: - "Manrope", "Plus Jakarta Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", - Roboto, sans-serif; - --vl-font-family-display: - "Space Grotesk", "Outfit", "Plus Jakarta Sans", system-ui, sans-serif; - --vl-font-family-mono: - "JetBrains Mono", "SF Mono", ui-monospace, "Cascadia Code", monospace; - - --vl-font-size-base: 16px; - --vl-font-size-xs: 0.75rem; - --vl-font-size-sm: 0.875rem; - --vl-font-size-md: 1rem; - --vl-font-size-lg: 1.125rem; - --vl-font-size-xl: 1.25rem; - --vl-font-size-2xl: 1.5rem; - --vl-font-size-3xl: 2rem; - --vl-font-size-4xl: clamp(2.25rem, 5vw, 3.5rem); - - --vl-font-weight-normal: 400; - --vl-font-weight-medium: 500; - --vl-font-weight-semibold: 600; - --vl-font-weight-bold: 700; - - --vl-leading-tight: 1.2; - --vl-leading-snug: 1.35; - --vl-leading-normal: 1.5; - --vl-leading-relaxed: 1.65; - - --vl-tracking-tight: -0.02em; - --vl-tracking-normal: 0; - --vl-tracking-wide: 0.04em; - --vl-tracking-display: 0.06em; - - /* --- Scrollbar (reset) --- */ - --vl-scrollbar-width: 10px; - --vl-scrollbar-track: #d7d8c0; - --vl-scrollbar-thumb: #a9b08b; - --vl-scrollbar-thumb-hover: #7f8668; - - /* --- Espaçamento --- */ - --vl-space-2xs: 0.125rem; - --vl-space-xs: 0.25rem; - --vl-space-sm: 0.5rem; - --vl-space-md: 1rem; - --vl-space-lg: 2rem; - --vl-space-xl: 4rem; - --vl-space-2xl: 6rem; - --vl-space-3xl: 8rem; - - /* --- Raios --- */ - --vl-radius-sm: 0.375rem; - --vl-radius-md: 0.5rem; - --vl-radius-lg: 0.75rem; - --vl-radius-xl: 1rem; - --vl-radius-2xl: 1.5rem; - --vl-radius-full: 999px; - - /* --- Layout (containers — usar em velora.layout) --- */ - --vl-container-max: 72rem; - --vl-container-max-narrow: 42rem; - --vl-container-max-wide: 90rem; - --vl-container-gutter: var(--vl-space-md); - --vl-measure-prose: 65ch; - /* Container query breakpoints (largura do container nomeado, não do viewport) */ - --vl-cq-sm: 36rem; - --vl-cq-md: 48rem; - --vl-cq-lg: 56rem; - --vl-cq-xl: 72rem; - - /* --- Elevação --- */ - --vl-shadow-sm: 0 1px 2px oklch(22% 0.02 var(--vl-hue-neutral) / 0.06); - --vl-shadow-md: - 0 4px 6px -1px oklch(22% 0.02 var(--vl-hue-neutral) / 0.08), - 0 2px 4px -2px oklch(22% 0.02 var(--vl-hue-neutral) / 0.06); - --vl-shadow-lg: - 0 10px 15px -3px oklch(22% 0.02 var(--vl-hue-neutral) / 0.1), - 0 4px 6px -4px oklch(22% 0.02 var(--vl-hue-neutral) / 0.08); - --vl-shadow-glow: - 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 25%, transparent), - 0 12px 40px -12px color-mix(in oklch, var(--vl-color-primary) 35%, transparent); - - /* --- Vidro / blur --- */ - --vl-backdrop-blur: 12px; - --vl-glass-bg: color-mix(in oklch, var(--vl-bg-surface) 85%, transparent); - - /* --- Motion --- */ - --vl-ease-cinematic: cubic-bezier(0.2, 0.8, 0.2, 1); - --vl-ease-out-soft: cubic-bezier(0.16, 1, 0.3, 1); - --vl-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); - --vl-ease-in-out-smooth: cubic-bezier(0.45, 0, 0.55, 1); - --vl-ease-fluid: cubic-bezier(0.25, 0.8, 0.25, 1); - --vl-ease-impact: cubic-bezier(0.0, 0.0, 0.2, 1); - --vl-ease-silk: cubic-bezier(0.4, 0.0, 0.2, 1); - - --vl-duration-instant: 80ms; - --vl-duration-fast: 150ms; - --vl-duration-normal: 300ms; - --vl-duration-slow: 500ms; - --vl-duration-slower: 800ms; - - --vl-stagger-step: 45ms; - - /* Handbook motion aliases (used by vl-* attribute system) */ - --vl-motion-duration: var(--vl-duration-normal); - --vl-motion-distance: 1.25rem; - --vl-motion-ease: var(--vl-ease-cinematic); - - /* --- Conditional Motion Engine (see 03a-motion-conditions.css) --- - * Central knobs consumed by the vl-motion="standard|subtle|cinematic|still" - * grammar. Presets read these instead of hardcoding per-selector values, so - * a single mode declaration adapts timing, travel, depth and blur globally. - * Defaults below are the neutral "standard" baseline (scale 1 / offset 0), - * so motion behaves identically when no vl-motion mode is applied. - */ - --vl-motion-mode: standard; /* active engine mode (queried via if(style())) */ - --vl-motion-preference: auto; /* auto | reduce — reflects reduced-motion intent */ - --vl-motion-speed-scale: 1; /* duration multiplier */ - --vl-motion-intensity: 1; /* travel-distance / displacement multiplier */ - --vl-motion-depth: 1; /* parallax / z-depth multiplier */ - --vl-motion-blur: 0px; /* additive entrance blur for cinematic mode */ - --vl-motion-mode-ease: var(--vl-ease-cinematic); /* easing selected by mode */ - - /* View Transitions (MPA) — 05-transitions.css */ - --vl-vt-root-duration: 0.55s; - --vl-vt-shared-duration: 0.58s; - - --vl-transition-fast: var(--vl-duration-fast) var(--vl-ease-out-soft); - --vl-transition-normal: var(--vl-duration-normal) var(--vl-ease-cinematic); - --vl-transition-slow: var(--vl-duration-slow) var(--vl-ease-cinematic); - - /* --- Gradiente (hero / texto clip) --- */ - --vl-gradient-brand: linear-gradient( - 135deg, - #c8c9b0 0%, - #a9b08b 42%, - #7f8668 76%, - #5c604c 100% - ); - --vl-gradient-canvas-beam: linear-gradient( - 135deg, - #f3f2e6 0%, - #e3e3cd 38%, - #c8c9b0 72%, - #a9b08b 100% - ); - --vl-gradient-olive-drift: linear-gradient( - 135deg, - #e3e3cd 0%, - #c8c9b0 35%, - #a9b08b 70%, - #7f8668 100% - ); - --vl-gradient-deep-motion: linear-gradient( - 135deg, - #c8c9b0 0%, - #a9b08b 30%, - #5c604c 70%, - #232321 100% - ); - --vl-gradient-surface: linear-gradient( - 180deg, - color-mix(in oklch, var(--vl-bg-surface) 96%, white), - color-mix(in oklch, var(--vl-bg-main) 94%, transparent) - ); - - /* --- z-index --- */ - --vl-z-base: 0; - --vl-z-dropdown: 100; - --vl-z-sticky: 200; - --vl-z-modal: 500; - --vl-z-toast: 800; - - /* --- Foco (acessível — usar com :focus-visible em components/utilities) --- */ - --vl-focus-ring-width: 2px; - --vl-focus-ring-offset: 2px; - --vl-focus-ring-color: color-mix(in oklch, var(--vl-color-primary) 65%, transparent); - - /* --- Logo filter (adapts per theme) --- */ - --vl-logo-filter: brightness(0); - --vl-logo-opacity: 0.9; - - /* --- Tipografia premium (serif para editorial / Awwwards feel) --- */ - --vl-font-family-serif: - "Cormorant Garamond", "Playfair Display", "DM Serif Display", Georgia, "Times New Roman", serif; - - /* --- Gradientes premium --- */ - --vl-gradient-aurora: linear-gradient( - 135deg, - oklch(55% 0.2 275), - oklch(58% 0.22 320), - oklch(62% 0.14 155), - oklch(55% 0.2 275) - ); - --vl-gradient-sunset: linear-gradient( - 135deg, - oklch(62% 0.22 350), - oklch(72% 0.18 50), - oklch(78% 0.15 85) - ); - --vl-gradient-ocean: linear-gradient( - 135deg, - oklch(45% 0.18 240), - oklch(55% 0.2 275), - oklch(58% 0.16 200) - ); - --vl-gradient-neutral: linear-gradient( - 135deg, - oklch(28% 0.02 var(--vl-hue-neutral)), - oklch(22% 0.02 var(--vl-hue-neutral)) - ); - - /* --- Sombras premium --- */ - --vl-shadow-dramatic: - 0 25px 50px -12px oklch(0% 0 0 / 0.25), - 0 12px 24px -8px oklch(0% 0 0 / 0.15); - --vl-shadow-inner: inset 0 2px 4px 0 oklch(0% 0 0 / 0.06); - --vl-shadow-float: - 0 20px 60px -15px color-mix(in oklch, var(--vl-color-primary) 20%, oklch(0% 0 0 / 0.15)), - 0 8px 20px -5px oklch(0% 0 0 / 0.1); - - /* --- Noise / grain --- */ - --vl-noise-opacity: 0.035; - - /* --- Easing premium --- */ - --vl-ease-elastic: cubic-bezier(0.68, -0.55, 0.265, 1.55); - --vl-ease-smooth-out: cubic-bezier(0, 0.55, 0.45, 1); - - color-scheme: light; - } - - /* Tema escuro automático quando não há data-theme */ - @media (prefers-color-scheme: dark) { - :root:not([data-theme]) { - --vl-bg-main: #0d0f0c; - --vl-bg-surface: #171b16; - --vl-bg-surface-elevated: #1c211b; - --vl-bg-surface-bright: #272e25; - --vl-bg-inset: #111410; - --vl-bg-overlay: oklch(8% 0.015 var(--vl-hue-neutral) / 0.82); - - --vl-border-subtle: #434a41; - --vl-border-strong: #71776e; - - --vl-text-primary: #e1e7dc; - --vl-text-secondary: #a7ada2; - --vl-text-muted: #71776e; - --vl-text-inverse: #0a0a0a; - - --vl-shadow-sm: 0 1px 2px oklch(0% 0 0 / 0.06); - --vl-shadow-md: - 0 4px 6px -1px oklch(0% 0 0 / 0.08), - 0 2px 4px -2px oklch(0% 0 0 / 0.06); - --vl-shadow-lg: - 0 10px 15px -3px oklch(0% 0 0 / 0.1), - 0 4px 6px -4px oklch(0% 0 0 / 0.08); - --vl-shadow-glow: - 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 25%, transparent), - 0 12px 40px -12px color-mix(in oklch, var(--vl-color-primary) 35%, transparent); - --vl-shadow-dramatic: - 0 25px 50px -12px oklch(0% 0 0 / 0.25), - 0 12px 24px -8px oklch(0% 0 0 / 0.15); - - --vl-scrollbar-track: #171b16; - --vl-scrollbar-thumb: #5c604c; - --vl-scrollbar-thumb-hover: #7f8668; - - --vl-glass-bg: color-mix(in oklch, var(--vl-bg-surface) 72%, transparent); - - --vl-logo-filter: brightness(0) invert(1); - --vl-logo-opacity: 0.88; - - color-scheme: dark; - } - } - - /* Tema explícito (sobrepõe preferência do sistema) */ - :root[data-theme="light"] { - --vl-bg-main: #e3e3cd; - --vl-bg-surface: #f3f2e6; - --vl-bg-surface-elevated: #ecebd9; - --vl-bg-inset: #d7d8c0; - --vl-bg-overlay: oklch(96% 0.01 var(--vl-hue-neutral) / 0.8); - - --vl-border-subtle: #bfc1aa; - --vl-border-strong: #a9b08b; - - --vl-text-primary: #0a0a0a; - --vl-text-secondary: #232321; - --vl-text-muted: #6f705f; - --vl-text-inverse: #f3f2e6; - - --vl-shadow-sm: 0 1px 2px oklch(22% 0.02 var(--vl-hue-neutral) / 0.06); - --vl-shadow-md: - 0 4px 6px -1px oklch(22% 0.02 var(--vl-hue-neutral) / 0.08), - 0 2px 4px -2px oklch(22% 0.02 var(--vl-hue-neutral) / 0.06); - --vl-shadow-lg: - 0 10px 15px -3px oklch(22% 0.02 var(--vl-hue-neutral) / 0.1), - 0 4px 6px -4px oklch(22% 0.02 var(--vl-hue-neutral) / 0.08); - --vl-shadow-glow: - 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 18%, transparent), - 0 12px 36px -14px color-mix(in oklch, var(--vl-color-primary) 28%, transparent); - --vl-shadow-dramatic: - 0 25px 50px -12px oklch(22% 0.02 var(--vl-hue-neutral) / 0.15), - 0 12px 24px -8px oklch(22% 0.02 var(--vl-hue-neutral) / 0.1); - - --vl-scrollbar-track: #d7d8c0; - --vl-scrollbar-thumb: #a9b08b; - --vl-scrollbar-thumb-hover: #7f8668; - - --vl-glass-bg: color-mix(in oklch, var(--vl-bg-surface) 85%, transparent); - - --vl-logo-filter: brightness(0); - --vl-logo-opacity: 0.85; - - color-scheme: light; - } - - :root[data-theme="dark"] { - --vl-bg-main: #0d0f0c; - --vl-bg-surface: #171b16; - --vl-bg-surface-elevated: #1c211b; - --vl-bg-surface-bright: #272e25; - --vl-bg-inset: #111410; - --vl-bg-overlay: oklch(8% 0.015 var(--vl-hue-neutral) / 0.82); - - --vl-border-subtle: #434a41; - --vl-border-strong: #71776e; - - --vl-text-primary: #e1e7dc; - --vl-text-secondary: #a7ada2; - --vl-text-muted: #71776e; - --vl-text-inverse: #0d0f0c; - - --vl-shadow-sm: 0 1px 2px oklch(0% 0 0 / 0.06); - --vl-shadow-md: - 0 4px 6px -1px oklch(0% 0 0 / 0.08), - 0 2px 4px -2px oklch(0% 0 0 / 0.06); - --vl-shadow-lg: - 0 10px 15px -3px oklch(0% 0 0 / 0.1), - 0 4px 6px -4px oklch(0% 0 0 / 0.08); - --vl-shadow-glow: - 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 25%, transparent), - 0 12px 40px -12px color-mix(in oklch, var(--vl-color-primary) 35%, transparent); - - --vl-scrollbar-track: #171b16; - --vl-scrollbar-thumb: #5c604c; - --vl-scrollbar-thumb-hover: #7f8668; - - --vl-glass-bg: color-mix(in oklch, var(--vl-bg-surface) 72%, transparent); - - --vl-logo-filter: brightness(0) invert(1); - --vl-logo-opacity: 0.88; - - color-scheme: dark; - } - - /* Densidade / “nível” de layout (ex.: compacto = mais conteúdo visível) */ - :root[data-layout="compact"] { - --vl-font-size-base: 15px; - --vl-space-md: 0.8125rem; - --vl-space-lg: 1.5rem; - --vl-space-xl: 2.5rem; - --vl-container-gutter: var(--vl-space-sm); - } - - :root[data-layout="presentation"] { - --vl-font-size-base: 17px; - --vl-space-md: 1.125rem; - --vl-space-lg: 2.25rem; - --vl-space-xl: 5rem; - --vl-leading-normal: 1.6; - } -} +/** + * Aggregator: motion knobs + visual/brand tokens. + * Prefer @velora/css/motion-core (motion only) or @velora/css/theme (visual) for hosts. + */ +@import "./01-motion-tokens.css"; +@import "./01-visual-tokens.css"; diff --git a/apps/showcase/public/css/01-visual-tokens.css b/apps/showcase/public/css/01-visual-tokens.css new file mode 100644 index 0000000..2cb040f --- /dev/null +++ b/apps/showcase/public/css/01-visual-tokens.css @@ -0,0 +1,335 @@ +@layer velora.tokens { + /* + * Visual / brand tokens — Velora skin (opt-in via @velora/css/theme or full). + * Not required for motion-core / host-agnostic scenes. + */ + :root { + --vl-hue-neutral: 110; + --vl-hue-accent: 128; + --vl-chroma-surface: 0.02; + + --vl-brand-stone: #c8c9b0; + --vl-brand-olive: #a9b08b; + --vl-brand-moss: #7f8668; + --vl-brand-deep: #5c604c; + + --vl-color-primary: var(--vl-brand-moss); + --vl-color-primary-soft: color-mix(in oklch, var(--vl-color-primary) 18%, transparent); + --vl-color-secondary: var(--vl-brand-olive); + --vl-color-accent: var(--vl-brand-deep); + --vl-color-success: #6f8661; + --vl-color-warning: #c1ab72; + --vl-color-danger: #8a5f50; + + --vl-bg-main: #e3e3cd; + --vl-bg-surface: #f3f2e6; + --vl-bg-surface-elevated: #ecebd9; + --vl-bg-inset: #d7d8c0; + --vl-bg-overlay: oklch(96% 0.01 var(--vl-hue-neutral) / 0.8); + + --vl-border-subtle: #bfc1aa; + --vl-border-strong: #a9b08b; + --vl-border-focus: color-mix(in oklch, var(--vl-color-primary) 55%, transparent); + + --vl-text-primary: #0a0a0a; + --vl-text-secondary: #232321; + --vl-text-muted: #6f705f; + --vl-text-inverse: #f3f2e6; + + --vl-font-family: + "Manrope", "Plus Jakarta Sans", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + Roboto, sans-serif; + --vl-font-family-display: + "Space Grotesk", "Outfit", "Plus Jakarta Sans", system-ui, sans-serif; + --vl-font-family-mono: + "JetBrains Mono", "SF Mono", ui-monospace, "Cascadia Code", monospace; + --vl-font-family-serif: + "Cormorant Garamond", "Playfair Display", "DM Serif Display", Georgia, "Times New Roman", serif; + + --vl-font-size-base: 16px; + --vl-font-size-xs: 0.75rem; + --vl-font-size-sm: 0.875rem; + --vl-font-size-md: 1rem; + --vl-font-size-lg: 1.125rem; + --vl-font-size-xl: 1.25rem; + --vl-font-size-2xl: 1.5rem; + --vl-font-size-3xl: 2rem; + --vl-font-size-4xl: clamp(2.25rem, 5vw, 3.5rem); + + --vl-font-weight-normal: 400; + --vl-font-weight-medium: 500; + --vl-font-weight-semibold: 600; + --vl-font-weight-bold: 700; + + --vl-leading-tight: 1.2; + --vl-leading-snug: 1.35; + --vl-leading-normal: 1.5; + --vl-leading-relaxed: 1.65; + + --vl-tracking-tight: -0.02em; + --vl-tracking-normal: 0; + --vl-tracking-wide: 0.04em; + --vl-tracking-display: 0.06em; + + --vl-scrollbar-width: 10px; + --vl-scrollbar-track: #d7d8c0; + --vl-scrollbar-thumb: #a9b08b; + --vl-scrollbar-thumb-hover: #7f8668; + + --vl-space-2xs: 0.125rem; + --vl-space-xs: 0.25rem; + --vl-space-sm: 0.5rem; + --vl-space-md: 1rem; + --vl-space-lg: 2rem; + --vl-space-xl: 4rem; + --vl-space-2xl: 6rem; + --vl-space-3xl: 8rem; + + --vl-radius-sm: 0.375rem; + --vl-radius-md: 0.5rem; + --vl-radius-lg: 0.75rem; + --vl-radius-xl: 1rem; + --vl-radius-2xl: 1.5rem; + --vl-radius-full: 999px; + + --vl-container-max: 72rem; + --vl-container-max-narrow: 42rem; + --vl-container-max-wide: 90rem; + --vl-container-gutter: var(--vl-space-md); + --vl-measure-prose: 65ch; + --vl-cq-sm: 36rem; + --vl-cq-md: 48rem; + --vl-cq-lg: 56rem; + --vl-cq-xl: 72rem; + + --vl-shadow-sm: 0 1px 2px oklch(22% 0.02 var(--vl-hue-neutral) / 0.06); + --vl-shadow-md: + 0 4px 6px -1px oklch(22% 0.02 var(--vl-hue-neutral) / 0.08), + 0 2px 4px -2px oklch(22% 0.02 var(--vl-hue-neutral) / 0.06); + --vl-shadow-lg: + 0 10px 15px -3px oklch(22% 0.02 var(--vl-hue-neutral) / 0.1), + 0 4px 6px -4px oklch(22% 0.02 var(--vl-hue-neutral) / 0.08); + --vl-shadow-glow: + 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 25%, transparent), + 0 12px 40px -12px color-mix(in oklch, var(--vl-color-primary) 35%, transparent); + --vl-shadow-dramatic: + 0 25px 50px -12px oklch(0% 0 0 / 0.25), + 0 12px 24px -8px oklch(0% 0 0 / 0.15); + --vl-shadow-inner: inset 0 2px 4px 0 oklch(0% 0 0 / 0.06); + --vl-shadow-float: + 0 20px 60px -15px color-mix(in oklch, var(--vl-color-primary) 20%, oklch(0% 0 0 / 0.15)), + 0 8px 20px -5px oklch(0% 0 0 / 0.1); + + --vl-backdrop-blur: 12px; + --vl-glass-bg: color-mix(in oklch, var(--vl-bg-surface) 85%, transparent); + + --vl-gradient-brand: linear-gradient( + 135deg, + #c8c9b0 0%, + #a9b08b 42%, + #7f8668 76%, + #5c604c 100% + ); + --vl-gradient-canvas-beam: linear-gradient( + 135deg, + #f3f2e6 0%, + #e3e3cd 38%, + #c8c9b0 72%, + #a9b08b 100% + ); + --vl-gradient-olive-drift: linear-gradient( + 135deg, + #e3e3cd 0%, + #c8c9b0 35%, + #a9b08b 70%, + #7f8668 100% + ); + --vl-gradient-deep-motion: linear-gradient( + 135deg, + #c8c9b0 0%, + #a9b08b 30%, + #5c604c 70%, + #232321 100% + ); + --vl-gradient-surface: linear-gradient( + 180deg, + color-mix(in oklch, var(--vl-bg-surface) 96%, white), + color-mix(in oklch, var(--vl-bg-main) 94%, transparent) + ); + --vl-gradient-aurora: linear-gradient( + 135deg, + oklch(55% 0.2 275), + oklch(58% 0.22 320), + oklch(62% 0.14 155), + oklch(55% 0.2 275) + ); + --vl-gradient-sunset: linear-gradient( + 135deg, + oklch(62% 0.22 350), + oklch(72% 0.18 50), + oklch(78% 0.15 85) + ); + --vl-gradient-ocean: linear-gradient( + 135deg, + oklch(45% 0.18 240), + oklch(55% 0.2 275), + oklch(58% 0.16 200) + ); + --vl-gradient-neutral: linear-gradient( + 135deg, + oklch(28% 0.02 var(--vl-hue-neutral)), + oklch(22% 0.02 var(--vl-hue-neutral)) + ); + + --vl-focus-ring-width: 2px; + --vl-focus-ring-offset: 2px; + --vl-focus-ring-color: color-mix(in oklch, var(--vl-color-primary) 65%, transparent); + + --vl-logo-filter: brightness(0); + --vl-logo-opacity: 0.9; + + --vl-noise-opacity: 0.035; + + color-scheme: light; + } + + @media (prefers-color-scheme: dark) { + :root:not([data-theme]) { + --vl-bg-main: #0d0f0c; + --vl-bg-surface: #171b16; + --vl-bg-surface-elevated: #1c211b; + --vl-bg-surface-bright: #272e25; + --vl-bg-inset: #111410; + --vl-bg-overlay: oklch(8% 0.015 var(--vl-hue-neutral) / 0.82); + + --vl-border-subtle: #434a41; + --vl-border-strong: #71776e; + + --vl-text-primary: #e1e7dc; + --vl-text-secondary: #a7ada2; + --vl-text-muted: #71776e; + --vl-text-inverse: #0a0a0a; + + --vl-shadow-sm: 0 1px 2px oklch(0% 0 0 / 0.06); + --vl-shadow-md: + 0 4px 6px -1px oklch(0% 0 0 / 0.08), + 0 2px 4px -2px oklch(0% 0 0 / 0.06); + --vl-shadow-lg: + 0 10px 15px -3px oklch(0% 0 0 / 0.1), + 0 4px 6px -4px oklch(0% 0 0 / 0.08); + --vl-shadow-glow: + 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 25%, transparent), + 0 12px 40px -12px color-mix(in oklch, var(--vl-color-primary) 35%, transparent); + --vl-shadow-dramatic: + 0 25px 50px -12px oklch(0% 0 0 / 0.25), + 0 12px 24px -8px oklch(0% 0 0 / 0.15); + + --vl-scrollbar-track: #171b16; + --vl-scrollbar-thumb: #5c604c; + --vl-scrollbar-thumb-hover: #7f8668; + + --vl-glass-bg: color-mix(in oklch, var(--vl-bg-surface) 72%, transparent); + + --vl-logo-filter: brightness(0) invert(1); + --vl-logo-opacity: 0.88; + + color-scheme: dark; + } + } + + :root[data-theme="light"] { + --vl-bg-main: #e3e3cd; + --vl-bg-surface: #f3f2e6; + --vl-bg-surface-elevated: #ecebd9; + --vl-bg-inset: #d7d8c0; + --vl-bg-overlay: oklch(96% 0.01 var(--vl-hue-neutral) / 0.8); + + --vl-border-subtle: #bfc1aa; + --vl-border-strong: #a9b08b; + + --vl-text-primary: #0a0a0a; + --vl-text-secondary: #232321; + --vl-text-muted: #6f705f; + --vl-text-inverse: #f3f2e6; + + --vl-shadow-sm: 0 1px 2px oklch(22% 0.02 var(--vl-hue-neutral) / 0.06); + --vl-shadow-md: + 0 4px 6px -1px oklch(22% 0.02 var(--vl-hue-neutral) / 0.08), + 0 2px 4px -2px oklch(22% 0.02 var(--vl-hue-neutral) / 0.06); + --vl-shadow-lg: + 0 10px 15px -3px oklch(22% 0.02 var(--vl-hue-neutral) / 0.1), + 0 4px 6px -4px oklch(22% 0.02 var(--vl-hue-neutral) / 0.08); + --vl-shadow-glow: + 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 18%, transparent), + 0 12px 36px -14px color-mix(in oklch, var(--vl-color-primary) 28%, transparent); + --vl-shadow-dramatic: + 0 25px 50px -12px oklch(22% 0.02 var(--vl-hue-neutral) / 0.15), + 0 12px 24px -8px oklch(22% 0.02 var(--vl-hue-neutral) / 0.1); + + --vl-scrollbar-track: #d7d8c0; + --vl-scrollbar-thumb: #a9b08b; + --vl-scrollbar-thumb-hover: #7f8668; + + --vl-glass-bg: color-mix(in oklch, var(--vl-bg-surface) 85%, transparent); + + --vl-logo-filter: brightness(0); + --vl-logo-opacity: 0.85; + + color-scheme: light; + } + + :root[data-theme="dark"] { + --vl-bg-main: #0d0f0c; + --vl-bg-surface: #171b16; + --vl-bg-surface-elevated: #1c211b; + --vl-bg-surface-bright: #272e25; + --vl-bg-inset: #111410; + --vl-bg-overlay: oklch(8% 0.015 var(--vl-hue-neutral) / 0.82); + + --vl-border-subtle: #434a41; + --vl-border-strong: #71776e; + + --vl-text-primary: #e1e7dc; + --vl-text-secondary: #a7ada2; + --vl-text-muted: #71776e; + --vl-text-inverse: #0d0f0c; + + --vl-shadow-sm: 0 1px 2px oklch(0% 0 0 / 0.06); + --vl-shadow-md: + 0 4px 6px -1px oklch(0% 0 0 / 0.08), + 0 2px 4px -2px oklch(0% 0 0 / 0.06); + --vl-shadow-lg: + 0 10px 15px -3px oklch(0% 0 0 / 0.1), + 0 4px 6px -4px oklch(0% 0 0 / 0.08); + --vl-shadow-glow: + 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 25%, transparent), + 0 12px 40px -12px color-mix(in oklch, var(--vl-color-primary) 35%, transparent); + + --vl-scrollbar-track: #171b16; + --vl-scrollbar-thumb: #5c604c; + --vl-scrollbar-thumb-hover: #7f8668; + + --vl-glass-bg: color-mix(in oklch, var(--vl-bg-surface) 72%, transparent); + + --vl-logo-filter: brightness(0) invert(1); + --vl-logo-opacity: 0.88; + + color-scheme: dark; + } + + :root[data-layout="compact"] { + --vl-font-size-base: 15px; + --vl-space-md: 0.8125rem; + --vl-space-lg: 1.5rem; + --vl-space-xl: 2.5rem; + --vl-container-gutter: var(--vl-space-sm); + } + + :root[data-layout="presentation"] { + --vl-font-size-base: 17px; + --vl-space-md: 1.125rem; + --vl-space-lg: 2.25rem; + --vl-space-xl: 5rem; + --vl-leading-normal: 1.6; + } +} diff --git a/apps/showcase/public/css/02-layout.css b/apps/showcase/public/css/02-layout.css index c5bbcbe..f2527fd 100644 --- a/apps/showcase/public/css/02-layout.css +++ b/apps/showcase/public/css/02-layout.css @@ -55,19 +55,8 @@ width: 100%; } - /* --- Scene containers -------------------------------------------------- - Scenes are local layout environments. They encapsulate responsive - behavior so multiple scenes can coexist on the same page without - depending on viewport-wide breakpoints. - ----------------------------------------------------------------------- */ + /* --- Scene containers (structural only — look helpers live in scene-recipes.css) */ [vl-scene] { - --vl-scene-gap: clamp(var(--vl-space-md), 2.4vw, var(--vl-space-xl)); - --vl-scene-pad-inline: clamp(var(--vl-space-md), 4vw, var(--vl-space-2xl)); - --vl-scene-pad-block: clamp(var(--vl-space-xl), 9vw, var(--vl-space-3xl)); - --vl-scene-max: 100%; - --vl-scene-pane-a: 1fr; - --vl-scene-pane-b: 1fr; - --vl-scene-cluster-min: 16rem; width: 100%; position: relative; isolation: isolate; @@ -75,79 +64,6 @@ container-name: vl-scene; } - [vl-scene][data-vl-scene-density="compact"] { - --vl-scene-gap: clamp(var(--vl-space-sm), 1.8vw, var(--vl-space-lg)); - --vl-scene-pad-inline: clamp(var(--vl-space-sm), 2.5vw, var(--vl-space-lg)); - --vl-scene-pad-block: clamp(var(--vl-space-lg), 5vw, var(--vl-space-xl)); - --vl-scene-cluster-min: 12rem; - } - - [vl-scene][data-vl-scene-density="editorial"] { - --vl-scene-gap: clamp(var(--vl-space-lg), 3vw, var(--vl-space-2xl)); - --vl-scene-pad-inline: clamp(var(--vl-space-md), 4.5vw, var(--vl-space-2xl)); - } - - [vl-scene][data-vl-scene-density="immersive"] { - --vl-scene-gap: clamp(var(--vl-space-lg), 3.2vw, var(--vl-space-2xl)); - --vl-scene-pad-inline: clamp(var(--vl-space-lg), 5vw, var(--vl-space-3xl)); - --vl-scene-pad-block: clamp(var(--vl-space-2xl), 10vw, calc(var(--vl-space-3xl) * 1.2)); - } - - [vl-scene][data-vl-scene-mode="full-bleed"] { - min-height: 100dvh; - align-content: center; - } - - [vl-scene] > :where(.vl-scene__inner, [data-vl-scene-inner]) { - display: grid; - gap: var(--vl-scene-gap); - width: min(100%, var(--vl-scene-max)); - margin-inline: auto; - padding-inline: var(--vl-scene-pad-inline); - min-width: 0; - } - - [vl-scene] :where(.vl-scene__layout--split, [data-vl-scene-layout~="split"]) { - display: grid; - grid-template-columns: minmax(0, 1fr); - gap: var(--vl-scene-gap); - align-items: start; - } - - [vl-scene] :where(.vl-scene__layout--cluster, [data-vl-scene-layout~="cluster"]) { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--vl-scene-cluster-min)), 1fr)); - gap: var(--vl-scene-gap); - } - - [vl-scene] :where(.vl-scene__rail, [data-vl-scene-role="rail"]) { - display: grid; - gap: var(--vl-space-sm); - align-content: start; - } - - [vl-scene] :where(.vl-scene__stack, [data-vl-scene-role="stack"]) { - display: grid; - gap: var(--vl-scene-gap); - min-width: 0; - } - - @container vl-scene (min-width: 48rem) { - [vl-scene] :where(.vl-scene__layout--split, [data-vl-scene-layout~="split"]) { - grid-template-columns: - minmax(0, var(--vl-scene-pane-a)) - minmax(0, var(--vl-scene-pane-b)); - align-items: center; - } - } - - @container vl-scene (max-width: 36rem) { - [vl-scene][data-vl-scene-density="immersive"] { - --vl-scene-pad-inline: clamp(var(--vl-space-md), 4vw, var(--vl-space-lg)); - --vl-scene-pad-block: clamp(var(--vl-space-xl), 6vw, var(--vl-space-2xl)); - } - } - /* --- Blocos verticais --- */ .vl-stack { display: flex; diff --git a/apps/showcase/public/css/03-motion.css b/apps/showcase/public/css/03-motion.css index 04b60d4..872c1b0 100644 --- a/apps/showcase/public/css/03-motion.css +++ b/apps/showcase/public/css/03-motion.css @@ -2230,102 +2230,7 @@ opacity: 1; } - /* ========================================================================= - NAMED SCENE PRESETS [vl-scene=""] - Provides ready-made layout + motion choreography for common surface types. - ========================================================================= */ - - /* cinematic-hero: full-viewport hero with depth-entry choreography */ - [vl-scene="cinematic-hero"] { - display: grid; - place-items: center; - min-height: 100svh; - overflow: clip; - isolation: isolate; - } - - [vl-scene="cinematic-hero"] > * { - animation: vl-reveal-cinematic var(--vl-duration-slower) var(--vl-ease-cinematic) both; - } - - [vl-scene="cinematic-hero"] > *:nth-child(2) { animation-delay: 160ms; } - [vl-scene="cinematic-hero"] > *:nth-child(3) { animation-delay: 300ms; } - [vl-scene="cinematic-hero"] > *:nth-child(4) { animation-delay: 440ms; } - [vl-scene="cinematic-hero"] > *:nth-child(n+5) { animation-delay: 560ms; } - - /* sticky-story: scroll-pinned storytelling section */ - [vl-scene="sticky-story"] { - position: sticky; - top: 0; - min-height: 100svh; - overflow: clip; - display: grid; - align-content: start; - } - - [vl-scene="sticky-story"] > * { - animation: vl-fade-up both linear; - animation-timeline: view(block); - animation-range: entry 0% cover 55%; - } - - /* glass-bento: auto-grid with glassmorphism tiles */ - [vl-scene="glass-bento"] { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr)); - gap: var(--vl-space-md, 1rem); - } - - [vl-scene="glass-bento"] > * { - background: var(--vl-glass-bg, oklch(100% 0 0 / 0.07)); - backdrop-filter: blur(12px) saturate(1.4); - -webkit-backdrop-filter: blur(12px) saturate(1.4); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle, oklch(60% 0 0)) 38%, transparent); - border-radius: var(--vl-radius-lg, 1rem); - animation: vl-scale-in both linear; - animation-timeline: view(block); - animation-range: entry 5% cover 45%; - } - - /* product-reveal: two-column media + copy reveal */ - [vl-scene="product-reveal"] { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--vl-space-xl, 3rem); - align-items: center; - container-type: inline-size; - } - - @container (max-width: 44rem) { - [vl-scene="product-reveal"] { - grid-template-columns: 1fr; - } - } - - [vl-scene="product-reveal"] > *:first-child { - animation: vl-tilt-in both linear; - animation-timeline: view(block); - animation-range: entry 5% cover 48%; - } - - [vl-scene="product-reveal"] > *:last-child { - animation: vl-fade-up both linear; - animation-timeline: view(block); - animation-range: entry 10% cover 50%; - } - - /* editorial-cinema: full-bleed editorial with clip-rise reveals */ - [vl-scene="editorial-cinema"] { - display: grid; - gap: var(--vl-space-xl, 3rem); - container-type: inline-size; - } - - [vl-scene="editorial-cinema"] > * { - animation: vl-clip-rise both linear; - animation-timeline: view(block); - animation-range: entry 5% cover 42%; - } + /* Named scene presets (look + choreography) live in scene-recipes.css (theme). */ /* ========================================================================= ACCESSIBILITY reduced motion diff --git a/apps/showcase/public/css/03c-scene-engine.css b/apps/showcase/public/css/03c-scene-engine.css new file mode 100644 index 0000000..e6e2875 --- /dev/null +++ b/apps/showcase/public/css/03c-scene-engine.css @@ -0,0 +1,159 @@ +/** + * Scene engine — track / stage / acts (host-agnostic). + * Shared clock for pin+scrub multi-act choreography. Zero JS. + * + * Markup: + *
+ *
+ *

+ *
+ *
+ */ +@layer velora.motion { + /* --- Track: named view timeline + optional pin height ----------------- */ + [vl-scene] { + --vl-clock: auto; + width: 100%; + position: relative; + isolation: isolate; + container-type: inline-size; + container-name: vl-scene; + contain: layout style; + view-timeline-name: --vl-scene; + view-timeline-axis: block; + timeline-scope: --vl-scene; + } + + [vl-scene][vl-timeline="view"] { + --vl-clock: view; + } + + [vl-scene][vl-timeline="auto"] { + --vl-clock: auto; + } + + /* Baseline pin height (boolean or missing typed attr) */ + [vl-scene][vl-pin] { + --vl-pin-factor: 1; + min-block-size: calc(var(--vl-pin-factor) * 100svh); + } + + [vl-scene][vl-pin="1"] { --vl-pin-factor: 1; } + [vl-scene][vl-pin="2"] { --vl-pin-factor: 2; } + [vl-scene][vl-pin="3"] { --vl-pin-factor: 3; } + [vl-scene][vl-pin="4"] { --vl-pin-factor: 4; } + [vl-scene][vl-pin="5"] { --vl-pin-factor: 5; } + [vl-scene][vl-pin="6"] { --vl-pin-factor: 6; } + + @supports (min-block-size: attr(vl-pin type())) { + [vl-scene][vl-pin] { + --vl-pin-factor: attr(vl-pin type(), 1); + min-block-size: calc(var(--vl-pin-factor) * 100svh); + } + } + + /* --- Stage: sticky viewport while track scrolls ----------------------- */ + [vl-stage] { + position: sticky; + inset-block-start: 0; + min-block-size: 100svh; + width: 100%; + box-sizing: border-box; + } + + /* --- Act / span mapping on stage children ----------------------------- */ + [vl-stage] > * { + --vl-act: 1; + --vl-span: 1; + --vl-act-start: calc((var(--vl-act) - 1) / var(--vl-beats) * 100%); + --vl-act-end: calc((var(--vl-act) + var(--vl-span) - 1) / var(--vl-beats) * 100%); + } + + /* Baseline act from DOM order */ + [vl-stage] > *:nth-child(1) { --vl-act: 1; } + [vl-stage] > *:nth-child(2) { --vl-act: 2; } + [vl-stage] > *:nth-child(3) { --vl-act: 3; } + [vl-stage] > *:nth-child(4) { --vl-act: 4; } + [vl-stage] > *:nth-child(5) { --vl-act: 5; } + [vl-stage] > *:nth-child(6) { --vl-act: 6; } + [vl-stage] > *:nth-child(7) { --vl-act: 7; } + [vl-stage] > *:nth-child(8) { --vl-act: 8; } + + [vl-stage] > *[vl-act="1"] { --vl-act: 1; } + [vl-stage] > *[vl-act="2"] { --vl-act: 2; } + [vl-stage] > *[vl-act="3"] { --vl-act: 3; } + [vl-stage] > *[vl-act="4"] { --vl-act: 4; } + [vl-stage] > *[vl-act="5"] { --vl-act: 5; } + [vl-stage] > *[vl-act="6"] { --vl-act: 6; } + [vl-stage] > *[vl-act="7"] { --vl-act: 7; } + [vl-stage] > *[vl-act="8"] { --vl-act: 8; } + + [vl-stage] > *[vl-span="1"] { --vl-span: 1; } + [vl-stage] > *[vl-span="2"] { --vl-span: 2; } + [vl-stage] > *[vl-span="3"] { --vl-span: 3; } + [vl-stage] > *[vl-span="4"] { --vl-span: 4; } + + @supports (z-index: sibling-index()) { + [vl-stage] > *:not([vl-act]) { + --vl-act: sibling-index(); + } + } + + @supports (opacity: attr(vl-act type())) { + [vl-stage] > *[vl-act] { + --vl-act: attr(vl-act type(), 1); + } + + [vl-stage] > *[vl-span] { + --vl-span: attr(vl-span type(), 1); + } + } + + /* View clock: bind stage children to the track timeline */ + [vl-scene][vl-timeline="view"] [vl-stage] > :is([vl-enter], [vl-scroll], [vl-effect], [vl-exit]):not([vl-range]) { + animation-timeline: --vl-scene; + animation-range: var(--vl-act-start) var(--vl-act-end); + animation-timing-function: linear; + animation-fill-mode: both; + } + + /* Auto clock: act → delay */ + [vl-scene][vl-timeline="auto"] [vl-stage] > :is([vl-enter], [vl-effect], [vl-exit]), + [vl-scene]:not([vl-timeline]) [vl-stage] > :is([vl-enter], [vl-effect], [vl-exit]) { + animation-delay: calc((var(--vl-act) - 1) * var(--vl-beat)); + } + + /* Scrub: force linear both on scene-driven children */ + [vl-scene][vl-scrub] [vl-stage] > :is([vl-enter], [vl-scroll], [vl-effect], [vl-exit]) { + animation-timing-function: linear; + animation-fill-mode: both; + } + + /* Progressive enhancement: single-rule clock switch via if() */ + @supports (width: if(style(--vl-probe: 1): 1px; else: 2px)) { + [vl-scene] [vl-stage] > :is([vl-enter], [vl-scroll], [vl-effect], [vl-exit]):not([vl-range]) { + animation-timeline: if( + style(--vl-clock: view): --vl-scene; + else: auto + ); + animation-range: if( + style(--vl-clock: view): var(--vl-act-start) var(--vl-act-end); + else: normal + ); + animation-delay: if( + style(--vl-clock: auto): calc((var(--vl-act) - 1) * var(--vl-beat)); + else: 0s + ); + } + } + + @media (prefers-reduced-motion: reduce) { + [vl-scene] [vl-stage] > * { + animation: none !important; + animation-delay: 0s !important; + opacity: 1; + transform: none; + filter: none; + } + } +} diff --git a/apps/showcase/public/css/base.css b/apps/showcase/public/css/base.css index 46001a8..4c9c933 100644 --- a/apps/showcase/public/css/base.css +++ b/apps/showcase/public/css/base.css @@ -1,10 +1,10 @@ /** * Velora base bundle. - * Includes reset, tokens, layout, and utilities. + * Reset + motion tokens + structural layout + utilities (no brand skin). */ @layer velora.reset, velora.tokens, velora.layout, velora.motion, velora.components, velora.transitions, velora.utilities, velora.overrides; @import "./00-reset.css"; -@import "./01-tokens.css"; +@import "./01-motion-tokens.css"; @import "./02-layout.css"; @import "./06-utilities.css"; diff --git a/apps/showcase/public/css/motion-core.css b/apps/showcase/public/css/motion-core.css index 858adf8..fd30dc0 100644 --- a/apps/showcase/public/css/motion-core.css +++ b/apps/showcase/public/css/motion-core.css @@ -1,8 +1,11 @@ /** * Velora motion core bundle. - * Includes foundational attribute-driven motion API. + * Motion tokens + attribute grammar + conditions + scene engine (track/stage/acts). + * Host-agnostic — pair with any UI. Add @velora/css/theme for Velora look. */ @layer velora.reset, velora.tokens, velora.layout, velora.motion, velora.components, velora.transitions, velora.utilities, velora.overrides; +@import "./01-motion-tokens.css"; @import "./03-motion.css"; @import "./03a-motion-conditions.css"; +@import "./03c-scene-engine.css"; diff --git a/apps/showcase/public/css/scene-recipes.css b/apps/showcase/public/css/scene-recipes.css new file mode 100644 index 0000000..bb58a9b --- /dev/null +++ b/apps/showcase/public/css/scene-recipes.css @@ -0,0 +1,197 @@ +/** + * Velora scene recipes — look + layout for named vl-scene presets. + * Part of the Velora skin (theme), not required for host-agnostic motion. + * Choreography (animation names / timelines) stays compatible via these selectors. + */ +@layer velora.layout { + /* Scene look helpers (moved from core layout) */ + [vl-scene] { + --vl-scene-gap: clamp(var(--vl-space-md, 1rem), 2.4vw, var(--vl-space-xl, 4rem)); + --vl-scene-pad-inline: clamp(var(--vl-space-md, 1rem), 4vw, var(--vl-space-2xl, 6rem)); + --vl-scene-pad-block: clamp(var(--vl-space-xl, 4rem), 9vw, var(--vl-space-3xl, 8rem)); + --vl-scene-max: 100%; + --vl-scene-pane-a: 1fr; + --vl-scene-pane-b: 1fr; + --vl-scene-cluster-min: 16rem; + } + + [vl-scene][data-vl-scene-density="compact"] { + --vl-scene-gap: clamp(var(--vl-space-sm, 0.5rem), 1.8vw, var(--vl-space-lg, 2rem)); + --vl-scene-pad-inline: clamp(var(--vl-space-sm, 0.5rem), 2.5vw, var(--vl-space-lg, 2rem)); + --vl-scene-pad-block: clamp(var(--vl-space-lg, 2rem), 5vw, var(--vl-space-xl, 4rem)); + --vl-scene-cluster-min: 12rem; + } + + [vl-scene][data-vl-scene-density="editorial"] { + --vl-scene-gap: clamp(var(--vl-space-lg, 2rem), 3vw, var(--vl-space-2xl, 6rem)); + --vl-scene-pad-inline: clamp(var(--vl-space-md, 1rem), 4.5vw, var(--vl-space-2xl, 6rem)); + } + + [vl-scene][data-vl-scene-density="immersive"] { + --vl-scene-gap: clamp(var(--vl-space-lg, 2rem), 3.2vw, var(--vl-space-2xl, 6rem)); + --vl-scene-pad-inline: clamp(var(--vl-space-lg, 2rem), 5vw, var(--vl-space-3xl, 8rem)); + --vl-scene-pad-block: clamp(var(--vl-space-2xl, 6rem), 10vw, calc(var(--vl-space-3xl, 8rem) * 1.2)); + } + + [vl-scene][data-vl-scene-mode="full-bleed"] { + min-height: 100dvh; + align-content: center; + } + + [vl-scene] > :where(.vl-scene__inner, [data-vl-scene-inner]) { + display: grid; + gap: var(--vl-scene-gap); + width: min(100%, var(--vl-scene-max)); + margin-inline: auto; + padding-inline: var(--vl-scene-pad-inline); + min-width: 0; + } + + [vl-scene] :where(.vl-scene__layout--split, [data-vl-scene-layout~="split"]) { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: var(--vl-scene-gap); + align-items: start; + } + + [vl-scene] :where(.vl-scene__layout--cluster, [data-vl-scene-layout~="cluster"]) { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--vl-scene-cluster-min)), 1fr)); + gap: var(--vl-scene-gap); + } + + [vl-scene] :where(.vl-scene__rail, [data-vl-scene-role="rail"]) { + display: grid; + gap: var(--vl-space-sm, 0.5rem); + align-content: start; + } + + [vl-scene] :where(.vl-scene__stack, [data-vl-scene-role="stack"]) { + display: grid; + gap: var(--vl-scene-gap); + min-width: 0; + } + + @container vl-scene (min-width: 48rem) { + [vl-scene] :where(.vl-scene__layout--split, [data-vl-scene-layout~="split"]) { + grid-template-columns: + minmax(0, var(--vl-scene-pane-a)) + minmax(0, var(--vl-scene-pane-b)); + align-items: center; + } + } + + @container vl-scene (max-width: 36rem) { + [vl-scene][data-vl-scene-density="immersive"] { + --vl-scene-pad-inline: clamp(var(--vl-space-md, 1rem), 4vw, var(--vl-space-lg, 2rem)); + --vl-scene-pad-block: clamp(var(--vl-space-xl, 4rem), 6vw, var(--vl-space-2xl, 6rem)); + } + } +} + +@layer velora.motion { + /* + * Named scene presets — Velora-look recipes (compat). + * Host-agnostic authorship prefers vl-stage + vl-act (see 03c-scene-engine.css). + */ + + [vl-scene="cinematic-hero"] { + display: grid; + place-items: center; + min-height: 100svh; + overflow: clip; + isolation: isolate; + } + + [vl-scene="cinematic-hero"] > * { + animation: vl-reveal-cinematic var(--vl-duration-slower) var(--vl-ease-cinematic) both; + } + + [vl-scene="cinematic-hero"] > *:nth-child(2) { animation-delay: 160ms; } + [vl-scene="cinematic-hero"] > *:nth-child(3) { animation-delay: 300ms; } + [vl-scene="cinematic-hero"] > *:nth-child(4) { animation-delay: 440ms; } + [vl-scene="cinematic-hero"] > *:nth-child(n+5) { animation-delay: 560ms; } + + [vl-scene="sticky-story"] { + position: sticky; + top: 0; + min-height: 100svh; + overflow: clip; + display: grid; + align-content: start; + } + + [vl-scene="sticky-story"] > * { + animation: vl-fade-up both linear; + animation-timeline: view(block); + animation-range: entry 0% cover 55%; + } + + [vl-scene="glass-bento"] { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(min(100%, 18rem), 1fr)); + gap: var(--vl-space-md, 1rem); + } + + [vl-scene="glass-bento"] > * { + background: var(--vl-glass-bg, oklch(100% 0 0 / 0.07)); + backdrop-filter: blur(12px) saturate(1.4); + -webkit-backdrop-filter: blur(12px) saturate(1.4); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle, oklch(60% 0 0)) 38%, transparent); + border-radius: var(--vl-radius-lg, 1rem); + animation: vl-scale-in both linear; + animation-timeline: view(block); + animation-range: entry 5% cover 45%; + } + + [vl-scene="product-reveal"] { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--vl-space-xl, 3rem); + align-items: center; + container-type: inline-size; + } + + @container (max-width: 44rem) { + [vl-scene="product-reveal"] { + grid-template-columns: 1fr; + } + } + + [vl-scene="product-reveal"] > *:first-child { + animation: vl-tilt-in both linear; + animation-timeline: view(block); + animation-range: entry 5% cover 48%; + } + + [vl-scene="product-reveal"] > *:last-child { + animation: vl-fade-up both linear; + animation-timeline: view(block); + animation-range: entry 10% cover 50%; + } + + [vl-scene="editorial-cinema"] { + display: grid; + gap: var(--vl-space-xl, 3rem); + container-type: inline-size; + } + + [vl-scene="editorial-cinema"] > * { + animation: vl-clip-rise both linear; + animation-timeline: view(block); + animation-range: entry 5% cover 42%; + } + + @media (prefers-reduced-motion: reduce) { + [vl-scene="cinematic-hero"] > *, + [vl-scene="sticky-story"] > *, + [vl-scene="glass-bento"] > *, + [vl-scene="product-reveal"] > *, + [vl-scene="editorial-cinema"] > * { + animation: none !important; + opacity: 1; + transform: none; + filter: none; + } + } +} diff --git a/apps/showcase/public/css/showcase-api-motion-catalog.css b/apps/showcase/public/css/showcase-api-motion-catalog.css index f7d5621..a135ffb 100644 --- a/apps/showcase/public/css/showcase-api-motion-catalog.css +++ b/apps/showcase/public/css/showcase-api-motion-catalog.css @@ -1,2235 +1,2673 @@ -@view-transition { navigation: auto; } - - body { - background: - radial-gradient(1200px 500px at 15% -10%, color-mix(in oklch, var(--vl-color-primary) 20%, transparent), transparent), - radial-gradient(900px 380px at 100% -8%, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 18%, transparent), transparent), - var(--vl-bg-main); - background-attachment: fixed; - background-repeat: no-repeat; - background-size: cover; - padding-bottom: clamp(10.5rem, 18vh, 13.5rem); - } - - .api-shell { - max-width: min(1320px, calc(100vw - 2.2rem)); - margin: 0 auto; - padding: 6.5rem 0 4.5rem; - } - - .api-hero { - position: relative; - isolation: isolate; - overflow: clip; - border-radius: clamp(1rem, 2.4vw, 1.6rem); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 66%, transparent); - min-height: clamp(28rem, 64vw, 42rem); - padding: clamp(1.2rem, 3vw, 2.2rem); - background: - radial-gradient(circle at 74% 22%, color-mix(in oklch, var(--vl-color-primary) 14%, transparent), transparent 34%), - linear-gradient(180deg, var(--vl-bg-main), color-mix(in oklch, var(--vl-bg-inset) 88%, transparent)); - } - - .api-hero-wash { - position: absolute; - inset: -18% -8% 40%; - z-index: 0; - pointer-events: none; - background: - radial-gradient(ellipse 80% 60% at 70% 20%, color-mix(in oklch, var(--vl-color-primary) 18%, transparent), transparent 55%), - radial-gradient(ellipse 50% 40% at 20% 78%, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 12%, transparent), transparent 52%); - opacity: 0.88; - animation: api-hero-wash 18s ease-in-out infinite alternate; - } - - @keyframes api-hero-wash { - from { transform: translate3d(-2%, 0, 0) scale(1); opacity: 0.74; } - to { transform: translate3d(2%, 1.5%, 0) scale(1.05); opacity: 0.96; } - } - - .api-hero-inner { - position: relative; - z-index: 1; - display: grid; - grid-template-columns: minmax(0, 1.1fr) minmax(18rem, 0.9fr); - gap: clamp(1rem, 2.5vw, 2rem); - align-items: stretch; - min-height: 100%; - } - - .api-hero-copy { - display: grid; - align-content: end; - gap: 1rem; - } - - .api-hero-kicker { - display: inline-flex; - align-items: center; - gap: 0.55rem; - margin: 0; - } - - .api-hero-kicker-line { - width: 2.2rem; - height: 1px; - background: linear-gradient(90deg, color-mix(in oklch, var(--vl-color-primary) 72%, transparent), transparent); - } - - .api-hero-title { - margin: 0; - max-width: 12ch; - font-family: var(--vl-font-family-display); - font-size: clamp(2.05rem, 5.6vw, 4.7rem); - line-height: 0.9; - letter-spacing: -0.05em; - text-transform: uppercase; - } - - .api-hero-title em { - font-style: normal; - color: color-mix(in oklch, var(--vl-color-primary) 72%, var(--vl-text-primary)); - } - - .api-hero-lead { - margin: 0; - max-width: 58ch; - color: var(--vl-text-secondary); - line-height: 1.62; - } - - .api-hero-actions { - display: flex; - flex-wrap: wrap; - gap: 0.7rem; - } - - .api-hero-btn { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 2.65rem; - padding: 0.58rem 1rem; - border-radius: 999px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); - text-decoration: none; - font-size: 0.76rem; - letter-spacing: 0.08em; - text-transform: uppercase; - font-family: var(--vl-font-family-mono); - color: var(--vl-text-primary); - background: color-mix(in oklch, var(--vl-bg-surface-elevated) 74%, transparent); - } - - .api-hero-btn--primary { - border-color: color-mix(in oklch, var(--vl-color-primary) 54%, transparent); - background: color-mix(in oklch, var(--vl-color-primary) 18%, transparent); - } - - .api-hero-chip-row { - display: flex; - flex-wrap: wrap; - gap: 0.55rem; - } - - .api-hero-chip-row a { - border-radius: 999px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 66%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 84%, transparent); - padding: 0.28rem 0.62rem; - text-decoration: none; - font-size: 0.7rem; - font-family: var(--vl-font-family-mono); - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--vl-text-secondary); - } - - .api-hero-stage { - position: relative; - border-radius: clamp(0.9rem, 2vw, 1.2rem); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - background: - radial-gradient(circle at 50% 45%, color-mix(in oklch, var(--vl-color-primary) 15%, transparent), transparent 34%), - linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-surface-elevated) 76%, transparent), color-mix(in oklch, var(--vl-bg-main) 96%, transparent)); - overflow: hidden; - min-height: clamp(20rem, 44vw, 30rem); - } - - .api-hero-orbit { - position: absolute; - inset: 0; - margin: auto; - width: min(100%, 25rem); - aspect-ratio: 1; - } - - .api-hero-orbit span { - position: absolute; - inset: 50%; - border-radius: 50%; - border: 1px solid color-mix(in oklch, var(--vl-color-primary) 28%, transparent); - translate: -50% -50%; - } - - .api-hero-orbit span:nth-child(1) { width: 100%; height: 100%; } - .api-hero-orbit span:nth-child(2) { width: 72%; height: 72%; } - .api-hero-orbit span:nth-child(3) { - width: 44%; - height: 44%; - background: color-mix(in oklch, var(--vl-color-primary) 12%, transparent); - } - - .api-hero-panel { - position: absolute; - display: grid; - gap: 0.5rem; - width: min(100%, 18rem); - padding: 1.05rem; - border-radius: 0.95rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); - background: color-mix(in oklch, var(--vl-bg-surface-elevated) 74%, transparent); - backdrop-filter: blur(8px); - } - - .api-hero-panel p, - .api-hero-panel strong, - .api-hero-panel span { margin: 0; } - - .api-hero-panel strong { - font-family: var(--vl-font-family-display); - font-size: clamp(1rem, 1.6vw, 1.35rem); - line-height: 0.98; - } - - .api-hero-panel span { - color: var(--vl-text-secondary); - font-size: 0.77rem; - } - - .api-hero-panel--primary { top: 10%; left: 6%; } - .api-hero-panel--secondary { right: 6%; bottom: 12%; } - - .api-hero-status { - position: absolute; - left: 1rem; - bottom: 1rem; - display: grid; - gap: 0.35rem; - text-transform: uppercase; - letter-spacing: 0.14em; - font-size: 0.62rem; - color: var(--vl-text-muted); - } - - .api-hero-status > span { - display: inline-flex; - align-items: center; - width: fit-content; - border-radius: 999px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 54%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 74%, transparent); - padding: 0.14rem 0.5rem; - } - - .api-kicker { - margin: 0; - font-family: var(--vl-font-family-mono); - text-transform: uppercase; - letter-spacing: 0.14em; - font-size: 0.72rem; - color: var(--vl-text-muted); - } - - .api-shell a[href], - .api-shell a[href]:visited { - color: var(--vl-text-primary); - } - - .api-quick-nav { - display: flex; - gap: 0.55rem; - flex-wrap: wrap; - } - - .api-quick-nav a { - color: var(--vl-text-secondary); - } - - .api-quick-nav a:visited { - color: var(--vl-text-secondary); - } - - .api-quick-nav a:hover { - color: var(--vl-text-primary); - } - - .api-controls { - position: fixed; - left: 50%; - bottom: clamp(0.65rem, 1.6vh, 1.2rem); - transform: translateX(-50%); - z-index: 42; - width: min(920px, calc(100vw - 1rem)); - padding: 0.5rem 0.56rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 68%, transparent); - border-radius: 12px; - background: - linear-gradient( - 180deg, - color-mix(in oklch, var(--vl-bg-surface-elevated) 62%, transparent), - color-mix(in oklch, var(--vl-bg-main) 72%, transparent) - ); - backdrop-filter: blur(14px) saturate(120%); - box-shadow: - 0 18px 54px color-mix(in oklch, black 34%, transparent), - inset 0 1px 0 color-mix(in oklch, white 10%, transparent); - } - - .api-controls-row { - display: flex; - align-items: center; - gap: 0.46rem; - flex-wrap: wrap; - overflow: visible; - } - - .api-controls-head { - display: none; - } - - .api-selected { - position: absolute; - top: 0; - left: 0; - transform: translate(-0.28rem, calc(-100% + 0.42rem)); - z-index: 2; - font-family: var(--vl-font-family-mono); - font-size: 0.62rem; - color: var(--vl-text-secondary); - letter-spacing: 0.08em; - text-transform: uppercase; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - border-radius: 999px; - background: - linear-gradient( - 180deg, - color-mix(in oklch, var(--vl-bg-surface-elevated) 72%, transparent), - color-mix(in oklch, var(--vl-bg-main) 86%, transparent) - ); - box-shadow: - 0 10px 22px color-mix(in oklch, black 22%, transparent), - inset 0 1px 0 color-mix(in oklch, white 10%, transparent); - padding: 0.28rem 0.62rem; - white-space: nowrap; - } - - .api-controls-grid { display: contents; } - - .api-runtime-commands { - display: flex; - align-items: center; - gap: 0.32rem; - flex-wrap: wrap; - } - - .api-runtime-command-chip { - display: inline-flex; - align-items: center; - min-height: 1.5rem; - padding: 0.22rem 0.46rem; - border-radius: 999px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 82%, transparent); - font-family: var(--vl-font-family-mono); - font-size: 0.58rem; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--vl-text-secondary); - white-space: nowrap; - } - - .api-runtime-command-chip.is-available { - border-color: color-mix(in oklch, var(--vl-color-primary) 46%, transparent); - background: color-mix(in oklch, var(--vl-color-primary) 14%, transparent); - color: var(--vl-text-primary); - } - - .api-runtime-command-chip.is-unavailable { - opacity: 0.58; - } - - .api-runtime-actions { - display: flex; - flex-wrap: nowrap; - gap: 0.34rem; - } - - .api-runtime-btn { - width: 2rem; - min-width: 2rem; - height: 2rem; - min-height: 2rem; - padding: 0; - border-radius: 10px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 66%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 84%, transparent); - color: var(--vl-text-primary); - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - } - - .api-runtime-btn svg { - width: 0.9rem; - height: 0.9rem; - } - - .api-runtime-btn.is-active { - border-color: color-mix(in oklch, var(--vl-color-primary) 56%, transparent); - background: color-mix(in oklch, var(--vl-color-primary) 16%, transparent); - } - - .api-control { - display: inline-flex; - align-items: center; - gap: 0.3rem; - padding: 0.18rem 0.28rem; - border-radius: 10px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 58%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 82%, transparent); - } - - .api-control label { - display: inline-flex; - align-items: center; - justify-content: center; - width: 1.2rem; - height: 1.2rem; - border-radius: 999px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - background: color-mix(in oklch, var(--vl-bg-surface-elevated) 70%, transparent); - font-size: 0.58rem; - text-transform: uppercase; - letter-spacing: 0.08em; - color: var(--vl-text-muted); - white-space: nowrap; - } - - .api-control .vl-range { - width: clamp(74px, 14vw, 132px); - max-width: 132px; - } - - .api-control-value { - min-width: 3.7rem; - font-family: var(--vl-font-family-mono); - font-size: 0.62rem; - color: var(--vl-text-primary); - text-align: right; - white-space: nowrap; - } - - .api-group { - margin-top: 2rem; - scroll-margin-top: clamp(6.2rem, 10vh, 8.2rem); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); - border-radius: 18px; - background: color-mix(in oklch, var(--vl-bg-surface-elevated) 72%, transparent); - padding: 1rem; - } - - .api-contract-legend { - margin-top: 1.1rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 58%, transparent); - border-radius: 14px; - background: color-mix(in oklch, var(--vl-bg-surface-elevated) 70%, transparent); - padding: 0.85rem 1rem; - } - - .api-contract-legend__inner { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 0.5rem 0.65rem; - } - - .api-contract-legend__inner p { - margin: 0; - color: var(--vl-text-secondary); - font-size: 0.76rem; - } - - .api-kind-filter { - margin-top: 0.7rem; - display: flex; - flex-wrap: wrap; - gap: 0.4rem; - } - - .api-kind-filter__btn { - border-radius: 999px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 86%, transparent); - color: var(--vl-text-secondary); - font-family: var(--vl-font-family-mono); - font-size: 0.62rem; - letter-spacing: 0.08em; - text-transform: uppercase; - padding: 0.24rem 0.58rem; - cursor: pointer; - } - - .api-kind-filter__btn.is-active { - border-color: color-mix(in oklch, var(--vl-color-primary) 42%, transparent); - background: color-mix(in oklch, var(--vl-color-primary) 16%, transparent); - color: color-mix(in oklch, var(--vl-color-primary) 72%, var(--vl-text-primary)); - } - - .api-scope-pill { - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 999px; - padding: 0.2rem 0.6rem; - border: 1px solid transparent; - font-size: 0.63rem; - letter-spacing: 0.08em; - text-transform: uppercase; - font-family: var(--vl-font-family-mono); - } - - .api-scope-pill--core { - border-color: color-mix(in oklch, var(--vl-color-primary) 44%, transparent); - background: color-mix(in oklch, var(--vl-color-primary) 14%, transparent); - color: color-mix(in oklch, var(--vl-color-primary) 66%, var(--vl-text-primary)); - } - - .api-scope-pill--extended { - border-color: color-mix(in oklch, var(--vl-color-secondary) 40%, transparent); - background: color-mix(in oklch, var(--vl-color-secondary) 14%, transparent); - color: color-mix(in oklch, var(--vl-color-secondary) 62%, var(--vl-text-primary)); - } - - .api-scope-pill--experimental { - border-color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 46%, transparent); - background: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 15%, transparent); - color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 72%, var(--vl-text-primary)); - } - - .api-group.api-scope-core { - border-left: 2px solid color-mix(in oklch, var(--vl-color-primary) 40%, transparent); - } - - .api-group.api-scope-extended { - border-left: 2px solid color-mix(in oklch, var(--vl-color-secondary) 36%, transparent); - } - - .api-group.api-scope-experimental { - border-left: 2px solid color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 44%, transparent); - } - - .api-group.api-scope-core .api-group-head h2::after, - .api-group.api-scope-extended .api-group-head h2::after, - .api-group.api-scope-experimental .api-group-head h2::after { - margin-left: 0.6rem; - border-radius: 999px; - padding: 0.16rem 0.48rem; - font-size: 0.58rem; - letter-spacing: 0.1em; - vertical-align: middle; - border: 1px solid transparent; - } - - .api-group.api-scope-core .api-group-head h2::after { - content: "core"; - color: color-mix(in oklch, var(--vl-color-primary) 70%, var(--vl-text-primary)); - background: color-mix(in oklch, var(--vl-color-primary) 13%, transparent); - border-color: color-mix(in oklch, var(--vl-color-primary) 40%, transparent); - } - - .api-group.api-scope-extended .api-group-head h2::after { - content: "extended"; - color: color-mix(in oklch, var(--vl-color-secondary) 68%, var(--vl-text-primary)); - background: color-mix(in oklch, var(--vl-color-secondary) 12%, transparent); - border-color: color-mix(in oklch, var(--vl-color-secondary) 34%, transparent); - } - - .api-group.api-scope-experimental .api-group-head h2::after { - content: "experimental"; - color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 78%, var(--vl-text-primary)); - background: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 13%, transparent); - border-color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 42%, transparent); - } - - #pin { - scroll-margin-top: clamp(7rem, 12vh, 8.8rem); - } - - .api-group-head { - display: grid; - gap: 0.35rem; - margin-bottom: 1rem; - } - - .api-group-head h2 { - margin: 0; - font-size: 1rem; - letter-spacing: 0.12em; - text-transform: uppercase; - color: var(--vl-text-muted); - } - - .api-group-head p { - margin: 0; - color: var(--vl-text-secondary); - font-size: 0.86rem; - } - - .api-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 0.9rem; - } - - .api-card { - position: relative; - min-height: 154px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 68%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 84%, transparent); - border-radius: 14px; - padding: 0.9rem; - display: grid; - gap: 0.6rem; - align-content: start; - overflow: clip; - } - - .api-card[data-api-kind]::before { - content: attr(data-api-kind); - position: absolute; - top: 0.45rem; - right: 0.45rem; - border-radius: 999px; - padding: 0.12rem 0.45rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 88%, transparent); - color: var(--vl-text-muted); - font-family: var(--vl-font-family-mono); - font-size: 0.56rem; - letter-spacing: 0.08em; - text-transform: uppercase; - } - - .api-card[data-api-kind="channel"]::before, - .api-card[data-api-kind="timeline"]::before, - .api-card[data-api-kind="transition"]::before { - border-color: color-mix(in oklch, var(--vl-color-primary) 36%, transparent); - color: color-mix(in oklch, var(--vl-color-primary) 70%, var(--vl-text-muted)); - } - - .api-card[data-api-kind="interaction"]::before, - .api-card[data-api-kind="text"]::before, - .api-card[data-api-kind="ambient"]::before { - border-color: color-mix(in oklch, var(--vl-color-secondary) 36%, transparent); - color: color-mix(in oklch, var(--vl-color-secondary) 68%, var(--vl-text-muted)); - } - - .api-card[data-api-kind="scene"]::before, - .api-card[data-api-kind="3d"]::before, - .api-card[data-api-kind="experimental"]::before { - border-color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 40%, transparent); - color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 76%, var(--vl-text-muted)); - } - - .api-card[data-api-kind="reference"]::before { - border-color: color-mix(in oklch, var(--vl-border-subtle) 72%, transparent); - color: var(--vl-text-muted); - } - - .api-card.is-selected { - border-color: color-mix(in oklch, var(--vl-color-primary) 55%, var(--vl-border-subtle)); - box-shadow: 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 35%, transparent); - } - - .api-card.is-filter-hidden { - display: none !important; - } - - .api-group.is-filter-empty { - display: none; - } - - .api-card--lead { - grid-column: span 2; - min-height: 170px; - } - - .api-card strong { - font-family: var(--vl-font-family-mono); - font-size: 0.72rem; - letter-spacing: 0.08em; - color: var(--vl-text-muted); - text-transform: uppercase; - } - - .api-code { - margin: 0; - padding: 0.72rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); - border-radius: 10px; - background: color-mix(in oklch, var(--vl-bg-main) 88%, transparent); - font-family: var(--vl-font-family-mono); - font-size: 0.67rem; - line-height: 1.5; - overflow: auto; - } - - .api-note { - margin: 0; - color: var(--vl-text-secondary); - font-size: 0.78rem; - } - - .api-target { - min-height: 84px; - border-radius: 10px; - background: color-mix(in oklch, var(--vl-bg-main) 80%, transparent); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 50%, transparent); - display: grid; - place-items: center; - text-align: center; - padding: 0.65rem; - font-size: 0.84rem; - } - - #ambient .api-target { - min-height: 8rem; - gap: 0.55rem; - align-content: center; - justify-items: center; - padding: 0.8rem; - background: linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-main) 70%, transparent), color-mix(in oklch, var(--vl-bg-surface) 72%, transparent)); - border: 0; - } - - #ambient .api-target::before { - content: ""; - width: 3.8rem; - height: 3.8rem; - border-radius: 1.1rem; - background: linear-gradient(145deg, var(--vl-color-primary), color-mix(in oklch, var(--vl-color-secondary) 72%, var(--vl-color-primary))); - box-shadow: var(--vl-shadow-glow); - animation: vl-morph 8s var(--vl-ease-in-out-smooth) infinite; - } - - #ambient .api-target { - font-size: 0.72rem; - letter-spacing: 0.08em; - text-transform: uppercase; - font-family: var(--vl-font-family-mono); - } - - @media (prefers-reduced-motion: reduce) { - #ambient .api-target::before { - animation: none; - } - } - - .api-target-inline { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 44px; - width: 100%; - text-decoration: none; - } - - .api-target-inline:visited { - color: var(--vl-text-primary); - } - - .api-chip-row { display: flex; gap: 0.45rem; flex-wrap: wrap; } - - .api-chip { - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 86%, transparent); - border-radius: 999px; - padding: 0.25rem 0.55rem; - font-size: 0.68rem; - font-family: var(--vl-font-family-mono); - } - - .api-scroll-track { display: flex; gap: 0.65rem; width: max-content; } - .api-scroll-track .api-chip { min-width: 120px; text-align: center; } - - .api-transition-stage { - position: relative; - min-height: 132px; - border-radius: 10px; - overflow: hidden; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 58%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 90%, transparent); - } - - .api-transition-screen { - position: absolute; - inset: 0; - display: grid; - align-content: center; - justify-items: start; - gap: 0.35rem; - padding: 0.85rem; - font-family: var(--vl-font-family-mono); - font-size: 0.67rem; - letter-spacing: 0.06em; - text-transform: uppercase; - } - - .api-transition-screen span { - display: inline-flex; - border-radius: 999px; - padding: 0.18rem 0.42rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); - } - - .api-transition-screen--from { - background: - radial-gradient(circle at 16% 18%, color-mix(in oklch, var(--vl-color-primary) 18%, transparent), transparent 44%), - color-mix(in oklch, var(--vl-bg-surface-elevated) 74%, transparent); - } - - .api-transition-screen--to { - background: - radial-gradient(circle at 84% 76%, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 20%, transparent), transparent 48%), - color-mix(in oklch, var(--vl-bg-main) 94%, transparent); - opacity: 0; - transform: translate3d(0, 0, 0) scale(1); - } - - .api-transition-hint { - margin: 0; - font-size: 0.72rem; - color: var(--vl-text-secondary); - } - - .api-transition-card:is(:hover, :focus-within, .is-playing) [data-transition-next] { - animation-duration: 900ms; - animation-fill-mode: both; - } - - .api-transition-card[data-api-transition="cinema"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { - animation-name: api-vt-cinema; - animation-timing-function: cubic-bezier(.2, .7, .2, 1); - } - - .api-transition-card[data-api-transition="wipe"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { - animation-name: api-vt-wipe; - animation-timing-function: cubic-bezier(.22, .8, .22, 1); - } - - .api-transition-card[data-api-transition="glide"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { - animation-name: api-vt-glide; - animation-timing-function: cubic-bezier(.23, .78, .21, 1); - } - - .api-transition-card[data-api-transition="iris"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { - animation-name: api-vt-iris; - animation-timing-function: cubic-bezier(.18, .74, .2, 1); - } - - .api-transition-card[data-api-transition="snap"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { - animation-name: api-vt-snap; - animation-timing-function: steps(2, end); - } - - @keyframes api-vt-cinema { - from { opacity: 0; transform: scale(1.06); filter: blur(8px); } - to { opacity: 1; transform: scale(1); filter: blur(0); } - } - - @keyframes api-vt-wipe { - from { opacity: 1; clip-path: inset(0 100% 0 0); } - to { opacity: 1; clip-path: inset(0 0 0 0); } - } - - @keyframes api-vt-glide { - from { opacity: 0.2; transform: translate3d(30%, 0, 0); } - to { opacity: 1; transform: translate3d(0, 0, 0); } - } - - @keyframes api-vt-iris { - from { opacity: 1; clip-path: circle(0% at 50% 50%); } - to { opacity: 1; clip-path: circle(140% at 50% 50%); } - } - - @keyframes api-vt-snap { - 0% { opacity: 0; filter: contrast(150%); } - 100% { opacity: 1; filter: contrast(100%); } - } - - .api-children > * { - min-height: 36px; - border-radius: 8px; - display: grid; - place-items: center; - background: color-mix(in oklch, var(--vl-color-primary) 10%, transparent); - border: 1px solid color-mix(in oklch, var(--vl-color-primary) 36%, transparent); - font-size: 0.75rem; - text-transform: uppercase; - letter-spacing: 0.08em; - } - - .api-target.api-children { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - gap: 0.45rem; - flex-wrap: nowrap; - } - - .api-target.api-children > * { - flex: 1 1 0; - min-width: 0; - } - - .api-typewriter { --vl-type-chars: 30ch; --vl-type-steps: 30; } - - .api-gradient-text-demo { - min-height: 5.4rem; - background: - radial-gradient(circle at 50% 50%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 56%), - color-mix(in oklch, var(--vl-bg-main) 90%, transparent); - } - - .api-gradient-text-demo > span { - font-family: var(--vl-font-family-display); - font-size: clamp(1.15rem, 2.4vw, 2rem); - line-height: 1.1; - letter-spacing: 0.01em; - } - - .api-card--text-ring { - grid-column: span 2; - } - - .api-target.api-text-ring-demo { - min-height: clamp(14rem, 28vw, 18rem); - display: grid; - place-items: center; - padding: 0; - text-align: initial; - background: - radial-gradient(circle at 50% 50%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 48%), - linear-gradient(135deg, color-mix(in oklch, black 92%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main))); - border-color: color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - } - - .api-text-ring-demo[vl-effect="text-ring-orbit"] { - --vl-text-ring-size: clamp(2rem, 6.2vw, 4rem); - pointer-events: none; - cursor: default; - } - - .api-text-ring-demo[vl-effect="text-ring-orbit"] * { - pointer-events: none; - } - - .api-text-ring-demo .vl-text-ring-stage { - width: min(100%, 23rem); - height: clamp(8rem, 22vw, 10.8rem); - } - - .api-card--circle-scroll { - grid-column: span 2; - } - - .api-card--code-greeting { - grid-column: span 2; - } - - .api-target.api-code-greeting-demo { - min-height: clamp(12rem, 24vw, 14rem); - display: grid; - place-items: center; - padding: clamp(1rem, 2vw, 1.4rem); - text-align: initial; - border-color: color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - background: - radial-gradient(circle at 50% 28%, color-mix(in oklch, var(--vl-color-primary) 16%, transparent), transparent 52%), - linear-gradient(145deg, color-mix(in oklch, black 90%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main))); - } - - .api-code-greeting-demo[vl-effect="code-greeting-stack"] { - font-size: clamp(1.02rem, 2.15vmin, 1.5rem); - max-width: min(100%, 34rem); - } - - .api-code-greeting-demo[vl-effect="code-greeting-stack"] .vl-code-line, - .api-code-greeting-demo[vl-effect="code-greeting-stack"] .vl-code-greetings-track > span { - white-space: nowrap; - font-weight: 510; - font-stretch: 101%; - letter-spacing: -0.01em; - } - - .api-code-greeting-demo[vl-effect="code-greeting-stack"]:is(:hover, :focus-within) .vl-code-line, - .api-code-greeting-demo[vl-effect="code-greeting-stack"]:is(:hover, :focus-within) .vl-code-greetings-track > span { - font-size: 1.04em; - font-weight: 660; - font-stretch: 112%; - letter-spacing: -0.015em; - } - - .api-target.api-circle-text-scroll-demo { - min-height: clamp(16rem, 34vw, 20rem); - display: grid; - place-items: center; - padding: 0; - text-align: initial; - border-color: color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - background: - radial-gradient(circle at 50% 50%, color-mix(in oklch, var(--vl-color-primary) 14%, transparent), transparent 48%), - linear-gradient(145deg, color-mix(in oklch, black 92%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main))); - } - - .api-circle-text-scroll-demo[vl-effect="circle-text-scroll"] { - --vl-circle-size: clamp(13.4rem, 38vmin, 20rem); - color: color-mix(in oklch, var(--vl-text-primary) 30%, white); - } - - .api-circle-text-scroll-demo[vl-effect="circle-text-scroll"] > ul > li > span { - font-size: clamp(0.82rem, 2.05vmin, 1.35rem); - font-weight: 510; - font-stretch: 98%; - letter-spacing: 0; - transition: - font-size var(--vl-transition-normal) var(--vl-ease-out-soft), - font-weight var(--vl-transition-normal) var(--vl-ease-out-soft), - font-stretch var(--vl-transition-normal) var(--vl-ease-out-soft), - letter-spacing var(--vl-transition-normal) var(--vl-ease-out-soft); - } - - .api-circle-text-scroll-demo[vl-effect="circle-text-scroll"]:is(:hover, :focus-within) > ul > li > span { - font-size: clamp(0.9rem, 2.25vmin, 1.45rem); - font-weight: 650; - font-stretch: 108%; - letter-spacing: 0.01em; - } - - .api-card--cube-triad { - grid-column: span 2; - } - - .api-grid.api-grid--cube-triad { - grid-template-columns: minmax(0, 1fr); - } - - .api-target.api-cube-triad-demo { - min-height: clamp(16rem, 35vw, 22rem); - display: grid; - place-items: center; - padding: 0; - text-align: initial; - border-color: color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - background: - radial-gradient(circle at 50% 22%, color-mix(in oklch, var(--vl-color-primary) 10%, transparent), transparent 50%), - linear-gradient(150deg, color-mix(in oklch, black 92%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main))); - } - - .api-cube-triad-demo[vl-effect="cube-triad-stage"] { - --vl-cube-unit: clamp(0.58rem, 0.94vmin, 0.86rem); - } - - .api-state-demo { opacity: 0.7; transform: translate3d(0, 8px, 0); } - .api-state-demo:hover { opacity: 1; transform: translate3d(0, 0, 0); } - .api-v2-stack { display: grid; gap: 0.4rem; } - .api-v2-stack > span { - font-family: var(--vl-font-family-mono); - font-size: 0.66rem; - letter-spacing: 0.05em; - color: var(--vl-text-secondary); - } - - .api-target[vl-effect="hover-orb-grid"] { min-height: 220px; display: block; text-align: initial; } - .api-target[vl-effect="hover-cross-swap"] { - min-height: 208px; - padding: 0; - display: block; - aspect-ratio: 0.92; - text-align: initial; - } - - .api-target[vl-effect="hover-card-fan"].api-hover-fan-demo { - min-height: 525px; - padding: 0.7rem; - display: flex; - flex-wrap: nowrap; - align-items: stretch; - justify-content: center; - text-align: initial; - background: - radial-gradient(circle at 50% 0%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 62%), - color-mix(in oklch, var(--vl-bg-main) 88%, transparent); - transition: all ease 0.3s; - } - - .api-target[vl-effect="hover-card-fan"].api-hover-fan-demo { - --vl-hover-fan-card-width: clamp(5.4rem, 30%, 16.8rem); - --vl-hover-fan-card-height: 11.2rem; - --vl-hover-fan-gap: 0.58rem; - --vl-hover-fan-angle: 26deg; - } - - .api-hover-fan-demo > button { - appearance: none; - -webkit-appearance: none; - font: inherit; - padding: 0.92rem; - } - - .api-hover-fan-demo .vl-hover-fan-index { - width: 5.4rem; - min-width: 5.4rem; - font-size: 2.45rem; - } - - .api-hover-fan-demo .vl-hover-fan-label { - font-size: 0.7rem; - } - - .api-grid.api-grid--hover-cards { - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(220px, 0.95fr); - align-items: stretch; - } - - .api-grid--hover-cards .api-card--hover-fan { - grid-column: 1 / span 2; - grid-row: 1 / span 2; - } - - .api-grid--hover-cards .api-card--hover-stack { - grid-column: 3; - grid-row: 1; - } - - .api-grid--hover-cards .api-card--hover-cross { - grid-column: 3; - grid-row: 2; - } - - .api-grid--hover-cards .api-card--hover-duotone { - grid-column: 1 / -1; - grid-row: 3; - } - - .api-target[vl-effect="duotone"].api-duotone-demo { - min-height: 250px; - display: grid; - place-items: center; - border-radius: 12px; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); - background: - radial-gradient(circle at 84% 18%, color-mix(in oklch, var(--vl-color-primary) 18%, transparent), transparent 52%), - color-mix(in oklch, var(--vl-bg-main) 90%, transparent); - --vl-duotone-a: color-mix(in oklch, var(--vl-color-primary) 84%, white 2%); - --vl-duotone-b: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-secondary)) 80%, black 8%); - } - - .api-duotone-demo > img { - width: clamp(12rem, 34vw, 20rem); - max-width: 92%; - height: auto; - } - - .api-card--illustration-editor { - grid-column: 1 / -1; - min-height: clamp(38rem, 74vw, 46rem); - } - - .api-target--illustration-editor { - min-height: clamp(31rem, 64vw, 38rem); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - border-radius: 1rem; - padding: clamp(1rem, 2.2vw, 1.5rem); - overflow: hidden; - background: - radial-gradient(circle at 14% 16%, color-mix(in oklch, var(--vl-color-primary) 14%, transparent), transparent 44%), - linear-gradient(135deg, color-mix(in oklch, black 92%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main)) 100%); - } - - .api-illustration-editor { - --api-editor-panel-bg: color-mix(in oklch, black 84%, var(--vl-bg-main)); - --api-editor-panel-border: color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); - --api-editor-icon: color-mix(in oklch, var(--vl-text-secondary) 86%, transparent); - --api-editor-content: color-mix(in oklch, white 94%, transparent); - position: relative; - width: min(100%, 45rem); - min-height: clamp(28rem, 58vw, 35rem); - margin-inline: auto; - } - - .api-editor-panels { - position: absolute; - inset: 0; - } - - .api-editor-code { - position: absolute; - width: clamp(16.5rem, 38vw, 18.5rem); - min-height: 9.4rem; - border-radius: 0.52rem; - border: 1px solid var(--api-editor-panel-border); - background: var(--api-editor-panel-bg); - box-shadow: 0 0.3rem 2rem color-mix(in oklch, black 68%, transparent); - z-index: 2; - } - - .api-editor-code header { - display: grid; - grid-template-columns: 2rem 1fr 2rem; - align-items: center; - } - - .api-editor-code header svg { - margin: 0.62rem; - color: var(--api-editor-icon); - } - - .api-editor-code header h3 { - margin: 0; - font-family: var(--vl-font-family-mono); - font-size: 0.75rem; - font-weight: 700; - letter-spacing: 0.1em; - color: color-mix(in oklch, var(--vl-text-secondary) 96%, transparent); - } - - .api-editor-code__content { - margin: 0 0.62rem 0.62rem; - } - - .api-editor-code__content code { - display: grid; - gap: 0.26rem; - color: var(--api-editor-content); - font-family: var(--vl-font-family-mono); - font-size: 0.68rem; - line-height: 1.4; - align-content: start; - justify-items: start; - min-height: 6.15rem; - padding: 0.05rem 0.1rem 0.3rem; - } - - .api-editor-code--html .api-editor-code__content code, - .api-editor-code--css .api-editor-code__content code { - display: block; - } - - .api-editor-code__content .c-r { color: color-mix(in oklch, #a88038 92%, var(--vl-color-warning, #c5902e)); } - .api-editor-code__content .c-o { color: color-mix(in oklch, #de7300 92%, var(--vl-color-warning, #de7300)); } - .api-editor-code__content .c-y { color: color-mix(in oklch, #e1ca72 92%, var(--vl-color-warning, #e1ca72)); } - .api-editor-code__content .c-g { color: color-mix(in oklch, #74b087 92%, var(--vl-color-success, #74b087)); } - .api-editor-code__content .c-p { color: color-mix(in oklch, #9f8198 92%, var(--vl-color-secondary)); } - .api-editor-code__content .c-b { color: color-mix(in oklch, #7a99ad 92%, var(--vl-color-accent, #7a99ad)); } - .api-editor-code__content .c-c { color: color-mix(in oklch, var(--vl-text-muted) 80%, transparent); } - .api-editor-code__content .c-w { color: color-mix(in oklch, white 92%, transparent); } - - .api-editor-code--html { - inset-inline-start: clamp(0.4rem, 4vw, 3.8rem); - inset-block-start: clamp(0.4rem, 2vw, 1rem); - } - - .api-editor-code--css { - inset-inline-start: clamp(1.8rem, 7.5vw, 7.4rem); - inset-block-start: clamp(11.2rem, 24vw, 12.8rem); - } - - .api-editor-code--js { - inset-inline-start: clamp(0rem, 1vw, 0.9rem); - inset-block-start: clamp(22rem, 44vw, 24.6rem); - } - - .api-editor-code--css .api-editor-code__content code { - gap: 0; - } - - .api-editor-line { - width: 0; - margin: 0; - border-right: 0.14em solid transparent; - overflow: hidden; - display: block; - white-space: nowrap; - } - - .api-editor-code--js .api-editor-code__content code { - min-height: 6.15rem; - display: grid; - place-items: center; - } - - .api-editor-empty-face { - display: inline-flex; - gap: 0.55rem; - align-items: center; - font-size: 0.86rem; - letter-spacing: 0.08em; - } - - .api-editor-line--2 { margin-inline-start: 1.6rem; } - .api-editor-line--3, - .api-editor-line--4, - .api-editor-line--5 { margin-inline-start: 3.1rem; } - - .api-editor-line--1 { - animation: api-editor-type-1 1s 0s steps(12, end) forwards, api-editor-cursor 1s linear 0s 2; - } - - .api-editor-line--2 { - animation: api-editor-type-2 3s 1s steps(30, end) forwards, api-editor-cursor 1s linear 1s 3; - } - - .api-editor-line--3 { - animation: api-editor-type-3 1s 4s steps(12, end) forwards, api-editor-cursor 1s linear 4s 2; - } - - .api-editor-line--4 { - animation: api-editor-type-4 1s 5s steps(12, end) forwards, api-editor-cursor 1s linear 5s 2; - } - - .api-editor-line--5 { - animation: api-editor-type-5 3s 6s steps(30, end) forwards, api-editor-cursor-persist 1s linear 6s infinite; - } - - .api-editor-block { - position: absolute; - inset-inline-end: clamp(0.2rem, 2vw, 1rem); - inset-block-start: clamp(1rem, 3vw, 1.4rem); - width: min(100%, 31.2rem); - height: clamp(23.8rem, 53vw, 26.3rem); - border-radius: 0.65rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 54%, transparent); - background: linear-gradient(109.61deg, color-mix(in oklch, var(--vl-text-secondary) 54%, transparent) 4.26%, color-mix(in oklch, var(--vl-bg-main) 90%, transparent) 84.84%); - } - - @keyframes api-editor-type-1 { - from { width: 0; } - to { width: 4.3rem; } - } - - @keyframes api-editor-type-2 { - from { width: 0; } - to { width: 14rem; } - } - - @keyframes api-editor-type-3 { - from { width: 0; } - to { width: 4.9rem; } - } - - @keyframes api-editor-type-4 { - from { width: 0; } - to { width: 5.4rem; } - } - - @keyframes api-editor-type-5 { - from { width: 0; } - to { width: 9rem; } - } - - @keyframes api-editor-cursor { - from, to, 20%, 80% { border-color: transparent; } - 25%, 75% { border-color: color-mix(in oklch, white 92%, transparent); } - } - - @keyframes api-editor-cursor-persist { - from, 45% { border-color: transparent; } - 50%, to { border-color: color-mix(in oklch, white 92%, transparent); } - } - - @media (prefers-reduced-motion: reduce) { - .api-editor-line { - width: auto; - border-right: 0; - animation: none; - } - } - - .api-cross-demo [data-vl-panel] { - font-family: var(--vl-font-family-mono); - } - - .api-cross-demo [data-vl-panel] strong, - .api-cross-demo [data-vl-panel] small { - position: relative; - z-index: 1; - margin: 0; - } - - .api-cross-demo [data-vl-panel] strong { - font-family: var(--vl-font-family-display); - font-size: clamp(0.92rem, 1.3vw, 1.12rem); - line-height: 0.96; - text-transform: none; - letter-spacing: 0; - } - - .api-cross-demo [data-vl-panel] small { - font-size: 0.58rem; - letter-spacing: 0.09em; - text-transform: uppercase; - color: var(--vl-text-secondary); - } - .api-orb-grid-link { - min-height: 100%; - display: grid !important; - gap: 0.28rem; - place-content: center; - text-transform: none !important; - letter-spacing: 0.01em !important; - padding: 1rem; - position: relative; - overflow: hidden; - isolation: isolate; - border: 0; - background: transparent; - transition: - color var(--vl-transition-fast), - transform 180ms var(--vl-ease-cinematic); - } - - .api-orb-grid-link::before { - content: ""; - position: absolute; - inset: 0; - z-index: 0; - background: linear-gradient( - 160deg, - color-mix(in oklch, var(--vl-color-primary) 84%, transparent), - color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 72%, transparent) - ); - transform: scaleY(0); - transform-origin: bottom center; - transition: transform 220ms var(--vl-ease-cinematic); - } - - .api-orb-grid-link::after { - content: ""; - position: absolute; - inset: 1px; - z-index: 1; - background: linear-gradient(180deg, color-mix(in oklch, white 14%, transparent), transparent 36%); - opacity: 0; - transition: opacity var(--vl-transition-fast); - } - - .api-orb-grid-link > * { pointer-events: none; } - - .api-orb-grid-link > * { - position: relative; - z-index: 2; - transition: color var(--vl-transition-fast); - } - - .api-orb-grid-link:hover, - .api-orb-grid-link:focus-visible { - transform: translateY(-1px); - } - - .api-orb-grid-link:hover::before, - .api-orb-grid-link:focus-visible::before { - transform: scaleY(1); - } - - .api-orb-grid-link:hover::after, - .api-orb-grid-link:focus-visible::after { - opacity: 1; - } - - .api-orb-grid-link:hover .api-orb-title, - .api-orb-grid-link:focus-visible .api-orb-title, - .api-orb-grid-link:hover .api-orb-meta, - .api-orb-grid-link:focus-visible .api-orb-meta { - color: var(--vl-text-inverse, oklch(98% 0.01 95)); - } - - .api-orb-grid-link:focus-visible { - outline: var(--vl-focus-ring-width) solid var(--vl-focus-ring-color); - outline-offset: -2px; - } - - .api-orb-title { - font-family: var(--vl-font-family-display); - font-size: clamp(0.95rem, 1.5vw, 1.2rem); - line-height: 1.02; - font-weight: 700; - } - - .api-orb-meta { - margin: 0; - font-family: var(--vl-font-family-mono); - font-size: clamp(0.62rem, 1vw, 0.72rem); - text-transform: uppercase; - letter-spacing: 0.11em; - color: color-mix(in oklch, var(--vl-text-secondary) 85%, var(--vl-color-primary)); - } - - .api-orb-stage { - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 68%, transparent); - border-radius: 14px; - overflow: clip; - background: color-mix(in oklch, var(--vl-bg-surface-elevated) 78%, transparent); - } - - .api-orb-stage .api-target { - min-height: min(52vw, 430px); - display: block; - } - - .api-orb-stage [vl-effect="hover-orb-grid"] { - --vl-orb-grid-bg: linear-gradient( - 170deg, - color-mix(in oklch, var(--vl-bg-surface-elevated) 76%, transparent), - color-mix(in oklch, var(--vl-bg-main) 92%, transparent) - ); - --vl-orb-grid-ink: color-mix(in oklch, var(--vl-text-primary) 90%, var(--vl-color-primary)); - --vl-orb-grid-size-a: clamp(3rem, 13vw, 5.8rem); - --vl-orb-grid-size-b: clamp(2.4rem, 9vw, 4.4rem); - } - - .api-orb-stage [vl-effect="hover-orb-grid"] > ul > li { - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 82%, transparent); - } - - .api-orb-stage [vl-effect="hover-orb-grid"] > ul { - display: flex; - flex-wrap: wrap; - justify-content: center; - align-content: center; - align-items: stretch; - gap: 0.7rem; - min-height: 100%; - max-width: 34rem; - margin: 0 auto !important; - padding: 1rem !important; - } - - .api-orb-stage [vl-effect="hover-orb-grid"] > ul > li { - flex: 0 1 clamp(10rem, 22vw, 14rem); - min-height: clamp(4.5rem, 9vw, 6rem); - display: flex; - align-items: center; - } - - .api-orb-stage [vl-effect="hover-orb-grid"] > ul > li > .api-orb-grid-link:hover, - .api-orb-stage [vl-effect="hover-orb-grid"] > ul > li > .api-orb-grid-link:focus-visible { - background: color-mix(in oklch, var(--vl-color-primary) 10%, transparent); - } - - .api-orb-stage [vl-effect="hover-orb-grid"]::after, - .api-orb-stage [vl-effect="hover-orb-grid"]::before { - border-radius: 999px; - filter: blur(14px) saturate(120%); - transition: - opacity var(--vl-transition-normal), - transform 220ms var(--vl-ease-cinematic); - } - - .api-orb-stage [vl-effect="hover-orb-grid"]::after { - background: radial-gradient(circle, color-mix(in oklch, var(--vl-color-primary) 44%, white), transparent 66%); - mix-blend-mode: screen; - box-shadow: 0 0 40px color-mix(in oklch, var(--vl-color-primary) 22%, transparent); - } - - .api-orb-stage [vl-effect="hover-orb-grid"]::before { - background: radial-gradient(circle, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 58%, transparent), transparent 70%); - mix-blend-mode: soft-light; - box-shadow: 0 0 34px color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 20%, transparent); - } - - .api-orb-stage [vl-effect="hover-orb-grid"]:not(:hover)::after, - .api-orb-stage [vl-effect="hover-orb-grid"]:not(:hover)::before { - opacity: 0.16; - } - - .api-card .api-target[vl-timeline="view"], - .api-card .api-target[vl-timeline="scroll"] { - animation-timeline: auto !important; - animation-range: normal !important; - } - - .api-card .api-target[vl-scroll], - .api-card .api-scroll-track[vl-scroll] { - animation-timeline: auto !important; - animation-range: normal !important; - } - - .api-card .api-target[vl-effect="rotate-scroll"], - .api-card .api-target[vl-effect="wipe-reveal"], - .api-card .api-target[vl-effect="depth-push"], - .api-card .api-scroll-track[vl-effect="scroll-marquee"] { - animation-duration: 1400ms !important; - } - - .api-card .api-target[vl-effect="shimmer-text"] { background: none; } - - .api-pin-showcase { - grid-column: 1 / -1; - min-height: clamp(58rem, 132vh, 84rem); - } - - .api-pin-story { - display: grid; - grid-template-columns: minmax(0, 0.88fr) minmax(0, 1.12fr); - gap: clamp(0.8rem, 2vw, 1.25rem); - align-items: start; - } - - .api-pin-flow { - display: grid; - gap: clamp(0.85rem, 1.8vw, 1.15rem); - } - - .api-pin-step { - min-height: clamp(11rem, 26vh, 16rem); - border-radius: 0.9rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); - background: - linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-surface-elevated) 72%, transparent), color-mix(in oklch, var(--vl-bg-main) 90%, transparent)); - padding: clamp(0.8rem, 1.8vw, 1.1rem); - display: grid; - gap: 0.6rem; - align-content: start; - } - - .api-pin-step strong { - margin: 0; - font-family: var(--vl-font-family-display); - font-size: clamp(1.05rem, 1.5vw, 1.35rem); - line-height: 0.98; - text-transform: none; - letter-spacing: 0; - color: var(--vl-text-primary); - } - - .api-pin-step p { - margin: 0; - color: var(--vl-text-secondary); - font-size: 0.78rem; - line-height: 1.55; - } - - .api-pin-anchor { - position: sticky; - top: var(--api-pin-top, clamp(6.1rem, 10vh, 7.6rem)); - min-height: clamp(13rem, 28vh, 18rem); - border-radius: 0.95rem; - border: 1px solid color-mix(in oklch, var(--vl-color-primary) 45%, transparent); - background: - radial-gradient(circle at 70% 30%, color-mix(in oklch, var(--vl-color-primary) 20%, transparent), transparent 55%), - color-mix(in oklch, var(--vl-bg-surface-elevated) 80%, transparent); - display: grid; - place-content: center; - text-align: center; - gap: 0.45rem; - padding: 1rem; - } - - .api-pin-anchor strong { - font-family: var(--vl-font-family-display); - font-size: clamp(1.5rem, 2.8vw, 2.2rem); - line-height: 1; - } - - .api-pin-anchor span { - font-family: var(--vl-font-family-mono); - font-size: 0.72rem; - letter-spacing: 0.09em; - text-transform: uppercase; - color: var(--vl-text-muted); - } - - .api-pin-overlap { - grid-column: 1 / -1; - min-height: clamp(92rem, 210vh, 132rem); - overflow: visible; - } - - .api-overlap-stage { - display: grid; - grid-template-columns: minmax(0, 1fr); - gap: 0; - align-items: start; - min-height: inherit; - } - - .api-overlap-stack { - position: relative; - display: grid; - min-height: clamp(92rem, 220vh, 136rem); - padding: 0.7rem; - border-radius: 1rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 58%, transparent); - background: - radial-gradient(circle at 50% 14%, color-mix(in oklch, var(--vl-color-primary) 10%, transparent), transparent 58%), - color-mix(in oklch, var(--vl-bg-main) 92%, transparent); - overflow: visible; - isolation: isolate; - } - - .api-overlap-runway { - min-height: clamp(24rem, 56vh, 34rem); - } - - .api-overlap-panel { - position: sticky; - top: var(--api-pin-top, clamp(6.1rem, 10vh, 7.6rem)); - min-height: clamp(17rem, 38vh, 22rem); - border-radius: 0.9rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 90%, transparent); - padding: clamp(1rem, 2.2vw, 1.35rem); - display: grid; - gap: 0.72rem; - align-content: start; - box-shadow: 0 14px 28px color-mix(in oklch, black 20%, transparent); - transform: translate3d(0, 0, 0); - transform-origin: top center; - } - - .api-overlap-panel + .api-overlap-panel { - margin-top: clamp(25rem, 56vh, 35rem); - } - - .api-overlap-panel:nth-child(1) { - z-index: 1; - background: - radial-gradient(circle at 18% 12%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 48%), - color-mix(in oklch, var(--vl-bg-main) 92%, transparent); - transform: translate3d(0, 0, 0) scale(1); - } - - .api-overlap-panel:nth-child(2) { - z-index: 2; - background: - radial-gradient(circle at 84% 20%, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 14%, transparent), transparent 52%), - color-mix(in oklch, var(--vl-bg-surface-elevated) 90%, transparent); - transform: translate3d(0.22rem, 0.5rem, 0) scale(0.992); - } - - .api-overlap-panel:nth-child(3) { - z-index: 3; - border-color: color-mix(in oklch, var(--vl-color-primary) 46%, transparent); - background: - radial-gradient(circle at 50% 16%, color-mix(in oklch, var(--vl-color-primary) 20%, transparent), transparent 56%), - color-mix(in oklch, var(--vl-bg-surface-elevated) 88%, transparent); - transform: translate3d(0.44rem, 1rem, 0) scale(0.985); - } - - .api-overlap-panel strong { - margin: 0; - font-family: var(--vl-font-family-display); - font-size: clamp(1.02rem, 1.5vw, 1.3rem); - text-transform: none; - letter-spacing: 0; - color: var(--vl-text-primary); - } - - .api-overlap-panel p { - margin: 0; - color: var(--vl-text-secondary); - font-size: 0.78rem; - line-height: 1.55; - } - - .api-timeline-board { - display: grid; - gap: 0.52rem; - } - - .api-timeline-lane { - border-radius: 0.65rem; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); - background: color-mix(in oklch, var(--vl-bg-main) 88%, transparent); - padding: 0.45rem 0.55rem; - display: grid; - gap: 0.32rem; - } - - .api-timeline-lane strong { - font-family: var(--vl-font-family-mono); - font-size: 0.66rem; - letter-spacing: 0.09em; - text-transform: uppercase; - color: var(--vl-text-muted); - } - - .api-timeline-axis { - display: flex; - align-items: center; - gap: 0.4rem; - } - - .api-timeline-axis span { - height: 5px; - border-radius: 999px; - flex: 1 1 0; - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); - background: color-mix(in oklch, var(--vl-bg-surface-elevated) 78%, transparent); - } - - .api-timeline-axis span.is-active { - background: color-mix(in oklch, var(--vl-color-primary) 42%, transparent); - border-color: color-mix(in oklch, var(--vl-color-primary) 56%, transparent); - } - - .api-timeline-note { - margin: 0; - font-size: 0.72rem; - color: var(--vl-text-secondary); - } - - @media (max-width: 48rem) { - .api-shell { max-width: calc(100vw - 1.3rem); padding-top: 5.8rem; } - .api-card--lead { grid-column: auto; } - .api-grid.api-grid--hover-cards { - grid-template-columns: 1fr; - } - .api-card--text-ring { - grid-column: auto; - } - .api-card--circle-scroll { - grid-column: auto; - } - .api-card--code-greeting { - grid-column: auto; - } - .api-card--cube-triad { - grid-column: auto; - } - .api-grid.api-grid--cube-triad { - grid-template-columns: 1fr; - } - .api-target.api-text-ring-demo { - min-height: clamp(12.8rem, 66vw, 16rem); - } - .api-target.api-circle-text-scroll-demo { - min-height: clamp(14rem, 70vw, 18rem); - } - .api-target.api-code-greeting-demo { - min-height: clamp(11rem, 62vw, 13.5rem); - } - .api-target.api-cube-triad-demo { - min-height: clamp(14rem, 72vw, 18.5rem); - } - .api-grid--hover-cards .api-card--hover-fan, - .api-grid--hover-cards .api-card--hover-stack, - .api-grid--hover-cards .api-card--hover-cross, - .api-grid--hover-cards .api-card--hover-duotone { - grid-column: auto; - grid-row: auto; - } - .api-controls { - width: calc(100vw - 1rem); - border-radius: 12px; - padding: 0.45rem; - } - .api-controls-row { - gap: 0.36rem; - } - .api-selected { - transform: translate(-0.12rem, calc(-100% + 0.34rem)); - max-width: calc(100vw - 3rem); - overflow: hidden; - text-overflow: ellipsis; - } - .api-runtime-commands { - width: 100%; - } - .api-control { - min-width: max-content; - padding: 0.16rem 0.22rem; - } - .api-control .vl-range { - width: 84px; - max-width: 84px; - } - .api-hero-inner { grid-template-columns: minmax(0, 1fr); } - .api-hero-title { max-width: 100%; } - .api-hero-stage { min-height: clamp(18rem, 62vw, 24rem); } - .api-pin-story { grid-template-columns: minmax(0, 1fr); } - .api-pin-showcase { min-height: auto; } - .api-card--illustration-editor { - min-height: auto; - } - .api-target--illustration-editor { - min-height: clamp(30rem, 160vw, 38rem); - padding: 0.78rem; - } - .api-illustration-editor { - min-height: clamp(27rem, 148vw, 35rem); - } - .api-editor-code { - width: min(100%, 17.2rem); - } - .api-editor-code--html { - inset-inline-start: 0; - } - .api-editor-code--css { - inset-inline-start: clamp(0.8rem, 6vw, 2.2rem); - inset-block-start: clamp(10.3rem, 55vw, 12.2rem); - } - .api-editor-code--js { - inset-inline-start: 0; - inset-block-start: clamp(20.6rem, 110vw, 24rem); - } - .api-editor-block { - inset-inline-end: 0; - width: min(100%, 21rem); - height: clamp(20.5rem, 112vw, 25rem); - } - .api-pin-anchor { top: clamp(5.9rem, 11vw, 7.2rem); } - .api-pin-overlap { min-height: clamp(66rem, 190vw, 112rem); } - .api-overlap-stack { min-height: clamp(74rem, 210vw, 118rem); padding: 0.62rem; } - .api-overlap-runway { min-height: clamp(16rem, 46vw, 26rem); } - .api-overlap-panel { - top: clamp(5.9rem, 11vw, 7.2rem); - min-height: clamp(13rem, 44vw, 18rem); - padding: clamp(0.85rem, 2.2vw, 1.05rem); - } - .api-overlap-panel + .api-overlap-panel { - margin-top: clamp(16rem, 50vw, 24rem); - } - .api-overlap-panel:nth-child(2) { transform: translate3d(0.16rem, 0.42rem, 0) scale(0.994); } - .api-overlap-panel:nth-child(3) { transform: translate3d(0.32rem, 0.84rem, 0) scale(0.988); } - body { background-attachment: fixed; } - } +@view-transition { navigation: auto; } + + body { + background: + radial-gradient(1200px 500px at 15% -10%, color-mix(in oklch, var(--vl-color-primary) 20%, transparent), transparent), + radial-gradient(900px 380px at 100% -8%, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 18%, transparent), transparent), + var(--vl-bg-main); + background-attachment: fixed; + background-repeat: no-repeat; + background-size: cover; + padding-bottom: clamp(8.5rem, 14vh, 11rem); + } + + .api-shell { + max-width: min(1320px, calc(100vw - 2.2rem)); + margin: 0 auto; + padding: 6.5rem 0 4.5rem; + } + + .api-hero { + position: relative; + isolation: isolate; + overflow: clip; + border-radius: clamp(1rem, 2.4vw, 1.6rem); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 66%, transparent); + min-height: clamp(28rem, 64vw, 42rem); + padding: clamp(1.2rem, 3vw, 2.2rem); + background: + radial-gradient(circle at 74% 22%, color-mix(in oklch, var(--vl-color-primary) 14%, transparent), transparent 34%), + linear-gradient(180deg, var(--vl-bg-main), color-mix(in oklch, var(--vl-bg-inset) 88%, transparent)); + } + + .api-hero-wash { + position: absolute; + inset: -18% -8% 40%; + z-index: 0; + pointer-events: none; + background: + radial-gradient(ellipse 80% 60% at 70% 20%, color-mix(in oklch, var(--vl-color-primary) 18%, transparent), transparent 55%), + radial-gradient(ellipse 50% 40% at 20% 78%, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 12%, transparent), transparent 52%); + opacity: 0.88; + animation: api-hero-wash 18s ease-in-out infinite alternate; + } + + @keyframes api-hero-wash { + from { transform: translate3d(-2%, 0, 0) scale(1); opacity: 0.74; } + to { transform: translate3d(2%, 1.5%, 0) scale(1.05); opacity: 0.96; } + } + + .api-hero-inner { + position: relative; + z-index: 1; + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(18rem, 0.9fr); + gap: clamp(1rem, 2.5vw, 2rem); + align-items: stretch; + min-height: 100%; + } + + .api-hero-copy { + display: grid; + align-content: end; + gap: 1rem; + } + + .api-hero-kicker { + display: inline-flex; + align-items: center; + gap: 0.55rem; + margin: 0; + } + + .api-hero-kicker-line { + width: 2.2rem; + height: 1px; + background: linear-gradient(90deg, color-mix(in oklch, var(--vl-color-primary) 72%, transparent), transparent); + } + + .api-hero-title { + margin: 0; + max-width: 12ch; + font-family: var(--vl-font-family-display); + font-size: clamp(2.05rem, 5.6vw, 4.7rem); + line-height: 0.9; + letter-spacing: -0.05em; + text-transform: uppercase; + } + + .api-hero-title em { + font-style: normal; + color: color-mix(in oklch, var(--vl-color-primary) 72%, var(--vl-text-primary)); + } + + .api-hero-lead { + margin: 0; + max-width: 58ch; + color: var(--vl-text-secondary); + line-height: 1.62; + } + + .api-hero-actions { + display: flex; + flex-wrap: wrap; + gap: 0.7rem; + } + + .api-hero-btn { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 2.65rem; + padding: 0.58rem 1rem; + border-radius: 999px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); + text-decoration: none; + font-size: 0.76rem; + letter-spacing: 0.08em; + text-transform: uppercase; + font-family: var(--vl-font-family-mono); + color: var(--vl-text-primary); + background: color-mix(in oklch, var(--vl-bg-surface-elevated) 74%, transparent); + } + + .api-hero-btn--primary { + border-color: color-mix(in oklch, var(--vl-color-primary) 54%, transparent); + background: color-mix(in oklch, var(--vl-color-primary) 18%, transparent); + } + + .api-hero-chip-row { + display: flex; + flex-wrap: wrap; + gap: 0.55rem; + } + + .api-hero-chip-row a { + border-radius: 999px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 66%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 84%, transparent); + padding: 0.28rem 0.62rem; + text-decoration: none; + font-size: 0.7rem; + font-family: var(--vl-font-family-mono); + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--vl-text-secondary); + } + + .api-hero-stage { + position: relative; + border-radius: clamp(0.9rem, 2vw, 1.2rem); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + background: + radial-gradient(circle at 50% 45%, color-mix(in oklch, var(--vl-color-primary) 15%, transparent), transparent 34%), + linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-surface-elevated) 76%, transparent), color-mix(in oklch, var(--vl-bg-main) 96%, transparent)); + overflow: hidden; + min-height: clamp(20rem, 44vw, 30rem); + } + + .api-hero-orbit { + position: absolute; + inset: 0; + margin: auto; + width: min(100%, 25rem); + aspect-ratio: 1; + } + + .api-hero-orbit span { + position: absolute; + inset: 50%; + border-radius: 50%; + border: 1px solid color-mix(in oklch, var(--vl-color-primary) 28%, transparent); + translate: -50% -50%; + } + + .api-hero-orbit span:nth-child(1) { width: 100%; height: 100%; } + .api-hero-orbit span:nth-child(2) { width: 72%; height: 72%; } + .api-hero-orbit span:nth-child(3) { + width: 44%; + height: 44%; + background: color-mix(in oklch, var(--vl-color-primary) 12%, transparent); + } + + .api-hero-panel { + position: absolute; + display: grid; + gap: 0.5rem; + width: min(100%, 18rem); + padding: 1.05rem; + border-radius: 0.95rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); + background: color-mix(in oklch, var(--vl-bg-surface-elevated) 74%, transparent); + backdrop-filter: blur(8px); + } + + .api-hero-panel p, + .api-hero-panel strong, + .api-hero-panel span { margin: 0; } + + .api-hero-panel strong { + font-family: var(--vl-font-family-display); + font-size: clamp(1rem, 1.6vw, 1.35rem); + line-height: 0.98; + } + + .api-hero-panel span { + color: var(--vl-text-secondary); + font-size: 0.77rem; + } + + .api-hero-panel--primary { top: 10%; left: 6%; } + .api-hero-panel--secondary { right: 6%; bottom: 12%; } + + .api-hero-status { + position: absolute; + left: 1rem; + bottom: 1rem; + display: grid; + gap: 0.35rem; + text-transform: uppercase; + letter-spacing: 0.14em; + font-size: 0.62rem; + color: var(--vl-text-muted); + } + + .api-hero-status > span { + display: inline-flex; + align-items: center; + width: fit-content; + border-radius: 999px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 54%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 74%, transparent); + padding: 0.14rem 0.5rem; + } + + .api-kicker { + margin: 0; + font-family: var(--vl-font-family-mono); + text-transform: uppercase; + letter-spacing: 0.14em; + font-size: 0.72rem; + color: var(--vl-text-muted); + } + + .api-shell a[href], + .api-shell a[href]:visited { + color: var(--vl-text-primary); + } + + .api-quick-nav { + display: flex; + gap: 0.55rem; + flex-wrap: wrap; + } + + .api-quick-nav a { + color: var(--vl-text-secondary); + } + + .api-quick-nav a:visited { + color: var(--vl-text-secondary); + } + + .api-quick-nav a:hover { + color: var(--vl-text-primary); + } + + .api-controls { + position: fixed; + left: 50%; + bottom: clamp(0.55rem, 1.4vh, 1rem); + transform: translateX(-50%); + z-index: 42; + width: min(960px, calc(100vw - 0.85rem)); + padding: 0.38rem 0.42rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 68%, transparent); + border-radius: 11px; + background: + linear-gradient( + 180deg, + color-mix(in oklch, var(--vl-bg-surface-elevated) 62%, transparent), + color-mix(in oklch, var(--vl-bg-main) 72%, transparent) + ); + backdrop-filter: blur(14px) saturate(120%); + box-shadow: + 0 14px 42px color-mix(in oklch, black 34%, transparent), + inset 0 1px 0 color-mix(in oklch, white 10%, transparent); + } + + .api-controls-row { + display: flex; + align-items: center; + gap: 0.38rem; + flex-wrap: nowrap; + overflow: hidden; + } + + .api-controls-readout { + flex: 1 1 auto; + min-width: 0; + display: grid; + gap: 0.12rem; + } + + .api-live-hint { + margin: 0; + font-size: 0.68rem; + line-height: 1.35; + color: var(--vl-text-secondary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .api-live-readout { + display: block; + margin: 0; + padding: 0; + font-family: var(--vl-font-family-mono); + font-size: 0.58rem; + line-height: 1.35; + color: color-mix(in oklch, var(--vl-color-primary) 78%, var(--vl-text-primary)); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + background: none; + border: 0; + } + + .api-controls-sliders { + display: inline-flex; + align-items: center; + gap: 0.28rem; + flex: 0 0 auto; + } + + .api-controls-drawer { + margin-top: 0.38rem; + padding-top: 0.38rem; + border-top: 1px solid color-mix(in oklch, var(--vl-border-subtle) 55%, transparent); + display: grid; + gap: 0.38rem; + } + + .api-controls-drawer[hidden] { + display: none !important; + } + + .api-controls-head { + display: none; + } + + .api-selected { + position: absolute; + top: 0; + left: 0; + transform: translate(-0.22rem, calc(-100% + 0.38rem)); + z-index: 2; + font-family: var(--vl-font-family-mono); + font-size: 0.58rem; + color: var(--vl-text-primary); + letter-spacing: 0.06em; + text-transform: uppercase; + border: 1px solid color-mix(in oklch, var(--vl-color-primary) 42%, transparent); + border-radius: 999px; + background: + linear-gradient( + 180deg, + color-mix(in oklch, var(--vl-bg-surface-elevated) 72%, transparent), + color-mix(in oklch, var(--vl-bg-main) 86%, transparent) + ); + box-shadow: + 0 8px 18px color-mix(in oklch, black 22%, transparent), + inset 0 1px 0 color-mix(in oklch, white 10%, transparent); + padding: 0.22rem 0.52rem; + white-space: nowrap; + max-width: min(18rem, calc(100vw - 2rem)); + overflow: hidden; + text-overflow: ellipsis; + } + + .api-controls-grid { display: contents; } + + .api-runtime-commands { + display: flex; + align-items: center; + gap: 0.28rem; + flex-wrap: wrap; + } + + .api-runtime-command-chip { + display: inline-flex; + align-items: center; + min-height: 1.35rem; + padding: 0.16rem 0.4rem; + border-radius: 999px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 82%, transparent); + font-family: var(--vl-font-family-mono); + font-size: 0.56rem; + letter-spacing: 0.04em; + color: var(--vl-text-secondary); + white-space: nowrap; + } + + .api-runtime-command-chip.is-available { + border-color: color-mix(in oklch, var(--vl-color-primary) 46%, transparent); + background: color-mix(in oklch, var(--vl-color-primary) 14%, transparent); + color: var(--vl-text-primary); + } + + .api-runtime-command-chip.is-empty { + opacity: 0.72; + font-style: italic; + } + + .api-runtime-actions { + display: flex; + flex-wrap: nowrap; + gap: 0.28rem; + flex: 0 0 auto; + } + + .api-runtime-btn { + width: 1.75rem; + min-width: 1.75rem; + height: 1.75rem; + min-height: 1.75rem; + padding: 0; + border-radius: 8px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 66%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 84%, transparent); + color: var(--vl-text-primary); + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + } + + .api-runtime-btn svg { + width: 0.82rem; + height: 0.82rem; + } + + .api-runtime-btn.is-active { + border-color: color-mix(in oklch, var(--vl-color-primary) 56%, transparent); + background: color-mix(in oklch, var(--vl-color-primary) 16%, transparent); + } + + .api-control { + display: inline-flex; + align-items: center; + gap: 0.24rem; + padding: 0.14rem 0.22rem; + border-radius: 8px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 58%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 82%, transparent); + } + + .api-control label { + display: inline-flex; + align-items: center; + justify-content: center; + width: 1.05rem; + height: 1.05rem; + border-radius: 999px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + background: color-mix(in oklch, var(--vl-bg-surface-elevated) 70%, transparent); + font-size: 0.54rem; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--vl-text-muted); + white-space: nowrap; + } + + .api-control .vl-range { + width: clamp(56px, 10vw, 96px); + max-width: 96px; + } + + .api-control-value { + min-width: 2.85rem; + font-family: var(--vl-font-family-mono); + font-size: 0.58rem; + color: var(--vl-text-primary); + text-align: right; + white-space: nowrap; + } + + .api-group { + margin-top: 2rem; + scroll-margin-top: clamp(6.2rem, 10vh, 8.2rem); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); + border-radius: 18px; + background: color-mix(in oklch, var(--vl-bg-surface-elevated) 72%, transparent); + padding: 1rem; + } + + .api-contract-legend { + margin-top: 1.1rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 58%, transparent); + border-radius: 14px; + background: color-mix(in oklch, var(--vl-bg-surface-elevated) 70%, transparent); + padding: 0.85rem 1rem; + } + + .api-contract-legend__inner { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.5rem 0.65rem; + } + + .api-contract-legend__inner p { + margin: 0; + color: var(--vl-text-secondary); + font-size: 0.76rem; + } + + .api-kind-filter { + margin-top: 0.7rem; + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + } + + .api-kind-filter__btn { + border-radius: 999px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 86%, transparent); + color: var(--vl-text-secondary); + font-family: var(--vl-font-family-mono); + font-size: 0.62rem; + letter-spacing: 0.08em; + text-transform: uppercase; + padding: 0.24rem 0.58rem; + cursor: pointer; + } + + .api-kind-filter__btn.is-active { + border-color: color-mix(in oklch, var(--vl-color-primary) 42%, transparent); + background: color-mix(in oklch, var(--vl-color-primary) 16%, transparent); + color: color-mix(in oklch, var(--vl-color-primary) 72%, var(--vl-text-primary)); + } + + .api-scope-pill { + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 999px; + padding: 0.2rem 0.6rem; + border: 1px solid transparent; + font-size: 0.63rem; + letter-spacing: 0.08em; + text-transform: uppercase; + font-family: var(--vl-font-family-mono); + } + + .api-scope-pill--core { + border-color: color-mix(in oklch, var(--vl-color-primary) 44%, transparent); + background: color-mix(in oklch, var(--vl-color-primary) 14%, transparent); + color: color-mix(in oklch, var(--vl-color-primary) 66%, var(--vl-text-primary)); + } + + .api-scope-pill--extended { + border-color: color-mix(in oklch, var(--vl-color-secondary) 40%, transparent); + background: color-mix(in oklch, var(--vl-color-secondary) 14%, transparent); + color: color-mix(in oklch, var(--vl-color-secondary) 62%, var(--vl-text-primary)); + } + + .api-scope-pill--experimental { + border-color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 46%, transparent); + background: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 15%, transparent); + color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 72%, var(--vl-text-primary)); + } + + .api-group.api-scope-core { + border-left: 2px solid color-mix(in oklch, var(--vl-color-primary) 40%, transparent); + } + + .api-group.api-scope-extended { + border-left: 2px solid color-mix(in oklch, var(--vl-color-secondary) 36%, transparent); + } + + .api-group.api-scope-experimental { + border-left: 2px solid color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 44%, transparent); + } + + .api-group.api-scope-core .api-group-head h2::after, + .api-group.api-scope-extended .api-group-head h2::after, + .api-group.api-scope-experimental .api-group-head h2::after { + margin-left: 0.6rem; + border-radius: 999px; + padding: 0.16rem 0.48rem; + font-size: 0.58rem; + letter-spacing: 0.1em; + vertical-align: middle; + border: 1px solid transparent; + } + + .api-group.api-scope-core .api-group-head h2::after { + content: "core"; + color: color-mix(in oklch, var(--vl-color-primary) 70%, var(--vl-text-primary)); + background: color-mix(in oklch, var(--vl-color-primary) 13%, transparent); + border-color: color-mix(in oklch, var(--vl-color-primary) 40%, transparent); + } + + .api-group.api-scope-extended .api-group-head h2::after { + content: "extended"; + color: color-mix(in oklch, var(--vl-color-secondary) 68%, var(--vl-text-primary)); + background: color-mix(in oklch, var(--vl-color-secondary) 12%, transparent); + border-color: color-mix(in oklch, var(--vl-color-secondary) 34%, transparent); + } + + .api-group.api-scope-experimental .api-group-head h2::after { + content: "experimental"; + color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 78%, var(--vl-text-primary)); + background: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 13%, transparent); + border-color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 42%, transparent); + } + + #pin { + scroll-margin-top: clamp(7rem, 12vh, 8.8rem); + } + + .api-group-head { + display: grid; + gap: 0.35rem; + margin-bottom: 1rem; + } + + .api-group-head h2 { + margin: 0; + font-size: 1rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--vl-text-muted); + } + + .api-group-head p { + margin: 0; + color: var(--vl-text-secondary); + font-size: 0.86rem; + } + + .api-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 0.9rem; + } + + .api-card { + position: relative; + min-height: 154px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 68%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 84%, transparent); + border-radius: 14px; + padding: 0.9rem; + display: grid; + gap: 0.6rem; + align-content: start; + overflow: clip; + } + + .api-card[data-api-kind]::before { + content: attr(data-api-kind); + position: absolute; + top: 0.45rem; + right: 0.45rem; + border-radius: 999px; + padding: 0.12rem 0.45rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 88%, transparent); + color: var(--vl-text-muted); + font-family: var(--vl-font-family-mono); + font-size: 0.56rem; + letter-spacing: 0.08em; + text-transform: uppercase; + } + + .api-card[data-api-kind="channel"]::before, + .api-card[data-api-kind="timeline"]::before, + .api-card[data-api-kind="transition"]::before { + border-color: color-mix(in oklch, var(--vl-color-primary) 36%, transparent); + color: color-mix(in oklch, var(--vl-color-primary) 70%, var(--vl-text-muted)); + } + + .api-card[data-api-kind="interaction"]::before, + .api-card[data-api-kind="text"]::before, + .api-card[data-api-kind="ambient"]::before { + border-color: color-mix(in oklch, var(--vl-color-secondary) 36%, transparent); + color: color-mix(in oklch, var(--vl-color-secondary) 68%, var(--vl-text-muted)); + } + + .api-card[data-api-kind="scene"]::before, + .api-card[data-api-kind="3d"]::before, + .api-card[data-api-kind="experimental"]::before { + border-color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 40%, transparent); + color: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 76%, var(--vl-text-muted)); + } + + .api-card[data-api-kind="reference"]::before { + border-color: color-mix(in oklch, var(--vl-border-subtle) 72%, transparent); + color: var(--vl-text-muted); + } + + .api-card.is-selected { + border-color: color-mix(in oklch, var(--vl-color-primary) 55%, var(--vl-border-subtle)); + box-shadow: 0 0 0 1px color-mix(in oklch, var(--vl-color-primary) 35%, transparent); + } + + .api-card.is-filter-hidden { + display: none !important; + } + + .api-group.is-filter-empty { + display: none; + } + + .api-card--lead { + grid-column: span 2; + min-height: 170px; + } + + .api-card strong { + font-family: var(--vl-font-family-mono); + font-size: 0.72rem; + letter-spacing: 0.08em; + color: var(--vl-text-muted); + text-transform: uppercase; + } + + .api-code { + margin: 0; + padding: 0.72rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); + border-radius: 10px; + background: color-mix(in oklch, var(--vl-bg-main) 88%, transparent); + font-family: var(--vl-font-family-mono); + font-size: 0.67rem; + line-height: 1.5; + overflow: auto; + } + + .api-note { + margin: 0; + color: var(--vl-text-secondary); + font-size: 0.78rem; + } + + .api-target { + position: relative; + isolation: isolate; + min-height: 84px; + border-radius: 10px; + background: color-mix(in oklch, var(--vl-bg-main) 80%, transparent); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 50%, transparent); + display: grid; + place-items: center; + text-align: center; + padding: 0.65rem; + font-size: 0.84rem; + overflow: hidden; + } + + .api-target::after { + content: ""; + position: absolute; + pointer-events: none; + opacity: 0.22; + z-index: 0; + } + + .api-target > * { + position: relative; + z-index: 1; + } + + /* Channel / driver visual dialects (generic demo targets only) */ + .api-target[vl-enter]:not([class*="api-"]) { + font-family: var(--vl-font-family-display); + letter-spacing: 0.03em; + border-left: 2px solid color-mix(in oklch, var(--vl-color-primary) 62%, transparent); + background: + linear-gradient(145deg, color-mix(in oklch, var(--vl-color-primary) 10%, transparent), transparent 55%), + color-mix(in oklch, var(--vl-bg-main) 82%, transparent); + } + + .api-target[vl-enter]:not([class*="api-"])::after { + top: 0.55rem; + right: 0.55rem; + width: 2.1rem; + height: 2.1rem; + border: 1px solid color-mix(in oklch, var(--vl-color-primary) 40%, transparent); + clip-path: polygon(50% 0, 100% 100%, 0 100%); + background: color-mix(in oklch, var(--vl-color-primary) 55%, transparent); + } + + .api-target[vl-scroll]:not([class*="api-"]) { + font-family: var(--vl-font-family-mono); + font-size: 0.78rem; + letter-spacing: 0.06em; + text-transform: uppercase; + background: + repeating-linear-gradient( + 90deg, + color-mix(in oklch, var(--vl-color-secondary) 8%, transparent) 0 1px, + transparent 1px 14px + ), + linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-surface) 70%, transparent), color-mix(in oklch, var(--vl-bg-main) 88%, transparent)); + } + + .api-target[vl-scroll]:not([class*="api-"])::after { + inset: auto 0.65rem 0.55rem auto; + width: 1.6rem; + height: 2.4rem; + border: 1px solid color-mix(in oklch, var(--vl-color-secondary) 45%, transparent); + border-radius: 0.35rem; + background: color-mix(in oklch, var(--vl-color-secondary) 18%, transparent); + } + + .api-target[vl-loop]:not([class*="api-"]) { + border-radius: 999px; + font-family: var(--vl-font-family-mono); + font-size: 0.76rem; + background: + radial-gradient(circle at 50% 50%, color-mix(in oklch, var(--vl-color-primary) 14%, transparent), transparent 62%), + color-mix(in oklch, var(--vl-bg-main) 84%, transparent); + } + + .api-target[vl-loop]:not([class*="api-"])::after { + inset: 0.45rem; + border-radius: 999px; + border: 1px dashed color-mix(in oklch, var(--vl-color-primary) 42%, transparent); + background: transparent; + opacity: 0.35; + } + + .api-target[vl-hover]:not([class*="api-"]) { + border-style: dashed; + border-color: color-mix(in oklch, var(--vl-color-secondary) 48%, transparent); + font-family: var(--vl-font-family-display); + background: + radial-gradient(circle at 18% 22%, color-mix(in oklch, var(--vl-color-secondary) 12%, transparent), transparent 48%), + color-mix(in oklch, var(--vl-bg-main) 84%, transparent); + } + + .api-target[vl-hover]:not([class*="api-"])::after { + top: 50%; + left: 50%; + width: 0.55rem; + height: 0.55rem; + border-radius: 999px; + transform: translate(-50%, -50%); + background: color-mix(in oklch, var(--vl-color-secondary) 70%, transparent); + box-shadow: 0 0 0 6px color-mix(in oklch, var(--vl-color-secondary) 16%, transparent); + } + + .api-target[vl-exit]:not([class*="api-"]) { + font-family: var(--vl-font-family-display); + background: + linear-gradient(225deg, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 12%, transparent), transparent 58%), + color-mix(in oklch, var(--vl-bg-main) 80%, transparent); + border-right: 2px solid color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 50%, transparent); + } + + .api-target[vl-exit]:not([class*="api-"])::after { + bottom: 0.55rem; + left: 0.55rem; + width: 2rem; + height: 0.35rem; + border-radius: 999px; + background: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 55%, transparent); + } + + .api-target[vl-state]:not([class*="api-"]) { + background: + conic-gradient(from 210deg at 50% 50%, color-mix(in oklch, var(--vl-color-primary) 8%, transparent), transparent 55%, color-mix(in oklch, var(--vl-color-secondary) 8%, transparent)), + color-mix(in oklch, var(--vl-bg-main) 86%, transparent); + } + + .api-target[vl-effect]:not([class*="api-"]) { + font-family: var(--vl-font-family-mono); + font-size: 0.76rem; + background: + linear-gradient(0deg, color-mix(in oklch, var(--vl-border-subtle) 18%, transparent) 1px, transparent 1px), + linear-gradient(90deg, color-mix(in oklch, var(--vl-border-subtle) 18%, transparent) 1px, transparent 1px), + color-mix(in oklch, var(--vl-bg-main) 84%, transparent); + background-size: 12px 12px, 12px 12px, auto; + } + + .api-card[data-api-kind="scene"] .api-target:not([class*="api-"]), + .api-card[data-api-kind="3d"] .api-target:not([class*="api-"]) { + min-height: 96px; + background: + radial-gradient(circle at 50% 120%, color-mix(in oklch, var(--vl-color-primary) 14%, transparent), transparent 58%), + linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-surface-elevated) 70%, transparent), color-mix(in oklch, var(--vl-bg-main) 88%, transparent)); + perspective: 720px; + } + + .api-card[data-api-kind="text"] .api-target:not([class*="api-"]) { + font-family: var(--vl-font-family-display); + font-size: clamp(0.95rem, 2.4vw, 1.15rem); + letter-spacing: -0.02em; + background: + linear-gradient(180deg, transparent 68%, color-mix(in oklch, var(--vl-color-primary) 16%, transparent) 68%, color-mix(in oklch, var(--vl-color-primary) 16%, transparent) 72%, transparent 72%), + color-mix(in oklch, var(--vl-bg-main) 84%, transparent); + } + + .api-card[data-api-kind="ambient"] .api-target:not([class*="api-"]) { + border: 0; + background: + radial-gradient(circle at 30% 30%, color-mix(in oklch, var(--vl-color-primary) 18%, transparent), transparent 52%), + radial-gradient(circle at 72% 68%, color-mix(in oklch, var(--vl-color-secondary) 14%, transparent), transparent 48%), + color-mix(in oklch, var(--vl-bg-main) 86%, transparent); + } + + .api-card[data-api-kind="interaction"] .api-target:not([class*="api-"]) { + background: + radial-gradient(circle at 100% 0%, color-mix(in oklch, var(--vl-color-secondary) 12%, transparent), transparent 42%), + color-mix(in oklch, var(--vl-bg-main) 84%, transparent); + } + + .api-grid .api-card:nth-child(6n + 2) .api-target:not([class*="api-"]) { + border-radius: 1.1rem 0.55rem 1.1rem 0.55rem; + } + + .api-grid .api-card:nth-child(6n + 4) .api-target:not([class*="api-"]) { + border-radius: 0.55rem 1.1rem 0.55rem 1.1rem; + } + + .api-grid .api-card:nth-child(6n + 5) .api-target:not([class*="api-"]) { + outline: 1px solid color-mix(in oklch, var(--vl-border-subtle) 35%, transparent); + outline-offset: 3px; + } + + #ambient .api-target { + min-height: 8rem; + gap: 0.55rem; + align-content: center; + justify-items: center; + padding: 0.8rem; + background: linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-main) 70%, transparent), color-mix(in oklch, var(--vl-bg-surface) 72%, transparent)); + border: 0; + } + + #ambient .api-target::before { + content: ""; + width: 3.8rem; + height: 3.8rem; + border-radius: 1.1rem; + background: linear-gradient(145deg, var(--vl-color-primary), color-mix(in oklch, var(--vl-color-secondary) 72%, var(--vl-color-primary))); + box-shadow: var(--vl-shadow-glow); + animation: vl-morph 8s var(--vl-ease-in-out-smooth) infinite; + } + + #ambient .api-target { + font-size: 0.72rem; + letter-spacing: 0.08em; + text-transform: uppercase; + font-family: var(--vl-font-family-mono); + } + + @media (prefers-reduced-motion: reduce) { + #ambient .api-target::before { + animation: none; + } + } + + .api-target-inline { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 44px; + width: 100%; + text-decoration: none; + } + + .api-target-inline:visited { + color: var(--vl-text-primary); + } + + .api-chip-row { display: flex; gap: 0.45rem; flex-wrap: wrap; } + + .api-chip { + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 86%, transparent); + border-radius: 999px; + padding: 0.25rem 0.55rem; + font-size: 0.68rem; + font-family: var(--vl-font-family-mono); + } + + .api-scroll-track { display: flex; gap: 0.65rem; width: max-content; } + .api-scroll-track .api-chip { min-width: 120px; text-align: center; } + + .api-transition-stage { + position: relative; + min-height: 132px; + border-radius: 10px; + overflow: hidden; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 58%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 90%, transparent); + } + + .api-transition-screen { + position: absolute; + inset: 0; + display: grid; + align-content: center; + justify-items: start; + gap: 0.35rem; + padding: 0.85rem; + font-family: var(--vl-font-family-mono); + font-size: 0.67rem; + letter-spacing: 0.06em; + text-transform: uppercase; + } + + .api-transition-screen span { + display: inline-flex; + border-radius: 999px; + padding: 0.18rem 0.42rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); + } + + .api-transition-screen--from { + background: + radial-gradient(circle at 16% 18%, color-mix(in oklch, var(--vl-color-primary) 18%, transparent), transparent 44%), + color-mix(in oklch, var(--vl-bg-surface-elevated) 74%, transparent); + } + + .api-transition-screen--to { + background: + radial-gradient(circle at 84% 76%, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 20%, transparent), transparent 48%), + color-mix(in oklch, var(--vl-bg-main) 94%, transparent); + opacity: 0; + transform: translate3d(0, 0, 0) scale(1); + } + + .api-transition-hint { + margin: 0; + font-size: 0.72rem; + color: var(--vl-text-secondary); + } + + .api-transition-card:is(:hover, :focus-within, .is-playing) [data-transition-next] { + animation-duration: 900ms; + animation-fill-mode: both; + } + + .api-transition-card[data-api-transition="cinema"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { + animation-name: api-vt-cinema; + animation-timing-function: cubic-bezier(.2, .7, .2, 1); + } + + .api-transition-card[data-api-transition="wipe"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { + animation-name: api-vt-wipe; + animation-timing-function: cubic-bezier(.22, .8, .22, 1); + } + + .api-transition-card[data-api-transition="glide"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { + animation-name: api-vt-glide; + animation-timing-function: cubic-bezier(.23, .78, .21, 1); + } + + .api-transition-card[data-api-transition="iris"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { + animation-name: api-vt-iris; + animation-timing-function: cubic-bezier(.18, .74, .2, 1); + } + + .api-transition-card[data-api-transition="snap"]:is(:hover, :focus-within, .is-playing) [data-transition-next] { + animation-name: api-vt-snap; + animation-timing-function: steps(2, end); + } + + @keyframes api-vt-cinema { + from { opacity: 0; transform: scale(1.06); filter: blur(8px); } + to { opacity: 1; transform: scale(1); filter: blur(0); } + } + + @keyframes api-vt-wipe { + from { opacity: 1; clip-path: inset(0 100% 0 0); } + to { opacity: 1; clip-path: inset(0 0 0 0); } + } + + @keyframes api-vt-glide { + from { opacity: 0.2; transform: translate3d(30%, 0, 0); } + to { opacity: 1; transform: translate3d(0, 0, 0); } + } + + @keyframes api-vt-iris { + from { opacity: 1; clip-path: circle(0% at 50% 50%); } + to { opacity: 1; clip-path: circle(140% at 50% 50%); } + } + + @keyframes api-vt-snap { + 0% { opacity: 0; filter: contrast(150%); } + 100% { opacity: 1; filter: contrast(100%); } + } + + .api-children > * { + min-height: 36px; + border-radius: 8px; + display: grid; + place-items: center; + background: color-mix(in oklch, var(--vl-color-primary) 10%, transparent); + border: 1px solid color-mix(in oklch, var(--vl-color-primary) 36%, transparent); + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.08em; + } + + .api-target.api-children { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 0.45rem; + flex-wrap: nowrap; + } + + .api-target.api-children > * { + flex: 1 1 0; + min-width: 0; + } + + .api-typewriter { --vl-type-chars: 30ch; --vl-type-steps: 30; } + + .api-gradient-text-demo { + min-height: 5.4rem; + background: + radial-gradient(circle at 50% 50%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 56%), + color-mix(in oklch, var(--vl-bg-main) 90%, transparent); + } + + .api-gradient-text-demo > span { + font-family: var(--vl-font-family-display); + font-size: clamp(1.15rem, 2.4vw, 2rem); + line-height: 1.1; + letter-spacing: 0.01em; + } + + .api-card--text-ring { + grid-column: span 2; + } + + .api-target.api-text-ring-demo { + min-height: clamp(14rem, 28vw, 18rem); + display: grid; + place-items: center; + padding: 0; + text-align: initial; + background: + radial-gradient(circle at 50% 50%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 48%), + linear-gradient(135deg, color-mix(in oklch, black 92%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main))); + border-color: color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + } + + .api-text-ring-demo[vl-effect="text-ring-orbit"] { + --vl-text-ring-size: clamp(2rem, 6.2vw, 4rem); + pointer-events: none; + cursor: default; + } + + .api-text-ring-demo[vl-effect="text-ring-orbit"] * { + pointer-events: none; + } + + .api-text-ring-demo .vl-text-ring-stage { + width: min(100%, 23rem); + height: clamp(8rem, 22vw, 10.8rem); + } + + .api-card--circle-scroll { + grid-column: span 2; + } + + .api-card--code-greeting { + grid-column: span 2; + } + + .api-target.api-code-greeting-demo { + min-height: clamp(12rem, 24vw, 14rem); + display: grid; + place-items: center; + padding: clamp(1rem, 2vw, 1.4rem); + text-align: initial; + border-color: color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + background: + radial-gradient(circle at 50% 28%, color-mix(in oklch, var(--vl-color-primary) 16%, transparent), transparent 52%), + linear-gradient(145deg, color-mix(in oklch, black 90%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main))); + } + + .api-code-greeting-demo[vl-effect="code-greeting-stack"] { + font-size: clamp(1.02rem, 2.15vmin, 1.5rem); + max-width: min(100%, 34rem); + } + + .api-code-greeting-demo[vl-effect="code-greeting-stack"] .vl-code-line, + .api-code-greeting-demo[vl-effect="code-greeting-stack"] .vl-code-greetings-track > span { + white-space: nowrap; + font-weight: 510; + font-stretch: 101%; + letter-spacing: -0.01em; + } + + .api-code-greeting-demo[vl-effect="code-greeting-stack"]:is(:hover, :focus-within) .vl-code-line, + .api-code-greeting-demo[vl-effect="code-greeting-stack"]:is(:hover, :focus-within) .vl-code-greetings-track > span { + font-size: 1.04em; + font-weight: 660; + font-stretch: 112%; + letter-spacing: -0.015em; + } + + .api-target.api-circle-text-scroll-demo { + min-height: clamp(16rem, 34vw, 20rem); + display: grid; + place-items: center; + padding: 0; + text-align: initial; + border-color: color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + background: + radial-gradient(circle at 50% 50%, color-mix(in oklch, var(--vl-color-primary) 14%, transparent), transparent 48%), + linear-gradient(145deg, color-mix(in oklch, black 92%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main))); + } + + .api-circle-text-scroll-demo[vl-effect="circle-text-scroll"] { + --vl-circle-size: clamp(13.4rem, 38vmin, 20rem); + color: color-mix(in oklch, var(--vl-text-primary) 30%, white); + } + + .api-circle-text-scroll-demo[vl-effect="circle-text-scroll"] > ul > li > span { + font-size: clamp(0.82rem, 2.05vmin, 1.35rem); + font-weight: 510; + font-stretch: 98%; + letter-spacing: 0; + transition: + font-size var(--vl-transition-normal) var(--vl-ease-out-soft), + font-weight var(--vl-transition-normal) var(--vl-ease-out-soft), + font-stretch var(--vl-transition-normal) var(--vl-ease-out-soft), + letter-spacing var(--vl-transition-normal) var(--vl-ease-out-soft); + } + + .api-circle-text-scroll-demo[vl-effect="circle-text-scroll"]:is(:hover, :focus-within) > ul > li > span { + font-size: clamp(0.9rem, 2.25vmin, 1.45rem); + font-weight: 650; + font-stretch: 108%; + letter-spacing: 0.01em; + } + + .api-card--cube-triad { + grid-column: span 2; + } + + .api-grid.api-grid--cube-triad { + grid-template-columns: minmax(0, 1fr); + } + + .api-target.api-cube-triad-demo { + min-height: clamp(16rem, 35vw, 22rem); + display: grid; + place-items: center; + padding: 0; + text-align: initial; + border-color: color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + background: + radial-gradient(circle at 50% 22%, color-mix(in oklch, var(--vl-color-primary) 10%, transparent), transparent 50%), + linear-gradient(150deg, color-mix(in oklch, black 92%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main))); + } + + .api-cube-triad-demo[vl-effect="cube-triad-stage"] { + --vl-cube-unit: clamp(0.58rem, 0.94vmin, 0.86rem); + } + + .api-state-demo { opacity: 0.7; transform: translate3d(0, 8px, 0); } + .api-state-demo:hover { opacity: 1; transform: translate3d(0, 0, 0); } + .api-v2-stack { display: grid; gap: 0.4rem; } + .api-v2-stack > span { + font-family: var(--vl-font-family-mono); + font-size: 0.66rem; + letter-spacing: 0.05em; + color: var(--vl-text-secondary); + } + + .api-target[vl-effect="hover-orb-grid"] { min-height: 220px; display: block; text-align: initial; } + .api-target[vl-effect="hover-cross-swap"] { + min-height: 208px; + padding: 0; + display: block; + aspect-ratio: 0.92; + text-align: initial; + } + + .api-target[vl-effect="hover-card-fan"].api-hover-fan-demo { + --vl-hover-fan-card-width: 7.25rem; + --vl-hover-fan-card-height: 10.5rem; + --vl-hover-fan-gap: 0.55rem; + --vl-hover-fan-angle: 22deg; + --vl-hover-fan-perspective: 920px; + min-height: 13.5rem; + padding: 1rem 0.75rem; + display: flex; + flex-wrap: nowrap; + align-items: center; + justify-content: center; + gap: var(--vl-hover-fan-gap); + overflow: visible; + text-align: initial; + place-items: unset; + background: + radial-gradient(circle at 50% 0%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 62%), + color-mix(in oklch, var(--vl-bg-main) 88%, transparent); + } + + .api-target[vl-effect="hover-card-fan"].api-hover-fan-demo > button { + appearance: none; + -webkit-appearance: none; + font: inherit; + margin: 0; + padding: 0.75rem; + flex: 0 0 var(--vl-hover-fan-card-width); + width: var(--vl-hover-fan-card-width); + min-width: var(--vl-hover-fan-card-width); + max-width: var(--vl-hover-fan-card-width); + border: none; + background: transparent; + cursor: pointer; + } + + .api-hover-fan-demo .vl-hover-fan-index { + width: 4.25rem; + min-width: 4.25rem; + font-size: 2rem; + } + + .api-hover-fan-demo .vl-hover-fan-label { + font-size: 0.68rem; + } + + .api-target.api-hover-stack-demo { + position: relative; + display: block; + min-height: 12.5rem; + padding: 1.5rem 1rem; + overflow: visible; + text-align: initial; + place-items: unset; + cursor: pointer; + background: + radial-gradient(circle at 50% 100%, color-mix(in oklch, var(--vl-color-primary) 10%, transparent), transparent 55%), + color-mix(in oklch, var(--vl-bg-main) 86%, transparent); + } + + .api-hover-stack-demo .api-hover-stack-layer { + position: absolute; + left: 0; + right: 0; + margin-inline: auto; + width: min(88%, 11rem); + min-height: 3.4rem; + display: grid; + place-content: center; + border-radius: 0.7rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 68%, transparent); + background: color-mix(in oklch, var(--vl-bg-surface-elevated) 88%, transparent); + box-shadow: 0 8px 20px color-mix(in oklch, black 18%, transparent); + font-family: var(--vl-font-family-display); + font-size: 0.92rem; + letter-spacing: 0.04em; + color: var(--vl-text-primary); + z-index: 1; + } + + .api-hover-stack-demo .api-hover-stack-layer:nth-child(1) { + top: 1.1rem; + z-index: 1; + } + + .api-hover-stack-demo .api-hover-stack-layer:nth-child(2) { + top: 2.35rem; + z-index: 2; + } + + .api-hover-stack-demo .api-hover-stack-layer:nth-child(3) { + top: 3.6rem; + z-index: 3; + } + + .api-grid.api-grid--hover-cards { + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: stretch; + gap: clamp(0.75rem, 1.8vw, 1rem); + } + + .api-grid--hover-cards .api-card--hover-fan, + .api-grid--hover-cards .api-card--hover-duotone { + grid-column: 1 / -1; + } + + .api-target[vl-effect="duotone"].api-duotone-demo { + min-height: 250px; + display: grid; + place-items: center; + border-radius: 12px; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); + background: + radial-gradient(circle at 84% 18%, color-mix(in oklch, var(--vl-color-primary) 18%, transparent), transparent 52%), + color-mix(in oklch, var(--vl-bg-main) 90%, transparent); + --vl-duotone-a: color-mix(in oklch, var(--vl-color-primary) 84%, white 2%); + --vl-duotone-b: color-mix(in oklch, var(--vl-color-accent, var(--vl-color-secondary)) 80%, black 8%); + } + + .api-duotone-demo > img { + width: clamp(12rem, 34vw, 20rem); + max-width: 92%; + height: auto; + } + + .api-card--illustration-editor { + grid-column: 1 / -1; + min-height: clamp(38rem, 74vw, 46rem); + } + + .api-target--illustration-editor { + min-height: clamp(31rem, 64vw, 38rem); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + border-radius: 1rem; + padding: clamp(1rem, 2.2vw, 1.5rem); + overflow: hidden; + background: + radial-gradient(circle at 14% 16%, color-mix(in oklch, var(--vl-color-primary) 14%, transparent), transparent 44%), + linear-gradient(135deg, color-mix(in oklch, black 92%, var(--vl-bg-main)), color-mix(in oklch, black 96%, var(--vl-bg-main)) 100%); + } + + .api-illustration-editor { + --api-editor-panel-bg: color-mix(in oklch, black 84%, var(--vl-bg-main)); + --api-editor-panel-border: color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); + --api-editor-icon: color-mix(in oklch, var(--vl-text-secondary) 86%, transparent); + --api-editor-content: color-mix(in oklch, white 94%, transparent); + position: relative; + width: min(100%, 45rem); + min-height: clamp(28rem, 58vw, 35rem); + margin-inline: auto; + } + + .api-editor-panels { + position: absolute; + inset: 0; + } + + .api-editor-code { + position: absolute; + width: clamp(16.5rem, 38vw, 18.5rem); + min-height: 9.4rem; + border-radius: 0.52rem; + border: 1px solid var(--api-editor-panel-border); + background: var(--api-editor-panel-bg); + box-shadow: 0 0.3rem 2rem color-mix(in oklch, black 68%, transparent); + z-index: 2; + } + + .api-editor-code header { + display: grid; + grid-template-columns: 2rem 1fr 2rem; + align-items: center; + } + + .api-editor-code header svg { + margin: 0.62rem; + color: var(--api-editor-icon); + } + + .api-editor-code header h3 { + margin: 0; + font-family: var(--vl-font-family-mono); + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.1em; + color: color-mix(in oklch, var(--vl-text-secondary) 96%, transparent); + } + + .api-editor-code__content { + margin: 0 0.62rem 0.62rem; + } + + .api-editor-code__content code { + display: grid; + gap: 0.26rem; + color: var(--api-editor-content); + font-family: var(--vl-font-family-mono); + font-size: 0.68rem; + line-height: 1.4; + align-content: start; + justify-items: start; + min-height: 6.15rem; + padding: 0.05rem 0.1rem 0.3rem; + } + + .api-editor-code--html .api-editor-code__content code, + .api-editor-code--css .api-editor-code__content code { + display: block; + } + + .api-editor-code__content .c-r { color: color-mix(in oklch, #a88038 92%, var(--vl-color-warning, #c5902e)); } + .api-editor-code__content .c-o { color: color-mix(in oklch, #de7300 92%, var(--vl-color-warning, #de7300)); } + .api-editor-code__content .c-y { color: color-mix(in oklch, #e1ca72 92%, var(--vl-color-warning, #e1ca72)); } + .api-editor-code__content .c-g { color: color-mix(in oklch, #74b087 92%, var(--vl-color-success, #74b087)); } + .api-editor-code__content .c-p { color: color-mix(in oklch, #9f8198 92%, var(--vl-color-secondary)); } + .api-editor-code__content .c-b { color: color-mix(in oklch, #7a99ad 92%, var(--vl-color-accent, #7a99ad)); } + .api-editor-code__content .c-c { color: color-mix(in oklch, var(--vl-text-muted) 80%, transparent); } + .api-editor-code__content .c-w { color: color-mix(in oklch, white 92%, transparent); } + + .api-editor-code--html { + inset-inline-start: clamp(0.4rem, 4vw, 3.8rem); + inset-block-start: clamp(0.4rem, 2vw, 1rem); + } + + .api-editor-code--css { + inset-inline-start: clamp(1.8rem, 7.5vw, 7.4rem); + inset-block-start: clamp(11.2rem, 24vw, 12.8rem); + } + + .api-editor-code--js { + inset-inline-start: clamp(0rem, 1vw, 0.9rem); + inset-block-start: clamp(22rem, 44vw, 24.6rem); + } + + .api-editor-code--css .api-editor-code__content code { + gap: 0; + } + + .api-editor-line { + width: 0; + margin: 0; + border-right: 0.14em solid transparent; + overflow: hidden; + display: block; + white-space: nowrap; + } + + .api-editor-code--js .api-editor-code__content code { + min-height: 6.15rem; + display: grid; + place-items: center; + } + + .api-editor-empty-face { + display: inline-flex; + gap: 0.55rem; + align-items: center; + font-size: 0.86rem; + letter-spacing: 0.08em; + } + + .api-editor-line--2 { margin-inline-start: 1.6rem; } + .api-editor-line--3, + .api-editor-line--4, + .api-editor-line--5 { margin-inline-start: 3.1rem; } + + .api-editor-line--1 { + animation: api-editor-type-1 1s 0s steps(12, end) forwards, api-editor-cursor 1s linear 0s 2; + } + + .api-editor-line--2 { + animation: api-editor-type-2 3s 1s steps(30, end) forwards, api-editor-cursor 1s linear 1s 3; + } + + .api-editor-line--3 { + animation: api-editor-type-3 1s 4s steps(12, end) forwards, api-editor-cursor 1s linear 4s 2; + } + + .api-editor-line--4 { + animation: api-editor-type-4 1s 5s steps(12, end) forwards, api-editor-cursor 1s linear 5s 2; + } + + .api-editor-line--5 { + animation: api-editor-type-5 3s 6s steps(30, end) forwards, api-editor-cursor-persist 1s linear 6s infinite; + } + + .api-editor-block { + position: absolute; + inset-inline-end: clamp(0.2rem, 2vw, 1rem); + inset-block-start: clamp(1rem, 3vw, 1.4rem); + width: min(100%, 31.2rem); + height: clamp(23.8rem, 53vw, 26.3rem); + border-radius: 0.65rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 54%, transparent); + background: linear-gradient(109.61deg, color-mix(in oklch, var(--vl-text-secondary) 54%, transparent) 4.26%, color-mix(in oklch, var(--vl-bg-main) 90%, transparent) 84.84%); + } + + @keyframes api-editor-type-1 { + from { width: 0; } + to { width: 4.3rem; } + } + + @keyframes api-editor-type-2 { + from { width: 0; } + to { width: 14rem; } + } + + @keyframes api-editor-type-3 { + from { width: 0; } + to { width: 4.9rem; } + } + + @keyframes api-editor-type-4 { + from { width: 0; } + to { width: 5.4rem; } + } + + @keyframes api-editor-type-5 { + from { width: 0; } + to { width: 9rem; } + } + + @keyframes api-editor-cursor { + from, to, 20%, 80% { border-color: transparent; } + 25%, 75% { border-color: color-mix(in oklch, white 92%, transparent); } + } + + @keyframes api-editor-cursor-persist { + from, 45% { border-color: transparent; } + 50%, to { border-color: color-mix(in oklch, white 92%, transparent); } + } + + @media (prefers-reduced-motion: reduce) { + .api-editor-line { + width: auto; + border-right: 0; + animation: none; + } + } + + .api-cross-demo [data-vl-panel] { + font-family: var(--vl-font-family-mono); + } + + .api-cross-demo [data-vl-panel] strong, + .api-cross-demo [data-vl-panel] small { + position: relative; + z-index: 1; + margin: 0; + } + + .api-cross-demo [data-vl-panel] strong { + font-family: var(--vl-font-family-display); + font-size: clamp(0.92rem, 1.3vw, 1.12rem); + line-height: 0.96; + text-transform: none; + letter-spacing: 0; + } + + .api-cross-demo [data-vl-panel] small { + font-size: 0.58rem; + letter-spacing: 0.09em; + text-transform: uppercase; + color: var(--vl-text-secondary); + } + .api-orb-grid-link { + min-height: 100%; + display: grid !important; + gap: 0.28rem; + place-content: center; + text-transform: none !important; + letter-spacing: 0.01em !important; + padding: 1rem; + position: relative; + overflow: hidden; + isolation: isolate; + border: 0; + background: transparent; + transition: + color var(--vl-transition-fast), + transform 180ms var(--vl-ease-cinematic); + } + + .api-orb-grid-link::before { + content: ""; + position: absolute; + inset: 0; + z-index: 0; + background: linear-gradient( + 160deg, + color-mix(in oklch, var(--vl-color-primary) 84%, transparent), + color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 72%, transparent) + ); + transform: scaleY(0); + transform-origin: bottom center; + transition: transform 220ms var(--vl-ease-cinematic); + } + + .api-orb-grid-link::after { + content: ""; + position: absolute; + inset: 1px; + z-index: 1; + background: linear-gradient(180deg, color-mix(in oklch, white 14%, transparent), transparent 36%); + opacity: 0; + transition: opacity var(--vl-transition-fast); + } + + .api-orb-grid-link > * { pointer-events: none; } + + .api-orb-grid-link > * { + position: relative; + z-index: 2; + transition: color var(--vl-transition-fast); + } + + .api-orb-grid-link:hover, + .api-orb-grid-link:focus-visible { + transform: translateY(-1px); + } + + .api-orb-grid-link:hover::before, + .api-orb-grid-link:focus-visible::before { + transform: scaleY(1); + } + + .api-orb-grid-link:hover::after, + .api-orb-grid-link:focus-visible::after { + opacity: 1; + } + + .api-orb-grid-link:hover .api-orb-title, + .api-orb-grid-link:focus-visible .api-orb-title, + .api-orb-grid-link:hover .api-orb-meta, + .api-orb-grid-link:focus-visible .api-orb-meta { + color: var(--vl-text-inverse, oklch(98% 0.01 95)); + } + + .api-orb-grid-link:focus-visible { + outline: var(--vl-focus-ring-width) solid var(--vl-focus-ring-color); + outline-offset: -2px; + } + + .api-orb-title { + font-family: var(--vl-font-family-display); + font-size: clamp(0.95rem, 1.5vw, 1.2rem); + line-height: 1.02; + font-weight: 700; + } + + .api-orb-meta { + margin: 0; + font-family: var(--vl-font-family-mono); + font-size: clamp(0.62rem, 1vw, 0.72rem); + text-transform: uppercase; + letter-spacing: 0.11em; + color: color-mix(in oklch, var(--vl-text-secondary) 85%, var(--vl-color-primary)); + } + + .api-orb-stage { + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 68%, transparent); + border-radius: 14px; + overflow: clip; + background: color-mix(in oklch, var(--vl-bg-surface-elevated) 78%, transparent); + } + + .api-orb-stage .api-target { + min-height: min(52vw, 430px); + display: block; + } + + .api-orb-stage [vl-effect="hover-orb-grid"] { + --vl-orb-grid-bg: linear-gradient( + 170deg, + color-mix(in oklch, var(--vl-bg-surface-elevated) 76%, transparent), + color-mix(in oklch, var(--vl-bg-main) 92%, transparent) + ); + --vl-orb-grid-ink: color-mix(in oklch, var(--vl-text-primary) 90%, var(--vl-color-primary)); + --vl-orb-grid-size-a: clamp(3rem, 13vw, 5.8rem); + --vl-orb-grid-size-b: clamp(2.4rem, 9vw, 4.4rem); + } + + .api-orb-stage [vl-effect="hover-orb-grid"] > ul > li { + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 82%, transparent); + } + + .api-orb-stage [vl-effect="hover-orb-grid"] > ul { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + align-items: stretch; + gap: 0.7rem; + min-height: 100%; + max-width: 34rem; + margin: 0 auto !important; + padding: 1rem !important; + } + + .api-orb-stage [vl-effect="hover-orb-grid"] > ul > li { + flex: 0 1 clamp(10rem, 22vw, 14rem); + min-height: clamp(4.5rem, 9vw, 6rem); + display: flex; + align-items: center; + } + + .api-orb-stage [vl-effect="hover-orb-grid"] > ul > li > .api-orb-grid-link:hover, + .api-orb-stage [vl-effect="hover-orb-grid"] > ul > li > .api-orb-grid-link:focus-visible { + background: color-mix(in oklch, var(--vl-color-primary) 10%, transparent); + } + + .api-orb-stage [vl-effect="hover-orb-grid"]::after, + .api-orb-stage [vl-effect="hover-orb-grid"]::before { + border-radius: 999px; + filter: blur(14px) saturate(120%); + transition: + opacity var(--vl-transition-normal), + transform 220ms var(--vl-ease-cinematic); + } + + .api-orb-stage [vl-effect="hover-orb-grid"]::after { + background: radial-gradient(circle, color-mix(in oklch, var(--vl-color-primary) 44%, white), transparent 66%); + mix-blend-mode: screen; + box-shadow: 0 0 40px color-mix(in oklch, var(--vl-color-primary) 22%, transparent); + } + + .api-orb-stage [vl-effect="hover-orb-grid"]::before { + background: radial-gradient(circle, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 58%, transparent), transparent 70%); + mix-blend-mode: soft-light; + box-shadow: 0 0 34px color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 20%, transparent); + } + + .api-orb-stage [vl-effect="hover-orb-grid"]:not(:hover)::after, + .api-orb-stage [vl-effect="hover-orb-grid"]:not(:hover)::before { + opacity: 0.16; + } + + .api-card .api-target[vl-timeline="view"], + .api-card .api-target[vl-timeline="scroll"] { + animation-timeline: auto !important; + animation-range: normal !important; + } + + .api-card .api-target[vl-scroll], + .api-card .api-scroll-track[vl-scroll] { + animation-timeline: auto !important; + animation-range: normal !important; + } + + .api-card .api-target[vl-effect="rotate-scroll"], + .api-card .api-target[vl-effect="wipe-reveal"], + .api-card .api-target[vl-effect="depth-push"], + .api-card .api-scroll-track[vl-effect="scroll-marquee"] { + animation-duration: 1400ms !important; + } + + .api-card .api-target[vl-effect="shimmer-text"] { background: none; } + + .api-pin-showcase { + grid-column: 1 / -1; + min-height: clamp(58rem, 132vh, 84rem); + } + + .api-pin-story { + display: grid; + grid-template-columns: minmax(0, 0.88fr) minmax(0, 1.12fr); + gap: clamp(0.8rem, 2vw, 1.25rem); + align-items: start; + } + + .api-pin-flow { + display: grid; + gap: clamp(0.85rem, 1.8vw, 1.15rem); + } + + .api-pin-step { + min-height: clamp(11rem, 26vh, 16rem); + border-radius: 0.9rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); + background: + linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-surface-elevated) 72%, transparent), color-mix(in oklch, var(--vl-bg-main) 90%, transparent)); + padding: clamp(0.8rem, 1.8vw, 1.1rem); + display: grid; + gap: 0.6rem; + align-content: start; + } + + .api-pin-step strong { + margin: 0; + font-family: var(--vl-font-family-display); + font-size: clamp(1.05rem, 1.5vw, 1.35rem); + line-height: 0.98; + text-transform: none; + letter-spacing: 0; + color: var(--vl-text-primary); + } + + .api-pin-step p { + margin: 0; + color: var(--vl-text-secondary); + font-size: 0.78rem; + line-height: 1.55; + } + + .api-pin-anchor { + position: sticky; + top: var(--api-pin-top, clamp(6.1rem, 10vh, 7.6rem)); + min-height: clamp(13rem, 28vh, 18rem); + border-radius: 0.95rem; + border: 1px solid color-mix(in oklch, var(--vl-color-primary) 45%, transparent); + background: + radial-gradient(circle at 70% 30%, color-mix(in oklch, var(--vl-color-primary) 20%, transparent), transparent 55%), + color-mix(in oklch, var(--vl-bg-surface-elevated) 80%, transparent); + display: grid; + place-content: center; + text-align: center; + gap: 0.45rem; + padding: 1rem; + } + + .api-pin-anchor strong { + font-family: var(--vl-font-family-display); + font-size: clamp(1.5rem, 2.8vw, 2.2rem); + line-height: 1; + } + + .api-pin-anchor span { + font-family: var(--vl-font-family-mono); + font-size: 0.72rem; + letter-spacing: 0.09em; + text-transform: uppercase; + color: var(--vl-text-muted); + } + + .api-pin-overlap { + grid-column: 1 / -1; + min-height: clamp(92rem, 210vh, 132rem); + overflow: visible; + } + + .api-overlap-stage { + display: grid; + grid-template-columns: minmax(0, 1fr); + gap: 0; + align-items: start; + min-height: inherit; + } + + .api-overlap-stack { + position: relative; + display: grid; + min-height: clamp(92rem, 220vh, 136rem); + padding: 0.7rem; + border-radius: 1rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 58%, transparent); + background: + radial-gradient(circle at 50% 14%, color-mix(in oklch, var(--vl-color-primary) 10%, transparent), transparent 58%), + color-mix(in oklch, var(--vl-bg-main) 92%, transparent); + overflow: visible; + isolation: isolate; + } + + .api-overlap-runway { + min-height: clamp(24rem, 56vh, 34rem); + } + + .api-overlap-panel { + position: sticky; + top: var(--api-pin-top, clamp(6.1rem, 10vh, 7.6rem)); + min-height: clamp(17rem, 38vh, 22rem); + border-radius: 0.9rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 90%, transparent); + padding: clamp(1rem, 2.2vw, 1.35rem); + display: grid; + gap: 0.72rem; + align-content: start; + box-shadow: 0 14px 28px color-mix(in oklch, black 20%, transparent); + transform: translate3d(0, 0, 0); + transform-origin: top center; + } + + .api-overlap-panel + .api-overlap-panel { + margin-top: clamp(25rem, 56vh, 35rem); + } + + .api-overlap-panel:nth-child(1) { + z-index: 1; + background: + radial-gradient(circle at 18% 12%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 48%), + color-mix(in oklch, var(--vl-bg-main) 92%, transparent); + transform: translate3d(0, 0, 0) scale(1); + } + + .api-overlap-panel:nth-child(2) { + z-index: 2; + background: + radial-gradient(circle at 84% 20%, color-mix(in oklch, var(--vl-color-accent, var(--vl-color-primary)) 14%, transparent), transparent 52%), + color-mix(in oklch, var(--vl-bg-surface-elevated) 90%, transparent); + transform: translate3d(0.22rem, 0.5rem, 0) scale(0.992); + } + + .api-overlap-panel:nth-child(3) { + z-index: 3; + border-color: color-mix(in oklch, var(--vl-color-primary) 46%, transparent); + background: + radial-gradient(circle at 50% 16%, color-mix(in oklch, var(--vl-color-primary) 20%, transparent), transparent 56%), + color-mix(in oklch, var(--vl-bg-surface-elevated) 88%, transparent); + transform: translate3d(0.44rem, 1rem, 0) scale(0.985); + } + + .api-overlap-panel strong { + margin: 0; + font-family: var(--vl-font-family-display); + font-size: clamp(1.02rem, 1.5vw, 1.3rem); + text-transform: none; + letter-spacing: 0; + color: var(--vl-text-primary); + } + + .api-overlap-panel p { + margin: 0; + color: var(--vl-text-secondary); + font-size: 0.78rem; + line-height: 1.55; + } + + .api-timeline-board { + display: grid; + gap: 0.52rem; + } + + .api-timeline-lane { + border-radius: 0.65rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); + background: color-mix(in oklch, var(--vl-bg-main) 88%, transparent); + padding: 0.45rem 0.55rem; + display: grid; + gap: 0.32rem; + } + + .api-timeline-lane strong { + font-family: var(--vl-font-family-mono); + font-size: 0.66rem; + letter-spacing: 0.09em; + text-transform: uppercase; + color: var(--vl-text-muted); + } + + .api-timeline-axis { + display: flex; + align-items: center; + gap: 0.4rem; + } + + .api-timeline-axis span { + height: 5px; + border-radius: 999px; + flex: 1 1 0; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 64%, transparent); + background: color-mix(in oklch, var(--vl-bg-surface-elevated) 78%, transparent); + } + + .api-timeline-axis span.is-active { + background: color-mix(in oklch, var(--vl-color-primary) 42%, transparent); + border-color: color-mix(in oklch, var(--vl-color-primary) 56%, transparent); + } + + .api-timeline-note { + margin: 0; + font-size: 0.72rem; + color: var(--vl-text-secondary); + } + + @media (max-width: 48rem) { + .api-shell { max-width: calc(100vw - 1.3rem); padding-top: 5.8rem; } + .api-card--lead { grid-column: auto; } + .api-grid.api-grid--hover-cards { + grid-template-columns: 1fr; + } + .api-card--text-ring { + grid-column: auto; + } + .api-card--circle-scroll { + grid-column: auto; + } + .api-card--code-greeting { + grid-column: auto; + } + .api-card--cube-triad { + grid-column: auto; + } + .api-grid.api-grid--cube-triad { + grid-template-columns: 1fr; + } + .api-target.api-text-ring-demo { + min-height: clamp(12.8rem, 66vw, 16rem); + } + .api-target.api-circle-text-scroll-demo { + min-height: clamp(14rem, 70vw, 18rem); + } + .api-target.api-code-greeting-demo { + min-height: clamp(11rem, 62vw, 13.5rem); + } + .api-target.api-cube-triad-demo { + min-height: clamp(14rem, 72vw, 18.5rem); + } + .api-grid--hover-cards .api-card--hover-fan, + .api-grid--hover-cards .api-card--hover-stack, + .api-grid--hover-cards .api-card--hover-cross, + .api-grid--hover-cards .api-card--hover-duotone { + grid-column: auto; + grid-row: auto; + } + .api-controls { + width: calc(100vw - 0.75rem); + border-radius: 10px; + padding: 0.34rem; + } + .api-controls-row { + flex-wrap: wrap; + gap: 0.32rem; + } + .api-controls-readout { + flex: 1 1 100%; + order: -1; + } + .api-live-hint { + white-space: normal; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + .api-controls-sliders { + flex: 1 1 100%; + flex-wrap: wrap; + } + .api-selected { + transform: translate(-0.1rem, calc(-100% + 0.3rem)); + max-width: calc(100vw - 2.5rem); + } + .api-runtime-commands { + width: 100%; + } + .api-control { + min-width: max-content; + padding: 0.12rem 0.18rem; + } + .api-control .vl-range { + width: 72px; + max-width: 72px; + } + .api-hero-inner { grid-template-columns: minmax(0, 1fr); } + .api-hero-title { max-width: 100%; } + .api-hero-stage { min-height: clamp(18rem, 62vw, 24rem); } + .api-pin-story { grid-template-columns: minmax(0, 1fr); } + .api-pin-showcase { min-height: auto; } + .api-card--illustration-editor { + min-height: auto; + } + .api-target--illustration-editor { + min-height: clamp(30rem, 160vw, 38rem); + padding: 0.78rem; + } + .api-illustration-editor { + min-height: clamp(27rem, 148vw, 35rem); + } + .api-editor-code { + width: min(100%, 17.2rem); + } + .api-editor-code--html { + inset-inline-start: 0; + } + .api-editor-code--css { + inset-inline-start: clamp(0.8rem, 6vw, 2.2rem); + inset-block-start: clamp(10.3rem, 55vw, 12.2rem); + } + .api-editor-code--js { + inset-inline-start: 0; + inset-block-start: clamp(20.6rem, 110vw, 24rem); + } + .api-editor-block { + inset-inline-end: 0; + width: min(100%, 21rem); + height: clamp(20.5rem, 112vw, 25rem); + } + .api-pin-anchor { top: clamp(5.9rem, 11vw, 7.2rem); } + .api-pin-overlap { min-height: clamp(66rem, 190vw, 112rem); } + .api-overlap-stack { min-height: clamp(74rem, 210vw, 118rem); padding: 0.62rem; } + .api-overlap-runway { min-height: clamp(16rem, 46vw, 26rem); } + .api-overlap-panel { + top: clamp(5.9rem, 11vw, 7.2rem); + min-height: clamp(13rem, 44vw, 18rem); + padding: clamp(0.85rem, 2.2vw, 1.05rem); + } + .api-overlap-panel + .api-overlap-panel { + margin-top: clamp(16rem, 50vw, 24rem); + } + .api-overlap-panel:nth-child(2) { transform: translate3d(0.16rem, 0.42rem, 0) scale(0.994); } + .api-overlap-panel:nth-child(3) { transform: translate3d(0.32rem, 0.84rem, 0) scale(0.988); } + body { background-attachment: fixed; } + } /* ── Design Catalog overrides (absorbed from showcase-api-design-catalog.css) ── */ -/* ============================================================================= - showcase-api-design-catalog.css - Design System & Component Catalog — live visual reference for all tokens, - primitives, components and library pages in the Velora framework. - Reuses api-shell / api-group / api-card / api-grid from - showcase-api-motion-catalog.css — only design-catalog-specific overrides live - here. - ============================================================================= */ - -/* ---- Color swatch cards ---- */ -.api-swatch { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.api-swatch-chip { - flex: 0 0 auto; - width: 100%; - height: 3.2rem; - border-radius: var(--vl-radius-lg, 0.6rem); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 50%, transparent); - transition: transform 0.2s; -} - -.api-swatch-chip--primary { background: var(--vl-color-primary); } -.api-swatch-chip--secondary { background: var(--vl-color-secondary); } -.api-swatch-chip--accent { background: var(--vl-color-accent); } -.api-swatch-chip--success { background: var(--vl-color-success); } -.api-swatch-chip--warning { background: var(--vl-color-warning); } -.api-swatch-chip--danger { background: var(--vl-color-danger); } -.api-swatch-chip--bg-main { background: var(--vl-bg-main); } -.api-swatch-chip--bg-surface { background: var(--vl-bg-surface); } -.api-swatch-chip--bg-elevated{ background: var(--vl-bg-surface-elevated); } -.api-swatch-chip--bg-inset { background: var(--vl-bg-inset); } -.api-swatch-chip--bg-overlay { background: var(--vl-bg-overlay, oklch(96% 0.01 110 / 0.8)); } -.api-swatch-chip--text-primary { background: var(--vl-text-primary); } -.api-swatch-chip--text-secondary { background: var(--vl-text-secondary); } -.api-swatch-chip--text-muted { background: var(--vl-text-muted); } -.api-swatch-chip--border-subtle { background: var(--vl-border-subtle); } -.api-swatch-chip--border-strong { background: var(--vl-border-strong); } - -.api-swatch-meta { - font-family: var(--vl-font-family-mono); - font-size: 0.68rem; - color: var(--vl-text-muted); - letter-spacing: 0.04em; - line-height: 1.4; -} - -/* ---- Typography scale cards ---- */ -.api-type-specimen { - display: flex; - flex-direction: column; - gap: 0.3rem; -} - -.api-type-sample { - color: var(--vl-text-primary); - line-height: 1.2; - word-break: break-word; -} - -.api-type-meta { - font-family: var(--vl-font-family-mono); - font-size: 0.68rem; - color: var(--vl-text-muted); - letter-spacing: 0.04em; -} - -/* Font family demos */ -.api-font-body { font-family: var(--vl-font-family); } -.api-font-display { font-family: var(--vl-font-family-display); } -.api-font-mono { font-family: var(--vl-font-family-mono); } - -/* Weight demos */ -.api-weight-normal { font-weight: 400; } -.api-weight-medium { font-weight: 500; } -.api-weight-semibold { font-weight: 600; } -.api-weight-bold { font-weight: 700; } - -/* ---- Spacing ruler cards ---- */ -.api-space-ruler { - display: flex; - flex-direction: column; - gap: 0.35rem; - width: 100%; -} - -.api-space-bar { - height: 0.55rem; - border-radius: 99px; - background: color-mix(in oklch, var(--vl-color-primary) 55%, transparent); - max-width: 100%; -} - -/* ---- Radius demo ---- */ -.api-radius-demo { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(2.8rem, 1fr)); - gap: 0.5rem; - padding-block: 0.25rem; -} - -.api-radius-block { - aspect-ratio: 1; - background: color-mix(in oklch, var(--vl-color-primary) 28%, transparent); - border: 1px solid color-mix(in oklch, var(--vl-color-primary) 55%, transparent); -} - -/* ---- Elevation/shadow demo ---- */ -.api-shadow-card { - width: 100%; - min-height: 2.6rem; - border-radius: var(--vl-radius-md, 0.5rem); - background: var(--vl-bg-surface); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 50%, transparent); - display: flex; - align-items: center; - justify-content: center; - font-family: var(--vl-font-family-mono); - font-size: 0.72rem; - color: var(--vl-text-muted); -} - -/* ---- Primitive demo staging ---- */ -.api-primitive-stage { - display: flex; - flex-wrap: wrap; - gap: 0.6rem; - align-items: flex-start; - padding-block: 0.25rem; -} - -/* ---- Component link cards ---- */ -.api-component-card { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.api-component-link { - display: inline-flex; - align-items: center; - gap: 0.4rem; - font-family: var(--vl-font-family-mono); - font-size: 0.72rem; - color: var(--vl-color-primary); - text-decoration: none; - transition: opacity 0.15s; -} - -.api-component-link:hover { opacity: 0.75; } - -.api-component-link::before { - content: "↗"; - font-style: normal; -} - -/* ---- Page directory grid ---- */ -.api-grid--pages { - grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); -} - -.api-page-link-card { - display: flex; - flex-direction: column; - gap: 0.4rem; -} - -.api-page-link { - display: flex; - align-items: center; - gap: 0.4rem; - text-decoration: none; - color: var(--vl-text-primary); - font-size: 0.8rem; - font-weight: 500; - transition: color 0.15s; -} - -.api-page-link:hover { color: var(--vl-color-primary); } - -.api-page-link-meta { - font-family: var(--vl-font-family-mono); - font-size: 0.65rem; - color: var(--vl-text-muted); -} - -/* ---- Form staging inside api-card ---- */ -.api-form-stage { - display: flex; - flex-direction: column; - gap: 0.55rem; - width: 100%; -} - -/* ---- Gradient text utility for hero ---- */ -.api-hero-title .vl-text-gradient { - -webkit-background-clip: text; - background-clip: text; - color: transparent; -} - -/* ---- Design hero right column (missing in shared motion css) ---- */ -.api-hero-panels { - position: relative; - min-height: clamp(20rem, 44vw, 30rem); - border-radius: clamp(0.9rem, 2vw, 1.2rem); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); - background: - radial-gradient(circle at 50% 45%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 36%), - linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-surface-elevated) 76%, transparent), color-mix(in oklch, var(--vl-bg-main) 96%, transparent)); - overflow: hidden; -} - -.api-hero-panels .api-hero-panel { - width: min(100%, 18rem); -} - -/* ---- Status badge row ---- */ -.api-status-row { - display: flex; - flex-wrap: wrap; - gap: 0.4rem; - align-items: center; -} - -/* ---- Component thumbnail placeholders ---- */ -.api-component-thumb { - width: 100%; - aspect-ratio: 16 / 9; - border-radius: var(--vl-radius-md, 0.5rem); - border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 50%, transparent); - background: - linear-gradient(135deg, - color-mix(in oklch, var(--vl-bg-surface-elevated) 90%, transparent), - color-mix(in oklch, var(--vl-bg-inset) 80%, transparent)); - display: flex; - align-items: center; - justify-content: center; - overflow: hidden; - font-family: var(--vl-font-family-mono); - font-size: 0.7rem; - color: var(--vl-text-muted); - letter-spacing: 0.05em; -} - -/* ---- Wider grid for component previews ---- */ -.api-grid--components { - grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); -} - -/* ---- lead card full width override for sections ---- */ -.api-card--full { grid-column: 1 / -1; } - -/* ---- Scrollable catalog hero status chips ---- */ -.api-hero-status > span { - white-space: nowrap; -} - -/* ---- Footer additions for this page ---- */ -.vl-footer__tagline { - margin: 0.6rem 0 0; - color: var(--vl-text-muted); - max-width: 48ch; - font-size: 0.84rem; -} - -.vl-footer__nav { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 1.25rem; -} - -.vl-footer__nav-col { - display: grid; - gap: 0.4rem; - align-content: start; -} - -.vl-footer__nav-col strong { - font-family: var(--vl-font-family-mono); - text-transform: uppercase; - font-size: 0.68rem; - letter-spacing: 0.08em; - color: var(--vl-text-secondary); -} - -.vl-footer__nav-col a { - text-decoration: none; - color: var(--vl-text-muted); - font-size: 0.82rem; -} - -.vl-footer__nav-col a:hover { - color: var(--vl-color-primary); -} - -.vl-footer__meta { - display: flex; - flex-wrap: wrap; - gap: 0.65rem; - align-items: center; - justify-content: flex-end; - color: var(--vl-text-muted); - font-size: 0.74rem; -} - -@media (max-width: 980px) { - .api-hero-panels { - min-height: 16rem; - } - - .vl-footer__nav { - grid-template-columns: 1fr; - } - - .vl-footer__meta { - justify-content: flex-start; - } -} +/* ============================================================================= + showcase-api-design-catalog.css + Design System & Component Catalog — live visual reference for all tokens, + primitives, components and library pages in the Velora framework. + Reuses api-shell / api-group / api-card / api-grid from + showcase-api-motion-catalog.css — only design-catalog-specific overrides live + here. + ============================================================================= */ + +/* ---- Color swatch cards ---- */ +.api-swatch { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.api-swatch-chip { + flex: 0 0 auto; + width: 100%; + height: 3.2rem; + border-radius: var(--vl-radius-lg, 0.6rem); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 50%, transparent); + transition: transform 0.2s; +} + +.api-swatch-chip--primary { background: var(--vl-color-primary); } +.api-swatch-chip--secondary { background: var(--vl-color-secondary); } +.api-swatch-chip--accent { background: var(--vl-color-accent); } +.api-swatch-chip--success { background: var(--vl-color-success); } +.api-swatch-chip--warning { background: var(--vl-color-warning); } +.api-swatch-chip--danger { background: var(--vl-color-danger); } +.api-swatch-chip--bg-main { background: var(--vl-bg-main); } +.api-swatch-chip--bg-surface { background: var(--vl-bg-surface); } +.api-swatch-chip--bg-elevated{ background: var(--vl-bg-surface-elevated); } +.api-swatch-chip--bg-inset { background: var(--vl-bg-inset); } +.api-swatch-chip--bg-overlay { background: var(--vl-bg-overlay, oklch(96% 0.01 110 / 0.8)); } +.api-swatch-chip--text-primary { background: var(--vl-text-primary); } +.api-swatch-chip--text-secondary { background: var(--vl-text-secondary); } +.api-swatch-chip--text-muted { background: var(--vl-text-muted); } +.api-swatch-chip--border-subtle { background: var(--vl-border-subtle); } +.api-swatch-chip--border-strong { background: var(--vl-border-strong); } + +.api-swatch-meta { + font-family: var(--vl-font-family-mono); + font-size: 0.68rem; + color: var(--vl-text-muted); + letter-spacing: 0.04em; + line-height: 1.4; +} + +/* ---- Typography scale cards ---- */ +.api-type-specimen { + display: flex; + flex-direction: column; + gap: 0.3rem; +} + +.api-type-sample { + color: var(--vl-text-primary); + line-height: 1.2; + word-break: break-word; +} + +.api-type-meta { + font-family: var(--vl-font-family-mono); + font-size: 0.68rem; + color: var(--vl-text-muted); + letter-spacing: 0.04em; +} + +/* Font family demos */ +.api-font-body { font-family: var(--vl-font-family); } +.api-font-display { font-family: var(--vl-font-family-display); } +.api-font-mono { font-family: var(--vl-font-family-mono); } + +/* Weight demos */ +.api-weight-normal { font-weight: 400; } +.api-weight-medium { font-weight: 500; } +.api-weight-semibold { font-weight: 600; } +.api-weight-bold { font-weight: 700; } + +/* ---- Spacing ruler cards ---- */ +.api-space-ruler { + display: flex; + flex-direction: column; + gap: 0.35rem; + width: 100%; +} + +.api-space-bar { + height: 0.55rem; + border-radius: 99px; + background: color-mix(in oklch, var(--vl-color-primary) 55%, transparent); + max-width: 100%; +} + +/* ---- Radius demo ---- */ +.api-radius-demo { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(2.8rem, 1fr)); + gap: 0.5rem; + padding-block: 0.25rem; +} + +.api-radius-block { + aspect-ratio: 1; + background: color-mix(in oklch, var(--vl-color-primary) 28%, transparent); + border: 1px solid color-mix(in oklch, var(--vl-color-primary) 55%, transparent); +} + +/* ---- Elevation/shadow demo ---- */ +.api-shadow-card { + width: 100%; + min-height: 2.6rem; + border-radius: var(--vl-radius-md, 0.5rem); + background: var(--vl-bg-surface); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 50%, transparent); + display: flex; + align-items: center; + justify-content: center; + font-family: var(--vl-font-family-mono); + font-size: 0.72rem; + color: var(--vl-text-muted); +} + +/* ---- Primitive demo staging ---- */ +.api-primitive-stage { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + align-items: flex-start; + padding-block: 0.25rem; +} + +/* ---- Component link cards ---- */ +.api-component-card { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.api-component-link { + display: inline-flex; + align-items: center; + gap: 0.4rem; + font-family: var(--vl-font-family-mono); + font-size: 0.72rem; + color: var(--vl-color-primary); + text-decoration: none; + transition: opacity 0.15s; +} + +.api-component-link:hover { opacity: 0.75; } + +.api-component-link::before { + content: "↗"; + font-style: normal; +} + +/* ---- Page directory grid ---- */ +.api-grid--pages { + grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); +} + +.api-page-link-card { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.api-page-link { + display: flex; + align-items: center; + gap: 0.4rem; + text-decoration: none; + color: var(--vl-text-primary); + font-size: 0.8rem; + font-weight: 500; + transition: color 0.15s; +} + +.api-page-link:hover { color: var(--vl-color-primary); } + +.api-page-link-meta { + font-family: var(--vl-font-family-mono); + font-size: 0.65rem; + color: var(--vl-text-muted); +} + +/* ---- Form staging inside api-card ---- */ +.api-form-stage { + display: flex; + flex-direction: column; + gap: 0.55rem; + width: 100%; +} + +/* ---- Gradient text utility for hero ---- */ +.api-hero-title .vl-text-gradient { + -webkit-background-clip: text; + background-clip: text; + color: transparent; +} + +/* ---- Design hero right column (missing in shared motion css) ---- */ +.api-hero-panels { + position: relative; + min-height: clamp(20rem, 44vw, 30rem); + border-radius: clamp(0.9rem, 2vw, 1.2rem); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 62%, transparent); + background: + radial-gradient(circle at 50% 45%, color-mix(in oklch, var(--vl-color-primary) 12%, transparent), transparent 36%), + linear-gradient(180deg, color-mix(in oklch, var(--vl-bg-surface-elevated) 76%, transparent), color-mix(in oklch, var(--vl-bg-main) 96%, transparent)); + overflow: hidden; +} + +.api-hero-panels .api-hero-panel { + width: min(100%, 18rem); +} + +/* ---- Status badge row ---- */ +.api-status-row { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + align-items: center; +} + +/* ---- Component thumbnail placeholders ---- */ +.api-component-thumb { + width: 100%; + aspect-ratio: 16 / 9; + border-radius: var(--vl-radius-md, 0.5rem); + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 50%, transparent); + background: + linear-gradient(135deg, + color-mix(in oklch, var(--vl-bg-surface-elevated) 90%, transparent), + color-mix(in oklch, var(--vl-bg-inset) 80%, transparent)); + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + font-family: var(--vl-font-family-mono); + font-size: 0.7rem; + color: var(--vl-text-muted); + letter-spacing: 0.05em; +} + +/* ---- Wider grid for component previews ---- */ +.api-grid--components { + grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); +} + +/* ---- lead card full width override for sections ---- */ +.api-card--full { grid-column: 1 / -1; } + +/* ---- Scrollable catalog hero status chips ---- */ +.api-hero-status > span { + white-space: nowrap; +} + +/* ---- Footer additions for this page ---- */ +.vl-footer__tagline { + margin: 0.6rem 0 0; + color: var(--vl-text-muted); + max-width: 48ch; + font-size: 0.84rem; +} + +.vl-footer__nav { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 1.25rem; +} + +.vl-footer__nav-col { + display: grid; + gap: 0.4rem; + align-content: start; +} + +.vl-footer__nav-col strong { + font-family: var(--vl-font-family-mono); + text-transform: uppercase; + font-size: 0.68rem; + letter-spacing: 0.08em; + color: var(--vl-text-secondary); +} + +.vl-footer__nav-col a { + text-decoration: none; + color: var(--vl-text-muted); + font-size: 0.82rem; +} + +.vl-footer__nav-col a:hover { + color: var(--vl-color-primary); +} + +.vl-footer__meta { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; + align-items: center; + justify-content: flex-end; + color: var(--vl-text-muted); + font-size: 0.74rem; +} + +@media (max-width: 980px) { + .api-hero-panels { + min-height: 16rem; + } + + .vl-footer__nav { + grid-template-columns: 1fr; + } + + .vl-footer__meta { + justify-content: flex-start; + } +} + +/* Catalog hardening — Engine/Skins lanes + scene-engine demo */ +.api-runtime-disclaimer { + margin: 0; + color: var(--vl-text-secondary); + font-size: 0.68rem; + line-height: 1.4; +} + +.api-lanes { + margin: 0 0 1.25rem; +} + +.api-lanes__inner { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.85rem; +} + +.api-lane { + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 65%, transparent); + border-radius: 0.9rem; + background: color-mix(in oklch, var(--vl-bg-surface) 88%, transparent); + padding: 1rem 1.1rem; +} + +.api-lane--engine { + border-color: color-mix(in oklch, var(--vl-color-primary) 35%, transparent); +} + +.api-lane--skins { + border-color: color-mix(in oklch, var(--vl-color-secondary) 30%, transparent); +} + +.api-lane__kicker { + margin: 0 0 0.35rem; + font-size: 0.7rem; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--vl-text-muted); +} + +.api-lane__title { + margin: 0 0 0.45rem; + font-family: var(--vl-font-family-display); + font-size: 1.15rem; + line-height: 1.1; +} + +.api-lane__body { + margin: 0; + color: var(--vl-text-secondary); + font-size: 0.84rem; + line-height: 1.5; +} + +.api-scene-engine-demo { + grid-column: 1 / -1; +} + +.api-scene-engine-track { + margin-top: 0.75rem; + border-radius: 0.9rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 70%, transparent); + background: + radial-gradient(circle at 20% 20%, color-mix(in oklch, var(--vl-color-primary) 16%, transparent), transparent 45%), + color-mix(in oklch, var(--vl-bg-main) 92%, transparent); + overflow: clip; +} + +.api-scene-engine-stage { + display: grid; + place-content: center; + gap: 0.75rem; + padding: clamp(1.25rem, 3vw, 2rem); + text-align: center; +} + +.api-scene-engine-kicker { + margin: 0; + letter-spacing: 0.12em; + text-transform: uppercase; + font-size: 0.72rem; + color: var(--vl-text-muted); +} + +.api-scene-engine-title { + margin: 0; + font-family: var(--vl-font-family-display); + font-size: clamp(1.6rem, 3.2vw, 2.4rem); + line-height: 1; +} + +.api-scene-engine-card { + margin: 0 auto; + max-width: 28rem; + padding: 0.85rem 1rem; + border-radius: 0.75rem; + border: 1px solid color-mix(in oklch, var(--vl-border-subtle) 60%, transparent); + background: color-mix(in oklch, var(--vl-bg-surface-elevated) 78%, transparent); + color: var(--vl-text-secondary); + font-size: 0.86rem; + line-height: 1.45; +} + +.api-scene-engine-track--compact { + min-block-size: auto; +} + +.api-scene-engine-track--compact[vl-pin="2"] { + min-block-size: calc(2 * 100svh); +} + +.api-scene-engine-track--auto { + min-block-size: auto; + padding-block: 0.5rem; +} + +.api-scene-engine-track--auto [vl-stage] { + position: static; + min-block-size: auto; + inset-block-start: auto; +} + +.api-scene-engine-track--auto .api-scene-engine-stage { + padding: 1rem; +} + +.api-scene-engine-overlap, +.api-scene-engine-auto { + grid-column: 1 / -1; +} + +.api-scroll-media { + min-height: 5.5rem; + display: grid; + place-content: center; + background: + linear-gradient(135deg, color-mix(in oklch, var(--vl-color-primary) 22%, transparent), color-mix(in oklch, var(--vl-bg-surface-elevated) 80%, transparent)); + border-radius: inherit; +} + +.api-scroll-highlight { + font-family: var(--vl-font-family-display); + font-size: 1.05rem; + letter-spacing: 0.02em; +} + +@media (max-width: 820px) { + .api-lanes__inner { + grid-template-columns: minmax(0, 1fr); + } +} diff --git a/apps/showcase/public/css/showcase-tw-demos.css b/apps/showcase/public/css/showcase-tw-demos.css index c5cf59f..fba3d54 100644 --- a/apps/showcase/public/css/showcase-tw-demos.css +++ b/apps/showcase/public/css/showcase-tw-demos.css @@ -1 +1 @@ -.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{left:0;right:0}.inset-x-12{left:3rem;right:3rem}.inset-y-0{top:0;bottom:0}.-bottom-4{bottom:-1rem}.-bottom-6{bottom:-1.5rem}.-left-12{left:-3rem}.-left-8{left:-2rem}.-right-4{right:-1rem}.-right-6{right:-1.5rem}.-top-12{top:-3rem}.-top-8{top:-2rem}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.bottom-6{bottom:1.5rem}.bottom-8{bottom:2rem}.left-0{left:0}.left-1\/2{left:50%}.left-4{left:1rem}.left-6{left:1.5rem}.left-8{left:2rem}.left-\[400px\]{left:400px}.right-0{right:0}.right-4{right:1rem}.right-6{right:1.5rem}.right-8{right:2rem}.top-0{top:0}.top-1\/2{top:50%}.top-16{top:4rem}.top-4{top:1rem}.top-6{top:1.5rem}.top-8{top:2rem}.top-\[320px\]{top:320px}.isolate{isolation:isolate}.-z-10{z-index:-10}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.col-span-12{grid-column:span 12/span 12}.mx-8{margin-left:2rem;margin-right:2rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-0{margin-bottom:0}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-20{margin-bottom:5rem}.mb-24{margin-bottom:6rem}.mb-3{margin-bottom:.75rem}.mb-4{margin-bottom:1rem}.mb-40{margin-bottom:10rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-64{margin-left:16rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-12{margin-top:3rem}.mt-16{margin-top:4rem}.mt-2{margin-top:.5rem}.mt-24{margin-top:6rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-40{margin-top:10rem}.mt-8{margin-top:2rem}.mt-auto{margin-top:auto}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.aspect-\[1\.6\/1\]{aspect-ratio:1.6/1}.aspect-video{aspect-ratio:16/9}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-10{height:2.5rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-20{height:5rem}.h-24{height:6rem}.h-3{height:.75rem}.h-32{height:8rem}.h-4{height:1rem}.h-40{height:10rem}.h-48{height:12rem}.h-52{height:13rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-80{height:20rem}.h-9{height:2.25rem}.h-96{height:24rem}.h-\[600px\]{height:600px}.h-\[800px\]{height:800px}.h-\[calc\(100vh-4rem\)\]{height:calc(100vh - 4rem)}.h-\[calc\(100vh-64px\)\]{height:calc(100vh - 64px)}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.min-h-\[180px\]{min-height:180px}.min-h-screen{min-height:100vh}.w-0\.5{width:.125rem}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-10{width:2.5rem}.w-12{width:3rem}.w-14{width:3.5rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\/3{width:66.666667%}.w-20{width:5rem}.w-24{width:6rem}.w-3{width:.75rem}.w-3\/4{width:75%}.w-32{width:8rem}.w-4\/6{width:66.666667%}.w-5\/6{width:83.333333%}.w-6{width:1.5rem}.w-64{width:16rem}.w-8{width:2rem}.w-\[800px\]{width:800px}.w-\[90vw\]{width:90vw}.w-full{width:100%}.w-px{width:1px}.min-w-\[100px\]{min-width:100px}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-4xl{max-width:56rem}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-\[90\%\]{max-width:90%}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-sm{max-width:24rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.flex-grow{flex-grow:1}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-y-1\/2{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-1\/2{--tw-translate-y:-50%}.-translate-y-full{--tw-translate-y:-100%}.-translate-y-full,.translate-y-4{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-4{--tw-translate-y:1rem}.scale-105{--tw-scale-x:1.05;--tw-scale-y:1.05}.scale-105,.scale-110{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1}.scale-90{--tw-scale-x:.9;--tw-scale-y:.9}.scale-90,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.resize{resize:both}.appearance-none{-webkit-appearance:none;-moz-appearance:none;appearance:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.gap-0{gap:0}.gap-1{gap:.25rem}.gap-12{gap:3rem}.gap-2{gap:.5rem}.gap-24{gap:6rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-6{gap:1.5rem}.gap-8{gap:2rem}.gap-x-12{-moz-column-gap:3rem;column-gap:3rem}.gap-y-24{row-gap:6rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(0px*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(0px*var(--tw-space-y-reverse))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.375rem*var(--tw-space-y-reverse))}.space-y-10>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2.5rem*var(--tw-space-y-reverse))}.space-y-16>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(4rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(4rem*var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-24>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(6rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(6rem*var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.75rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem*var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-outline-variant\/10>:not([hidden])~:not([hidden]){border-color:rgba(67,74,65,.1)}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-x-hidden{overflow-x:hidden}.rounded{border-radius:.125rem}.rounded-2xl{border-radius:1rem}.rounded-\[0\.125rem\]{border-radius:.125rem}.rounded-full{border-radius:.75rem}.rounded-lg{border-radius:.25rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.5rem}.rounded-t-2xl{border-top-left-radius:1rem;border-top-right-radius:1rem}.rounded-t-xl{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border{border-width:1px}.border-2{border-width:2px}.border-x{border-left-width:1px;border-right-width:1px}.border-b{border-bottom-width:1px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-r-2{border-right-width:2px}.border-t{border-top-width:1px}.border-none{border-style:none}.border-\[\#6b7a63\]{--tw-border-opacity:1;border-color:rgb(107 122 99/var(--tw-border-opacity,1))}.border-\[\#bbcbb1\]{--tw-border-opacity:1;border-color:rgb(187 203 177/var(--tw-border-opacity,1))}.border-outline-variant{--tw-border-opacity:1;border-color:rgb(67 74 65/var(--tw-border-opacity,1))}.border-outline-variant\/10{border-color:rgba(67,74,65,.1)}.border-outline-variant\/15{border-color:rgba(67,74,65,.15)}.border-outline-variant\/20{border-color:rgba(67,74,65,.2)}.border-outline-variant\/25{border-color:rgba(67,74,65,.25)}.border-outline-variant\/30{border-color:rgba(67,74,65,.3)}.border-outline-variant\/40{border-color:rgba(67,74,65,.4)}.border-outline-variant\/5{border-color:rgba(67,74,65,.05)}.border-primary{--tw-border-opacity:1;border-color:rgb(187 203 177/var(--tw-border-opacity,1))}.border-primary\/10{border-color:hsla(97,20%,75%,.1)}.border-primary\/20{border-color:hsla(97,20%,75%,.2)}.border-primary\/30{border-color:hsla(97,20%,75%,.3)}.border-primary\/40{border-color:hsla(97,20%,75%,.4)}.border-transparent{border-color:transparent}.border-white\/20{border-color:hsla(0,0%,100%,.2)}.border-white\/5{border-color:hsla(0,0%,100%,.05)}.bg-\[\#0d0f0c\]{--tw-bg-opacity:1;background-color:rgb(13 15 12/var(--tw-bg-opacity,1))}.bg-\[\#0d0f0c\]\/80{background-color:rgba(13,15,12,.8)}.bg-blue-500{--tw-bg-opacity:1;background-color:rgb(59 130 246/var(--tw-bg-opacity,1))}.bg-error-dim{--tw-bg-opacity:1;background-color:rgb(186 87 63/var(--tw-bg-opacity,1))}.bg-on-surface\/10{background-color:hsla(93,19%,88%,.1)}.bg-on-surface\/20{background-color:hsla(93,19%,88%,.2)}.bg-on-surface\/30{background-color:hsla(93,19%,88%,.3)}.bg-on-surface\/80{background-color:hsla(93,19%,88%,.8)}.bg-outline-variant{--tw-bg-opacity:1;background-color:rgb(67 74 65/var(--tw-bg-opacity,1))}.bg-outline-variant\/20{background-color:rgba(67,74,65,.2)}.bg-outline-variant\/30{background-color:rgba(67,74,65,.3)}.bg-primary{--tw-bg-opacity:1;background-color:rgb(187 203 177/var(--tw-bg-opacity,1))}.bg-primary-container{--tw-bg-opacity:1;background-color:rgb(61 75 54/var(--tw-bg-opacity,1))}.bg-primary-container\/20{background-color:rgba(61,75,54,.2)}.bg-primary-container\/30{background-color:rgba(61,75,54,.3)}.bg-primary\/10{background-color:hsla(97,20%,75%,.1)}.bg-primary\/15{background-color:hsla(97,20%,75%,.15)}.bg-primary\/20{background-color:hsla(97,20%,75%,.2)}.bg-primary\/40{background-color:hsla(97,20%,75%,.4)}.bg-secondary-container{--tw-bg-opacity:1;background-color:rgb(58 60 57/var(--tw-bg-opacity,1))}.bg-secondary\/15{background-color:hsla(75,2%,61%,.15)}.bg-secondary\/20{background-color:hsla(75,2%,61%,.2)}.bg-surface{--tw-bg-opacity:1;background-color:rgb(13 15 12/var(--tw-bg-opacity,1))}.bg-surface-container{--tw-bg-opacity:1;background-color:rgb(23 27 22/var(--tw-bg-opacity,1))}.bg-surface-container-high{--tw-bg-opacity:1;background-color:rgb(28 33 27/var(--tw-bg-opacity,1))}.bg-surface-container-highest{--tw-bg-opacity:1;background-color:rgb(33 39 32/var(--tw-bg-opacity,1))}.bg-surface-container-low{--tw-bg-opacity:1;background-color:rgb(17 20 16/var(--tw-bg-opacity,1))}.bg-surface-container-low\/50{background-color:rgba(17,20,16,.5)}.bg-surface-container-lowest{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity,1))}.bg-transparent{background-color:transparent}.bg-\[linear-gradient\(to_right\2c \#1c211b_1px\2c transparent_1px\)\2c linear-gradient\(to_bottom\2c \#1c211b_1px\2c transparent_1px\)\]{background-image:linear-gradient(90deg,#1c211b 1px,transparent 0),linear-gradient(180deg,#1c211b 1px,transparent 0)}.bg-\[radial-gradient\(circle_at_center\2c _var\(--tw-gradient-from\)_0\%\2c _transparent_70\%\)\]{background-image:radial-gradient(circle at center,var(--tw-gradient-from) 0,transparent 70%)}.bg-\[radial-gradient\(ellipse_at_center\2c _transparent_0\%\2c _\#0d0f0c_85\%\)\]{background-image:radial-gradient(ellipse at center,transparent 0,#0d0f0c 85%)}.bg-gradient-to-br{background-image:linear-gradient(to bottom right,var(--tw-gradient-stops))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.bg-gradient-to-t{background-image:linear-gradient(to top,var(--tw-gradient-stops))}.bg-gradient-to-tr{background-image:linear-gradient(to top right,var(--tw-gradient-stops))}.from-primary{--tw-gradient-from:#bbcbb1 var(--tw-gradient-from-position);--tw-gradient-to:hsla(97,20%,75%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-primary\/10{--tw-gradient-from:hsla(97,20%,75%,.1) var(--tw-gradient-from-position);--tw-gradient-to:hsla(97,20%,75%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-surface{--tw-gradient-from:#0d0f0c var(--tw-gradient-from-position);--tw-gradient-to:rgba(13,15,12,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-surface-container-high\/50{--tw-gradient-from:rgba(28,33,27,.5) var(--tw-gradient-from-position);--tw-gradient-to:rgba(28,33,27,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-transparent{--tw-gradient-from:transparent var(--tw-gradient-from-position);--tw-gradient-to:transparent var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.via-primary\/30{--tw-gradient-to:hsla(97,20%,75%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),hsla(97,20%,75%,.3) var(--tw-gradient-via-position),var(--tw-gradient-to)}.via-primary\/40{--tw-gradient-to:hsla(97,20%,75%,0) var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),hsla(97,20%,75%,.4) var(--tw-gradient-via-position),var(--tw-gradient-to)}.via-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),transparent var(--tw-gradient-via-position),var(--tw-gradient-to)}.to-transparent{--tw-gradient-to:transparent var(--tw-gradient-to-position)}.bg-\[size\:40px_40px\]{background-size:40px 40px}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-1{padding:.25rem}.p-10{padding:2.5rem}.p-12{padding:3rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-12{padding-bottom:3rem}.pb-24{padding-bottom:6rem}.pb-32{padding-bottom:8rem}.pb-4{padding-bottom:1rem}.pl-16{padding-left:4rem}.pl-4{padding-left:1rem}.pl-8{padding-left:2rem}.pr-6{padding-right:1.5rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-10{padding-top:2.5rem}.pt-2{padding-top:.5rem}.pt-24{padding-top:6rem}.pt-28{padding-top:7rem}.pt-32{padding-top:8rem}.pt-4{padding-top:1rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-\[\'Manrope\'\]{font-family:Manrope}.font-body{font-family:Manrope,system-ui,sans-serif}.font-headline{font-family:Space Grotesk,system-ui,sans-serif}.font-label{font-family:Manrope,system-ui,sans-serif}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-6xl{font-size:3.75rem;line-height:1}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[18px\]{font-size:18px}.text-\[8px\]{font-size:8px}.text-\[9px\]{font-size:9px}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.normal-case{text-transform:none}.leading-\[0\.9\]{line-height:.9}.leading-none{line-height:1}.leading-relaxed{line-height:1.625}.tracking-\[-0\.04em\]{letter-spacing:-.04em}.tracking-\[0\.15em\]{letter-spacing:.15em}.tracking-\[0\.1em\]{letter-spacing:.1em}.tracking-\[0\.25em\]{letter-spacing:.25em}.tracking-\[0\.2em\]{letter-spacing:.2em}.tracking-\[0\.4em\]{letter-spacing:.4em}.tracking-tight{letter-spacing:-.025em}.tracking-tighter{letter-spacing:-.05em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-\[\#6b7a63\]{--tw-text-opacity:1;color:rgb(107 122 99/var(--tw-text-opacity,1))}.text-\[\#727d6d\]{--tw-text-opacity:1;color:rgb(114 125 109/var(--tw-text-opacity,1))}.text-\[\#bbcbb1\]{--tw-text-opacity:1;color:rgb(187 203 177/var(--tw-text-opacity,1))}.text-error{--tw-text-opacity:1;color:rgb(237 127 100/var(--tw-text-opacity,1))}.text-on-primary{--tw-text-opacity:1;color:rgb(54 68 48/var(--tw-text-opacity,1))}.text-on-primary-container{--tw-text-opacity:1;color:rgb(197 213 186/var(--tw-text-opacity,1))}.text-on-secondary-container{--tw-text-opacity:1;color:rgb(191 192 187/var(--tw-text-opacity,1))}.text-on-surface{--tw-text-opacity:1;color:rgb(225 231 220/var(--tw-text-opacity,1))}.text-on-surface-variant{--tw-text-opacity:1;color:rgb(167 173 162/var(--tw-text-opacity,1))}.text-on-surface-variant\/40{color:hsla(93,6%,66%,.4)}.text-on-surface-variant\/50{color:hsla(93,6%,66%,.5)}.text-on-surface\/40{color:hsla(93,19%,88%,.4)}.text-on-surface\/60{color:hsla(93,19%,88%,.6)}.text-outline{--tw-text-opacity:1;color:rgb(113 119 110/var(--tw-text-opacity,1))}.text-primary{--tw-text-opacity:1;color:rgb(187 203 177/var(--tw-text-opacity,1))}.text-primary-dim{--tw-text-opacity:1;color:rgb(174 190 164/var(--tw-text-opacity,1))}.text-primary\/20{color:hsla(97,20%,75%,.2)}.text-primary\/40{color:hsla(97,20%,75%,.4)}.text-primary\/50{color:hsla(97,20%,75%,.5)}.text-primary\/60{color:hsla(97,20%,75%,.6)}.text-primary\/70{color:hsla(97,20%,75%,.7)}.text-primary\/80{color:hsla(97,20%,75%,.8)}.text-secondary{--tw-text-opacity:1;color:rgb(157 158 154/var(--tw-text-opacity,1))}.text-tertiary{--tw-text-opacity:1;color:rgb(241 255 212/var(--tw-text-opacity,1))}.underline{text-decoration-line:underline}.decoration-primary\/50{text-decoration-color:hsla(97,20%,75%,.5)}.underline-offset-4{text-underline-offset:4px}.accent-primary{accent-color:#bbcbb1}.opacity-0{opacity:0}.opacity-10{opacity:.1}.opacity-20{opacity:.2}.opacity-40{opacity:.4}.opacity-60{opacity:.6}.opacity-90{opacity:.9}.opacity-95{opacity:.95}.opacity-\[0\.03\]{opacity:.03}.mix-blend-overlay{mix-blend-mode:overlay}.mix-blend-luminosity{mix-blend-mode:luminosity}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-2xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px rgba(0,0,0,.25);--tw-shadow-colored:0 25px 50px -12px var(--tw-shadow-color)}.shadow-\[0_-20px_50px_rgba\(0\2c 0\2c 0\2c 0\.5\)\]{--tw-shadow:0 -20px 50px rgba(0,0,0,.5);--tw-shadow-colored:0 -20px 50px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_-30px_60px_rgba\(0\2c 0\2c 0\2c 0\.6\)\]{--tw-shadow:0 -30px 60px rgba(0,0,0,.6);--tw-shadow-colored:0 -30px 60px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_15px_rgba\(187\2c 203\2c 177\2c 0\.8\)\]{--tw-shadow:0 0 15px hsla(97,20%,75%,.8);--tw-shadow-colored:0 0 15px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_30px_rgba\(187\2c 203\2c 177\2c 0\.3\)\]{--tw-shadow:0 0 30px hsla(97,20%,75%,.3);--tw-shadow-colored:0 0 30px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[0_0_50px_rgba\(187\2c 203\2c 177\2c 0\.4\)\]{--tw-shadow:0 0 50px hsla(97,20%,75%,.4);--tw-shadow-colored:0 0 50px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[20px_0_40px_rgba\(0\2c 0\2c 0\2c 0\.4\)\]{--tw-shadow:20px 0 40px rgba(0,0,0,.4);--tw-shadow-colored:20px 0 40px var(--tw-shadow-color)}.shadow-\[20px_0_40px_rgba\(0\2c 0\2c 0\2c 0\.4\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-md,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-primary\/10{--tw-shadow-color:hsla(97,20%,75%,.1);--tw-shadow:var(--tw-shadow-colored)}.outline{outline-style:solid}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring,.ring-2{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-2{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-4{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-primary\/20{--tw-ring-color:hsla(97,20%,75%,.2)}.blur{--tw-blur:blur(8px)}.blur,.grayscale{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.grayscale{--tw-grayscale:grayscale(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.backdrop-blur-xl{--tw-backdrop-blur:blur(24px)}.backdrop-blur-xl,.backdrop-filter{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-1000{transition-duration:1s}.duration-300{transition-duration:.3s}.duration-500{transition-duration:.5s}.duration-700{transition-duration:.7s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.selection\:bg-primary ::-moz-selection{--tw-bg-opacity:1;background-color:rgb(187 203 177/var(--tw-bg-opacity,1))}.selection\:bg-primary ::selection{--tw-bg-opacity:1;background-color:rgb(187 203 177/var(--tw-bg-opacity,1))}.selection\:text-on-primary ::-moz-selection{--tw-text-opacity:1;color:rgb(54 68 48/var(--tw-text-opacity,1))}.selection\:text-on-primary ::selection{--tw-text-opacity:1;color:rgb(54 68 48/var(--tw-text-opacity,1))}.selection\:bg-primary::-moz-selection{--tw-bg-opacity:1;background-color:rgb(187 203 177/var(--tw-bg-opacity,1))}.selection\:bg-primary::selection{--tw-bg-opacity:1;background-color:rgb(187 203 177/var(--tw-bg-opacity,1))}.selection\:text-on-primary::-moz-selection{--tw-text-opacity:1;color:rgb(54 68 48/var(--tw-text-opacity,1))}.selection\:text-on-primary::selection{--tw-text-opacity:1;color:rgb(54 68 48/var(--tw-text-opacity,1))}.placeholder\:text-on-surface-variant\/40::-moz-placeholder{color:hsla(93,6%,66%,.4)}.placeholder\:text-on-surface-variant\/40::placeholder{color:hsla(93,6%,66%,.4)}.hover\:-translate-y-1:hover{--tw-translate-y:-0.25rem}.hover\:-translate-y-1:hover,.hover\:translate-y-\[-10px\]:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:translate-y-\[-10px\]:hover{--tw-translate-y:-10px}.hover\:translate-y-\[-20px\]:hover{--tw-translate-y:-20px}.hover\:translate-y-\[-20px\]:hover,.hover\:translate-y-\[-2px\]:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:translate-y-\[-2px\]:hover{--tw-translate-y:-2px}.hover\:translate-y-\[-30px\]:hover{--tw-translate-y:-30px}.hover\:scale-95:hover,.hover\:translate-y-\[-30px\]:hover{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:scale-95:hover{--tw-scale-x:.95;--tw-scale-y:.95}.hover\:bg-\[\#1a1d18\]:hover{--tw-bg-opacity:1;background-color:rgb(26 29 24/var(--tw-bg-opacity,1))}.hover\:bg-primary:hover{--tw-bg-opacity:1;background-color:rgb(187 203 177/var(--tw-bg-opacity,1))}.hover\:bg-primary-container:hover{--tw-bg-opacity:1;background-color:rgb(61 75 54/var(--tw-bg-opacity,1))}.hover\:bg-surface-bright:hover{--tw-bg-opacity:1;background-color:rgb(39 46 37/var(--tw-bg-opacity,1))}.hover\:bg-surface-container-high:hover{--tw-bg-opacity:1;background-color:rgb(28 33 27/var(--tw-bg-opacity,1))}.hover\:bg-surface-container-highest:hover{--tw-bg-opacity:1;background-color:rgb(33 39 32/var(--tw-bg-opacity,1))}.hover\:bg-white\/5:hover{background-color:hsla(0,0%,100%,.05)}.hover\:text-\[\#bbcbb1\]:hover{--tw-text-opacity:1;color:rgb(187 203 177/var(--tw-text-opacity,1))}.hover\:text-on-primary:hover{--tw-text-opacity:1;color:rgb(54 68 48/var(--tw-text-opacity,1))}.hover\:text-on-surface:hover{--tw-text-opacity:1;color:rgb(225 231 220/var(--tw-text-opacity,1))}.hover\:text-primary:hover{--tw-text-opacity:1;color:rgb(187 203 177/var(--tw-text-opacity,1))}.hover\:opacity-90:hover{opacity:.9}.hover\:shadow-\[0_0_20px_rgba\(187\2c 203\2c 177\2c 0\.3\)\]:hover{--tw-shadow:0 0 20px hsla(97,20%,75%,.3);--tw-shadow-colored:0 0 20px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hover\:brightness-110:hover{--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.focus\:border-primary\/60:focus{border-color:hsla(97,20%,75%,.6)}.focus\:ring-0:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-0:focus,.focus\:ring-1:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-primary\/40:focus{--tw-ring-color:hsla(97,20%,75%,.4)}.active\:scale-95:active{--tw-scale-x:.95;--tw-scale-y:.95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:focus-within .group-focus-within\:text-primary{--tw-text-opacity:1;color:rgb(187 203 177/var(--tw-text-opacity,1))}.group:hover .group-hover\:-translate-y-1{--tw-translate-y:-0.25rem}.group:hover .group-hover\:-translate-y-1,.group:hover .group-hover\:translate-x-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:translate-x-1{--tw-translate-x:0.25rem}.group:hover .group-hover\:translate-y-0{--tw-translate-y:0px}.group:hover .group-hover\:rotate-180,.group:hover .group-hover\:translate-y-0{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:rotate-180{--tw-rotate:180deg}.group:hover .group-hover\:scale-110{--tw-scale-x:1.1;--tw-scale-y:1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.group:hover .group-hover\:border-primary\/20{border-color:hsla(97,20%,75%,.2)}.group:hover .group-hover\:text-primary{--tw-text-opacity:1;color:rgb(187 203 177/var(--tw-text-opacity,1))}.group:hover .group-hover\:text-primary\/40{color:hsla(97,20%,75%,.4)}.group:hover .group-hover\:opacity-100{opacity:1}.group:hover .group-hover\:opacity-20{opacity:.2}.group:hover .group-hover\:brightness-110{--tw-brightness:brightness(1.1);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.dark\:bg-\[\#0d0f0c\]:is(.dark *){--tw-bg-opacity:1;background-color:rgb(13 15 12/var(--tw-bg-opacity,1))}@media (min-width:768px){.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-5{grid-column:span 5/span 5}.md\:col-span-7{grid-column:span 7/span 7}.md\:hidden{display:none}.md\:w-1\/2{width:50%}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:items-end{align-items:flex-end}}@media (min-width:1024px){.lg\:col-span-5{grid-column:span 5/span 5}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:px-12{padding-left:3rem;padding-right:3rem}}@media (min-width:1280px){.xl\:text-7xl{font-size:4.5rem;line-height:1}} \ No newline at end of file +.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.visible{visibility:visible}.static{position:static}.sticky{position:sticky}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.min-h-svh{min-height:100svh}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.gap-6{gap:1.5rem}.border{border-width:1px}.text-5xl{font-size:3rem;line-height:1}.uppercase{text-transform:uppercase}.ring{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s} \ No newline at end of file diff --git a/apps/showcase/public/css/theme.css b/apps/showcase/public/css/theme.css new file mode 100644 index 0000000..37ed720 --- /dev/null +++ b/apps/showcase/public/css/theme.css @@ -0,0 +1,10 @@ +/** + * Velora theme (opt-in skin). + * Visual tokens + editorial themes + scene look recipes. + * Motion hosts should prefer @velora/css/motion-core instead. + */ +@layer velora.reset, velora.tokens, velora.layout, velora.motion, velora.components, velora.transitions, velora.utilities, velora.overrides; + +@import "./01-visual-tokens.css"; +@import "./01b-editorial-themes.css"; +@import "./scene-recipes.css"; diff --git a/apps/showcase/public/css/velora.css b/apps/showcase/public/css/velora.css index 6f7ded6..fb3fee0 100644 --- a/apps/showcase/public/css/velora.css +++ b/apps/showcase/public/css/velora.css @@ -1,5 +1,6 @@ /** - * Velora — entry CSS + * Velora — entry CSS (full convenience bundle: motion + theme skin + components). + * For host-agnostic motion only, prefer @velora/css/motion-core (+ theme opt-in). * Layer order defines unambiguous specificity across the framework. */ @layer velora.reset, velora.tokens, velora.layout, velora.motion, velora.components, velora.transitions, velora.utilities, velora.overrides; @@ -11,12 +12,13 @@ @import "./03-motion.css"; @import "./03b-motion-extended.css"; @import "./03a-motion-conditions.css"; +@import "./03c-scene-engine.css"; +@import "./scene-recipes.css"; @import "./04-components.css"; @import "./04a-forms.css"; @import "./04b-dialogs.css"; @import "./04c-structures.css"; @import "./04d-premium.css"; -@import "./04e-showcase-elevation-patterns.css"; @import "./05-transitions.css"; @import "./06-utilities.css"; @import "./07-overrides.css"; diff --git a/apps/showcase/public/img/catalog-glyphs.svg b/apps/showcase/public/img/catalog-glyphs.svg new file mode 100644 index 0000000..ced2680 --- /dev/null +++ b/apps/showcase/public/img/catalog-glyphs.svg @@ -0,0 +1,37 @@ + diff --git a/apps/showcase/public/js/showcase-api-motion-catalog.js b/apps/showcase/public/js/showcase-api-motion-catalog.js new file mode 100644 index 0000000..0e771e3 --- /dev/null +++ b/apps/showcase/public/js/showcase-api-motion-catalog.js @@ -0,0 +1,518 @@ +/** + * Motion API Catalog — DX runtime toolbar (showcase-only). + * Tunes delay / duration / speed for the selected card against Velora CONTRACT attrs. + * Shipped motion remains CSS-only; this script does not drive animation logic. + */ +(function () { + "use strict"; + + /** Stable motion attrs from docs/project/CONTRACT.md §2.1 (card-scoped). */ + const API_COMMANDS = [ + "vl-effect", + "vl-enter", + "vl-exit", + "vl-scroll", + "vl-loop", + "vl-loop-effect", + "vl-hover", + "vl-state", + "vl-base", + "vl-motion", + "vl-timeline", + "vl-range", + "vl-duration", + "vl-speed", + "vl-direction", + "vl-scene", + "vl-stage", + "vl-act", + "vl-span", + "vl-pin", + "vl-scrub", + "vl-once", + "vl-children", + "vl-stagger", + ]; + + const MOTION_TARGET_SELECTOR = [ + "[vl-effect]", + "[vl-enter]", + "[vl-exit]", + "[vl-scroll]", + "[vl-loop]", + "[vl-loop-effect]", + "[vl-hover]", + "[vl-state]", + "[vl-base]", + "[vl-motion]", + "[vl-children] > *", + "[vl-stage] > *", + ].join(", "); + + const SECTION_KIND_MAP = { + channels: "channel", + "timeline-modes": "timeline", + entrances: "timeline", + "stage-3d": "3d", + hover: "interaction", + "hover-cards": "interaction", + "editor-illustration": "interaction", + text: "text", + ambient: "ambient", + "cube-triad": "scene", + scroll: "timeline", + "scene-engine": "scene", + timeline: "timeline", + transitions: "transition", + children: "channel", + params: "reference", + }; + + const cards = Array.from(document.querySelectorAll(".api-card")); + if (!cards.length) return; + + cards.forEach((card) => { + const sectionId = card.closest("section[id]")?.id || ""; + const fallbackKind = card.classList.contains("api-card--lead") ? "reference" : "channel"; + card.dataset.apiKind = SECTION_KIND_MAP[sectionId] || fallbackKind; + }); + + const filterButtons = Array.from(document.querySelectorAll(".api-kind-filter__btn")); + const groups = Array.from(document.querySelectorAll(".api-group")); + const delayRange = document.getElementById("api-delay-range"); + const durationRange = document.getElementById("api-duration-range"); + const speedRange = document.getElementById("api-speed-range"); + const delayValue = document.getElementById("api-delay-value"); + const durationValue = document.getElementById("api-duration-value"); + const speedValue = document.getElementById("api-speed-value"); + const selectedLabel = document.getElementById("api-selected-card"); + const commandList = document.getElementById("api-command-list"); + const liveHint = document.getElementById("api-live-hint"); + const liveReadout = document.getElementById("api-live-readout"); + const drawerBtn = document.getElementById("api-controls-drawer-btn"); + const drawer = document.getElementById("api-controls-drawer"); + const playBtn = document.getElementById("api-play-btn"); + const pauseBtn = document.getElementById("api-pause-btn"); + const replayBtn = document.getElementById("api-replay-btn"); + + /** Primary attrs shown first in the live readout. */ + const READOUT_PRIORITY = [ + "vl-scene", + "vl-stage", + "vl-act", + "vl-span", + "vl-pin", + "vl-scrub", + "vl-effect", + "vl-enter", + "vl-exit", + "vl-scroll", + "vl-loop", + "vl-loop-effect", + "vl-hover", + "vl-state", + "vl-timeline", + "vl-range", + "vl-duration", + "vl-speed", + "vl-direction", + "vl-children", + "vl-stagger", + "vl-once", + "vl-base", + "vl-motion", + ]; + + const MOTION_HINTS = { + "vl-enter": "Entrada quando o alvo cruza o viewport (ou timeline view).", + "vl-exit": "Saída ao sair do viewport ou fim de cena.", + "vl-scroll": "Motion acoplada ao scroll — scrub no eixo da página.", + "vl-loop": "Loop CSS contínuo ou N repetições via keyframes.", + "vl-loop-effect": "Preset de loop (orbit, wobble, glow…).", + "vl-hover": "Canal de pointer — hover/focus disparam transições.", + "vl-effect": "Efeito legacy/interaction no atributo vl-effect.", + "vl-state": "Estado suave persistente no elemento.", + "vl-timeline": "Driver: view, scroll, hover ou tempo.", + "vl-range": "Janela de scroll/viewport onde a motion é ativa.", + "vl-scene": "Relógio de cena — acts com timing relativo.", + "vl-stage": "Palco 3D/2D — filhos posicionados por vars.", + "vl-pin": "Pin numérico durante scrub de cena.", + "vl-scrub": "Liga progresso de scroll ao relógio da cena.", + "vl-children": "Orquestra filhos (stagger / orchestrate).", + "vl-stagger": "Atraso entre filhos em ms.", + }; + + const TIMELINE_HINTS = { + view: "Timeline view — dispara ao entrar no viewport.", + scroll: "Timeline scroll — progresso ligado ao scroll.", + hover: "Timeline hover — gate por pointer/focus.", + }; + + let selectedCard = null; + let isPaused = false; + let replayTimer = null; + + function parseTimeMs(value, fallback = 0) { + if (value == null || value === "") return fallback; + const match = String(value).trim().match(/^([\d.]+)\s*(ms|s)?$/i); + if (!match) return fallback; + const amount = Number.parseFloat(match[1]); + const unit = (match[2] || "ms").toLowerCase(); + return unit === "s" ? amount * 1000 : amount; + } + + function getRuntimeValues() { + return { + delayMs: Number(delayRange?.value || 0), + durationMs: Number(durationRange?.value || 1400), + speedRate: Number(speedRange?.value || 1), + }; + } + + function getMotionTargets(card) { + const seen = new Set(); + const nodes = []; + + card.querySelectorAll(MOTION_TARGET_SELECTOR).forEach((node) => { + if (seen.has(node)) return; + seen.add(node); + nodes.push(node); + }); + + return nodes; + } + + function getStaggerDelayMs(node) { + const container = node.closest("[vl-children]"); + if (!container || !container.contains(node)) return 0; + + const step = parseTimeMs(container.getAttribute("vl-stagger"), 100); + const siblings = Array.from(container.children); + const index = siblings.indexOf(node); + return index >= 0 ? index * step : 0; + } + + function applyDurationVars(node, durationMs) { + const duration = `${durationMs}ms`; + node.style.setProperty("--vl-motion-duration", duration); + node.style.setProperty("--vl-enter-duration", duration); + node.style.setProperty("--vl-exit-duration", duration); + node.style.setProperty("--vl-loop-duration", duration); + node.setAttribute("vl-duration", duration); + } + + function clearForcedAnimationStyles(nodes) { + nodes.forEach((node) => { + node.style.removeProperty("animation"); + node.style.removeProperty("animation-play-state"); + }); + } + + function resetScrollStories(card) { + const scenes = card.querySelectorAll("[vl-scene]"); + if (!scenes.length) return; + + const scene = scenes[0]; + const top = window.scrollY + scene.getBoundingClientRect().top - 96; + window.scrollTo({ top: Math.max(0, top), behavior: "auto" }); + } + + function syncWebAnimations(card, values, paused) { + card.getAnimations({ subtree: true }).forEach((anim) => { + const target = anim.effect?.target; + const stagger = + target instanceof Element && card.contains(target) ? getStaggerDelayMs(target) : 0; + const delay = values.delayMs + stagger; + + anim.playbackRate = values.speedRate; + if (anim.effect && typeof anim.effect.updateTiming === "function") { + try { + anim.effect.updateTiming({ + duration: values.durationMs, + delay, + }); + } catch { + /* scroll/view timelines may reject timing updates */ + } + } + if (paused) anim.pause(); + else anim.play(); + }); + } + + function applyRuntimeToCard(card) { + if (!card) return; + + const values = getRuntimeValues(); + const duration = `${values.durationMs}ms`; + const targets = getMotionTargets(card); + + card.style.setProperty("--vl-motion-duration", duration); + card.style.setProperty("--vl-enter-duration", duration); + card.style.setProperty("--vl-exit-duration", duration); + card.style.setProperty("--vl-loop-duration", duration); + card.style.setProperty("--vl-motion-speed-scale", String(values.speedRate)); + + targets.forEach((node) => { + const totalDelay = values.delayMs + getStaggerDelayMs(node); + applyDurationVars(node, values.durationMs); + node.style.animationDelay = `${totalDelay}ms`; + node.style.animationPlayState = isPaused ? "paused" : "running"; + }); + + syncWebAnimations(card, values, isPaused); + } + + function replayCardAnimations(card, options = {}) { + if (!card) return; + + const resetScroll = options.resetScroll === true; + window.clearTimeout(replayTimer); + const values = getRuntimeValues(); + + replayTimer = window.setTimeout(() => { + if (resetScroll) resetScrollStories(card); + const targets = getMotionTargets(card); + + targets.forEach((node) => { + node.getAnimations().forEach((anim) => anim.cancel()); + }); + + requestAnimationFrame(() => { + targets.forEach((node) => { + node.style.animation = "none"; + void node.offsetHeight; + }); + + requestAnimationFrame(() => { + clearForcedAnimationStyles(targets); + isPaused = false; + updateActionState(); + applyRuntimeToCard(card); + }); + }); + }, values.delayMs); + } + + function collectActiveAttributes(card) { + const found = new Map(); + + function add(node, attr) { + const value = node.getAttribute(attr); + const key = `${attr}=${value ?? ""}`; + if (found.has(key)) return; + found.set(key, { attr, value: value ?? "", node }); + } + + API_COMMANDS.forEach((attr) => { + if (card.hasAttribute(attr)) add(card, attr); + card.querySelectorAll(`[${attr}]`).forEach((node) => add(node, attr)); + }); + + return Array.from(found.values()).sort((a, b) => { + const ai = READOUT_PRIORITY.indexOf(a.attr); + const bi = READOUT_PRIORITY.indexOf(b.attr); + return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi); + }); + } + + function formatAttrToken({ attr, value }) { + if (value === "") return attr; + return `${attr}="${value}"`; + } + + function deriveMotionHint(card, active) { + if (!active.length) { + return "Card de referência — sem attrs de motion no alvo."; + } + + const primary = + active.find(({ attr }) => attr === "vl-scene") || + active.find(({ attr }) => attr === "vl-enter") || + active.find(({ attr }) => attr === "vl-scroll") || + active.find(({ attr }) => attr === "vl-effect") || + active.find(({ attr }) => attr === "vl-hover") || + active.find(({ attr }) => attr === "vl-loop") || + active[0]; + + let hint = MOTION_HINTS[primary.attr] || "Motion via attrs Velora no alvo selecionado."; + + const timeline = active.find(({ attr }) => attr === "vl-timeline"); + if (timeline?.value && TIMELINE_HINTS[timeline.value]) { + hint = `${hint} ${TIMELINE_HINTS[timeline.value]}`; + } + + if (card.querySelector("[vl-scene]")) { + hint = `${hint} Replay reposiciona scroll da cena.`; + } else if (primary.attr === "vl-hover" || primary.attr === "vl-effect") { + hint = `${hint} Passe o pointer para re-disparar.`; + } + + return hint; + } + + function renderLiveReadout(card) { + if (!card) { + if (liveHint) liveHint.textContent = "Clique num card para ver o que está em motion."; + if (liveReadout) { + liveReadout.hidden = true; + liveReadout.textContent = ""; + } + return; + } + + const active = collectActiveAttributes(card); + const tokens = active.map(formatAttrToken); + + if (liveHint) liveHint.textContent = deriveMotionHint(card, active); + if (liveReadout) { + if (tokens.length) { + liveReadout.hidden = false; + liveReadout.textContent = tokens.join(" · "); + } else { + liveReadout.hidden = true; + liveReadout.textContent = ""; + } + } + } + + function renderAvailableCommands(card) { + if (!commandList || !card) return; + + const active = collectActiveAttributes(card); + if (!active.length) { + commandList.innerHTML = + 'Sem attrs de motion neste card'; + return; + } + + commandList.innerHTML = active + .map(({ attr, value }) => { + const label = value ? `${attr}="${value}"` : attr; + return `${label}`; + }) + .join(""); + } + + function setDrawerOpen(open) { + if (!drawer || !drawerBtn) return; + drawer.hidden = !open; + drawerBtn.classList.toggle("is-active", open); + drawerBtn.setAttribute("aria-expanded", open ? "true" : "false"); + } + + function updateActionState() { + if (playBtn) playBtn.classList.toggle("is-active", !isPaused); + if (pauseBtn) pauseBtn.classList.toggle("is-active", isPaused); + } + + function selectCard(card) { + cards.forEach((item) => item.classList.remove("is-selected")); + selectedCard = card; + card.classList.add("is-selected"); + const label = card.querySelector("strong")?.textContent?.trim() || "card"; + if (selectedLabel) selectedLabel.textContent = label; + renderLiveReadout(card); + renderAvailableCommands(card); + applyRuntimeToCard(card); + } + + function getVisibleCards() { + return cards.filter((card) => !card.classList.contains("is-filter-hidden")); + } + + function applyCardFilter(kind) { + cards.forEach((card) => { + const show = kind === "all" || card.dataset.apiKind === kind; + card.classList.toggle("is-filter-hidden", !show); + }); + + groups.forEach((group) => { + const groupCards = Array.from(group.querySelectorAll(".api-card")); + const hasVisible = groupCards.some((card) => !card.classList.contains("is-filter-hidden")); + group.classList.toggle("is-filter-empty", !hasVisible); + }); + + filterButtons.forEach((btn) => { + btn.classList.toggle("is-active", btn.dataset.filterKind === kind); + }); + + if (!selectedCard || selectedCard.classList.contains("is-filter-hidden")) { + const firstVisible = getVisibleCards()[0]; + if (firstVisible) selectCard(firstVisible); + else if (selectedLabel) selectedLabel.textContent = "Nenhum"; + } + } + + cards.forEach((card) => { + card.addEventListener("click", () => selectCard(card)); + card.addEventListener("focusin", () => { + if (!selectedCard) selectCard(card); + }); + card.addEventListener("mouseenter", () => replayCardAnimations(card)); + card.addEventListener("focusin", () => replayCardAnimations(card)); + }); + + filterButtons.forEach((button) => { + button.addEventListener("click", () => { + const nextKind = button.dataset.filterKind || "all"; + applyCardFilter(nextKind); + }); + }); + + if (delayRange && delayValue) { + delayRange.addEventListener("input", () => { + delayValue.textContent = `${Number(delayRange.value)}ms`; + replayCardAnimations(selectedCard, { resetScroll: true }); + }); + } + + if (durationRange && durationValue) { + durationRange.addEventListener("input", () => { + durationValue.textContent = `${Number(durationRange.value)}ms`; + replayCardAnimations(selectedCard, { resetScroll: true }); + }); + } + + if (speedRange && speedValue) { + speedRange.addEventListener("input", () => { + speedValue.textContent = `${Number(speedRange.value).toFixed(2)}x`; + applyRuntimeToCard(selectedCard); + }); + } + + if (playBtn) { + playBtn.addEventListener("click", () => { + isPaused = false; + updateActionState(); + applyRuntimeToCard(selectedCard); + }); + } + + if (pauseBtn) { + pauseBtn.addEventListener("click", () => { + isPaused = true; + updateActionState(); + applyRuntimeToCard(selectedCard); + }); + } + + if (replayBtn) { + replayBtn.addEventListener("click", () => { + isPaused = false; + updateActionState(); + replayCardAnimations(selectedCard, { resetScroll: true }); + }); + } + + if (drawerBtn && drawer) { + drawerBtn.addEventListener("click", () => { + setDrawerOpen(drawer.hidden); + }); + } + + const firstVisible = getVisibleCards()[0] || cards[0]; + if (firstVisible) selectCard(firstVisible); + applyCardFilter("all"); + updateActionState(); +})(); diff --git a/apps/showcase/scripts/apply-lean-nav.mjs b/apps/showcase/scripts/apply-lean-nav.mjs new file mode 100644 index 0000000..8731505 --- /dev/null +++ b/apps/showcase/scripts/apply-lean-nav.mjs @@ -0,0 +1,83 @@ +/** + * Apply lean Showcase primary nav + footer to live HTML files. + * Live IA is six routes only. + */ +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + +const NAV = ` `; + +const MOBILE = ` `; + +const FOOTER_COLS = ` `; + +const files = [ + "index.html", + "pages/scenes/scene-timeline.html", + "pages/motion/api-motion-catalog.html", + "pages/core/skins.html", + "pages/core/hosts.html", + "pages/core/archive.html", +]; + +function patch(html) { + let out = html.replace( + /