mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
1289 lines
60 KiB
TypeScript
1289 lines
60 KiB
TypeScript
'use client'
|
||
|
||
import { useState, useEffect, type ReactNode } from 'react'
|
||
import Link from 'next/link'
|
||
import { ChevronRight, Pause, Play, Radio } from 'lucide-react'
|
||
import { Box, Text, View, XStack, YStack } from '@/components/ui'
|
||
import { Grid } from '@hanzo/ui/grid'
|
||
import type { CreativeModel, Tone } from '@/lib/creative-models'
|
||
|
||
interface CreativeSurfaceProps {
|
||
model: CreativeModel
|
||
}
|
||
|
||
/** A design-system colour mixed toward another, transparent by default. Every
|
||
* shade on the stage is one of the system's colours at a strength, never a literal. */
|
||
const mix = (color: string, pct: number, toward = 'transparent') => `color-mix(in oklab, ${color} ${pct}%, ${toward})` as Tone
|
||
const lit = (pct: number) => mix('var(--pure-white)', pct)
|
||
const shade = (pct: number) => mix('var(--pure-black)', pct)
|
||
/** The studio's stage: black, a breath off it, in both themes. */
|
||
const STAGE = mix('var(--pure-black)', 97, 'var(--pure-white)')
|
||
const REC: Tone = 'var(--state-error)'
|
||
const INK: Tone = 'var(--pure-black)'
|
||
const VIGNETTE = `linear-gradient(180deg, ${shade(70)} 0%, ${shade(10)} 40%, ${shade(30)} 60%, ${shade(85)} 100%)`
|
||
|
||
/** One hue per stem, the same in every style: the art palette and the success green. */
|
||
const STEM_TONE: Record<string, Tone> = {
|
||
drums: 'var(--art-ember)',
|
||
bass: 'var(--art-iris)',
|
||
harmony: 'var(--art-tide)',
|
||
vocals: 'var(--state-success)',
|
||
}
|
||
|
||
const MUSIC_STYLES = [
|
||
{
|
||
id: 'symphonic',
|
||
name: 'Symphonic Orchestral',
|
||
badge: 'CINEMA SCORE',
|
||
bpm: '96 BPM',
|
||
key: 'D MINOR',
|
||
meter: '4/4',
|
||
cue: 'Timpani crescendo into full orchestral tutti with soaring legato violins and brass choir.',
|
||
trailerDesc: 'Live Philharmonic Hall Recording · Abbey Road Master',
|
||
stems: [
|
||
{ id: 'drums', name: 'Timpani & Concert Percussion', activity: 88 },
|
||
{ id: 'bass', name: 'Double Bass & Tuba Sub 40Hz', activity: 76 },
|
||
{ id: 'harmony', name: 'French Horns & Symphonic Strings', activity: 94 },
|
||
{ id: 'vocals', name: 'Chamber Choir & Solo Cello', activity: 82 },
|
||
],
|
||
},
|
||
{
|
||
id: 'synthwave',
|
||
name: 'Cyberpunk Synthwave',
|
||
badge: 'HYPER-SYNTH',
|
||
bpm: '128 BPM',
|
||
key: 'F# MINOR',
|
||
meter: '4/4',
|
||
cue: 'Sidechained analog saw bass with gated 80s reverb snare and cascading arpeggiators.',
|
||
trailerDesc: 'Neo-Tokyo Midnight Sequence · Anamorphic Cyberpunk Score',
|
||
stems: [
|
||
{ id: 'drums', name: 'Analog Drum Machine & Claps', activity: 95 },
|
||
{ id: 'bass', name: 'Moog Acid Bassline & Sub', activity: 88 },
|
||
{ id: 'harmony', name: 'Juno-106 Chords & Arps', activity: 92 },
|
||
{ id: 'vocals', name: 'Vocoder Lead & Cyber Stems', activity: 70 },
|
||
],
|
||
},
|
||
{
|
||
id: 'trailer',
|
||
name: 'Film Trailer Braam',
|
||
badge: 'EPIC HYBRID',
|
||
bpm: '75 BPM',
|
||
key: 'C MINOR',
|
||
meter: '3/4',
|
||
cue: 'Low-frequency seismic braam hit into descending string ostinato and ticking trailer clock.',
|
||
trailerDesc: 'Theatrical Feature Teaser · 7.1.4 Dolby Atmos Print Master',
|
||
stems: [
|
||
{ id: 'drums', name: 'Trailer Taiko & Anvil Slams', activity: 92 },
|
||
{ id: 'bass', name: 'Sub Seismic Downshifter', activity: 84 },
|
||
{ id: 'harmony', name: 'Aggressive Spiccato Violins', activity: 90 },
|
||
{ id: 'vocals', name: 'Epic Hybrid Sound Design', activity: 75 },
|
||
],
|
||
},
|
||
{
|
||
id: 'lofi',
|
||
name: 'Lo-Fi Ambient Chill',
|
||
badge: 'WARM TAPE',
|
||
bpm: '82 BPM',
|
||
key: 'Ab MAJOR',
|
||
meter: '4/4',
|
||
cue: 'Dusty vinyl flutter with warm Rhodes electric piano and soft unquantized boom-bap.',
|
||
trailerDesc: 'Vintage Reel-to-Reel Tape Master · 1/2 Inch Studer A800',
|
||
stems: [
|
||
{ id: 'drums', name: 'Brushed Snare & Tape Kick', activity: 65 },
|
||
{ id: 'bass', name: 'Warm Upright Acoustic Bass', activity: 70 },
|
||
{ id: 'harmony', name: 'Fender Rhodes & Vinyl Crackle', activity: 80 },
|
||
{ id: 'vocals', name: 'Muted Trumpet & Vocal Chop', activity: 58 },
|
||
],
|
||
},
|
||
]
|
||
|
||
const FOLEY_CUES = [
|
||
{
|
||
id: 'drumming',
|
||
name: 'Acoustic Drumming & Floor Impacts',
|
||
icon: '🥁',
|
||
timecode: '00:01.12',
|
||
category: 'Transient Percussion',
|
||
material: 'Birch Mallets on Resonant Mahogany Stage Pit',
|
||
spl: '98.4 dBA',
|
||
faders: { prop: 92, pit: 78, sub: 88, ribbon: 60 },
|
||
bands: [85, 95, 75, 50, 40, 30, 25, 20, 18, 15, 12, 10, 8, 8, 6, 5],
|
||
description: 'Resonant acoustic drum strike with low-end sub bloom and crisp wooden rim decay.',
|
||
},
|
||
{
|
||
id: 'footsteps',
|
||
name: 'Gravel Footsteps (120 BPM)',
|
||
icon: '👟',
|
||
timecode: '00:02.40',
|
||
category: 'Kinematics',
|
||
material: 'Heavy Leather Boots on Crushed Granite Pit',
|
||
spl: '74.2 dBA',
|
||
faders: { prop: 80, pit: 85, sub: 45, ribbon: 72 },
|
||
bands: [30, 45, 60, 80, 92, 85, 70, 55, 45, 35, 25, 20, 15, 12, 10, 8],
|
||
description: 'Dynamic weight shifting with granular stone displacement and heel-to-toe compression.',
|
||
},
|
||
{
|
||
id: 'vault',
|
||
name: 'Heavy Vault Door Latch',
|
||
icon: '🔐',
|
||
timecode: '00:03.85',
|
||
category: 'Mechanical Metallic',
|
||
material: 'Case-Hardened Steel Cams & Hydraulic Locks',
|
||
spl: '92.1 dBA',
|
||
faders: { prop: 95, pit: 68, sub: 90, ribbon: 82 },
|
||
bands: [60, 85, 90, 70, 60, 75, 90, 95, 80, 65, 50, 40, 35, 25, 20, 15],
|
||
description: 'Deep resonant vault tumblers interlocking, followed by solid hydraulic latch lock.',
|
||
},
|
||
{
|
||
id: 'rain',
|
||
name: 'Rain on Stained Glass',
|
||
icon: '🌧️',
|
||
timecode: '00:05.10',
|
||
category: 'Environmental Hydro',
|
||
material: 'Variable Droplets on 8mm Annealed Glass Plate',
|
||
spl: '62.8 dBA',
|
||
faders: { prop: 70, pit: 90, sub: 25, ribbon: 88 },
|
||
bands: [20, 25, 30, 40, 55, 68, 75, 82, 88, 92, 95, 88, 80, 72, 60, 45],
|
||
description: 'High-density droplet spatters with distinct velocity micro-transients and surface roll.',
|
||
},
|
||
]
|
||
|
||
/**
|
||
* What gui props cannot say, keyed by data attributes on the elements that need
|
||
* it: the keyframes, a media element filling its frame through its own filter, an
|
||
* SVG overlay's place in the stack (named off --z-base, under the HUD's z={4}),
|
||
* and the easing the controls and meters move with.
|
||
*/
|
||
const STUDIO_CSS = `
|
||
@keyframes recBlink {
|
||
0%, 100% { opacity: 1; transform: scale(1); }
|
||
50% { opacity: 0.2; transform: scale(0.85); }
|
||
}
|
||
@keyframes laserScan {
|
||
0% { transform: translateY(0); opacity: 0.8; }
|
||
50% { opacity: 1; }
|
||
100% { transform: translateY(420px); opacity: 0.8; }
|
||
}
|
||
@keyframes crosshairFloat {
|
||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||
50% { transform: translate(-6px, 4px) scale(1.05); }
|
||
}
|
||
@keyframes trajectoryDash {
|
||
to { stroke-dashoffset: -40; }
|
||
}
|
||
@keyframes iroScan {
|
||
0% { transform: translateX(0); opacity: 0.7; }
|
||
50% { opacity: 1; }
|
||
100% { transform: translateX(800px); opacity: 0.7; }
|
||
}
|
||
[data-studio] [data-fill] { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
|
||
[data-studio] [data-look='koma'] { filter: brightness(0.9) contrast(1.08); }
|
||
[data-studio] [data-look='iro'] { filter: brightness(0.92) contrast(1.05); }
|
||
[data-studio] [data-look='mori'] { filter: brightness(0.35) blur(2px); }
|
||
[data-studio] [data-look='koe'] { opacity: 0.22; filter: contrast(1.2) brightness(0.9); }
|
||
[data-studio] [data-look='oto'] { filter: brightness(0.88) contrast(1.05); transition: filter 0.15s ease; }
|
||
[data-studio] [data-look='oto'][data-lit] { filter: brightness(1.05) contrast(1.15); }
|
||
[data-studio] [data-look='uta'] { filter: brightness(0.85) contrast(1.05); transition: filter 0.2s ease; }
|
||
[data-studio] [data-look='uta'][data-lit] { filter: brightness(1) contrast(1.15); }
|
||
[data-studio] svg[data-overlay] { position: absolute; top: 0; left: 0; z-index: calc(var(--z-base) + 3); pointer-events: none; }
|
||
[data-studio] svg[data-overlay='turntable'] { z-index: calc(var(--z-base) + 2); pointer-events: auto; }
|
||
[data-studio] [data-beam] { z-index: calc(var(--z-base) + 2); }
|
||
[data-studio] [data-beam='koma'] { animation: laserScan 3.5s ease-in-out infinite; }
|
||
[data-studio] [data-beam='iro'] { animation: iroScan 3.5s ease-in-out infinite; }
|
||
[data-studio] [data-blink] { animation: recBlink 1s ease-in-out infinite; }
|
||
[data-studio] [data-dash] { animation: trajectoryDash 1.5s linear infinite; }
|
||
[data-studio] [data-dash='slow'] { animation-duration: 1.8s; }
|
||
[data-studio] [data-float] { animation: crosshairFloat 4s ease-in-out infinite; transform-origin: 480px 270px; }
|
||
[data-studio] [data-ease] { transition: all 0.15s ease; }
|
||
[data-studio] [data-meter] { transition: width 0.12s ease, height 0.12s ease; }
|
||
[data-studio] [data-trace] { transition: d 0.1s ease; }
|
||
[data-studio] [data-play] > svg { margin-left: 2px; }
|
||
[data-studio] a { text-decoration: none; }
|
||
`
|
||
|
||
/** A frame on the stage: a monitor, an artboard, a viewport. */
|
||
function Frame({ children, ...rest }: { children: ReactNode } & Record<string, unknown>) {
|
||
return (
|
||
<Box position="relative" width="100%" bg={STAGE} rounded={10} borderWidth={1} borderColor={lit(10)} overflow="hidden" {...rest}>
|
||
{children}
|
||
</Box>
|
||
)
|
||
}
|
||
|
||
/** A toggle on the stage: filled with the model's tone when on. */
|
||
function Chip({
|
||
on,
|
||
tone,
|
||
onPress,
|
||
children,
|
||
ground = shade(65),
|
||
px = 8,
|
||
py = 2,
|
||
rounded = 4,
|
||
weight,
|
||
upper,
|
||
}: {
|
||
on: boolean
|
||
tone: Tone
|
||
onPress: () => void
|
||
children: ReactNode
|
||
ground?: Tone
|
||
px?: number
|
||
py?: number
|
||
rounded?: number
|
||
weight?: '600'
|
||
upper?: boolean
|
||
}) {
|
||
return (
|
||
<View
|
||
render={<button type="button" onClick={onPress} />}
|
||
data-ease=""
|
||
px={px}
|
||
py={py}
|
||
rounded={rounded}
|
||
borderWidth={1}
|
||
borderColor={on ? tone : 'var(--border)'}
|
||
bg={on ? tone : ground}
|
||
cursor="pointer"
|
||
>
|
||
<Text fontSize="$1" fontFamily="$mono" fontWeight={weight} color={on ? INK : 'var(--pure-white)'} textTransform={upper ? 'uppercase' : undefined}>
|
||
{children}
|
||
</Text>
|
||
</View>
|
||
)
|
||
}
|
||
|
||
/** One of the three facts under each viewport. */
|
||
function Spec({ label, value, note, tone }: { label: string; value: string; note: string; tone: Tone }) {
|
||
return (
|
||
<Box p={12} rounded={8} borderWidth={1} borderColor="var(--border)" bg={lit(3)}>
|
||
<Text fontSize="$1" color="var(--muted-foreground)" mb={4}>
|
||
{label}
|
||
</Text>
|
||
<XStack justify="space-between" items="center">
|
||
<Text fontSize="$2" fontWeight="600" color="var(--foreground)">
|
||
{value}
|
||
</Text>
|
||
<Text fontSize="$1" color={tone} fontWeight="600">
|
||
{note}
|
||
</Text>
|
||
</XStack>
|
||
</Box>
|
||
)
|
||
}
|
||
|
||
/** A round play/pause control filled with the model's tone. */
|
||
function Transport({ playing, tone, size, onPress, title }: { playing: boolean; tone: Tone; size: number; onPress: () => void; title: string }) {
|
||
const icon = Math.round(size / 2)
|
||
return (
|
||
<View
|
||
render={<button type="button" onClick={onPress} title={title} />}
|
||
data-play={playing ? undefined : ''}
|
||
width={size}
|
||
height={size}
|
||
rounded={9999}
|
||
items="center"
|
||
justify="center"
|
||
borderWidth={0}
|
||
bg={tone}
|
||
cursor="pointer"
|
||
>
|
||
{playing ? <Pause width={icon} height={icon} fill={INK} color={INK} /> : <Play width={icon} height={icon} fill={INK} color={INK} />}
|
||
</View>
|
||
)
|
||
}
|
||
|
||
/**
|
||
* High-fidelity, model-aligned interactive studio viewport.
|
||
* Rendered natively with interactive split views, audio transport, and DAW telemetry.
|
||
*/
|
||
export function CreativeSurface({ model }: CreativeSurfaceProps) {
|
||
const tone = model.accentColor
|
||
const [activeShot, setActiveShot] = useState(0)
|
||
const [activeAspect, setActiveAspect] = useState('16:9')
|
||
const [viewMode3D, setViewMode3D] = useState<'wireframe' | 'shaded' | 'normals'>('wireframe')
|
||
const [mutedStems, setMutedStems] = useState<Record<string, boolean>>({})
|
||
const [activeVoiceStyle, setActiveVoiceStyle] = useState(0)
|
||
const [activeMusicStyle, setActiveMusicStyle] = useState(0)
|
||
const [isPlayingMusic, setIsPlayingMusic] = useState(true)
|
||
const [activeFoleyCue, setActiveFoleyCue] = useState(0)
|
||
const [foleyTriggered, setFoleyTriggered] = useState(false)
|
||
const [simTimecode, setSimTimecode] = useState(4.3)
|
||
const [diffusionStep, setDiffusionStep] = useState(38)
|
||
const [isMoriSpinning, setIsMoriSpinning] = useState(true)
|
||
const [moriRotation, setMoriRotation] = useState(0)
|
||
const [isPlayingVoice, setIsPlayingVoice] = useState(true)
|
||
|
||
useEffect(() => {
|
||
const timer = setInterval(() => {
|
||
setSimTimecode((prev) => {
|
||
const next = prev + 0.1
|
||
return next > 12.0 ? 0.0 : Number(next.toFixed(2))
|
||
})
|
||
setDiffusionStep((prev) => (prev >= 50 ? 1 : prev + 1))
|
||
setMoriRotation((prev) => (prev + 3) % 360)
|
||
}, 120)
|
||
return () => clearInterval(timer)
|
||
}, [])
|
||
|
||
const toggleStem = (stem: string) => {
|
||
setMutedStems((prev) => ({ ...prev, [stem]: !prev[stem] }))
|
||
}
|
||
|
||
const triggerFoleyCue = (idx: number) => {
|
||
setActiveFoleyCue(idx)
|
||
setFoleyTriggered(true)
|
||
setTimeout(() => setFoleyTriggered(false), 600)
|
||
}
|
||
|
||
return (
|
||
<Box
|
||
data-studio=""
|
||
position="relative"
|
||
width="100%"
|
||
maxW={1040}
|
||
mx="auto"
|
||
mt="$8"
|
||
rounded={16}
|
||
borderWidth={1}
|
||
borderColor="var(--border)"
|
||
overflow="hidden"
|
||
bg={mix('var(--pure-black)', 85, 'var(--secondary)')}
|
||
boxShadow={`0 24px 64px -16px ${mix(tone, 15)}, 0 0 0 1px ${lit(6)}`}
|
||
>
|
||
<style jsx global>{STUDIO_CSS}</style>
|
||
|
||
{/* Top Console Bar */}
|
||
<XStack items="center" justify="space-between" px="$4" py="$3" borderBottomWidth={1} borderColor="var(--border)" bg={lit(3)}>
|
||
<XStack items="center" gap="$2.5">
|
||
<XStack gap="$1.5">
|
||
<View width={10} height={10} rounded={9999} bg="var(--chrome-dot-red)" />
|
||
<View width={10} height={10} rounded={9999} bg="var(--chrome-dot-yellow)" />
|
||
<View width={10} height={10} rounded={9999} bg="var(--chrome-dot-green)" />
|
||
</XStack>
|
||
<Text fontSize="$1" color="var(--muted-foreground)" fontFamily="$mono" ml="$2">
|
||
studio.hanzo.ai / <Text render="span" color="var(--foreground)" fontWeight="600">{model.slug}</Text> · {model.id}
|
||
</Text>
|
||
</XStack>
|
||
|
||
<XStack items="center" gap="$2">
|
||
<View px={8} py={2} rounded={4} borderWidth={1} borderColor="var(--border)">
|
||
<Text fontSize="$1" color="var(--muted-foreground)" fontFamily="$mono">
|
||
example
|
||
</Text>
|
||
</View>
|
||
</XStack>
|
||
</XStack>
|
||
|
||
{/* Main Model-Aligned Viewport */}
|
||
<Box p="$4" $md={{ p: '$6' }}>
|
||
{model.slug === 'koma' && (
|
||
<YStack gap="$4">
|
||
{/* 16:9 Cinematic Video Monitor */}
|
||
<Frame $platform-web={{ aspectRatio: '16/9' }}>
|
||
{/* Full-Motion Cinematic Demo Video */}
|
||
<video data-fill="" data-look="koma" poster="/images/creative/koma-splash.jpg" autoPlay muted loop playsInline>
|
||
<source src="/videos/koma-demo.webm" type="video/webm" />
|
||
<source src="/videos/koma-demo.mp4" type="video/mp4" />
|
||
</video>
|
||
|
||
{/* Cinematic Vignette */}
|
||
<Box
|
||
position="absolute"
|
||
inset={0}
|
||
pointerEvents="none"
|
||
backgroundImage={`linear-gradient(180deg, ${shade(65)} 0%, ${shade(5)} 35%, ${shade(20)} 65%, ${shade(88)} 100%)`}
|
||
/>
|
||
|
||
{/* Animated Laser Scanline Beam */}
|
||
<Box
|
||
data-beam="koma"
|
||
position="absolute"
|
||
t={0}
|
||
l={0}
|
||
r={0}
|
||
height={2}
|
||
pointerEvents="none"
|
||
backgroundImage={`linear-gradient(90deg, transparent, ${tone}, transparent)`}
|
||
boxShadow={`0 0 12px ${tone}`}
|
||
/>
|
||
|
||
{/* Cinematic Viewport Graphic Overlay */}
|
||
<svg data-overlay="" width="100%" height="100%" viewBox="0 0 960 540" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
{/* Horizon & Perspective Grid */}
|
||
<defs>
|
||
<linearGradient id="grid-fade" x1="0" y1="0" x2="0" y2="1">
|
||
<stop offset="0%" stopColor={tone} stopOpacity="0.05" />
|
||
<stop offset="70%" stopColor={tone} stopOpacity="0.35" />
|
||
<stop offset="100%" stopColor={tone} stopOpacity="0.0" />
|
||
</linearGradient>
|
||
</defs>
|
||
|
||
{/* Perspective Horizon Lines */}
|
||
<path d="M0 340 L960 340" stroke={lit(12)} strokeWidth="1" strokeDasharray="4 4" />
|
||
<path d="M480 0 L480 540" stroke={lit(8)} strokeWidth="1" strokeDasharray="4 4" />
|
||
|
||
{/* Camera Focal Frustum Wireframe */}
|
||
<path d="M480 270 L120 540" stroke="url(#grid-fade)" strokeWidth="1.5" />
|
||
<path d="M480 270 L840 540" stroke="url(#grid-fade)" strokeWidth="1.5" />
|
||
<path d="M480 270 L240 540" stroke="url(#grid-fade)" strokeWidth="1" />
|
||
<path d="M480 270 L720 540" stroke="url(#grid-fade)" strokeWidth="1" />
|
||
<path d="M480 270 L360 540" stroke="url(#grid-fade)" strokeWidth="1" />
|
||
<path d="M480 270 L600 540" stroke="url(#grid-fade)" strokeWidth="1" />
|
||
|
||
{/* Spatial Temporal Motion Trajectory Spline */}
|
||
<path
|
||
data-dash=""
|
||
d="M160 380 C 280 220, 420 320, 560 210 C 680 120, 780 280, 860 190"
|
||
stroke={tone}
|
||
strokeWidth="2.5"
|
||
strokeDasharray="6 4"
|
||
fill="none"
|
||
/>
|
||
|
||
{/* Keyframe Nodes along Path */}
|
||
<circle cx="160" cy="380" r="5" fill={REC} />
|
||
<circle cx="560" cy="210" r="6" fill={REC} />
|
||
<circle cx="860" cy="190" r="5" fill={REC} />
|
||
|
||
{/* Camera Safe Frame Area */}
|
||
<rect x="80" y="45" width="800" height="450" rx="4" stroke={lit(15)} strokeWidth="1" strokeDasharray="2 6" fill="none" />
|
||
|
||
{/* Floating Tracking Crosshairs */}
|
||
<g data-float="">
|
||
<circle cx="480" cy="270" r="28" stroke={mix(REC, 70)} strokeWidth="1" fill="none" />
|
||
<line x1="470" y1="270" x2="490" y2="270" stroke={REC} strokeWidth="1.5" />
|
||
<line x1="480" y1="260" x2="480" y2="280" stroke={REC} strokeWidth="1.5" />
|
||
<rect x="460" y="250" width="40" height="40" stroke={lit(30)} strokeWidth="1" strokeDasharray="2 2" fill="none" />
|
||
</g>
|
||
|
||
{/* Volumetric Depth Contours */}
|
||
<ellipse cx="480" cy="330" rx="180" ry="40" stroke={lit(6)} strokeWidth="1" />
|
||
<ellipse cx="480" cy="330" rx="280" ry="70" stroke={lit(4)} strokeWidth="1" />
|
||
</svg>
|
||
|
||
{/* Viewport HUD Overlays */}
|
||
<XStack position="absolute" t={12} l={16} items="center" gap="$2" z={4}>
|
||
<View data-blink="" width={8} height={8} rounded={9999} bg={REC} boxShadow={`0 0 10px ${REC}`} />
|
||
<Text fontSize="$1" fontWeight="700" color={REC} fontFamily="$mono">REC · 4K 60FPS</Text>
|
||
<Text fontSize="$1" color={lit(70)} fontFamily="$mono">| SHOT 0{activeShot + 1}/04</Text>
|
||
</XStack>
|
||
|
||
<XStack position="absolute" t={12} r={16} items="center" gap="$3" z={4}>
|
||
<Text fontSize="$1" color="var(--pure-white)" fontFamily="$mono" bg={shade(65)} px={8} py={2} rounded={4}>
|
||
GUIDANCE: 7.5 · LATENT: 1080p
|
||
</Text>
|
||
</XStack>
|
||
|
||
<XStack position="absolute" b={12} l={16} items="center" gap="$3" z={4}>
|
||
<Text fontSize="$1" color={lit(85)} fontFamily="$mono">
|
||
CAMERA: ORBITAL 35mm
|
||
</Text>
|
||
</XStack>
|
||
|
||
<XStack position="absolute" b={12} r={16} items="center" gap="$2" z={4}>
|
||
<Text fontSize="$1" color={tone} fontFamily="$mono" fontWeight="600">
|
||
Hanzo Koma-1 · Spatio-temporal DiT
|
||
</Text>
|
||
</XStack>
|
||
</Frame>
|
||
|
||
{/* Multi-Shot Sequencing Strip */}
|
||
<Box>
|
||
<XStack justify="space-between" items="center" mb="$2">
|
||
<Text fontSize="$1" color="var(--muted-foreground)" fontWeight="600" textTransform="uppercase">
|
||
Continuity Shot Pipeline (120 Frames Temporal Flow)
|
||
</Text>
|
||
<Text fontSize="$1" color={tone} fontFamily="$mono" fontWeight="600">
|
||
00:{Math.floor(simTimecode).toString().padStart(2, '0')}.{Math.floor((simTimecode % 1) * 100).toString().padStart(2, '0')} / 00:12.00
|
||
</Text>
|
||
</XStack>
|
||
|
||
<Grid columns={{ min: 140, max: 4 }} gap={10}>
|
||
{[
|
||
{ name: 'Shot 01: Orbital Master', focal: '24mm Prime', move: 'Slow Push', length: '72 frames' },
|
||
{ name: 'Shot 02: Tracking Hero', focal: '50mm Anamorphic', move: 'Lateral Arc', length: '96 frames' },
|
||
{ name: 'Shot 03: Depth Cut', focal: '85mm T1.5', move: 'Parallax Pan', length: '48 frames' },
|
||
{ name: 'Shot 04: Reveal Plate', focal: '35mm Wide', move: 'Crane Up', length: '120 frames' },
|
||
].map((shot, idx) => (
|
||
<View
|
||
key={shot.name}
|
||
render={<button type="button" onClick={() => setActiveShot(idx)} />}
|
||
data-ease=""
|
||
p={10}
|
||
rounded={8}
|
||
borderWidth={1}
|
||
borderColor={activeShot === idx ? tone : 'var(--border)'}
|
||
bg={activeShot === idx ? lit(8) : 'transparent'}
|
||
cursor="pointer"
|
||
$platform-web={{ textAlign: 'left' }}
|
||
>
|
||
<XStack justify="space-between" items="center" mb={4}>
|
||
<Text fontSize="$1" fontWeight="600" color={activeShot === idx ? 'var(--foreground)' : 'var(--muted-foreground)'}>
|
||
{shot.name}
|
||
</Text>
|
||
{activeShot === idx && <View width={6} height={6} rounded={9999} bg={tone} />}
|
||
</XStack>
|
||
<Text fontSize="$1" color="var(--muted-foreground)" lineHeight={16}>
|
||
{shot.focal} · {shot.move}
|
||
</Text>
|
||
</View>
|
||
))}
|
||
</Grid>
|
||
</Box>
|
||
</YStack>
|
||
)}
|
||
|
||
{model.slug === 'iro' && (
|
||
<YStack gap="$4">
|
||
{/* Image Artboard & Canvas Viewport */}
|
||
<Frame
|
||
maxH={460}
|
||
mx="auto"
|
||
$platform-web={{
|
||
aspectRatio: activeAspect === '16:9' ? '16/9' : activeAspect === '1:1' ? '1/1' : '21/9',
|
||
transition: 'aspect-ratio 0.3s ease',
|
||
}}
|
||
>
|
||
{/* High-Resolution Generated Master Image */}
|
||
<img data-fill="" data-look="iro" src="/images/creative/iro-splash.jpg" alt="Iro High-Fidelity Diffusion Master" />
|
||
|
||
{/* Generative Diffusion Shimmer Beam */}
|
||
<Box
|
||
data-beam="iro"
|
||
position="absolute"
|
||
t={0}
|
||
b={0}
|
||
width={3}
|
||
pointerEvents="none"
|
||
backgroundImage="linear-gradient(180deg, transparent, var(--art-iris), var(--art-tide), transparent)"
|
||
boxShadow="0 0 16px var(--art-iris)"
|
||
/>
|
||
|
||
{/* Vignette */}
|
||
<Box
|
||
position="absolute"
|
||
inset={0}
|
||
pointerEvents="none"
|
||
backgroundImage={`linear-gradient(180deg, ${shade(60)} 0%, ${shade(5)} 30%, ${shade(20)} 70%, ${shade(85)} 100%)`}
|
||
/>
|
||
|
||
{/* Grid & Alignment SVG */}
|
||
<svg data-overlay="" width="100%" height="100%" viewBox="0 0 800 450" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
{/* Fine Pixel / Diffusion Alignment Grid */}
|
||
<g stroke={lit(8)} strokeWidth="0.75">
|
||
<line x1="0" y1="112" x2="800" y2="112" />
|
||
<line x1="0" y1="225" x2="800" y2="225" />
|
||
<line x1="0" y1="337" x2="800" y2="337" />
|
||
<line x1="200" y1="0" x2="200" y2="450" />
|
||
<line x1="400" y1="0" x2="400" y2="450" />
|
||
<line x1="600" y1="0" x2="600" y2="450" />
|
||
</g>
|
||
|
||
{/* Multi-Subject Aesthetic Geometry */}
|
||
<circle cx="400" cy="210" r="110" stroke={mix('var(--art-iris)', 80)} strokeWidth="1.5" strokeDasharray="3 3" />
|
||
<rect x="290" y="100" width="220" height="220" rx="8" stroke={lit(20)} strokeWidth="1" />
|
||
<path d="M290 100 L510 320 M510 100 L290 320" stroke={lit(8)} strokeWidth="1" />
|
||
|
||
{/* Typography Anchor Alignment Crosshairs */}
|
||
<circle cx="290" cy="100" r="4" fill="var(--art-iris)" />
|
||
<circle cx="510" cy="100" r="4" fill="var(--art-iris)" />
|
||
<circle cx="290" cy="320" r="4" fill="var(--art-iris)" />
|
||
<circle cx="510" cy="320" r="4" fill="var(--art-iris)" />
|
||
</svg>
|
||
|
||
{/* Viewport HUD */}
|
||
<XStack position="absolute" t={12} l={16} items="center" gap="$2" z={4}>
|
||
<Text fontSize="$1" fontWeight="700" color={tone} fontFamily="$mono">
|
||
CANVAS: 2048 × 2048 · 16-BIT FLOAT
|
||
</Text>
|
||
</XStack>
|
||
|
||
<XStack position="absolute" t={12} r={16} items="center" gap="$2" z={4}>
|
||
{['16:9', '1:1', '21:9'].map((ratio) => (
|
||
<Chip key={ratio} on={activeAspect === ratio} tone={tone} onPress={() => setActiveAspect(ratio)}>
|
||
{ratio}
|
||
</Chip>
|
||
))}
|
||
</XStack>
|
||
|
||
<XStack position="absolute" b={12} l={16} items="center" gap="$3" z={4}>
|
||
<Text fontSize="$1" color="var(--pure-white)" fontFamily="$mono">
|
||
FLOW-MATCHING CONVERGENCE: STEP {diffusionStep}/50 · CFG: 7.0
|
||
</Text>
|
||
</XStack>
|
||
|
||
<XStack position="absolute" b={12} r={16} items="center" gap="$2" z={4}>
|
||
<Text fontSize="$1" color={tone} fontFamily="$mono" fontWeight="600">
|
||
Hanzo Iro-1 · Flow-matching DiT
|
||
</Text>
|
||
</XStack>
|
||
</Frame>
|
||
|
||
{/* Creative Parameter Sliders */}
|
||
<Grid columns={{ min: 180, max: 3 }} gap={12}>
|
||
<Spec label="Typography Exactness" value="Font Locking" note="Locked" tone={tone} />
|
||
<Spec label="Color Space" value="Wide Gamut" note="Display P3" tone={tone} />
|
||
<Spec label="Prompt Adherence" value="Semantic Flow" note="High Precision" tone={tone} />
|
||
</Grid>
|
||
</YStack>
|
||
)}
|
||
|
||
{model.slug === 'mori' && (
|
||
<YStack gap="$4">
|
||
{/* 3D Geometry Viewport */}
|
||
<Frame maxH={460} $platform-web={{ aspectRatio: '16/9' }}>
|
||
{/* Background 3D Spatial Scene */}
|
||
<img data-fill="" data-look="mori" src="/images/creative/mori-splash.jpg" alt="Mori 3D Spatial Scene" />
|
||
|
||
{/* Holographic Vignette */}
|
||
<Box
|
||
position="absolute"
|
||
inset={0}
|
||
pointerEvents="none"
|
||
backgroundImage={`radial-gradient(circle at 50% 50%, ${mix(tone, 8)} 0%, ${shade(85)} 80%)`}
|
||
/>
|
||
|
||
{/* 3D Turntable SVG with Animated Rotation */}
|
||
<svg data-overlay="turntable" width="100%" height="100%" viewBox="0 0 800 450" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
{/* 3D Isometric Coordinate Plane & Orbital Ring */}
|
||
<g stroke={lit(6)} strokeWidth="1">
|
||
<line x1="100" y1="360" x2="700" y2="360" />
|
||
<line x1="250" y1="420" x2="550" y2="120" />
|
||
<line x1="550" y1="420" x2="250" y2="120" />
|
||
<circle cx="400" cy="270" r="140" stroke={mix(tone, 20)} strokeDasharray="4 4" />
|
||
</g>
|
||
|
||
{/* Animated Rotating 3D Polyhedral Sculpture */}
|
||
<g
|
||
transform={`rotate(${isMoriSpinning ? moriRotation : 0}, 400, 260)`}
|
||
stroke={viewMode3D === 'normals' ? 'var(--art-tide)' : tone}
|
||
strokeWidth="1.5"
|
||
fill={viewMode3D === 'shaded' ? mix(tone, 18) : 'none'}
|
||
>
|
||
{/* Outer Bounding Polyhedron */}
|
||
<polygon points="400,130 550,205 400,280 250,205" strokeWidth="1.5" />
|
||
<polygon points="250,205 400,280 400,380 250,305" strokeWidth="1.5" />
|
||
<polygon points="550,205 400,280 400,380 550,305" strokeWidth="1.5" />
|
||
|
||
{/* Subdivision Wireframe Edges */}
|
||
<line x1="325" y1="168" x2="475" y2="242" strokeDasharray={viewMode3D === 'wireframe' ? '2 2' : 'none'} />
|
||
<line x1="475" y1="168" x2="325" y2="242" strokeDasharray={viewMode3D === 'wireframe' ? '2 2' : 'none'} />
|
||
<line x1="325" y1="242" x2="325" y2="342" strokeDasharray={viewMode3D === 'wireframe' ? '2 2' : 'none'} />
|
||
<line x1="475" y1="242" x2="475" y2="342" strokeDasharray={viewMode3D === 'wireframe' ? '2 2' : 'none'} />
|
||
|
||
{/* Normals Vectors Indicator */}
|
||
{viewMode3D === 'normals' && (
|
||
<g stroke="var(--art-tide)" strokeWidth="1.5">
|
||
<line x1="400" y1="130" x2="400" y2="90" />
|
||
<line x1="550" y1="205" x2="585" y2="195" />
|
||
<line x1="250" y1="205" x2="215" y2="195" />
|
||
<line x1="400" y1="280" x2="400" y2="250" />
|
||
</g>
|
||
)}
|
||
|
||
{/* Center Core Node */}
|
||
<circle cx="400" cy="260" r="5" fill={tone} />
|
||
</g>
|
||
|
||
{/* Origin Triad (X, Y, Z Axes) */}
|
||
<g strokeWidth="2" transform="translate(40, 360)">
|
||
<line x1="0" y1="0" x2="35" y2="0" stroke={REC} />
|
||
<line x1="0" y1="0" x2="0" y2="-35" stroke="var(--state-success)" />
|
||
<line x1="0" y1="0" x2="-20" y2="20" stroke="var(--art-tide)" />
|
||
<text x="40" y="4" fill={REC} fontSize="10" fontFamily="monospace">X</text>
|
||
<text x="-4" y="-40" fill="var(--state-success)" fontSize="10" fontFamily="monospace">Y</text>
|
||
<text x="-32" y="26" fill="var(--art-tide)" fontSize="10" fontFamily="monospace">Z</text>
|
||
</g>
|
||
</svg>
|
||
|
||
{/* Viewport Mode Switcher & Turntable Toggle */}
|
||
<XStack position="absolute" t={12} l={16} items="center" gap="$2" z={4}>
|
||
<Text fontSize="$1" fontWeight="700" color={tone} fontFamily="$mono">
|
||
SDF ➔ QUAD MESH: 245,820 TRIS
|
||
</Text>
|
||
</XStack>
|
||
|
||
<XStack position="absolute" t={12} r={16} items="center" gap="$2" z={4}>
|
||
<View
|
||
render={<button type="button" onClick={() => setIsMoriSpinning(!isMoriSpinning)} />}
|
||
px={8}
|
||
py={2}
|
||
rounded={4}
|
||
borderWidth={1}
|
||
borderColor="var(--border)"
|
||
bg={isMoriSpinning ? mix(tone, 20) : shade(65)}
|
||
cursor="pointer"
|
||
>
|
||
<Text fontSize="$1" fontFamily="$mono" color={isMoriSpinning ? tone : 'var(--muted-foreground)'}>
|
||
{isMoriSpinning ? 'SPINNING · 360°' : 'TURNTABLE PAUSED'}
|
||
</Text>
|
||
</View>
|
||
{(['wireframe', 'shaded', 'normals'] as const).map((mode) => (
|
||
<Chip key={mode} on={viewMode3D === mode} tone={tone} onPress={() => setViewMode3D(mode)} upper>
|
||
{mode}
|
||
</Chip>
|
||
))}
|
||
</XStack>
|
||
|
||
<XStack position="absolute" b={12} l={16} items="center" gap="$3" z={4}>
|
||
<Text fontSize="$1" color="var(--pure-white)" fontFamily="$mono">
|
||
PBR CHANNELS: ALBEDO · NORMAL · ROUGHNESS · METALLIC
|
||
</Text>
|
||
</XStack>
|
||
|
||
<XStack position="absolute" b={12} r={16} items="center" gap="$2" z={4}>
|
||
<Text fontSize="$1" color={lit(70)} fontFamily="$mono">
|
||
EXPORTS: GLTF · USDZ · FBX
|
||
</Text>
|
||
</XStack>
|
||
</Frame>
|
||
|
||
{/* Mesh Properties Row */}
|
||
<Grid columns={{ min: 180, max: 3 }} gap={12}>
|
||
<Spec label="Topology Quality" value="Quad-Dominant" note="Animation Ready" tone={tone} />
|
||
<Spec label="UV Unwrapping" value="Automatic Atlas" note="On" tone={tone} />
|
||
<Spec label="Geometry Engine" value="Hanzo Mori-1 Engine" note="SDF to mesh" tone={tone} />
|
||
</Grid>
|
||
</YStack>
|
||
)}
|
||
|
||
{model.slug === 'oto' && (
|
||
<YStack gap="$4">
|
||
{/* Split View: Live Foley Soundstage Stage Plate + Professional DAW Mixer */}
|
||
<Grid columns={{ min: 320, max: 2 }} gap={16}>
|
||
{/* Left Column: Live Soundstage Pit Cam & Interactive Cues */}
|
||
<YStack gap="$3">
|
||
<Frame $platform-web={{ aspectRatio: '16/9' }}>
|
||
{/* Foley Artist Live Plate in Pit */}
|
||
<img
|
||
data-fill=""
|
||
data-look="oto"
|
||
data-lit={foleyTriggered ? '' : undefined}
|
||
src="/images/creative/oto-splash.jpg"
|
||
alt="Oto Foley Artist Soundstage Live Stage Feed"
|
||
/>
|
||
|
||
{/* Gradient Vignette */}
|
||
<Box position="absolute" inset={0} pointerEvents="none" backgroundImage={VIGNETTE} />
|
||
|
||
{/* Camera HUD Overlays */}
|
||
<XStack position="absolute" t={10} l={12} items="center" gap="$2">
|
||
<View width={8} height={8} rounded={9999} bg={REC} boxShadow={`0 0 8px ${REC}`} />
|
||
<Text fontSize="$1" fontWeight="700" color={REC} fontFamily="$mono">
|
||
LIVE SOUNDSTAGE PIT · CAM 01
|
||
</Text>
|
||
</XStack>
|
||
|
||
<XStack position="absolute" t={10} r={12} items="center" gap="$2">
|
||
<Text fontSize="$1" color={lit(80)} fontFamily="$mono" bg={shade(65)} px={8} py={2} rounded={4}>
|
||
48 kHz / 24-BIT FLAC
|
||
</Text>
|
||
</XStack>
|
||
|
||
{/* Transient Flash Reticle */}
|
||
{foleyTriggered && (
|
||
<Box
|
||
position="absolute"
|
||
t="50%"
|
||
l="50%"
|
||
x="-50%"
|
||
y="-50%"
|
||
pointerEvents="none"
|
||
px={14}
|
||
py={6}
|
||
rounded={6}
|
||
bg={mix(tone, 87)}
|
||
boxShadow={`0 0 24px ${tone}`}
|
||
>
|
||
<Text fontSize="$1" fontWeight="700" fontFamily="$mono" color={INK}>
|
||
TRANSIENT HIT: {FOLEY_CUES[activeFoleyCue].spl}
|
||
</Text>
|
||
</Box>
|
||
)}
|
||
|
||
<XStack position="absolute" b={10} l={12} r={12} justify="space-between" items="center">
|
||
<Text fontSize="$1" color="var(--pure-white)" fontFamily="$mono">
|
||
{FOLEY_CUES[activeFoleyCue].category.toUpperCase()} · {FOLEY_CUES[activeFoleyCue].timecode}
|
||
</Text>
|
||
<Text fontSize="$1" color={tone} fontFamily="$mono" fontWeight="600">
|
||
CONTACT SYNC: 0.00ms
|
||
</Text>
|
||
</XStack>
|
||
</Frame>
|
||
|
||
{/* Interactive Soundstage Cue Trigger Pads */}
|
||
<Box>
|
||
<Text fontSize="$1" color="var(--muted-foreground)" fontWeight="600" textTransform="uppercase" mb="$2">
|
||
Soundstage Props & Kinematics (Trigger to Sync)
|
||
</Text>
|
||
<Grid columns={{ min: 140, max: 2 }} gap={8}>
|
||
{FOLEY_CUES.map((cue, idx) => {
|
||
const isActive = activeFoleyCue === idx
|
||
return (
|
||
<View
|
||
key={cue.id}
|
||
render={<button type="button" onClick={() => triggerFoleyCue(idx)} />}
|
||
data-ease=""
|
||
px={10}
|
||
py={8}
|
||
rounded={6}
|
||
borderWidth={1}
|
||
borderColor={isActive ? tone : 'var(--border)'}
|
||
bg={isActive ? lit(8) : 'transparent'}
|
||
cursor="pointer"
|
||
$platform-web={{ textAlign: 'left' }}
|
||
>
|
||
<XStack items="center" justify="space-between" mb={2}>
|
||
<Text fontSize="$1" fontWeight="600" color={isActive ? 'var(--foreground)' : 'var(--muted-foreground)'}>
|
||
{cue.icon} {cue.name}
|
||
</Text>
|
||
{isActive && <View width={6} height={6} rounded={9999} bg={tone} />}
|
||
</XStack>
|
||
<Text fontSize="$1" color="var(--muted-foreground)" fontFamily="$mono" lineHeight={14}>
|
||
{cue.spl} · {cue.timecode}
|
||
</Text>
|
||
</View>
|
||
)
|
||
})}
|
||
</Grid>
|
||
</Box>
|
||
</YStack>
|
||
|
||
{/* Right Column: Multi-Channel DAW Foley Mixer & Spectrum Analyzer */}
|
||
<YStack justify="space-between" p="$3.5" rounded={10} borderWidth={1} borderColor="var(--border)" bg={STAGE}>
|
||
{/* Console Header */}
|
||
<XStack justify="space-between" items="center" pb="$2.5" borderBottomWidth={1} borderColor={lit(8)}>
|
||
<XStack items="center" gap="$2">
|
||
<Radio width={14} height={14} color={tone} />
|
||
<Text fontSize="$1" fontWeight="700" color={tone} fontFamily="$mono" textTransform="uppercase">
|
||
DAW multi-channel bus
|
||
</Text>
|
||
</XStack>
|
||
<Text fontSize="$1" color={lit(60)} fontFamily="$mono">
|
||
PEAK: -1.2 dBFS · LUFS: -16.4
|
||
</Text>
|
||
</XStack>
|
||
|
||
{/* 4 Multi-Channel DAW Faders */}
|
||
<YStack gap="$2" my="$2">
|
||
{[
|
||
{ label: 'Direct Prop Spot Mic', key: 'prop', val: FOLEY_CUES[activeFoleyCue].faders.prop, color: 'var(--art-ember)' as Tone },
|
||
{ label: 'Foley Pit Room Atmos', key: 'pit', val: FOLEY_CUES[activeFoleyCue].faders.pit, color: 'var(--art-tide)' as Tone },
|
||
{ label: 'Sub-Harmonic Exciter (40Hz)', key: 'sub', val: FOLEY_CUES[activeFoleyCue].faders.sub, color: 'var(--art-iris)' as Tone },
|
||
{ label: 'Ribbon High Air Texture', key: 'ribbon', val: FOLEY_CUES[activeFoleyCue].faders.ribbon, color: 'var(--state-success)' as Tone },
|
||
].map((fader, faderIdx) => {
|
||
const dynamicFaderVal = Math.min(100, Math.max(10, Math.round(fader.val + Math.sin(simTimecode * 4 + faderIdx * 1.5) * 4 + (foleyTriggered ? 6 : 0))))
|
||
return (
|
||
<Box key={fader.key} p={8} rounded={6} bg={lit(3)}>
|
||
<XStack justify="space-between" items="center" mb={4}>
|
||
<Text fontSize="$1" fontWeight="600" color="var(--foreground)">
|
||
{fader.label}
|
||
</Text>
|
||
<Text fontSize="$1" fontFamily="$mono" color={fader.color} fontWeight="600">
|
||
{dynamicFaderVal}%
|
||
</Text>
|
||
</XStack>
|
||
<Box width="100%" height={6} rounded={3} bg={lit(8)} overflow="hidden">
|
||
<Box data-meter="" width={`${dynamicFaderVal}%`} height="100%" rounded={3} bg={fader.color} />
|
||
</Box>
|
||
</Box>
|
||
)
|
||
})}
|
||
</YStack>
|
||
|
||
{/* Real-time 16-Band Dynamic Spectrum Visualizer */}
|
||
<Box my="$2">
|
||
<Text fontSize="$1" color={lit(60)} fontFamily="$mono" mb="$1.5">
|
||
FREQUENCY SPECTRUM · {FOLEY_CUES[activeFoleyCue].material}
|
||
</Text>
|
||
<XStack items="flex-end" justify="space-between" height={52} px={6} py={4} rounded={6} bg={shade(50)}>
|
||
{FOLEY_CUES[activeFoleyCue].bands.map((h, i) => {
|
||
const wave = Math.sin(simTimecode * 6 + i * 0.75) * 12
|
||
const dynamicH = Math.min(100, Math.max(12, Math.round(h + wave + (foleyTriggered ? 25 : 0))))
|
||
return (
|
||
<View
|
||
key={i}
|
||
data-meter=""
|
||
width={12}
|
||
height={`${dynamicH}%`}
|
||
rounded={2}
|
||
bg={i === 1 || i === 4 ? tone : lit(35)}
|
||
boxShadow={dynamicH > 80 ? `0 0 6px ${tone}` : 'none'}
|
||
/>
|
||
)
|
||
})}
|
||
</XStack>
|
||
</Box>
|
||
|
||
{/* Bottom Status */}
|
||
<XStack justify="space-between" items="center" pt="$2.5" borderTopWidth={1} borderColor={lit(8)}>
|
||
<Text fontSize="$1" color={lit(60)} fontFamily="$mono">
|
||
DOLBY ATMOS · 96kHz / 32-BIT
|
||
</Text>
|
||
<Text fontSize="$1" color={tone} fontFamily="$mono" fontWeight="600">
|
||
Hanzo Oto-1 Neural Foley Engine
|
||
</Text>
|
||
</XStack>
|
||
</YStack>
|
||
</Grid>
|
||
|
||
{/* Foley Sound Classes */}
|
||
<Grid columns={{ min: 180, max: 3 }} gap={12}>
|
||
<Spec label="Synchronous Foley" value="Impacts & Steps" note="Sample-Accurate" tone={tone} />
|
||
<Spec label="Environmental Ambience" value="Spatial Reverb" note="B-format Ambisonics" tone={tone} />
|
||
<Spec label="Master Output" value="Lossless FLAC" note="96kHz / 24-bit" tone={tone} />
|
||
</Grid>
|
||
</YStack>
|
||
)}
|
||
|
||
{model.slug === 'koe' && (
|
||
<YStack gap="$4">
|
||
{/* Voice Soundstage Viewport */}
|
||
<Frame maxH={440} px={24} py={20} $platform-web={{ aspectRatio: '16/9' }}>
|
||
{/* Background Acoustic Stage Graphic */}
|
||
<img data-fill="" data-look="koe" src="/images/creative/koe-splash.jpg" alt="Koe Voice Studio Stage" />
|
||
|
||
{/* Dark Gradient Vignette */}
|
||
<Box
|
||
position="absolute"
|
||
inset={0}
|
||
pointerEvents="none"
|
||
backgroundImage={`linear-gradient(180deg, ${mix(STAGE, 85)} 0%, ${mix(STAGE, 40)} 40%, ${mix(STAGE, 92)} 100%)`}
|
||
/>
|
||
|
||
<YStack justify="space-between" height="100%" position="relative" z={2}>
|
||
{/* Wraps on a phone: the stage's name and its clock are two mono lines wider than 390px together. */}
|
||
<XStack justify="space-between" items="center" flexWrap="wrap" gap="$2">
|
||
<XStack items="center" gap="$2.5" minW={0} shrink={1}>
|
||
<Transport
|
||
playing={isPlayingVoice}
|
||
tone={tone}
|
||
size={26}
|
||
onPress={() => setIsPlayingVoice(!isPlayingVoice)}
|
||
title={isPlayingVoice ? 'Pause Voice Stream' : 'Resume Voice Stream'}
|
||
/>
|
||
<Text flex={1} minW={0} fontSize="$1" fontWeight="700" color={tone} fontFamily="$mono">
|
||
FLOW-MATCHING ACOUSTIC STAGE · {isPlayingVoice ? 'STREAMING PHONEMES' : 'STAGE READY'}
|
||
</Text>
|
||
</XStack>
|
||
<Text fontSize="$1" color={lit(70)} fontFamily="$mono">
|
||
00:0{Math.floor(simTimecode % 5)}.{String(Math.floor((simTimecode * 100) % 100)).padStart(2, '0')} / 00:05.00 · TTFT: 180ms
|
||
</Text>
|
||
</XStack>
|
||
|
||
{/* Mel-Spectrogram & Prosody Contour */}
|
||
<Box my="$2" position="relative">
|
||
<svg width="100%" height="150" viewBox="0 0 800 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
{/* Mel Spectrogram Heatmap Simulation */}
|
||
<g opacity="0.6">
|
||
{Array.from({ length: 24 }).map((_, i) => {
|
||
const animatedH = isPlayingVoice
|
||
? Math.min(105, Math.max(16, Math.round(55 + Math.sin(simTimecode * 7 + i * 0.75) * 38)))
|
||
: Math.round(40 + Math.cos(i) * 20)
|
||
const animatedY = 125 - animatedH
|
||
return (
|
||
<rect
|
||
key={i}
|
||
data-ease=""
|
||
x={i * 33 + 6}
|
||
y={animatedY}
|
||
width={24}
|
||
height={animatedH}
|
||
rx={3}
|
||
fill={i % 2 === 0 ? tone : 'var(--art-ember)'}
|
||
opacity={0.35 + ((Math.sin(simTimecode * 4 + i) + 1) / 2) * 0.45}
|
||
/>
|
||
)
|
||
})}
|
||
</g>
|
||
|
||
{/* Emotional Pitch / Prosody Contour Spline */}
|
||
<path
|
||
data-dash={isPlayingVoice ? 'slow' : undefined}
|
||
d="M 20 110 C 140 30, 260 140, 380 60 C 500 20, 640 100, 780 40"
|
||
stroke={tone}
|
||
strokeWidth="2.5"
|
||
fill="none"
|
||
strokeDasharray="6 4"
|
||
/>
|
||
|
||
{/* Phoneme Segmentation Markers */}
|
||
{['/h/', '/æ/', '/n/', '/z/', '/oʊ/'].map((ph, idx) => (
|
||
<g key={ph}>
|
||
<line x1={100 + idx * 130} y1="20" x2={100 + idx * 130} y2="130" stroke={lit(18)} strokeDasharray="2 4" />
|
||
<text x={110 + idx * 130} y="142" fill={lit(75)} fontSize="11" fontFamily="monospace">
|
||
{ph}
|
||
</text>
|
||
</g>
|
||
))}
|
||
|
||
{/* Sweeping Phoneme Playhead Laser */}
|
||
{isPlayingVoice && (
|
||
<g>
|
||
<line
|
||
x1={20 + ((simTimecode % 5) / 5) * 750}
|
||
y1={15}
|
||
x2={20 + ((simTimecode % 5) / 5) * 750}
|
||
y2={135}
|
||
stroke={REC}
|
||
strokeWidth="2"
|
||
/>
|
||
<circle cx={20 + ((simTimecode % 5) / 5) * 750} cy={15} r={3.5} fill={REC} />
|
||
<circle cx={20 + ((simTimecode % 5) / 5) * 750} cy={135} r={3.5} fill={REC} />
|
||
</g>
|
||
)}
|
||
</svg>
|
||
</Box>
|
||
|
||
{/* Emotional Acting Preset Toggles */}
|
||
<XStack items="center" justify="space-between" flexWrap="wrap" gap="$2" pt={8} borderTopWidth={1} borderColor={lit(10)}>
|
||
<Text fontSize="$1" color={lit(70)} fontFamily="$mono">
|
||
ACTING PRESET:
|
||
</Text>
|
||
<XStack gap="$2" flexWrap="wrap" maxW="100%">
|
||
{['Cinematic Dramatic', 'Intimate Documentary', 'Gritty Character', 'Warm Commercial'].map((preset, idx) => (
|
||
<Chip key={preset} on={activeVoiceStyle === idx} tone={tone} onPress={() => setActiveVoiceStyle(idx)} ground={shade(50)} py={3}>
|
||
{preset}
|
||
</Chip>
|
||
))}
|
||
</XStack>
|
||
</XStack>
|
||
</YStack>
|
||
</Frame>
|
||
|
||
{/* Performance Parameters */}
|
||
<Grid columns={{ min: 180, max: 3 }} gap={12}>
|
||
<Spec label="Zero-Shot Cloning" value="Reference Audio" note="3.0s Clean Sample" tone={tone} />
|
||
<Spec label="Sub-Second Latency" value="First Token" note="< 200ms" tone={tone} />
|
||
<Spec label="Synthesis Engine" value="Hanzo Koe-1 Engine" note="Neural Flow-Matching" tone={tone} />
|
||
</Grid>
|
||
</YStack>
|
||
)}
|
||
|
||
{model.slug === 'uta' && (
|
||
<YStack gap="$4">
|
||
{/* Style Selector Tabs */}
|
||
<XStack items="center" justify="space-between" flexWrap="wrap" gap="$2">
|
||
<Text fontSize="$1" color="var(--muted-foreground)" fontWeight="600" textTransform="uppercase">
|
||
Composition Style Presets
|
||
</Text>
|
||
<XStack gap="$1.5" flexWrap="wrap" maxW="100%">
|
||
{MUSIC_STYLES.map((style, idx) => (
|
||
<Chip
|
||
key={style.id}
|
||
on={activeMusicStyle === idx}
|
||
tone={tone}
|
||
onPress={() => setActiveMusicStyle(idx)}
|
||
ground={lit(4)}
|
||
px={10}
|
||
py={4}
|
||
rounded={6}
|
||
weight="600"
|
||
>
|
||
{style.name}
|
||
</Chip>
|
||
))}
|
||
</XStack>
|
||
</XStack>
|
||
|
||
{/* Split View: Multitrack Stem Arranger + Cinematic Music Video Trailer */}
|
||
<Grid columns={{ min: 320, max: 2 }} gap={16}>
|
||
{/* Left Column: 4-Stem Multitrack Arranger & Transport */}
|
||
<YStack justify="space-between" p="$3.5" rounded={10} borderWidth={1} borderColor="var(--border)" bg={STAGE}>
|
||
{/* Transport Controls Header */}
|
||
<XStack justify="space-between" items="center" pb="$2.5" borderBottomWidth={1} borderColor={lit(8)}>
|
||
<XStack items="center" gap="$2.5">
|
||
<Transport
|
||
playing={isPlayingMusic}
|
||
tone={tone}
|
||
size={28}
|
||
onPress={() => setIsPlayingMusic(!isPlayingMusic)}
|
||
title={isPlayingMusic ? 'Pause Playback' : 'Play Preview'}
|
||
/>
|
||
<YStack>
|
||
<Text fontSize="$1" fontWeight="700" color={tone} fontFamily="$mono">
|
||
{MUSIC_STYLES[activeMusicStyle].bpm} · {MUSIC_STYLES[activeMusicStyle].key}
|
||
</Text>
|
||
<Text fontSize="$1" color={lit(50)} fontFamily="$mono">
|
||
{isPlayingMusic ? 'STREAMING ACTIVE' : 'TRANSPORT READY'}
|
||
</Text>
|
||
</YStack>
|
||
</XStack>
|
||
|
||
<Text fontSize="$1" color={lit(70)} fontFamily="$mono">
|
||
01:{String(Math.floor(simTimecode * 5) % 60).padStart(2, '0')}.{String(Math.floor((simTimecode * 100) % 100)).padStart(2, '0')} / 03:45.00
|
||
</Text>
|
||
</XStack>
|
||
|
||
{/* 4 Isolated Music Stems */}
|
||
<YStack gap="$2" my="$2">
|
||
{MUSIC_STYLES[activeMusicStyle].stems.map((stem, stemIdx) => {
|
||
const isMuted = mutedStems[stem.id]
|
||
const stemTone = STEM_TONE[stem.id]
|
||
return (
|
||
<XStack key={stem.id} items="center" justify="space-between" p={8} rounded={6} bg={lit(3)}>
|
||
<XStack items="center" gap="$2.5" minW={140}>
|
||
<View
|
||
render={<button type="button" onClick={() => toggleStem(stem.id)} />}
|
||
px={6}
|
||
py={2}
|
||
rounded={4}
|
||
borderWidth={1}
|
||
borderColor="var(--border)"
|
||
bg={isMuted ? REC : 'transparent'}
|
||
cursor="pointer"
|
||
>
|
||
<Text fontSize="$1" fontFamily="$mono" fontWeight="700" color={isMuted ? 'var(--pure-white)' : 'var(--muted-foreground)'}>
|
||
{isMuted ? 'MUTE' : 'SOLO'}
|
||
</Text>
|
||
</View>
|
||
<Text fontSize="$1" fontWeight="600" color={isMuted ? 'var(--muted-foreground)' : 'var(--foreground)'} numberOfLines={1}>
|
||
{stem.name}
|
||
</Text>
|
||
</XStack>
|
||
|
||
{/* Waveform */}
|
||
<Box flex={1} mx="$2.5" height={16} overflow="hidden" position="relative">
|
||
<svg width="100%" height="16" viewBox="0 0 240 16" preserveAspectRatio="none">
|
||
<path
|
||
data-trace=""
|
||
d={`M ${Array.from({ length: 17 }).map((_, ptIdx) => {
|
||
const x = (ptIdx / 16) * 240
|
||
const y = isMuted
|
||
? 8
|
||
: isPlayingMusic
|
||
? 8 + Math.sin(simTimecode * 6 + ptIdx * 0.7 + stemIdx * 1.3) * (stem.activity / 100 * 6.5)
|
||
: 8 + Math.sin(ptIdx * 0.7) * 3
|
||
return `${x.toFixed(1)} ${y.toFixed(1)}`
|
||
}).join(' L ')}`}
|
||
stroke={isMuted ? lit(10) : stemTone}
|
||
strokeWidth="1.75"
|
||
fill="none"
|
||
/>
|
||
</svg>
|
||
</Box>
|
||
|
||
<Text fontSize="$1" fontFamily="$mono" color={isMuted ? 'var(--muted-foreground)' : stemTone} fontWeight="600">
|
||
{isMuted ? 'MUTED' : `${stem.activity}%`}
|
||
</Text>
|
||
</XStack>
|
||
)
|
||
})}
|
||
</YStack>
|
||
|
||
{/* Arranger Timeline Section Bar */}
|
||
<Box pt="$2.5" borderTopWidth={1} borderColor={lit(8)}>
|
||
<XStack justify="space-between" items="center" mb={6}>
|
||
<Text fontSize="$1" color={lit(50)} fontFamily="$mono">
|
||
TIMELINE FLOW:
|
||
</Text>
|
||
<Text fontSize="$1" color={tone} fontFamily="$mono" fontWeight="600">
|
||
CHORUS [BAR 17-32]
|
||
</Text>
|
||
</XStack>
|
||
<Box width="100%" height={4} rounded={2} bg={lit(8)} overflow="hidden">
|
||
<Box data-meter="" width={`${((simTimecode % 12) / 12) * 100}%`} height="100%" rounded={2} bg={tone} />
|
||
</Box>
|
||
</Box>
|
||
</YStack>
|
||
|
||
{/* Right Column: Sync'd Music Video Trailer / Holographic Orchestra Plate */}
|
||
<YStack gap="$3">
|
||
<Frame $platform-web={{ aspectRatio: '16/9' }}>
|
||
{/* High-res cinematic visualizer image */}
|
||
<img
|
||
data-fill=""
|
||
data-look="uta"
|
||
data-lit={isPlayingMusic ? '' : undefined}
|
||
src="/images/creative/uta-splash.jpg"
|
||
alt="Uta Live Philharmonic Orchestra and Music Video Trailer"
|
||
/>
|
||
|
||
{/* Gradient Vignette & Letterbox */}
|
||
<Box position="absolute" inset={0} pointerEvents="none" backgroundImage={VIGNETTE} />
|
||
|
||
{/* Top HUD */}
|
||
<XStack position="absolute" t={10} l={12} items="center" gap="$2">
|
||
<View
|
||
width={8}
|
||
height={8}
|
||
rounded={9999}
|
||
bg={isPlayingMusic ? 'var(--state-success)' : 'var(--chrome-dot-yellow)'}
|
||
boxShadow={isPlayingMusic ? '0 0 8px var(--state-success)' : 'none'}
|
||
/>
|
||
<Text fontSize="$1" fontWeight="700" color="var(--pure-white)" fontFamily="$mono">
|
||
{isPlayingMusic ? 'PLAYING · 4K 60FPS' : 'PREVIEW TRAILER · 4K 60FPS'}
|
||
</Text>
|
||
</XStack>
|
||
|
||
<XStack position="absolute" t={10} r={12} items="center" gap="$2">
|
||
<View px={6} py={2} rounded={4} bg={mix(tone, 15)} borderWidth={1} borderColor={mix(tone, 33)}>
|
||
<Text fontSize="$1" color={tone} fontWeight="700" fontFamily="$mono">
|
||
{MUSIC_STYLES[activeMusicStyle].badge}
|
||
</Text>
|
||
</View>
|
||
</XStack>
|
||
|
||
{/* Center Audio Spectrum Overlay when playing */}
|
||
<XStack position="absolute" t="42%" l="50%" x="-50%" y="-50%" items="flex-end" gap="$1" height={36} pointerEvents="none">
|
||
{[24, 38, 55, 75, 90, 100, 85, 60, 45, 70, 95, 80, 50, 30].map((bar, i) => {
|
||
const dynamicHeight = isPlayingMusic
|
||
? Math.min(100, Math.max(16, Math.round(bar + Math.sin(simTimecode * 7 + i * 0.85) * 26)))
|
||
: Math.max(15, Math.round(bar * 0.35))
|
||
return <View key={i} data-meter="" width={4} height={`${dynamicHeight}%`} rounded={2} bg={tone} boxShadow={`0 0 8px ${tone}`} />
|
||
})}
|
||
</XStack>
|
||
|
||
{/* Bottom Score Cue Subtitle */}
|
||
<Box position="absolute" b={10} l={12} r={12}>
|
||
{/* A block, so the one line ends in an ellipsis inside the frame rather than past it. */}
|
||
<Text display="block" fontSize="$1" color="var(--pure-white)" fontFamily="$mono" numberOfLines={1} mb={2}>
|
||
SCORE CUE: “{MUSIC_STYLES[activeMusicStyle].cue}”
|
||
</Text>
|
||
<XStack justify="space-between" items="center">
|
||
<Text fontSize="$1" color={lit(60)} fontFamily="$mono">
|
||
{MUSIC_STYLES[activeMusicStyle].trailerDesc}
|
||
</Text>
|
||
<Text fontSize="$1" color={tone} fontFamily="$mono" fontWeight="600">
|
||
SURROUND 7.1.4
|
||
</Text>
|
||
</XStack>
|
||
</Box>
|
||
</Frame>
|
||
|
||
{/* Score Note */}
|
||
<Box p={10} rounded={8} borderWidth={1} borderColor="var(--border)" bg={lit(3)}>
|
||
<Text fontSize="$1" color="var(--muted-foreground)">
|
||
Hanzo Uta-1 generates full broadcast multi-stem compositions with synchronized video cue anchors and lossless stem isolation.
|
||
</Text>
|
||
</Box>
|
||
</YStack>
|
||
</Grid>
|
||
|
||
{/* Music Specs */}
|
||
<Grid columns={{ min: 180, max: 3 }} gap={12}>
|
||
<Spec label="Arrangement Span" value="Full Compositions" note="Up to 4 Minutes" tone={tone} />
|
||
<Spec label="Stem Separation" value="Isolated Codebooks" note="4 Master Stems" tone={tone} />
|
||
<Spec label="Score Synchronization" value="Frame-Accurate" note="Zero Cue Drift" tone={tone} />
|
||
</Grid>
|
||
</YStack>
|
||
)}
|
||
</Box>
|
||
|
||
{/* Bottom Bar — Deep Link to Studio */}
|
||
<XStack justify="space-between" items="center" px="$4" py="$3" borderTopWidth={1} borderColor="var(--border)" bg={lit(2)} flexWrap="wrap" gap="$2">
|
||
<Text fontSize="$1" color="var(--muted-foreground)">
|
||
An example of the canvas in Hanzo Studio. Generation runs on Hanzo Cloud.
|
||
</Text>
|
||
<XStack render={<Link href={`/research-access?models=${model.slug}`} />} items="center" gap={6}>
|
||
<Text fontSize="$1" fontWeight="600" color="var(--foreground)">
|
||
Request access
|
||
</Text>
|
||
<ChevronRight width={14} height={14} color="var(--foreground)" />
|
||
</XStack>
|
||
</XStack>
|
||
</Box>
|
||
)
|
||
}
|