From 467af72b2262b2ef372f36a205b8a1c72b186db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20Bokisch?= Date: Thu, 21 May 2026 14:11:18 +0200 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20polish=20landing=20=E2=80=94=20full?= =?UTF-8?q?-bleed=20hero,=20readable=20nav,=20fixed=20code=20highlighting?= =?UTF-8?q?=20+=20favicon=20bg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four follow-up fixes after #17: 1. **Hero is now full-bleed.** The previous 1440-wide container clipped the radial chartreuse glow at the right edge of the constrained box, leaving a hard cut on wide viewports. The hero now spans the viewport (`padding: 0 clamp(24px, 4vw, 64px)`), inner content stays centered at `max-width: 1480px`, and the glow + grid fade naturally to the viewport edges. Glow size also bumped to `clamp(600px, 60vw, 1100px)` so it scales with the new room. 2. **Nav logo is bigger and readable.** `Lockup` size in the fumadocs nav went 22 → 32, and the wordmark/mark ratio went 0.44 → 0.60 (text now ~60% of mark height instead of 44%). "vitus·labs" was 10px before — now ~19px and actually legible. 3. **Code-block syntax highlighting fixed.** The previous regex-based highlighter ran *after* HTML-escaping, so JSX angle brackets stopped keyword matches dead and quoted strings (already encoded as `'`) never matched the string regex. Replaced with explicit per-token JSX spans — same approach as the source design — with the original `.kw / .str / .fn / .ty / .cm` palette intact. 4. **Favicon has a solid background.** Both `icon.svg` and `apple-icon.svg` now use a dark ink (`#0a0b0d`) rounded background with a chartreuse mark. At 16/32px the previous transparent-bg chartreuse triangles disappeared on light tab backgrounds; with the dark plate they read consistently across any browser theme. Co-Authored-By: Claude Opus 4.7 --- src/app/(home)/page.tsx | 114 ++++++++++++++++++++------------ src/app/apple-icon.svg | 6 +- src/app/global.css | 30 ++++++--- src/app/icon.svg | 7 +- src/components/landing/Mark.tsx | 12 ++-- src/lib/layout.shared.tsx | 2 +- 6 files changed, 109 insertions(+), 62 deletions(-) diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 44d4bf5..3237036 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -127,38 +127,72 @@ const ECO = [ }, ] -const SAMPLE = `import { init } from '@vitus-labs/core' -import * as connector from '@vitus-labs/connector-styler' -import { Element, Text, List } from '@vitus-labs/elements' - -// One-line setup -init({ ...connector, component: 'div', textComponent: 'span' }) - -function FeatureCard({ icon, title, items }) { - return ( - - - {title} - - - +/** + * Hand-tokenized code block. JSX-in-template-literal can't be regex-highlighted + * reliably (escape-then-match order, JSX angle brackets, mixed identifiers), so + * we render each token as an explicit JSX span — same approach as the source + * design HTML. CSS classes: .kw (keyword), .str (string), .fn (function/number), + * .ty (component type), .cm (comment). + */ +function CodeBlock() { + const Kw = ({ children }: { children: React.ReactNode }) => ( + {children} + ) + const Str = ({ children }: { children: React.ReactNode }) => ( + {children} + ) + const Fn = ({ children }: { children: React.ReactNode }) => ( + {children} + ) + const Ty = ({ children }: { children: React.ReactNode }) => ( + {children} + ) + const Cm = ({ children }: { children: React.ReactNode }) => ( + {children} ) -}` -function CodeBlock() { return (
-      /g, '>')
-            .replace(/\b(import|from|function|return|const)\b/g, '$1')
-            .replace(/('[^&]*'|'[^&]*'|'[^']*')/g, '$1')
-            .replace(/(\/\/[^\n]*)/g, '$1'),
-        }}
-      />
+      import{' '}{'{ init }'}{' '}from{' '}
+      '@vitus-labs/core'
+      {'\n'}
+      import * as connector from{' '}
+      '@vitus-labs/connector-styler'
+      {'\n'}
+      import{' '}{'{ Element, Text, List }'}{' '}from{' '}
+      '@vitus-labs/elements'
+      {'\n\n'}
+      {'// One-line setup'}
+      {'\n'}
+      init({'{ ...connector, component: '}'div'
+      {', textComponent: '}'span'{' }'})
+      {'\n\n'}
+      function FeatureCard
+      {'({ icon, title, items }) {'}
+      {'\n  '}
+      return {'('}
+      {'\n    '}
+      {'<'}Element tag="article" direction=
+      "rows" gap={'{'}16{'}'} padding={'{'}24
+      {'}'}{'>'}
+      {'\n      '}
+      {'<'}Element beforeContent={'{icon}'} gap={'{'}8
+      {'}'} alignY="center"{'>'}
+      {'\n        '}
+      {'<'}Text tag="h3"{'>'}
+      {'{title}'}
+      {'Text{'>'}
+      {'\n      '}
+      {'Element{'>'}
+      {'\n      '}
+      {'<'}List data={'{items}'} gap={'{'}4{'}'}{' '}
+      direction="rows" {'/>'}
+      {'\n    '}
+      {'Element{'>'}
+      {'\n  '}
+      {')'}
+      {'\n'}
+      {'}'}
     
) } @@ -166,20 +200,14 @@ function CodeBlock() { export default function HomePage() { return (
-
- {/* HERO */} -
-
-
-
-
+ {/* HERO — full-bleed so the radial glow + grid extend to viewport edges */} +
+
+
+
+
+
@@ -285,8 +313,10 @@ export default function HomePage() {
-
+
+
+
{/* TICKER */} diff --git a/src/app/apple-icon.svg b/src/app/apple-icon.svg index 7ef5632..deebd3a 100644 --- a/src/app/apple-icon.svg +++ b/src/app/apple-icon.svg @@ -1,8 +1,8 @@ - - - + + + diff --git a/src/app/global.css b/src/app/global.css index 9ee5a05..f84377e 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -127,10 +127,24 @@ background: var(--vl-surface); } -/* ============= HERO ============= */ +/* ============= CONTAINER (post-hero sections) ============= */ +.vl-landing .vl-container { + max-width: 1440px; + margin: 0 auto; + padding: 0 clamp(24px, 4vw, 64px); +} + +/* ============= HERO — full-bleed ============= */ .vl-landing .vl-hero { - padding: 80px 0 80px; position: relative; + padding: 80px clamp(24px, 4vw, 64px); + overflow: hidden; +} +.vl-landing .vl-hero-content { + max-width: 1480px; + margin: 0 auto; + position: relative; + z-index: 1; } .vl-landing .vl-hero-bg { position: absolute; @@ -159,16 +173,16 @@ } .vl-landing .vl-hero-bg .glow { position: absolute; - top: -10%; - right: -10%; - width: 700px; - height: 700px; + top: -20%; + right: -15%; + width: clamp(600px, 60vw, 1100px); + height: clamp(600px, 60vw, 1100px); background: radial-gradient(circle, var(--vl-accent) 0%, transparent 60%); opacity: 0.4; - filter: blur(40px); + filter: blur(60px); } .dark .vl-landing .vl-hero-bg .glow { - opacity: 0.15; + opacity: 0.18; } .vl-landing .vl-hero-inner { diff --git a/src/app/icon.svg b/src/app/icon.svg index ef6a0e0..e008f90 100644 --- a/src/app/icon.svg +++ b/src/app/icon.svg @@ -1,5 +1,6 @@ - - - + + + + diff --git a/src/components/landing/Mark.tsx b/src/components/landing/Mark.tsx index bc9c052..1f9ca1e 100644 --- a/src/components/landing/Mark.tsx +++ b/src/components/landing/Mark.tsx @@ -74,25 +74,27 @@ type LockupProps = { size?: number gap?: number variant?: MarkVariant + textSize?: number className?: string } export function Lockup({ size = 36, - gap = 14, + gap = 12, variant = 'solid', + textSize, className, }: LockupProps) { + // Default the wordmark text to ~60% of mark height so it reads as a unit + // with the mark rather than as a small label next to a big symbol. + const fontSize = textSize ?? Math.round(size * 0.6) return ( - + vitus·labs diff --git a/src/lib/layout.shared.tsx b/src/lib/layout.shared.tsx index e19966a..c650c12 100644 --- a/src/lib/layout.shared.tsx +++ b/src/lib/layout.shared.tsx @@ -4,7 +4,7 @@ import { Lockup } from '@/components/landing/Mark' export function baseOptions(): BaseLayoutProps { return { nav: { - title: , + title: , }, links: [ { From 66dd8fd62362e745771ee9557dcce6952df2af6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20Bokisch?= Date: Thu, 21 May 2026 14:23:16 +0200 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20port=20new=20design=20iteration=20?= =?UTF-8?q?=E2=80=94=20aurora=20bg,=20verb=20rotator,=20hero=20variant=20s?= =?UTF-8?q?witcher,=20syntax=20theme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulls in the latest design package from Claude Design (J3lE5xxSeGBbwc_pj3bClg). Same brand system, more motion: **Hero background — aurora stack** Six layered effects replace the single radial glow: - `.conic` — slow-rotating chartreuse + cobalt conic-gradient, blurred 80px (60s rotation) - `.aurora` — large dual-radial bloom that drifts on `vl-aurora` (18s alternating) - `.glow-2` — bottom-left cobalt counter-note (22s alternate-reverse) - `.grid` — 72px dot grid with radial mask - `.noise` — SVG fractal-noise grain at 6%/4% opacity (light/dark) with multiply/overlay blend - `.vignette` — bottom fade so the hero meets the next section cleanly **Rotating verb in the headline** `VerbRotator` client component cycles through `ship → theme → test → animate → bundle` every 2.6s with a 420ms 3D flip (rotateX out → in). Respects `prefers-reduced-motion`. **Hero visual — three switchable variants** `HeroVisual` client component (state-driven) replaces the static orbit. Variant switcher pill (icons: orbital / stacked bars / radar circles) at the bottom of the visual fades between: - **Orbit** — the existing four rings + floating labels (`core · engine`, `styler · 4.82 KB`, `kinetic · 123 presets`, `elements · 5 primitives`) - **Stack** — animated bars assembling top-down (`vl-sbar` keyframes) plus a token row underneath - **Pulse** — radar sweep (conic + mask), three expanding rings (`vl-radar-ring`), four blips (`vl-blip`), Mark in the center **Syntax theme tokens** Brand css gained `--vl-code-{fg,kw,str,fn,ty,num,cm,punct}` with light + dark variants (chartreuse keywords / amber strings / cobalt fns in dark; olive / terracotta / true cobalt in light). The demo code block now uses those tokens including a new `` span for numeric literals. **hero-inner** Grid bumped 1.15fr 0.85fr → 1.1fr 0.9fr, gap 64 → 80, max-width 1480 → 1640 to match the new design. Hero `min-height` set to `calc(100vh - 64px)` so it fills the fold above other sections. Co-Authored-By: Claude Opus 4.7 --- src/app/(home)/page.tsx | 59 +-- src/app/global.css | 553 +++++++++++++++++++++++-- src/components/landing/HeroVisual.tsx | 184 ++++++++ src/components/landing/VerbRotator.tsx | 40 ++ 4 files changed, 770 insertions(+), 66 deletions(-) create mode 100644 src/components/landing/HeroVisual.tsx create mode 100644 src/components/landing/VerbRotator.tsx diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 3237036..630320d 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -1,8 +1,10 @@ import Link from 'next/link' import { Counter } from '@/components/landing/Counter' -import { Lockup, Mark } from '@/components/landing/Mark' +import { HeroVisual } from '@/components/landing/HeroVisual' +import { Lockup } from '@/components/landing/Mark' import { Reveal } from '@/components/landing/Reveal' import { Ticker } from '@/components/landing/Ticker' +import { VerbRotator } from '@/components/landing/VerbRotator' // Objective inventory — verified against the monorepo on 2026-05-21: // 15 packages (packages/* count) @@ -144,6 +146,9 @@ function CodeBlock() { const Fn = ({ children }: { children: React.ReactNode }) => ( {children} ) + const Num = ({ children }: { children: React.ReactNode }) => ( + {children} + ) const Ty = ({ children }: { children: React.ReactNode }) => ( {children} ) @@ -173,10 +178,10 @@ function CodeBlock() { return {'('} {'\n '} {'<'}Element tag="article" direction= - "rows" gap={'{'}16{'}'} padding={'{'}24 + "rows" gap={'{'}16{'}'} padding={'{'}24 {'}'}{'>'} {'\n '} - {'<'}Element beforeContent={'{icon}'} gap={'{'}8 + {'<'}Element beforeContent={'{icon}'} gap={'{'}8 {'}'} alignY="center"{'>'} {'\n '} {'<'}Text tag="h3"{'>'} @@ -185,7 +190,7 @@ function CodeBlock() { {'\n '} {'Element{'>'} {'\n '} - {'<'}List data={'{items}'} gap={'{'}4{'}'}{' '} + {'<'}List data={'{items}'} gap={'{'}4{'}'}{' '} direction="rows" {'/>'} {'\n '} {'Element{'>'} @@ -200,11 +205,15 @@ function CodeBlock() { export default function HomePage() { return (
- {/* HERO — full-bleed so the radial glow + grid extend to viewport edges */} + {/* HERO — full-bleed; aurora + conic + grain stack so the radial gradient extends past the viewport */}
+
+
+
-
+
+
@@ -221,7 +230,7 @@ export default function HomePage() {

Build, style
- & ship React + & React
apps composable.

@@ -276,41 +285,9 @@ export default function HomePage() {
- {/* Visual */} + {/* Visual — 3 switchable variants */} -
-
-
- -
-
- -
-
- -
-
- -
-
- - - core · engine - - - styler · 4.82 KB - - - kinetic · 123 presets - - - elements · 5 primitives - - -
- -
-
+
diff --git a/src/app/global.css b/src/app/global.css index f84377e..2e709b8 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -44,6 +44,16 @@ --vl-r-md: 8px; --vl-r-lg: 14px; --vl-r-xl: 24px; + + /* Syntax theme — light defaults */ + --vl-code-fg: #1f1d17; + --vl-code-kw: #6e8e18; + --vl-code-str: #b86a1f; + --vl-code-fn: #2f4dff; + --vl-code-ty: #1f1d17; + --vl-code-num: #b86a1f; + --vl-code-cm: #908b7c; + --vl-code-punct: #908b7c; } .dark { @@ -54,6 +64,16 @@ --vl-fg-muted: #a8a69c; --vl-border: #2e3138; --vl-accent: var(--vl-acid); + + /* Syntax theme — dark */ + --vl-code-fg: #dcdad2; + --vl-code-kw: #c8ff3a; + --vl-code-str: #f3b05b; + --vl-code-fn: #7aa2ff; + --vl-code-ty: #e8e6dd; + --vl-code-num: #f3b05b; + --vl-code-cm: #6b6e78; + --vl-code-punct: #6b6e78; } /* ============================================ @@ -137,11 +157,15 @@ /* ============= HERO — full-bleed ============= */ .vl-landing .vl-hero { position: relative; - padding: 80px clamp(24px, 4vw, 64px); + padding: 100px clamp(24px, 4vw, 64px) 80px; + min-height: calc(100vh - 64px); + display: flex; + align-items: center; overflow: hidden; } .vl-landing .vl-hero-content { - max-width: 1480px; + max-width: 1640px; + width: 100%; margin: 0 auto; position: relative; z-index: 1; @@ -158,39 +182,140 @@ background-image: linear-gradient(to right, var(--vl-border) 1px, transparent 1px), linear-gradient(to bottom, var(--vl-border) 1px, transparent 1px); - background-size: 56px 56px; - opacity: 0.35; + background-size: 72px 72px; + opacity: 0.4; mask-image: radial-gradient( - ellipse 80% 60% at 70% 20%, + ellipse 80% 60% at 60% 50%, black 0%, - transparent 70% + transparent 80% ); -webkit-mask-image: radial-gradient( - ellipse 80% 60% at 70% 20%, + ellipse 80% 60% at 60% 50%, black 0%, - transparent 70% + transparent 80% ); } -.vl-landing .vl-hero-bg .glow { +/* Aurora — large animated chartreuse + cobalt bloom */ +.vl-landing .vl-hero-bg .aurora { position: absolute; - top: -20%; - right: -15%; - width: clamp(600px, 60vw, 1100px); - height: clamp(600px, 60vw, 1100px); - background: radial-gradient(circle, var(--vl-accent) 0%, transparent 60%); - opacity: 0.4; + top: -40%; + right: -20%; + width: 1100px; + height: 1100px; + background: + radial-gradient(circle at 30% 30%, var(--vl-accent) 0%, transparent 55%), + radial-gradient( + circle at 70% 60%, + color-mix(in srgb, var(--vl-accent) 80%, var(--vl-cobalt)) 0%, + transparent 50% + ); + opacity: 0.45; + mix-blend-mode: multiply; filter: blur(60px); + animation: vl-aurora 18s ease-in-out infinite alternate; +} +.dark .vl-landing .vl-hero-bg .aurora { + opacity: 0.22; + mix-blend-mode: screen; +} +@keyframes vl-aurora { + 0% { + transform: translate(0, 0) scale(1) rotate(0deg); + } + 50% { + transform: translate(-60px, 40px) scale(1.1) rotate(20deg); + } + 100% { + transform: translate(80px, -30px) scale(0.95) rotate(-15deg); + } +} +/* Secondary cobalt counter-note */ +.vl-landing .vl-hero-bg .glow-2 { + position: absolute; + bottom: -30%; + left: -10%; + width: 800px; + height: 800px; + background: radial-gradient(circle, var(--vl-cobalt) 0%, transparent 55%); + opacity: 0.14; + filter: blur(50px); + animation: vl-aurora 22s ease-in-out infinite alternate-reverse; +} +.dark .vl-landing .vl-hero-bg .glow-2 { + opacity: 0.1; +} +/* Conic shimmer — slow rotating aurora at center */ +.vl-landing .vl-hero-bg .conic { + position: absolute; + top: 50%; + left: 50%; + width: 1600px; + height: 1600px; + transform: translate(-50%, -50%); + background: conic-gradient( + from 0deg, + transparent 0deg, + color-mix(in srgb, var(--vl-accent) 22%, transparent) 60deg, + transparent 140deg, + color-mix(in srgb, var(--vl-cobalt) 18%, transparent) 220deg, + transparent 300deg, + color-mix(in srgb, var(--vl-accent) 18%, transparent) 360deg + ); + opacity: 0.6; + filter: blur(80px); + animation: vl-spin 60s linear infinite; + mask-image: radial-gradient(circle, black 0%, black 40%, transparent 70%); + -webkit-mask-image: radial-gradient( + circle, + black 0%, + black 40%, + transparent 70% + ); } -.dark .vl-landing .vl-hero-bg .glow { - opacity: 0.18; +.dark .vl-landing .vl-hero-bg .conic { + opacity: 0.5; +} +@keyframes vl-spin { + from { + transform: translate(-50%, -50%) rotate(0); + } + to { + transform: translate(-50%, -50%) rotate(360deg); + } +} +/* Vertical vignette so the hero meets the next section cleanly */ +.vl-landing .vl-hero-bg .vignette { + position: absolute; + inset: 0; + background: linear-gradient( + to bottom, + transparent 0%, + transparent 60%, + var(--vl-bg) 100% + ); +} +/* Subtle organic film grain */ +.vl-landing .vl-hero-bg .noise { + position: absolute; + inset: 0; + opacity: 0.06; + background-image: url("data:image/svg+xml;utf8,"); + mix-blend-mode: multiply; +} +.dark .vl-landing .vl-hero-bg .noise { + opacity: 0.04; + mix-blend-mode: overlay; } .vl-landing .vl-hero-inner { display: grid; - grid-template-columns: 1.15fr 0.85fr; - gap: 64px; + grid-template-columns: 1.1fr 0.9fr; + gap: 80px; align-items: center; position: relative; + width: 100%; + max-width: 1640px; + margin: 0 auto; } @media (max-width: 1100px) { .vl-landing .vl-hero-inner { @@ -198,6 +323,43 @@ } } +/* ===== Verb slot — rotating word in headline ===== */ +.vl-landing .vl-verb-slot { + display: inline-block; + color: var(--vl-accent); + font-style: italic; + font-weight: 300; + min-width: 4.5ch; + text-align: left; + transition: + transform 0.42s cubic-bezier(0.7, 0, 0.3, 1), + opacity 0.42s; + will-change: transform, opacity; +} +.vl-landing .vl-verb-slot.out { + transform: translateY(-0.4em) rotateX(60deg); + opacity: 0; +} +.vl-landing .vl-verb-slot.in { + animation: vl-verb-in 0.42s cubic-bezier(0.2, 0.7, 0.2, 1); +} +@keyframes vl-verb-in { + from { + transform: translateY(0.4em) rotateX(-60deg); + opacity: 0; + } + to { + transform: translateY(0) rotateX(0); + opacity: 1; + } +} +@media (prefers-reduced-motion: reduce) { + .vl-landing .vl-verb-slot { + transition: none; + animation: none; + } +} + .vl-landing .vl-hero-tag { display: inline-flex; align-items: center; @@ -439,6 +601,343 @@ animation-delay: -3s; } +/* ============= HERO VISUAL — 3 variants ============= */ +.vl-landing .vl-hv-variant { + position: absolute; + inset: 0; + opacity: 1; + pointer-events: auto; + transition: opacity 0.4s ease; +} +.vl-landing .vl-hv-variant[hidden] { + display: none !important; +} +.vl-landing .vl-hv-variant.fading { + opacity: 0; +} + +/* ----- Variant: Stack ----- */ +.vl-landing .vl-hv-stack-bg { + position: absolute; + inset: 0; + background-image: + linear-gradient(to right, var(--vl-border) 1px, transparent 1px), + linear-gradient(to bottom, var(--vl-border) 1px, transparent 1px); + background-size: 40px 40px; + opacity: 0.5; + mask-image: radial-gradient(circle at center, black 30%, transparent 70%); + -webkit-mask-image: radial-gradient( + circle at center, + black 30%, + transparent 70% + ); + border-radius: 50%; +} +.vl-landing .vl-hv-stack { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + gap: 14px; + align-items: center; + justify-content: center; + transform-style: preserve-3d; +} +.vl-landing .vl-sbar { + background: var(--vl-fg); + border-radius: 4px; + transform-origin: center; + animation: vl-sbar 3.8s cubic-bezier(0.2, 0.7, 0.2, 1) infinite; + box-shadow: 0 8px 24px color-mix(in srgb, var(--vl-fg) 12%, transparent); +} +.vl-landing .vl-sb1 { + width: 64%; + height: 32px; + animation-delay: 0s; +} +.vl-landing .vl-sb2 { + width: 50%; + height: 32px; + animation-delay: 0.12s; +} +.vl-landing .vl-sb3 { + width: 36%; + height: 32px; + animation-delay: 0.24s; +} +.vl-landing .vl-sb4 { + width: 22%; + height: 32px; + background: var(--vl-accent); + animation-delay: 0.36s; + box-shadow: 0 0 32px var(--vl-accent); +} +@keyframes vl-sbar { + 0% { + transform: translateY(-40px) scaleX(0.3); + opacity: 0; + } + 18% { + transform: translateY(0) scaleX(1); + opacity: 1; + } + 78% { + transform: translateY(0) scaleX(1); + opacity: 1; + } + 92% { + transform: translateY(20px) scaleX(0.4); + opacity: 0; + } + 100% { + opacity: 0; + } +} +.vl-landing .vl-hv-stack-tokens { + position: absolute; + left: 0; + right: 0; + bottom: 8%; + display: flex; + justify-content: center; + gap: 10px; + flex-wrap: wrap; + font-family: var(--vl-font-mono); + font-size: 11px; + color: var(--vl-fg-muted); +} +.vl-landing .vl-hv-stack-tokens span { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 10px; + background: var(--vl-surface); + border: 1px solid var(--vl-border); + border-radius: 999px; + animation: vl-tk-pop 3.8s cubic-bezier(0.2, 0.7, 0.2, 1) infinite; +} +.vl-landing .vl-hv-stack-tokens span:nth-child(1) { + animation-delay: 0.1s; +} +.vl-landing .vl-hv-stack-tokens span:nth-child(2) { + animation-delay: 0.22s; +} +.vl-landing .vl-hv-stack-tokens span:nth-child(3) { + animation-delay: 0.34s; +} +.vl-landing .vl-hv-stack-tokens span:nth-child(4) { + animation-delay: 0.46s; + color: var(--vl-accent); + border-color: var(--vl-accent); +} +.vl-landing .vl-hv-stack-tokens .d { + width: 6px; + height: 6px; + border-radius: 50%; + background: currentColor; +} +@keyframes vl-tk-pop { + 0%, + 10% { + opacity: 0; + transform: translateY(6px); + } + 25%, + 80% { + opacity: 1; + transform: translateY(0); + } + 95%, + 100% { + opacity: 0; + transform: translateY(-6px); + } +} + +/* ----- Variant: Radar/Pulse ----- */ +.vl-landing .vl-hv-radar { + position: absolute; + inset: 0; + border-radius: 50%; + overflow: hidden; + background: radial-gradient( + circle at center, + color-mix(in srgb, var(--vl-accent) 8%, transparent) 0%, + transparent 60% + ); + border: 1px solid var(--vl-border); +} +.vl-landing .vl-radar-grid { + position: absolute; + inset: 0; + background-image: + linear-gradient(to right, var(--vl-border) 1px, transparent 1px), + linear-gradient(to bottom, var(--vl-border) 1px, transparent 1px); + background-size: 36px 36px; + opacity: 0.4; + mask-image: radial-gradient(circle, black 50%, transparent 80%); + -webkit-mask-image: radial-gradient(circle, black 50%, transparent 80%); +} +.vl-landing .vl-radar-sweep { + position: absolute; + inset: 0; + background: conic-gradient( + from 0deg, + transparent 0deg, + transparent 280deg, + color-mix(in srgb, var(--vl-accent) 35%, transparent) 340deg, + var(--vl-accent) 360deg + ); + border-radius: 50%; + mask-image: radial-gradient( + circle, + black 0%, + black 60%, + transparent 65% + ); + -webkit-mask-image: radial-gradient( + circle, + black 0%, + black 60%, + transparent 65% + ); + animation: vl-sweep 4s linear infinite; +} +@keyframes vl-sweep { + from { + transform: rotate(0); + } + to { + transform: rotate(360deg); + } +} +.vl-landing .vl-radar-ring { + position: absolute; + inset: 50%; + width: 0; + height: 0; + border: 1.5px solid var(--vl-accent); + border-radius: 50%; + transform: translate(-50%, -50%); + animation: vl-radar-ring 3s ease-out infinite; +} +@keyframes vl-radar-ring { + 0% { + width: 0; + height: 0; + opacity: 0.8; + border-width: 2px; + } + 100% { + width: 90%; + height: 90%; + opacity: 0; + border-width: 0.5px; + } +} +.vl-landing .vl-radar-ring.r2 { + animation-delay: 1s; +} +.vl-landing .vl-radar-ring.r3 { + animation-delay: 2s; +} +.vl-landing .vl-radar-blip { + position: absolute; + width: 8px; + height: 8px; + background: var(--vl-accent); + border-radius: 50%; + box-shadow: 0 0 12px var(--vl-accent); + animation: vl-blip 2.2s ease-out infinite; +} +.vl-landing .vl-radar-blip.b1 { + top: 22%; + left: 30%; + animation-delay: 0s; +} +.vl-landing .vl-radar-blip.b2 { + top: 68%; + left: 72%; + animation-delay: 0.5s; + background: var(--vl-fg); + box-shadow: none; +} +.vl-landing .vl-radar-blip.b3 { + top: 40%; + left: 80%; + animation-delay: 1s; + background: var(--vl-signal); + box-shadow: 0 0 12px var(--vl-signal); +} +.vl-landing .vl-radar-blip.b4 { + top: 76%; + left: 28%; + animation-delay: 1.5s; +} +@keyframes vl-blip { + 0%, + 100% { + transform: scale(0); + opacity: 0; + } + 50% { + transform: scale(1); + opacity: 1; + } +} +.vl-landing .vl-radar-center { + position: absolute; + inset: 0; + margin: auto; + width: 28%; + height: 28%; + display: flex; + align-items: center; + justify-content: center; + background: var(--vl-bg); + border: 1px solid var(--vl-accent); + border-radius: 50%; +} + +/* ----- Variant switcher pill ----- */ +.vl-landing .vl-hv-switch { + position: absolute; + bottom: -8px; + left: 50%; + transform: translateX(-50%); + display: inline-flex; + gap: 4px; + padding: 4px; + background: var(--vl-surface); + border: 1px solid var(--vl-border); + border-radius: 999px; + z-index: 10; +} +.vl-landing .vl-hv-switch button { + width: 32px; + height: 32px; + display: inline-flex; + align-items: center; + justify-content: center; + background: transparent; + border: 0; + border-radius: 999px; + cursor: pointer; + color: var(--vl-fg-muted); + transition: all 0.18s; +} +.vl-landing .vl-hv-switch button svg { + width: 16px; + height: 16px; +} +.vl-landing .vl-hv-switch button:hover { + color: var(--vl-fg); +} +.vl-landing .vl-hv-switch button.active { + background: var(--vl-accent); + color: var(--vl-ink-0); +} + /* ============= TICKER ============= */ .vl-landing .vl-ticker { margin: 60px 0; @@ -717,24 +1216,28 @@ font-family: var(--vl-font-mono); font-size: 13px; line-height: 1.7; - color: var(--vl-fg); + color: var(--vl-code-fg); overflow: auto; } .vl-landing .vl-demo-code .kw { - color: var(--vl-accent); + color: var(--vl-code-kw); + font-weight: 500; } .vl-landing .vl-demo-code .str { - color: var(--vl-signal); + color: var(--vl-code-str); } .vl-landing .vl-demo-code .fn { - color: var(--vl-cobalt); + color: var(--vl-code-fn); +} +.vl-landing .vl-demo-code .num { + color: var(--vl-code-num); } .vl-landing .vl-demo-code .cm { - color: var(--vl-fg-muted); + color: var(--vl-code-cm); font-style: italic; } .vl-landing .vl-demo-code .ty { - color: var(--vl-fg-muted); + color: var(--vl-code-ty); } /* ============= ECOSYSTEM ============= */ diff --git a/src/components/landing/HeroVisual.tsx b/src/components/landing/HeroVisual.tsx new file mode 100644 index 0000000..26d881f --- /dev/null +++ b/src/components/landing/HeroVisual.tsx @@ -0,0 +1,184 @@ +'use client' + +import { useState } from 'react' +import { Mark } from './Mark' + +type Variant = 'orbit' | 'stack' | 'pulse' + +export function HeroVisual() { + const [active, setActive] = useState('orbit') + const [fading, setFading] = useState(false) + + const swap = (v: Variant) => { + if (v === active) return + setFading(true) + window.setTimeout(() => { + setActive(v) + setFading(false) + }, 220) + } + + const cls = (v: Variant) => + `vl-hv-variant${fading ? ' fading' : ''}${active === v ? '' : ' vl-hidden'}` + + return ( +
+ {/* Variant A: Orbital */} + + + {/* Variant B: Stack */} +