buildmymcpserver/apps/web/components/particle-hero/index.tsx

164 lines
6.1 KiB
TypeScript
Raw Normal View History

feat(web): hero redesign — cycling step rotator + full-width video section Restructures the landing page above-the-fold into two distinct sections: 1. **Hero — left copy + cycling tile, no static stack of three blocks** New `<HeroStepRotator>` (Framer Motion client component) shows ONE tile centred in the column, cycling prompt.txt → build.log → claude_desktop_config.json every 3.5s. Auto-advance pauses on hover and exposes a 3-dot tablist so users can jump to any step. The active dot grows wide with an accent glow. Mouse interaction: spring-smoothed 3D tilt on rotateX/rotateY plus a radial glow that translates toward the cursor — both driven by motion values, so the transforms stay on the GPU compositor instead of re-rendering on every mousemove. `useReducedMotion()` strips the tilt + glow translation and collapses the page transition to an instant cross-fade (the rotation itself still advances — it's content, not decoration). Hero padding tightened (py-12/14/16 vs py-14/20/28) so the video section below is teased above the fold. New scroll cue ("see it run" + animated chevron) sits at the bottom of the hero, anchored to #flow. 2. **Flow video — full-width edge-to-edge under the hero (new section)** The hero.mp4 / hero.webm pair moves out of the "How it works" section into its own #flow section. No max-w wrapper — it spans the viewport with `w-full aspect-video`, so on a 1080p monitor the video gets the full 1920px width. Adds a subtle radial vignette so the black edges blend into the page chrome. 3. **"How it works" — now lean** Video removed (it's the flow section now). Just the three textual cards as supporting copy. Adds `framer-motion@11.18.2` to apps/web/package.json. Build passes typecheck + Next.js production build with no new warnings; LCP path is untouched since the rotator is client-hydrated after first paint and Framer Motion is tree-shaken to the components we import. Note: visitors with `prefers-reduced-motion: reduce` will still see the video's poster instead of autoplay — Chrome blocks the network fetch entirely for autoplay media when reduced-motion is set. The flow video remains visible for the rest, and the step rotator continues to cycle its content (with instant cross-fade instead of slide+scale). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:05:28 +02:00
'use client';
/**
* ParticleHero public entry to the WebGL particle background.
*
* Responsibilities (kept OUT of ParticleField so that component can
* assume happy-path WebGL2):
*
* 1. WebGL2 missing static gradient.
* 2. Mobile / low-power profile either 16k particles or skip.
* 3. prefers-reduced-motion still WebGL + cursor tracking, but
* capped at 16k particles with halved drift and halved push
* velocity. The ring still follows the cursor at full fidelity
* because that's the interaction the user explicitly wants; we
* only damp the *ambient* motion so the field reads as calm.
* 4. Lazy-load Three.js via next/dynamic so the hero LCP text isn't
* blocked by ~150kb of WebGL plumbing.
*
* The static fallback is a CSS-only radial gradient + faint dot mask.
* It looks intentional, not broken same color story as the live
* particle field, just without motion.
*/
import dynamic from 'next/dynamic';
import { useEffect, useState } from 'react';
type Capability =
| { kind: 'unknown' }
| { kind: 'fallback' }
| { kind: 'webgl'; textureSize: 128 | 256; motionScale: number };
// Dynamic import keeps three out of the initial bundle. ssr:false
// because there's no DOM/Canvas during SSR anyway.
const ParticleField = dynamic(
() => import('./ParticleField').then((m) => ({ default: m.ParticleField })),
{
ssr: false,
// No loading UI — the static gradient already lives at z-0 above
// until this resolves, and the canvas paints into the same slot.
loading: () => null,
},
);
/**
* Detect WebGL2 + float-render-target support without keeping the
* context around. We create a throwaway canvas, ask for `webgl2`, and
* probe `EXT_color_buffer_float`. If anything fails we fall back.
*
* Runs sync-only in the browser; never during SSR.
*/
function detectWebGL2(): boolean {
try {
const c = document.createElement('canvas');
const gl = c.getContext('webgl2');
if (!gl) return false;
const ext = gl.getExtension('EXT_color_buffer_float');
// Losing context to ensure we don't leak the probe.
const loseExt = gl.getExtension('WEBGL_lose_context');
loseExt?.loseContext();
return Boolean(ext);
} catch {
return false;
}
}
export function ParticleHero() {
// Start in 'unknown' so SSR markup matches the first client render —
// the fallback gradient is rendered until we resolve capability, so
// there's no flash either way.
const [cap, setCap] = useState<Capability>({ kind: 'unknown' });
useEffect(() => {
// 1. WebGL2 + float targets — hard gate. Without these the sim
// can't run at all, fall through to the static gradient.
if (!detectWebGL2()) {
setCap({ kind: 'fallback' });
return;
}
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)');
/**
* Pick the right particle tier for the device.
*
* Returns a non-fallback WebGL config OR null when the device is
* too constrained to render the field at all (low-core phones).
* Reduced-motion does NOT shrink the tier here it's applied as
* a separate motion scalar on top, because the user still wants
* the cursor-tracking interaction.
*/
const pickTier = (): Capability => {
// Heuristic: small viewport OR an absurd DPR (low-DPI phones lying
// about retina) with no high-end signal. hardwareConcurrency is a
// rough but free proxy; logical cores <= 4 on a small viewport is
// a strong hint we shouldn't push 65k particles.
const isNarrow = window.matchMedia('(max-width: 768px)').matches;
const dpr = window.devicePixelRatio || 1;
const cores = navigator.hardwareConcurrency ?? 4;
const reduced = reduce.matches;
// Motion-reduce caps drift + ring-push velocity at 50% but keeps
// pointer position fidelity at 100%. 1.0 means "default motion".
const motionScale = reduced ? 0.5 : 1.0;
if (isNarrow) {
// Phones: drop to 16k. Going lower than that and the field
// visibly thins out; going higher and we cook batteries.
// 4-core phones get the static fallback — those are the
// budget Androids most likely to thermal-throttle.
if (cores <= 4) {
return { kind: 'fallback' };
}
return { kind: 'webgl', textureSize: 128, motionScale };
}
if (dpr > 2.5 && cores <= 4) {
// High-DPI low-core — likely a low-end tablet.
return { kind: 'webgl', textureSize: 128, motionScale };
}
// Desktop / capable tablet. Reduced-motion users get the same 128
// tier as mobile — fewer particles means less ambient activity in
// peripheral vision, which is what the motion preference is for.
if (reduced) {
return { kind: 'webgl', textureSize: 128, motionScale };
}
return { kind: 'webgl', textureSize: 256, motionScale };
};
setCap(pickTier());
// Respond to motion-preference changes mid-session — re-pick the
// tier so toggling the OS setting takes effect without a reload.
const onReduceChange = () => setCap(pickTier());
reduce.addEventListener('change', onReduceChange);
return () => reduce.removeEventListener('change', onReduceChange);
}, []);
feat(video): v10 hero video with mute toggle — voice + bg music Ships the long-form (71.5 s) hero video to the marketing /flow section along with the iteration trail of architectural visual fixes the owner worked through over the last sprint. ## Video composition (remotion/) Eight phases driven by the 71.47 s voice-over in `audio.mp3` plus the `Sub-bass Lullaby.wav` background music (ducked to 0.16 with fade in / fade out). Every scene was rebuilt for v10 with concrete fixes: - **HookScene** (12 s) — adds FloatingChaos overlay: a docker-compose excerpt, an oauth_callback.ts snippet, an .env file with a yellow squiggle warning ("in git history since v0.3.1"), and a live-ticking 502 retry toast. Tangle now reads as a developer's desktop right before they give up, not as four icons drifting. - **PromptScene** (12.2 s) — 6.5 s post-typing dead-zone replaced with the parse beat: three sequential highlights on the prompt text (MCP server / searches / Notion workspace), three chips below the input (intent / tool / secret → vault), three-stat summary panel (tools · 2, secrets · 1, targets · 3). At local frame 250 (≈ 21 s global, on the voice line "the prompt path and the secret path never cross") a mini two-rail diagram with an explicit X-marker ring lands, visualising the architectural promise the moment it's spoken. - **SecretsScene** (15.2 s) — kept the arrow-fork + AES-256 stamp + env-var injection beats; added the lock-snap flash at frame 66, pinned the vault at full opacity throughout, and added a dashed vault → container connector so the secret's provenance is visible. The "what the AI sees" panel is now 680 px wide with an eye icon, four corner viewfinder brackets around the prompt text, and three explicit denied lines (no secrets / no environment variables / no tokens). - **BuildScene** (7.2 s) — unchanged beats: streaming log, server card emerges with code + 🔒 NOTION_API_KEY slot pills, isolated- container caption, <60s countdown. - **IsolationScene** (14 s) — completely restructured. Orbit-and-dock chips that collided with the card and with the tokens-only badge are replaced by a clean vertical chip column at x=760: read-only filesystem · dropped capabilities · no new privileges · 512 MB memory cap · 0.5 CPU limit · ✓ your token only (last in green). A vault graphic now sits below the server card with a dashed arrow up into its env slot so the architecture story is complete in one frame. PKCE jargon removed: "OAuth 2.1 · PKCE" → "only your token gets in" with a small "oauth 2.1 · proof-key flow" subtitle for the curious. Handshake stages simplified to your client → verified → scoped token. Final settlement arrow in success-green curves from the scoped-token pill back into the card. - **LibraryScene** (7 s) — cards enlarged from 340×180 to 400×220 with 36 px gaps. The "templates carry code, not credentials" sub-caption was pulled (felt on-the-nose; the detached lock and empty NOTION_API_KEY=? slot carry the story visually). - **DiscoveryScene** (3 s) — the most-iterated scene. Earlier versions had a fake "1,200+ developers building" fork counter (pulled — solo-founder, hadn't earned). Replaced with a two-lane architecture diagram that visualises "no paths cross" literally: top lane prompt → AI → code, bottom lane vault → encrypted → env, both converging at the server box on the right. v10 refinements: all seven boxes visible from frame 0 (no late server arrival), a parallel glow tour walks across both lanes simultaneously, a dashed vertical divider with a "no shared node" chip pinned in the middle, and the closing line "One sentence in. Live server out." slides down from above and lands centred while the diagram fades to 0.12 opacity behind it — no overlap. - **LogoLockup** (1.7 s) — wordmark + fade-to-black for a clean loop seam. The Subtitle / CAPTIONS layer added in v7 was pulled wholesale — owner found the kinetic-typography overlay aggressive and noted that technical terms (PKCE etc.) created friction with no payoff. Scene visuals and voice now carry the whole story; the Subtitle component file is retained for possible future use. Render pipeline (`render:mp4` / `render:webm` / `render:poster` in remotion/package.json) is unchanged. The MP4 is post-processed to H.264 Main / yuv420p / TV-range with faststart + AAC audio. The WebM is re-encoded at VP9 CRF 38 / Opus 64k to stay under the 3 MB budget. Final artefacts in apps/web/public/videos/: 2.59 MB mp4, 2.99 MB webm, 62 KB poster. ## Web integration (apps/web/components/hero-video.tsx) New client component wraps the <video> element and pins a frosted- glass mute toggle bottom-right of the player. Why not native `controls`: the browser chrome fights the section's design vocabulary and we only need one affordance — unmute — so we render exactly that. The toggle's icon flips between VolumeX (currently muted) and Volume2 (currently unmuted), accent colour switches indigo when sound is on. Initial state is muted so autoplay still fires; on unmute we call .play() defensively because mobile Safari pauses on muted-property changes mid-playback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:31:10 +02:00
// Static fallback: radial indigo glow + faint dotted mask.
// Used both for 'unknown' (pre-hydration) and 'fallback'.
feat(web): hero redesign — cycling step rotator + full-width video section Restructures the landing page above-the-fold into two distinct sections: 1. **Hero — left copy + cycling tile, no static stack of three blocks** New `<HeroStepRotator>` (Framer Motion client component) shows ONE tile centred in the column, cycling prompt.txt → build.log → claude_desktop_config.json every 3.5s. Auto-advance pauses on hover and exposes a 3-dot tablist so users can jump to any step. The active dot grows wide with an accent glow. Mouse interaction: spring-smoothed 3D tilt on rotateX/rotateY plus a radial glow that translates toward the cursor — both driven by motion values, so the transforms stay on the GPU compositor instead of re-rendering on every mousemove. `useReducedMotion()` strips the tilt + glow translation and collapses the page transition to an instant cross-fade (the rotation itself still advances — it's content, not decoration). Hero padding tightened (py-12/14/16 vs py-14/20/28) so the video section below is teased above the fold. New scroll cue ("see it run" + animated chevron) sits at the bottom of the hero, anchored to #flow. 2. **Flow video — full-width edge-to-edge under the hero (new section)** The hero.mp4 / hero.webm pair moves out of the "How it works" section into its own #flow section. No max-w wrapper — it spans the viewport with `w-full aspect-video`, so on a 1080p monitor the video gets the full 1920px width. Adds a subtle radial vignette so the black edges blend into the page chrome. 3. **"How it works" — now lean** Video removed (it's the flow section now). Just the three textual cards as supporting copy. Adds `framer-motion@11.18.2` to apps/web/package.json. Build passes typecheck + Next.js production build with no new warnings; LCP path is untouched since the rotator is client-hydrated after first paint and Framer Motion is tree-shaken to the components we import. Note: visitors with `prefers-reduced-motion: reduce` will still see the video's poster instead of autoplay — Chrome blocks the network fetch entirely for autoplay media when reduced-motion is set. The flow video remains visible for the rest, and the step rotator continues to cycle its content (with instant cross-fade instead of slide+scale). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:05:28 +02:00
if (cap.kind !== 'webgl') {
return (
<div
aria-hidden="true"
className="absolute inset-0 size-full overflow-hidden"
style={{
feat(video): v10 hero video with mute toggle — voice + bg music Ships the long-form (71.5 s) hero video to the marketing /flow section along with the iteration trail of architectural visual fixes the owner worked through over the last sprint. ## Video composition (remotion/) Eight phases driven by the 71.47 s voice-over in `audio.mp3` plus the `Sub-bass Lullaby.wav` background music (ducked to 0.16 with fade in / fade out). Every scene was rebuilt for v10 with concrete fixes: - **HookScene** (12 s) — adds FloatingChaos overlay: a docker-compose excerpt, an oauth_callback.ts snippet, an .env file with a yellow squiggle warning ("in git history since v0.3.1"), and a live-ticking 502 retry toast. Tangle now reads as a developer's desktop right before they give up, not as four icons drifting. - **PromptScene** (12.2 s) — 6.5 s post-typing dead-zone replaced with the parse beat: three sequential highlights on the prompt text (MCP server / searches / Notion workspace), three chips below the input (intent / tool / secret → vault), three-stat summary panel (tools · 2, secrets · 1, targets · 3). At local frame 250 (≈ 21 s global, on the voice line "the prompt path and the secret path never cross") a mini two-rail diagram with an explicit X-marker ring lands, visualising the architectural promise the moment it's spoken. - **SecretsScene** (15.2 s) — kept the arrow-fork + AES-256 stamp + env-var injection beats; added the lock-snap flash at frame 66, pinned the vault at full opacity throughout, and added a dashed vault → container connector so the secret's provenance is visible. The "what the AI sees" panel is now 680 px wide with an eye icon, four corner viewfinder brackets around the prompt text, and three explicit denied lines (no secrets / no environment variables / no tokens). - **BuildScene** (7.2 s) — unchanged beats: streaming log, server card emerges with code + 🔒 NOTION_API_KEY slot pills, isolated- container caption, <60s countdown. - **IsolationScene** (14 s) — completely restructured. Orbit-and-dock chips that collided with the card and with the tokens-only badge are replaced by a clean vertical chip column at x=760: read-only filesystem · dropped capabilities · no new privileges · 512 MB memory cap · 0.5 CPU limit · ✓ your token only (last in green). A vault graphic now sits below the server card with a dashed arrow up into its env slot so the architecture story is complete in one frame. PKCE jargon removed: "OAuth 2.1 · PKCE" → "only your token gets in" with a small "oauth 2.1 · proof-key flow" subtitle for the curious. Handshake stages simplified to your client → verified → scoped token. Final settlement arrow in success-green curves from the scoped-token pill back into the card. - **LibraryScene** (7 s) — cards enlarged from 340×180 to 400×220 with 36 px gaps. The "templates carry code, not credentials" sub-caption was pulled (felt on-the-nose; the detached lock and empty NOTION_API_KEY=? slot carry the story visually). - **DiscoveryScene** (3 s) — the most-iterated scene. Earlier versions had a fake "1,200+ developers building" fork counter (pulled — solo-founder, hadn't earned). Replaced with a two-lane architecture diagram that visualises "no paths cross" literally: top lane prompt → AI → code, bottom lane vault → encrypted → env, both converging at the server box on the right. v10 refinements: all seven boxes visible from frame 0 (no late server arrival), a parallel glow tour walks across both lanes simultaneously, a dashed vertical divider with a "no shared node" chip pinned in the middle, and the closing line "One sentence in. Live server out." slides down from above and lands centred while the diagram fades to 0.12 opacity behind it — no overlap. - **LogoLockup** (1.7 s) — wordmark + fade-to-black for a clean loop seam. The Subtitle / CAPTIONS layer added in v7 was pulled wholesale — owner found the kinetic-typography overlay aggressive and noted that technical terms (PKCE etc.) created friction with no payoff. Scene visuals and voice now carry the whole story; the Subtitle component file is retained for possible future use. Render pipeline (`render:mp4` / `render:webm` / `render:poster` in remotion/package.json) is unchanged. The MP4 is post-processed to H.264 Main / yuv420p / TV-range with faststart + AAC audio. The WebM is re-encoded at VP9 CRF 38 / Opus 64k to stay under the 3 MB budget. Final artefacts in apps/web/public/videos/: 2.59 MB mp4, 2.99 MB webm, 62 KB poster. ## Web integration (apps/web/components/hero-video.tsx) New client component wraps the <video> element and pins a frosted- glass mute toggle bottom-right of the player. Why not native `controls`: the browser chrome fights the section's design vocabulary and we only need one affordance — unmute — so we render exactly that. The toggle's icon flips between VolumeX (currently muted) and Volume2 (currently unmuted), accent colour switches indigo when sound is on. Initial state is muted so autoplay still fires; on unmute we call .play() defensively because mobile Safari pauses on muted-property changes mid-playback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:31:10 +02:00
backgroundImage: [
// Soft indigo glow centered on the hero
'radial-gradient(60% 80% at 50% 45%, rgba(99,102,241,0.18), rgba(99,102,241,0) 70%)',
// Very faint dotted texture — reads as "field of particles
// at rest" rather than a flat gradient.
'radial-gradient(circle at 1px 1px, rgba(255,255,255,0.05) 1px, transparent 1.5px)',
].join(', '),
backgroundSize: '100% 100%, 24px 24px',
feat(web): hero redesign — cycling step rotator + full-width video section Restructures the landing page above-the-fold into two distinct sections: 1. **Hero — left copy + cycling tile, no static stack of three blocks** New `<HeroStepRotator>` (Framer Motion client component) shows ONE tile centred in the column, cycling prompt.txt → build.log → claude_desktop_config.json every 3.5s. Auto-advance pauses on hover and exposes a 3-dot tablist so users can jump to any step. The active dot grows wide with an accent glow. Mouse interaction: spring-smoothed 3D tilt on rotateX/rotateY plus a radial glow that translates toward the cursor — both driven by motion values, so the transforms stay on the GPU compositor instead of re-rendering on every mousemove. `useReducedMotion()` strips the tilt + glow translation and collapses the page transition to an instant cross-fade (the rotation itself still advances — it's content, not decoration). Hero padding tightened (py-12/14/16 vs py-14/20/28) so the video section below is teased above the fold. New scroll cue ("see it run" + animated chevron) sits at the bottom of the hero, anchored to #flow. 2. **Flow video — full-width edge-to-edge under the hero (new section)** The hero.mp4 / hero.webm pair moves out of the "How it works" section into its own #flow section. No max-w wrapper — it spans the viewport with `w-full aspect-video`, so on a 1080p monitor the video gets the full 1920px width. Adds a subtle radial vignette so the black edges blend into the page chrome. 3. **"How it works" — now lean** Video removed (it's the flow section now). Just the three textual cards as supporting copy. Adds `framer-motion@11.18.2` to apps/web/package.json. Build passes typecheck + Next.js production build with no new warnings; LCP path is untouched since the rotator is client-hydrated after first paint and Framer Motion is tree-shaken to the components we import. Note: visitors with `prefers-reduced-motion: reduce` will still see the video's poster instead of autoplay — Chrome blocks the network fetch entirely for autoplay media when reduced-motion is set. The flow video remains visible for the rest, and the step rotator continues to cycle its content (with instant cross-fade instead of slide+scale). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:05:28 +02:00
}}
/>
);
}
feat(video): v10 hero video with mute toggle — voice + bg music Ships the long-form (71.5 s) hero video to the marketing /flow section along with the iteration trail of architectural visual fixes the owner worked through over the last sprint. ## Video composition (remotion/) Eight phases driven by the 71.47 s voice-over in `audio.mp3` plus the `Sub-bass Lullaby.wav` background music (ducked to 0.16 with fade in / fade out). Every scene was rebuilt for v10 with concrete fixes: - **HookScene** (12 s) — adds FloatingChaos overlay: a docker-compose excerpt, an oauth_callback.ts snippet, an .env file with a yellow squiggle warning ("in git history since v0.3.1"), and a live-ticking 502 retry toast. Tangle now reads as a developer's desktop right before they give up, not as four icons drifting. - **PromptScene** (12.2 s) — 6.5 s post-typing dead-zone replaced with the parse beat: three sequential highlights on the prompt text (MCP server / searches / Notion workspace), three chips below the input (intent / tool / secret → vault), three-stat summary panel (tools · 2, secrets · 1, targets · 3). At local frame 250 (≈ 21 s global, on the voice line "the prompt path and the secret path never cross") a mini two-rail diagram with an explicit X-marker ring lands, visualising the architectural promise the moment it's spoken. - **SecretsScene** (15.2 s) — kept the arrow-fork + AES-256 stamp + env-var injection beats; added the lock-snap flash at frame 66, pinned the vault at full opacity throughout, and added a dashed vault → container connector so the secret's provenance is visible. The "what the AI sees" panel is now 680 px wide with an eye icon, four corner viewfinder brackets around the prompt text, and three explicit denied lines (no secrets / no environment variables / no tokens). - **BuildScene** (7.2 s) — unchanged beats: streaming log, server card emerges with code + 🔒 NOTION_API_KEY slot pills, isolated- container caption, <60s countdown. - **IsolationScene** (14 s) — completely restructured. Orbit-and-dock chips that collided with the card and with the tokens-only badge are replaced by a clean vertical chip column at x=760: read-only filesystem · dropped capabilities · no new privileges · 512 MB memory cap · 0.5 CPU limit · ✓ your token only (last in green). A vault graphic now sits below the server card with a dashed arrow up into its env slot so the architecture story is complete in one frame. PKCE jargon removed: "OAuth 2.1 · PKCE" → "only your token gets in" with a small "oauth 2.1 · proof-key flow" subtitle for the curious. Handshake stages simplified to your client → verified → scoped token. Final settlement arrow in success-green curves from the scoped-token pill back into the card. - **LibraryScene** (7 s) — cards enlarged from 340×180 to 400×220 with 36 px gaps. The "templates carry code, not credentials" sub-caption was pulled (felt on-the-nose; the detached lock and empty NOTION_API_KEY=? slot carry the story visually). - **DiscoveryScene** (3 s) — the most-iterated scene. Earlier versions had a fake "1,200+ developers building" fork counter (pulled — solo-founder, hadn't earned). Replaced with a two-lane architecture diagram that visualises "no paths cross" literally: top lane prompt → AI → code, bottom lane vault → encrypted → env, both converging at the server box on the right. v10 refinements: all seven boxes visible from frame 0 (no late server arrival), a parallel glow tour walks across both lanes simultaneously, a dashed vertical divider with a "no shared node" chip pinned in the middle, and the closing line "One sentence in. Live server out." slides down from above and lands centred while the diagram fades to 0.12 opacity behind it — no overlap. - **LogoLockup** (1.7 s) — wordmark + fade-to-black for a clean loop seam. The Subtitle / CAPTIONS layer added in v7 was pulled wholesale — owner found the kinetic-typography overlay aggressive and noted that technical terms (PKCE etc.) created friction with no payoff. Scene visuals and voice now carry the whole story; the Subtitle component file is retained for possible future use. Render pipeline (`render:mp4` / `render:webm` / `render:poster` in remotion/package.json) is unchanged. The MP4 is post-processed to H.264 Main / yuv420p / TV-range with faststart + AAC audio. The WebM is re-encoded at VP9 CRF 38 / Opus 64k to stay under the 3 MB budget. Final artefacts in apps/web/public/videos/: 2.59 MB mp4, 2.99 MB webm, 62 KB poster. ## Web integration (apps/web/components/hero-video.tsx) New client component wraps the <video> element and pins a frosted- glass mute toggle bottom-right of the player. Why not native `controls`: the browser chrome fights the section's design vocabulary and we only need one affordance — unmute — so we render exactly that. The toggle's icon flips between VolumeX (currently muted) and Volume2 (currently unmuted), accent colour switches indigo when sound is on. Initial state is muted so autoplay still fires; on unmute we call .play() defensively because mobile Safari pauses on muted-property changes mid-playback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 02:31:10 +02:00
return <ParticleField textureSize={cap.textureSize} motionScale={cap.motionScale} />;
feat(web): hero redesign — cycling step rotator + full-width video section Restructures the landing page above-the-fold into two distinct sections: 1. **Hero — left copy + cycling tile, no static stack of three blocks** New `<HeroStepRotator>` (Framer Motion client component) shows ONE tile centred in the column, cycling prompt.txt → build.log → claude_desktop_config.json every 3.5s. Auto-advance pauses on hover and exposes a 3-dot tablist so users can jump to any step. The active dot grows wide with an accent glow. Mouse interaction: spring-smoothed 3D tilt on rotateX/rotateY plus a radial glow that translates toward the cursor — both driven by motion values, so the transforms stay on the GPU compositor instead of re-rendering on every mousemove. `useReducedMotion()` strips the tilt + glow translation and collapses the page transition to an instant cross-fade (the rotation itself still advances — it's content, not decoration). Hero padding tightened (py-12/14/16 vs py-14/20/28) so the video section below is teased above the fold. New scroll cue ("see it run" + animated chevron) sits at the bottom of the hero, anchored to #flow. 2. **Flow video — full-width edge-to-edge under the hero (new section)** The hero.mp4 / hero.webm pair moves out of the "How it works" section into its own #flow section. No max-w wrapper — it spans the viewport with `w-full aspect-video`, so on a 1080p monitor the video gets the full 1920px width. Adds a subtle radial vignette so the black edges blend into the page chrome. 3. **"How it works" — now lean** Video removed (it's the flow section now). Just the three textual cards as supporting copy. Adds `framer-motion@11.18.2` to apps/web/package.json. Build passes typecheck + Next.js production build with no new warnings; LCP path is untouched since the rotator is client-hydrated after first paint and Framer Motion is tree-shaken to the components we import. Note: visitors with `prefers-reduced-motion: reduce` will still see the video's poster instead of autoplay — Chrome blocks the network fetch entirely for autoplay media when reduced-motion is set. The flow video remains visible for the rest, and the step rotator continues to cycle its content (with instant cross-fade instead of slide+scale). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 12:05:28 +02:00
}
export default ParticleHero;