Compare commits

..
86 changed files with 4941 additions and 3946 deletions

No files matched your search

-2
View File
@@ -1,2 +0,0 @@
node_modules
dist
-46
View File
@@ -1,46 +0,0 @@
# PRD: Keith Rana Portfolio (built from the "Jack 3D Creator" template)
## The restaurant analogy
| Stage | Restaurant | This project |
|---|---|---|
| Phase 1 (done) | Food stand: one great dish, open today | The full single-page site with Keith's real content, photos and animations |
| Phase 2 | Small restaurant: a few tables, a real menu | Make it easy to edit (content in one file), better mobile + accessibility |
| Phase 3 | Full restaurant: front of house + back of house | Real contact form, email, project pages, CMS, analytics |
Front of house = what visitors see. Back of house = forms, email, storage, security.
## Core features
1. Hero: giant "Hi, i'm jack" title, nav, magnetic portrait, Contact button
2. Marquee: two rows of GIFs sliding left/right as you scroll
3. About: floating 3D icons + words that light up as you scroll
4. Services: white panel, 5 numbered services
5. Projects: 3 cards that stack and shrink as you scroll
6. Reusable parts: ContactButton, LiveProjectButton, FadeIn, Magnet, AnimatedText
## Phased roadmap
**Phase 1 — Food stand (today).** Static site, all 5 sections, all animations, responsive.
Done when: builds with no errors; page loads with no console errors; every button/link works; all image URLs load.
**Phase 2 — Small restaurant.** Nav links smooth-scroll to sections, Contact Me opens a mailto, content moved into editable data files, reduced-motion support, alt text, SEO tags, image fallbacks.
**Phase 3 — Full restaurant.** Contact form with a backend (server-side validation, spam protection, rate limits), project detail pages, CMS, analytics, hosting.
## Test ladder (each step must pass before the next)
1. Page loads with no errors
2. Buttons / links / forms work
3. Real external APIs/assets work (images load)
4. Security check (no secrets in code, safe external links, dependency audit)
## Security notes
Phase 1 has no forms, no logins, no secrets, so risk is low. External links use rel="noopener noreferrer". When Phase 3 adds a form, validation and spam protection must live on the server, and API keys must never be in front-end code.
## Phase 1 status (done)
- Content comes from Keith's existing portfolio: profile, 8 capabilities, 3 roles + 5 earlier roles, 4 projects, 10 certifications, contact details (all in `src/data/content.ts`).
- Each project card has its own illustrative diagram (drawn from the deliverables in Keith's portfolio text; swap for real screenshots when available).
- Sections: Hero (portrait), scrolling photo/skills rows, About, Capability, Experience, Projects (stacking cards), Credentials, Contact.
- Photos and the Kanit font ship inside the site (no outside image hosts needed).
- Tested at 360, 390, 768, 1024, 1440 and 1920 px wide: no sideways scroll, no console errors, all images load.
- Cloud preview: `node scripts/build-preview.mjs out.html` builds one self-contained page.
## Not used yet
- `it_helpdesk.db` (87 IT help-desk FAQs) was supplied but is not portfolio content. Candidate for a Phase 2 FAQ section if wanted.
-13
View File
@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Keith Rana, IT Service Delivery Manager in Melbourne, Australia." />
<title>Keith Rana -- IT Service Delivery</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
-2754
View File
File diff suppressed because it is too large. Load diff
-28
View File
@@ -1,28 +0,0 @@
{
"name": "keith-portfolio",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc --noEmit && vite build",
"preview": "vite preview"
},
"dependencies": {
"@fontsource/kanit": "^5.3.0",
"framer-motion": "^12.38.0",
"lucide-react": "^0.344.0",
"react": "^18.3.1",
"react-dom": "^18.3.1"
},
"devDependencies": {
"@types/react": "^18.3.5",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"autoprefixer": "^10.4.18",
"postcss": "^8.4.35",
"tailwindcss": "^3.4.1",
"typescript": "^5.5.3",
"vite": "^5.4.2"
}
}
-3
View File
@@ -1,3 +0,0 @@
export default {
plugins: { tailwindcss: {}, autoprefixer: {} },
};
-24
View File
@@ -1,24 +0,0 @@
// Builds ONE self-contained HTML page for the Claude cloud preview.
// Claude's cloud pages block outside images and files, so photos and fonts are baked in.
// Usage: node scripts/build-preview.mjs <output.html>
import { execFileSync } from 'node:child_process';
import { readFileSync, readdirSync, writeFileSync } from 'node:fs';
const out = process.argv[2] ?? 'preview.html';
execFileSync('npm', ['run', 'build'], { stdio: 'inherit', env: { ...process.env, INLINE: '1' } });
const dir = 'dist/assets/';
const files = readdirSync(dir);
const js = readFileSync(dir + files.find((f) => f.endsWith('.js')), 'utf8').replace(/<\/script/g, '<\\/script');
const css = readFileSync(dir + files.find((f) => f.endsWith('.css')), 'utf8');
const html = `<meta charset="utf-8">
<title>Keith Rana Portfolio</title>
<style>${css}
html,body{background:#0C0C0C}</style>
<div id="root"></div>
<script type="module">${js}</script>
`;
writeFileSync(out, html);
const mb = Buffer.byteLength(html) / 1048576;
console.log(`wrote ${out} (${mb.toFixed(1)} MB)${mb > 16 ? ' WARNING: over the 16 MB page limit' : ''}`);
-26
View File
@@ -1,26 +0,0 @@
import HeroSection from './sections/HeroSection';
import MarqueeSection from './sections/MarqueeSection';
import AboutSection from './sections/AboutSection';
import ServicesSection from './sections/ServicesSection';
import ExperienceSection from './sections/ExperienceSection';
import ProjectsSection from './sections/ProjectsSection';
import CredentialsSection from './sections/CredentialsSection';
import ContactSection from './sections/ContactSection';
import { ContactProvider } from './components/ContactDialog';
export default function App() {
return (
<ContactProvider>
<main style={{ background: '#0C0C0C', overflowX: 'clip' }}>
<HeroSection />
<MarqueeSection />
<AboutSection />
<ServicesSection />
<ExperienceSection />
<ProjectsSection />
<CredentialsSection />
<ContactSection />
</main>
</ContactProvider>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 641 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

@@ -1,30 +0,0 @@
import { useRef } from 'react';
import { motion, useScroll, useTransform } from 'framer-motion';
import type { MotionValue } from 'framer-motion';
function Char({ char, index, total, progress }: { char: string; index: number; total: number; progress: MotionValue<number> }) {
const start = index / total;
const end = start + 1 / total;
const opacity = useTransform(progress, [start, end], [0.2, 1]);
return (
<span className="relative">
<span className="invisible">{char}</span>
<motion.span className="absolute left-0 top-0" style={{ opacity }}>
{char}
</motion.span>
</span>
);
}
export default function AnimatedText({ text, className, style }: { text: string; className?: string; style?: React.CSSProperties }) {
const ref = useRef<HTMLParagraphElement>(null);
const { scrollYProgress } = useScroll({ target: ref, offset: ['start 0.8', 'end 0.2'] });
const chars = Array.from(text);
return (
<p ref={ref} className={className} style={style} aria-label={text}>
{chars.map((c, i) =>
c === ' ' ? ' ' : <Char key={i} char={c} index={i} total={chars.length} progress={scrollYProgress} />
)}
</p>
);
}
@@ -1,21 +0,0 @@
import { useOpenContact } from './ContactDialog';
export default function ContactButton() {
const openContact = useOpenContact();
return (
<button
type="button"
onClick={openContact}
className="inline-block whitespace-nowrap rounded-full px-8 py-3 text-xs font-medium uppercase tracking-widest text-white sm:px-10 sm:py-3.5 sm:text-sm md:px-12 md:py-4 md:text-base"
style={{
background: 'linear-gradient(123deg, #18011F 7%, #B600A8 37%, #7621B0 72%, #BE4C00 100%)',
boxShadow: '0px 4px 4px rgba(181, 1, 167, 0.25), 4px 4px 12px #7721B1 inset',
outline: '2px solid white',
outlineOffset: '-3px',
cursor: 'pointer',
}}
>
Contact Me
</button>
);
}
@@ -1,101 +0,0 @@
import { createContext, useCallback, useContext, useEffect, useRef, useState } from 'react';
import type { ReactNode } from 'react';
import { Check, Copy, Mail, X } from 'lucide-react';
import { profile } from '../data/content';
const OpenContext = createContext<() => void>(() => {});
export const useOpenContact = () => useContext(OpenContext);
const subject = encodeURIComponent('Portfolio enquiry');
const to = encodeURIComponent(profile.email);
const options = [
{ label: 'Open my email app', href: `mailto:${profile.email}?subject=${subject}`, newTab: false },
{ label: 'Write in Gmail', href: `https://mail.google.com/mail/?view=cm&fs=1&to=${to}&su=${subject}`, newTab: true },
{ label: 'Write in Yahoo Mail', href: `https://compose.mail.yahoo.com/?to=${to}&subject=${subject}`, newTab: true },
];
const optionClass =
'flex items-center justify-between gap-3 rounded-full border-2 border-[#D7E2EA] px-6 py-3 text-sm font-medium uppercase tracking-widest text-[#D7E2EA] transition-colors hover:bg-[#D7E2EA]/10 sm:text-base';
export function ContactProvider({ children }: { children: ReactNode }) {
const [open, setOpen] = useState(false);
const [copied, setCopied] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const closeRef = useRef<HTMLButtonElement>(null);
const opener = useRef<HTMLElement | null>(null);
const show = useCallback(() => {
opener.current = document.activeElement as HTMLElement | null;
setCopied(false);
setOpen(true);
}, []);
const hide = useCallback(() => {
setOpen(false);
opener.current?.focus?.();
}, []);
useEffect(() => {
if (!open) return;
closeRef.current?.focus();
const onKey = (e: KeyboardEvent) => e.key === 'Escape' && hide();
document.addEventListener('keydown', onKey);
const prev = document.body.style.overflow;
document.body.style.overflow = 'hidden';
return () => {
document.removeEventListener('keydown', onKey);
document.body.style.overflow = prev;
};
}, [open, hide]);
const copy = async () => {
try {
await navigator.clipboard.writeText(profile.email);
setCopied(true);
} catch {
inputRef.current?.select();
}
};
return (
<OpenContext.Provider value={show}>
{children}
{open && (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 p-4 backdrop-blur-sm"
onMouseDown={(e) => e.target === e.currentTarget && hide()}
>
<div role="dialog" aria-modal="true" aria-labelledby="contact-title" className="relative flex w-full max-w-md flex-col gap-5 rounded-[32px] border-2 border-[#D7E2EA] bg-[#0C0C0C] p-6 sm:p-8">
<button ref={closeRef} onClick={hide} aria-label="Close" className="absolute right-4 top-4 rounded-full p-2 text-[#D7E2EA] transition-colors hover:bg-[#D7E2EA]/10">
<X size={22} />
</button>
<h2 id="contact-title" className="hero-heading pr-10 text-4xl font-black uppercase leading-none sm:text-5xl">Email Keith</h2>
<p className="font-light leading-relaxed text-[#D7E2EA]/80">Pick how you would like to write. Your message goes to the address below.</p>
<div className="flex items-center gap-2">
<input
ref={inputRef}
id="contact-email"
readOnly
value={profile.email}
aria-label="Email address"
onFocus={(e) => e.currentTarget.select()}
className="min-w-0 flex-1 rounded-full border border-[#D7E2EA]/40 bg-transparent px-5 py-3 text-[#D7E2EA]"
/>
<button onClick={copy} className="flex shrink-0 items-center gap-2 rounded-full border border-[#D7E2EA]/40 px-4 py-3 text-sm text-[#D7E2EA] transition-colors hover:bg-[#D7E2EA]/10">
{copied ? <Check size={16} /> : <Copy size={16} />}
{copied ? 'Copied' : 'Copy'}
</button>
</div>
<div className="flex flex-col gap-3">
{options.map((o) => (
<a key={o.label} href={o.href} className={optionClass} {...(o.newTab ? { target: '_blank', rel: 'noopener noreferrer' } : {})}>
{o.label}
<Mail size={18} />
</a>
))}
</div>
</div>
</div>
)}
</OpenContext.Provider>
);
}
-42
View File
@@ -1,42 +0,0 @@
import { motion } from 'framer-motion';
import type { MotionProps } from 'framer-motion';
import type { ComponentType, ElementType, ReactNode } from 'react';
type FadeInProps = {
children: ReactNode;
delay?: number;
duration?: number;
x?: number;
y?: number;
className?: string;
as?: ElementType;
};
const cache = new Map<ElementType, ComponentType<MotionProps & { className?: string }>>();
const getMotion = (el: ElementType) => {
if (!cache.has(el)) cache.set(el, motion.create(el as 'div') as ComponentType<MotionProps & { className?: string }>);
return cache.get(el)!;
};
export default function FadeIn({
children,
delay = 0,
duration = 0.7,
x = 0,
y = 30,
className,
as = 'div',
}: FadeInProps) {
const Component = getMotion(as);
return (
<Component
className={className}
initial={{ opacity: 0, x, y }}
whileInView={{ opacity: 1, x: 0, y: 0 }}
viewport={{ once: true, margin: '50px', amount: 0 }}
transition={{ duration, delay, ease: [0.25, 0.1, 0.25, 1] }}
>
{children}
</Component>
);
}
-58
View File
@@ -1,58 +0,0 @@
import { useEffect, useRef, useState } from 'react';
import type { ReactNode } from 'react';
type MagnetProps = {
children: ReactNode;
padding?: number;
strength?: number;
activeTransition?: string;
inactiveTransition?: string;
className?: string;
};
export default function Magnet({
children,
padding = 100,
strength = 2,
activeTransition = 'transform 0.3s ease-out',
inactiveTransition = 'transform 0.6s ease-in-out',
className,
}: MagnetProps) {
const [active, setActive] = useState(false);
const [pos, setPos] = useState({ x: 0, y: 0 });
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const onMove = (e: MouseEvent) => {
if (!ref.current) return;
const { left, top, width, height } = ref.current.getBoundingClientRect();
const cx = left + width / 2;
const cy = top + height / 2;
const dx = Math.abs(cx - e.clientX);
const dy = Math.abs(cy - e.clientY);
if (dx < width / 2 + padding && dy < height / 2 + padding) {
setActive(true);
setPos({ x: (e.clientX - cx) / strength, y: (e.clientY - cy) / strength });
} else {
setActive(false);
setPos({ x: 0, y: 0 });
}
};
window.addEventListener('mousemove', onMove);
return () => window.removeEventListener('mousemove', onMove);
}, [padding, strength]);
return (
<div ref={ref} className={className} style={{ position: 'relative', display: 'inline-block' }}>
<div
style={{
transform: `translate3d(${pos.x}px, ${pos.y}px, 0)`,
transition: active ? activeTransition : inactiveTransition,
willChange: 'transform',
}}
>
{children}
</div>
</div>
);
}
@@ -1,149 +0,0 @@
import type { CSSProperties, ReactNode } from 'react';
export type DiagramKind = 'network' | 'server' | 'sccm' | 'voice';
const INK = '#D7E2EA';
const TEAL = '#2BB3A6';
const MUTED = 'rgba(215,226,234,0.55)';
const FILL = '#14161a';
function Box({ x, y, w, h, label, sub, accent }: { x: number; y: number; w: number; h: number; label: string; sub?: string; accent?: boolean }) {
return (
<g>
<rect x={x} y={y} width={w} height={h} rx={14} fill={FILL} stroke={accent ? TEAL : INK} strokeWidth={accent ? 2.5 : 1.5} />
<text x={x + w / 2} y={y + h / 2 + (sub ? -4 : 8)} textAnchor="middle" fontSize={22} fontWeight={500} fill={INK}>{label}</text>
{sub && <text x={x + w / 2} y={y + h / 2 + 22} textAnchor="middle" fontSize={18} fill={MUTED}>{sub}</text>}
</g>
);
}
function Line({ x1, y1, x2, y2, dashed }: { x1: number; y1: number; x2: number; y2: number; dashed?: boolean }) {
return <line x1={x1} y1={y1} x2={x2} y2={y2} stroke={MUTED} strokeWidth={2} strokeDasharray={dashed ? '6 6' : undefined} />;
}
function Arrow({ x1, y, x2, label }: { x1: number; y: number; x2: number; label?: string }) {
return (
<g>
<line x1={x1} y1={y} x2={x2 - 10} y2={y} stroke={TEAL} strokeWidth={3} />
<path d={`M${x2 - 14} ${y - 8} L${x2} ${y} L${x2 - 14} ${y + 8} Z`} fill={TEAL} />
{label && <text x={(x1 + x2) / 2} y={y - 14} textAnchor="middle" fontSize={20} fill={TEAL}>{label}</text>}
</g>
);
}
function Dot({ cx, cy, r = 14 }: { cx: number; cy: number; r?: number }) {
return <circle cx={cx} cy={cy} r={r} fill={FILL} stroke={TEAL} strokeWidth={2.5} />;
}
function Wifi({ cx, cy }: { cx: number; cy: number }) {
return (
<g fill="none" stroke={TEAL} strokeWidth={2.5} strokeLinecap="round">
<path d={`M${cx - 18} ${cy - 4} Q${cx} ${cy - 22} ${cx + 18} ${cy - 4}`} />
<path d={`M${cx - 10} ${cy + 4} Q${cx} ${cy - 6} ${cx + 10} ${cy + 4}`} />
<circle cx={cx} cy={cy + 12} r={2.5} fill={TEAL} />
</g>
);
}
const diagrams: Record<DiagramKind, { label: string; body: ReactNode }> = {
network: {
label: 'Network topology: Fortinet firewall connects to Fortinet 48P switches, which connect to access points.',
body: (
<>
<Box x={190} y={30} w={220} h={64} label="Fortinet firewall" accent />
<Line x1={300} y1={94} x2={300} y2={130} />
<Line x1={150} y1={130} x2={450} y2={130} />
<Line x1={150} y1={130} x2={150} y2={160} />
<Line x1={450} y1={130} x2={450} y2={160} />
<Box x={50} y={160} w={200} h={64} label="48P switch" sub="Fortinet" />
<Box x={350} y={160} w={200} h={64} label="48P switch" sub="Fortinet" />
{[90, 210, 390, 510].map((x, i) => (
<g key={x}>
<Line x1={i < 2 ? 150 : 450} y1={224} x2={x} y2={274} dashed />
<Dot cx={x} cy={296} r={22} />
<Wifi cx={x} cy={292} />
</g>
))}
<text x={300} y={372} textAnchor="middle" fontSize={20} fill={MUTED}>Access point uplift</text>
<text x={300} y={400} textAnchor="middle" fontSize={20} fill={MUTED}>Network topology upgrade</text>
</>
),
},
server: {
label: 'Server upgrade: virtual machines and services move from the existing server to the upgraded server, with updated network settings.',
body: (
<>
<Box x={20} y={70} w={190} h={200} label="" />
<text x={115} y={105} textAnchor="middle" fontSize={22} fontWeight={500} fill={INK}>Existing server</text>
<Box x={40} y={130} w={150} h={44} label="VMs" />
<Box x={40} y={192} w={150} h={44} label="Services" />
<Arrow x1={222} y={170} x2={378} label="Data migrated" />
<Box x={390} y={70} w={190} h={200} label="" accent />
<text x={485} y={105} textAnchor="middle" fontSize={22} fontWeight={500} fill={INK}>Upgraded server</text>
<Box x={410} y={130} w={150} h={44} label="VMs" />
<Box x={410} y={192} w={150} h={44} label="Services" />
<Line x1={485} y1={270} x2={485} y2={320} dashed />
<Box x={150} y={320} w={320} h={56} label="Updated network settings" />
<Line x1={470} y1={348} x2={485} y2={348} dashed />
</>
),
},
sccm: {
label: 'SCCM implementation: install and configure SCCM, prepare settings, then establish the task-sequence process for endpoints.',
body: (
<>
<Box x={200} y={26} w={200} h={64} label="SCCM" accent />
<Line x1={300} y1={90} x2={300} y2={122} />
<Box x={40} y={122} w={150} h={92} label="Install" sub="& configure" />
<Box x={225} y={122} w={150} h={92} label="Prepare" sub="settings" />
<Box x={410} y={122} w={150} h={92} label="Task" sub="sequence" accent />
<Arrow x1={190} y={168} x2={225} />
<Arrow x1={375} y={168} x2={410} />
<Line x1={485} y1={214} x2={485} y2={262} dashed />
<Line x1={150} y1={262} x2={450} y2={262} dashed />
{[150, 250, 350, 450].map((x) => (
<g key={x}>
<Line x1={x} y1={262} x2={x} y2={292} dashed />
<rect x={x - 30} y={292} width={60} height={40} rx={6} fill={FILL} stroke={TEAL} strokeWidth={2.5} />
<line x1={x - 40} y1={344} x2={x + 40} y2={344} stroke={TEAL} strokeWidth={3} strokeLinecap="round" />
</g>
))}
<text x={300} y={392} textAnchor="middle" fontSize={20} fill={MUTED}>Endpoints built through the task sequence</text>
</>
),
},
voice: {
label: 'Microsoft Teams calling: number porting, dial-plan and call-flow design, then users supported by training, documentation and adoption monitoring.',
body: (
<>
<Box x={20} y={50} w={160} h={70} label="Number" sub="porting" />
<Box x={220} y={50} w={160} h={70} label="Dial plan" accent />
<Box x={420} y={50} w={160} h={70} label="Call flow" />
<Arrow x1={180} y={85} x2={220} />
<Arrow x1={380} y={85} x2={420} />
<Line x1={300} y1={120} x2={300} y2={168} dashed />
<Line x1={100} y1={168} x2={500} y2={168} dashed />
{[100, 300, 500].map((x) => (
<g key={x}>
<Line x1={x} y1={168} x2={x} y2={196} dashed />
<circle cx={x} cy={214} r={16} fill={FILL} stroke={TEAL} strokeWidth={2.5} />
<path d={`M${x - 28} ${264} Q${x} ${226} ${x + 28} ${264}`} fill={FILL} stroke={TEAL} strokeWidth={2.5} />
</g>
))}
<text x={100} y={310} textAnchor="middle" fontSize={20} fill={INK}>User training</text>
<text x={300} y={310} textAnchor="middle" fontSize={20} fill={INK}>Documentation</text>
<text x={500} y={310} textAnchor="middle" fontSize={20} fill={INK}>Adoption monitoring</text>
<text x={300} y={380} textAnchor="middle" fontSize={20} fill={MUTED}>Microsoft Teams calling</text>
</>
),
},
};
export default function ProjectDiagram({ kind, className, style }: { kind: DiagramKind; className?: string; style?: CSSProperties }) {
const d = diagrams[kind];
return (
<svg viewBox="0 0 600 420" role="img" aria-label={`Illustrative diagram. ${d.label}`} className={className} style={{ fontFamily: 'Kanit, sans-serif', ...style }}>
{d.body}
</svg>
);
}
-140
View File
@@ -1,140 +0,0 @@
import portrait from '../assets/portrait.jpg';
import leadership from '../assets/leadership.webp';
import systems from '../assets/systems.webp';
import signalMark from '../assets/signal-mark.webp';
import networkPhoto from '../assets/projects/network.jpg';
import serverPhoto from '../assets/projects/server.jpg';
import sccmPhoto from '../assets/projects/sccm.jpg';
import voicePhoto from '../assets/projects/voice.jpg';
import type { DiagramKind } from '../components/ProjectDiagram';
export const images = { portrait, leadership, systems, signalMark };
export const profile = {
name: 'Keith Rana',
firstName: 'keith',
role: 'IT Service Delivery Manager',
location: 'Melbourne, Australia',
email: 'keith_rana@yahoo.com',
phone: '04 1668 7558',
phoneHref: 'tel:+61416687558',
tagline: 'an it service delivery manager keeping critical services steady while the work keeps moving',
};
export const aboutText =
'A results-driven IT professional with over 16 years of experience across telecommunications, education, and managed services, including more than 8 years leading technical teams. I make technology service delivery visible, stable, and continually better. Let\'s make delivery more dependable.';
export const stats = [
{ value: '16+', label: 'Years in IT' },
{ value: '8+', label: 'Years leading' },
];
export const expertise = [
{ name: 'IT Service Delivery', description: 'End-to-end accountability for service delivery against agreed SLAs, with reporting to senior stakeholders.' },
{ name: 'Enterprise Infrastructure', description: 'Enterprise network, server and endpoint environments, including Fortinet, Zscaler and SCCM.' },
{ name: 'ITIL & Change Control', description: 'ITIL process, measured change and service reporting that improve compliance and service quality.' },
{ name: 'Incident & Escalation Leadership', description: 'Major incident management, workload prioritisation and escalations led with a calm, evidence-based approach.' },
{ name: 'Vendor & Stakeholder Management', description: 'Clear ownership of the vendor relationships that sit behind every service the business relies on.' },
{ name: 'Microsoft 365 & Intune', description: 'Hands-on depth in Microsoft 365, Azure and Intune across cloud applications and end-user devices.' },
{ name: 'Cybersecurity & Access', description: 'Security, identity and access practices built into daily operations, backed by Microsoft security certifications.' },
{ name: 'Team Development', description: 'Coaching, performance conversations, training and mentoring that grow capable, confident support teams.' },
];
export const roles = [
{
dates: 'Oct 2023 — Present',
role: 'IT Service Delivery Manager',
organisation: 'AFL Telecommunications',
summary: 'Leading the daily delivery of business-critical IT services, balancing prompt resolution, availability, security, and a dependable experience for end users.',
points: [
'Own service operations across cloud applications, Windows and Linux environments, desktops, virtual apps, networks, and end-user support.',
'Lead a support and service-desk team through coaching, performance conversations, workload prioritisation, and escalations.',
'Use incident patterns, service-performance reporting, change controls, and feedback loops to improve ITIL compliance and service quality.',
],
},
{
dates: 'Dec 2021 — Sep 2023',
role: 'Information Technology Manager',
organisation: 'Suzanne Cory High School',
summary: 'Managed technology services, documentation, knowledge systems, infrastructure capacity, and Microsoft 365 operations in a fast-moving education environment.',
points: [
'Oversaw Microsoft 365 services, endpoint operations, security, quality, and the administration and maintenance of core IT infrastructure.',
'Led a skilled IT specialist team with a focus on strategic direction, mentoring, service effectiveness, and superior customer outcomes.',
'Coordinated systems migrations, upgrades, and deployments to defined scope and schedule constraints.',
],
},
{
dates: 'May 2018 — Nov 2021',
role: 'IT Support Specialist — Team Lead',
organisation: 'Wesley College Melbourne',
summary: 'Directed a six-person IT support team and helped translate organisational priorities into reliable, well-documented technical service.',
points: [
'Established project scope, supported IT initiatives, and aligned delivery activity with strategic objectives.',
'Worked across Casper Management, AirWatch MDM, Microsoft Intune, and operational processes informed by ITIL principles.',
'Identified skills gaps, organised training, coordinated with project managers, and maintained clear project documentation.',
],
},
];
export const foundations = [
{ role: 'IT Support Officer', organisation: 'Mentone Grammar School', dates: 'Apr 2017 — Dec 2017' },
{ role: 'IT Project Manager', organisation: 'Archirayan Infotech', dates: 'Aug 2015 — Jul 2016' },
{ role: 'IT Technical Support', organisation: 'Has Tech Solutions', dates: 'Aug 2014 — Jul 2015' },
{ role: 'IT Technical Engineer', organisation: 'Hindustan Cyber Services', dates: 'Oct 2012 — Aug 2014' },
{ role: 'Assistant Systems Analyst', organisation: 'Prutha Technologies', dates: 'Oct 2009 — Sep 2010' },
];
export const projects = [
{
category: 'Network',
name: 'Network project',
body: 'Fortinet Firewall upgrade, Fortinet 48P Switches upgrade, Fortinet access point uplift, and network topology upgrade.',
delivered: ['Fortinet firewall upgrade', 'Fortinet 48P switches upgrade', 'Fortinet access point uplift', 'Network topology upgrade'],
diagram: 'network' as DiagramKind,
photo: networkPhoto,
photoPosition: 'center',
},
{
category: 'Infrastructure',
name: 'Server upgrade',
body: 'Upgraded the server with existing VMs and services, migrated data, and configured updated network settings.',
delivered: ['Server upgraded with existing VMs and services', 'Data migrated', 'Updated network settings configured'],
diagram: 'server' as DiagramKind,
photo: serverPhoto,
photoPosition: 'center',
},
{
category: 'Endpoint',
name: 'SCCM implementation',
body: 'Installed and configured SCCM, prepared the required settings, and established the task-sequence process.',
delivered: ['SCCM installed and configured', 'Required settings prepared', 'Task-sequence process established'],
diagram: 'sccm' as DiagramKind,
photo: sccmPhoto,
photoPosition: 'center',
},
{
category: 'Voice',
name: 'Microsoft Teams calling',
body: 'Delivered number-porting support, call-flow and dial-plan design, user training, documentation, and adoption monitoring.',
delivered: ['Number-porting support', 'Call-flow and dial-plan design', 'User training and documentation', 'Adoption monitoring'],
diagram: 'voice' as DiagramKind,
photo: voicePhoto,
photoPosition: 'center',
},
];
export const certifications = [
'Introduction to Cyber Security',
'Windows Client',
'Microsoft Teams Voice Engineer',
'M365 Teams Administrator Associate',
'Microsoft Azure Administrator Associate',
'Microsoft Azure Security Engineer Associate',
'M365 Security Administrator Associate',
'Microsoft Information Protection Administrator Associate',
'Project Management Essentials Certified',
'Cloud Computing',
];
export const education = { degree: 'Bachelor of Information Technology', school: 'Federation University', year: '2009' };
-25
View File
@@ -1,25 +0,0 @@
@import '@fontsource/kanit/latin-300.css';
@import '@fontsource/kanit/latin-400.css';
@import '@fontsource/kanit/latin-500.css';
@import '@fontsource/kanit/latin-700.css';
@import '@fontsource/kanit/latin-900.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body, #root { background: #0C0C0C; }
html { scroll-behavior: smooth; }
body { font-family: 'Kanit', sans-serif; }
.hero-heading {
background: linear-gradient(180deg, #646973 0%, #BBCCD7 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
}
-10
View File
@@ -1,10 +0,0 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import './index.css';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<App />
</StrictMode>
);
@@ -1,46 +0,0 @@
import FadeIn from '../components/FadeIn';
import AnimatedText from '../components/AnimatedText';
import ContactButton from '../components/ContactButton';
import { aboutText, images, stats } from '../data/content';
export default function AboutSection() {
return (
<section id="about" className="relative flex min-h-screen flex-col items-center justify-center gap-16 px-5 py-20 sm:gap-20 sm:px-8 md:gap-24 md:px-10">
<FadeIn delay={0.1} x={-80} y={0} duration={0.9} className="absolute left-[1%] top-[3%] w-[90px] sm:left-[2%] sm:w-[130px] md:left-[4%] md:w-[170px]">
<img src={images.signalMark} alt="" className="aspect-square w-full rounded-2xl object-cover" />
</FadeIn>
<FadeIn delay={0.25} x={-80} y={0} duration={0.9} className="absolute bottom-[4%] left-[3%] w-[100px] sm:left-[6%] sm:w-[150px] md:left-[10%] md:w-[200px]">
<img src={images.systems} alt="" className="aspect-[4/3] w-full -rotate-6 rounded-2xl object-cover" />
</FadeIn>
<FadeIn delay={0.15} x={80} y={0} duration={0.9} className="absolute right-[1%] top-[3%] w-[100px] sm:right-[2%] sm:w-[150px] md:right-[4%] md:w-[210px]">
<img src={images.leadership} alt="" className="aspect-[3/2] w-full rotate-6 rounded-2xl object-cover" />
</FadeIn>
<FadeIn delay={0.3} x={80} y={0} duration={0.9} className="absolute bottom-[4%] right-[3%] sm:right-[6%] md:right-[10%]">
<div className="flex gap-2 sm:gap-3">
{stats.map((s) => (
<div key={s.label} className="rounded-2xl border border-[#D7E2EA]/30 px-2.5 py-2 text-center sm:px-5 sm:py-3">
<div className="hero-heading text-2xl font-black leading-none sm:text-5xl md:text-6xl">{s.value}</div>
<div className="mt-1 whitespace-nowrap text-[9px] font-light uppercase tracking-wide text-[#D7E2EA] sm:text-xs">{s.label}</div>
</div>
))}
</div>
</FadeIn>
<div className="relative z-10 flex flex-col items-center gap-10 sm:gap-14 md:gap-16">
<FadeIn delay={0} y={40}>
<h2 className="hero-heading text-center font-black uppercase leading-none tracking-tight" style={{ fontSize: 'clamp(3rem, 12vw, 160px)' }}>
About me
</h2>
</FadeIn>
<AnimatedText
text={aboutText}
className="max-w-[560px] text-center font-medium leading-relaxed text-[#D7E2EA]"
style={{ fontSize: 'clamp(1rem, 2vw, 1.35rem)' }}
/>
</div>
<div className="relative z-10 mb-24 sm:mb-28 md:mb-0">
<ContactButton />
</div>
</section>
);
}
@@ -1,47 +0,0 @@
import { ArrowUpRight, Phone } from 'lucide-react';
import FadeIn from '../components/FadeIn';
import ContactButton from '../components/ContactButton';
import { profile } from '../data/content';
export default function ContactSection() {
return (
<section id="contact" className="flex flex-col items-center gap-10 px-5 pb-10 pt-20 sm:gap-14 sm:px-8 md:px-10 md:pt-28">
<FadeIn>
<h2 className="hero-heading text-center font-black uppercase leading-none tracking-tight" style={{ fontSize: 'clamp(2.6rem, 10vw, 140px)' }}>
Let’s talk
</h2>
</FadeIn>
<FadeIn>
<p className="max-w-[560px] text-center font-medium leading-relaxed text-[#D7E2EA]" style={{ fontSize: 'clamp(1rem, 2vw, 1.35rem)' }}>
Based in {profile.location}. Available for the right conversation.
</p>
</FadeIn>
<FadeIn>
<ContactButton />
</FadeIn>
<FadeIn className="w-full max-w-3xl">
<div className="grid gap-3 sm:grid-cols-2">
<a href={`mailto:${profile.email}`} className="flex min-w-0 items-center justify-between gap-3 rounded-3xl border border-[#D7E2EA]/25 p-5 text-[#D7E2EA] transition-colors hover:bg-[#D7E2EA]/10">
<span className="min-w-0">
<span className="block text-xs font-light uppercase tracking-widest opacity-70">Email</span>
<strong className="block break-words font-medium">{profile.email}</strong>
</span>
<ArrowUpRight size={20} className="shrink-0" />
</a>
<a href={profile.phoneHref} className="flex min-w-0 items-center justify-between gap-3 rounded-3xl border border-[#D7E2EA]/25 p-5 text-[#D7E2EA] transition-colors hover:bg-[#D7E2EA]/10">
<span className="min-w-0">
<span className="block text-xs font-light uppercase tracking-widest opacity-70">Phone</span>
<strong className="block font-medium">{profile.phone}</strong>
</span>
<Phone size={18} className="shrink-0" />
</a>
</div>
</FadeIn>
<footer className="mt-10 flex w-full flex-wrap justify-between gap-2 border-t border-[#D7E2EA]/20 pt-6 text-xs font-light uppercase tracking-wider text-[#D7E2EA]/70">
<span>{profile.name}</span>
<span>{profile.role}</span>
<span>{profile.location}</span>
</footer>
</section>
);
}
@@ -1,31 +0,0 @@
import FadeIn from '../components/FadeIn';
import { certifications, education } from '../data/content';
export default function CredentialsSection() {
return (
<section id="credentials" className="px-5 py-20 sm:px-8 sm:py-24 md:px-10 md:py-32">
<FadeIn>
<h2 className="hero-heading mb-10 text-center font-black uppercase leading-none tracking-tight sm:mb-14" style={{ fontSize: 'clamp(3rem, 12vw, 160px)' }}>
Credentials
</h2>
</FadeIn>
<FadeIn>
<div className="mx-auto mb-12 flex max-w-5xl flex-col items-center gap-1 text-center text-[#D7E2EA] sm:mb-16">
<span className="text-xs font-light uppercase tracking-widest opacity-70 sm:text-sm">Education</span>
<strong className="font-medium uppercase" style={{ fontSize: 'clamp(1.1rem, 2.2vw, 2.1rem)' }}>{education.degree}</strong>
<span className="font-light opacity-70">{education.school}, {education.year}</span>
</div>
</FadeIn>
<div className="mx-auto grid max-w-5xl gap-3 sm:grid-cols-2">
{certifications.map((c, i) => (
<FadeIn key={c} delay={(i % 2) * 0.08}>
<div className="flex h-full items-center gap-4 rounded-3xl border border-[#D7E2EA]/25 p-5 sm:p-6">
<span className="hero-heading text-3xl font-black leading-none sm:text-4xl">{String(i + 1).padStart(2, '0')}</span>
<p className="min-w-0 font-medium uppercase leading-snug text-[#D7E2EA]" style={{ fontSize: 'clamp(0.85rem, 1.4vw, 1.1rem)' }}>{c}</p>
</div>
</FadeIn>
))}
</div>
</section>
);
}
@@ -1,55 +0,0 @@
import { Check } from 'lucide-react';
import FadeIn from '../components/FadeIn';
import { foundations, roles } from '../data/content';
export default function ExperienceSection() {
return (
<section id="experience" className="bg-white px-5 pb-28 pt-20 sm:px-8 sm:pb-32 sm:pt-24 md:px-10 md:pb-40 md:pt-32">
<FadeIn>
<h2 className="mb-16 text-center font-black uppercase leading-none text-[#0C0C0C] sm:mb-20 md:mb-28" style={{ fontSize: 'clamp(3rem, 12vw, 160px)' }}>
Experience
</h2>
</FadeIn>
<div className="mx-auto max-w-5xl">
{roles.map((r, i) => (
<FadeIn key={r.role + r.organisation}>
<article className="grid gap-4 py-8 sm:py-10 md:grid-cols-[220px_1fr] md:gap-12 md:py-12" style={{ borderTop: '1px solid rgba(12, 12, 12, 0.15)' }}>
<div className="flex flex-col gap-1 text-[#0C0C0C]">
<span className="text-sm font-medium uppercase tracking-wider opacity-60">{r.dates}</span>
<span className="font-black uppercase leading-tight" style={{ fontSize: 'clamp(1.1rem, 1.8vw, 1.5rem)' }}>{r.organisation}</span>
<span className="text-sm font-light opacity-60">{String(i + 1).padStart(2, '0')}</span>
</div>
<div className="flex min-w-0 flex-col gap-4 text-[#0C0C0C]">
<h3 className="font-medium uppercase" style={{ fontSize: 'clamp(1rem, 2.2vw, 2.1rem)' }}>{r.role}</h3>
<p className="max-w-2xl font-light leading-relaxed opacity-70" style={{ fontSize: 'clamp(0.9rem, 1.6vw, 1.2rem)' }}>{r.summary}</p>
<ul className="flex max-w-2xl flex-col gap-3">
{r.points.map((p) => (
<li key={p} className="flex gap-3 font-light leading-relaxed" style={{ fontSize: 'clamp(0.85rem, 1.4vw, 1.05rem)' }}>
<Check size={18} className="mt-1 shrink-0" />
<span>{p}</span>
</li>
))}
</ul>
</div>
</article>
</FadeIn>
))}
<FadeIn>
<div className="grid gap-6 py-10 md:grid-cols-[220px_1fr] md:gap-12 md:py-12" style={{ borderTop: '1px solid rgba(12, 12, 12, 0.15)' }}>
<h3 className="text-sm font-medium uppercase tracking-wider text-[#0C0C0C] opacity-60">Earlier foundations</h3>
<div className="grid gap-x-8 gap-y-6 sm:grid-cols-2">
{foundations.map((f) => (
<div key={f.role + f.organisation} className="flex min-w-0 flex-col text-[#0C0C0C]">
<span className="text-xs font-medium uppercase tracking-wider opacity-60">{f.dates}</span>
<strong className="font-medium uppercase">{f.role}</strong>
<span className="font-light opacity-70">{f.organisation}</span>
</div>
))}
</div>
</div>
</FadeIn>
</div>
</section>
);
}
@@ -1,66 +0,0 @@
import FadeIn from '../components/FadeIn';
import Magnet from '../components/Magnet';
import ContactButton from '../components/ContactButton';
import { images, profile } from '../data/content';
const links = [
{ label: 'About', href: '#about' },
{ label: 'Skills', href: '#skills' },
{ label: 'Projects', href: '#projects' },
{ label: 'Contact', href: '#contact' },
];
export default function HeroSection() {
return (
<section className="relative flex h-screen min-h-[600px] flex-col" style={{ overflowX: 'clip' }}>
<FadeIn delay={0} y={-20}>
<nav className="flex justify-between px-6 pt-6 md:px-10 md:pt-8" aria-label="Section navigation">
{links.map((l) => (
<a
key={l.label}
href={l.href}
className="text-sm font-medium uppercase tracking-wider text-[#D7E2EA] transition-opacity duration-200 hover:opacity-70 md:text-lg lg:text-[1.4rem]"
>
{l.label}
</a>
))}
</nav>
</FadeIn>
<div className="overflow-hidden">
<FadeIn delay={0.15} y={40}>
<h1 className="hero-heading mt-6 w-full whitespace-nowrap text-center text-[16.5vw] font-black uppercase leading-none tracking-tight sm:mt-4 md:-mt-5">
Hi, i’m {profile.firstName}
</h1>
</FadeIn>
</div>
<div className="absolute left-1/2 top-1/2 z-10 w-[min(280px,52vh)] -translate-x-1/2 -translate-y-1/2 sm:bottom-0 sm:top-auto sm:w-[min(360px,56vh)] sm:translate-y-0 md:w-[min(440px,58vh)] lg:w-[min(520px,58vh)]">
<FadeIn delay={0.6} y={30}>
<Magnet padding={150} strength={3} activeTransition="transform 0.3s ease-out" inactiveTransition="transform 0.6s ease-in-out" className="!block">
<img
src={images.portrait}
alt="Keith Rana at his work desk"
className="aspect-square w-full border border-[#D7E2EA]/30 object-cover object-top"
style={{ borderRadius: '999px 999px 32px 32px' }}
/>
</Magnet>
</FadeIn>
</div>
<div className="mt-auto flex items-end justify-between gap-4 px-6 pb-7 sm:pb-8 md:px-10 md:pb-10">
<FadeIn delay={0.35} y={20}>
<p
className="max-w-[160px] font-light uppercase leading-snug tracking-wide text-[#D7E2EA] sm:max-w-[220px] md:max-w-[260px]"
style={{ fontSize: 'clamp(0.7rem, 1.4vw, 1.5rem)' }}
>
{profile.tagline}
</p>
</FadeIn>
<FadeIn delay={0.5} y={20} className="relative z-20">
<ContactButton />
</FadeIn>
</div>
</section>
);
}
@@ -1,75 +0,0 @@
import { useEffect, useRef, useState } from 'react';
import type { ReactNode } from 'react';
import { expertise, images } from '../data/content';
const TILE_W = 420;
const GAP = 12;
const photoTiles: { src: string; alt: string; position: string }[] = [
{ src: images.portrait, alt: 'Keith Rana at his work desk', position: 'center 20%' },
{ src: images.leadership, alt: 'Hands organising a service-delivery briefing', position: 'center' },
{ src: images.systems, alt: 'Abstract composition of connected service pathways', position: 'center' },
];
const photos = photoTiles.map((t) => (
<img
key={t.alt}
src={t.src}
alt={t.alt}
loading="lazy"
className="h-[270px] w-[420px] shrink-0 rounded-2xl object-cover"
style={{ objectPosition: t.position }}
/>
));
const skills = expertise.map((e, i) => (
<div
key={e.name}
className="flex h-[270px] w-[420px] shrink-0 flex-col justify-between rounded-2xl border border-[#D7E2EA]/25 p-6"
style={{ background: 'linear-gradient(135deg, #14161a, #1d2127)' }}
>
<span className="hero-heading text-5xl font-black leading-none">{String(i + 1).padStart(2, '0')}</span>
<span className="text-2xl font-medium uppercase leading-tight text-[#D7E2EA]">{e.name}</span>
</div>
));
function Row({ tiles, setWidth, transform }: { tiles: ReactNode[]; setWidth: number; transform: string }) {
return (
<div className="flex" style={{ gap: GAP, marginLeft: -setWidth, transform, willChange: 'transform' }} aria-hidden={tiles === skills}>
{[0, 1, 2].map((copy) => (
<div key={copy} className="flex" style={{ gap: GAP }}>
{tiles}
</div>
))}
</div>
);
}
export default function MarqueeSection() {
const ref = useRef<HTMLElement>(null);
const [offset, setOffset] = useState(0);
useEffect(() => {
const onScroll = () => {
if (!ref.current) return;
const top = ref.current.getBoundingClientRect().top + window.scrollY;
setOffset((window.scrollY - top + window.innerHeight) * 0.3);
};
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
window.addEventListener('resize', onScroll);
return () => {
window.removeEventListener('scroll', onScroll);
window.removeEventListener('resize', onScroll);
};
}, []);
const width = (n: number) => n * (TILE_W + GAP) + GAP;
return (
<section ref={ref} className="flex flex-col gap-3 bg-[#0C0C0C] pb-10 pt-24 sm:pt-32 md:pt-40" style={{ overflow: 'clip' }}>
<Row tiles={photos} setWidth={width(photos.length)} transform={`translateX(${offset - 200}px)`} />
<Row tiles={skills} setWidth={width(skills.length)} transform={`translateX(${-(offset - 200)}px)`} />
</section>
);
}
@@ -1,87 +0,0 @@
import { useRef } from 'react';
import { motion, useScroll, useTransform } from 'framer-motion';
import { Check } from 'lucide-react';
import FadeIn from '../components/FadeIn';
import ProjectDiagram from '../components/ProjectDiagram';
import { projects } from '../data/content';
type Project = (typeof projects)[number];
const radius = 'rounded-[40px] sm:rounded-[50px] md:rounded-[60px]';
function ProjectCard({ project, index, total }: { project: Project; index: number; total: number }) {
const ref = useRef<HTMLDivElement>(null);
const { scrollYProgress } = useScroll({ target: ref, offset: ['start end', 'start start'] });
const targetScale = 1 - (total - 1 - index) * 0.03;
const scale = useTransform(scrollYProgress, [0, 1], [1, targetScale]);
return (
<div ref={ref} className="sticky top-24 min-h-[85vh]">
<motion.div
className={`border-2 border-[#D7E2EA] bg-[#0C0C0C] p-4 sm:p-6 md:p-8 ${radius}`}
style={{ scale, top: `${index * 28}px`, position: 'relative', transformOrigin: 'top center' }}
>
<div className="mb-4 flex flex-col gap-3 sm:mb-6 md:flex-row md:items-center md:justify-between md:gap-10">
<div className="flex items-center gap-4 sm:gap-8">
<span className="hero-heading font-black leading-none" style={{ fontSize: 'clamp(3rem, 10vw, 140px)' }}>
{String(index + 1).padStart(2, '0')}
</span>
<div className="min-w-0 text-[#D7E2EA]">
<p className="text-xs font-light uppercase tracking-widest opacity-70 sm:text-sm">{project.category}</p>
<h3 className="font-medium uppercase" style={{ fontSize: 'clamp(1rem, 2.2vw, 2.1rem)' }}>{project.name}</h3>
</div>
</div>
<p className="max-w-xl px-1 font-light leading-relaxed text-[#D7E2EA]/70" style={{ fontSize: 'clamp(0.85rem, 1.5vw, 1.15rem)' }}>
{project.body}
</p>
</div>
<div className="flex flex-col gap-3 sm:flex-row sm:gap-4">
<div className="flex flex-col gap-3 sm:w-[40%] sm:gap-4">
<img
src={project.photo}
alt={`${project.name}: illustrative photo`}
loading="lazy"
className={`w-full object-cover ${radius}`}
style={{ height: 'clamp(150px, 16vw, 230px)', objectPosition: project.photoPosition }}
/>
<div
className={`flex flex-1 flex-col justify-center gap-3 border border-[#D7E2EA]/25 p-5 sm:gap-4 sm:p-6 md:p-8 ${radius}`}
style={{ background: 'linear-gradient(135deg, #14161a, #1d2127)', minHeight: 'clamp(160px, 22vw, 340px)' }}
>
<span className="text-xs font-medium uppercase tracking-widest text-[#D7E2EA]/60 sm:text-sm">Delivered</span>
<ul className="flex flex-col gap-2 sm:gap-3">
{project.delivered.map((d) => (
<li key={d} className="flex gap-2 font-light leading-snug text-[#D7E2EA]" style={{ fontSize: 'clamp(0.8rem, 1.3vw, 1.1rem)' }}>
<Check className="mt-0.5 h-4 w-4 shrink-0" />
<span>{d}</span>
</li>
))}
</ul>
</div>
</div>
<div
className={`order-first flex items-center justify-center border border-[#D7E2EA]/25 p-3 sm:order-none sm:w-[60%] sm:p-5 md:p-8 ${radius}`}
style={{ background: 'linear-gradient(135deg, #14161a, #1d2127)' }}
>
<ProjectDiagram kind={project.diagram} className="h-auto w-full" style={{ maxHeight: "clamp(240px, 30vw, 430px)" }} />
</div>
</div>
</motion.div>
</div>
);
}
export default function ProjectsSection() {
return (
<section id="projects" className="relative z-10 -mt-10 rounded-t-[40px] bg-[#0C0C0C] px-5 py-20 sm:-mt-12 sm:rounded-t-[50px] sm:px-8 sm:py-24 md:-mt-14 md:rounded-t-[60px] md:px-10 md:py-32">
<FadeIn>
<h2 className="hero-heading mb-16 text-center font-black uppercase leading-none tracking-tight sm:mb-20 md:mb-28" style={{ fontSize: 'clamp(3rem, 12vw, 160px)' }}>
Projects
</h2>
</FadeIn>
{projects.map((p, i) => (
<ProjectCard key={p.name} project={p} index={i} total={projects.length} />
))}
</section>
);
}
@@ -1,32 +0,0 @@
import FadeIn from '../components/FadeIn';
import { expertise } from '../data/content';
export default function ServicesSection() {
return (
<section id="skills" className="rounded-t-[40px] bg-white px-5 pt-20 sm:rounded-t-[50px] sm:px-8 sm:pt-24 md:rounded-t-[60px] md:px-10 md:pt-32">
<h2 className="mb-16 text-center font-black uppercase text-[#0C0C0C] sm:mb-20 md:mb-28" style={{ fontSize: 'clamp(3rem, 12vw, 160px)' }}>
Capability
</h2>
<div className="mx-auto max-w-5xl">
{expertise.map((s, i) => (
<FadeIn key={s.name} delay={(i % 4) * 0.1}>
<div
className="flex items-start gap-4 py-8 sm:gap-8 sm:py-10 md:gap-12 md:py-12"
style={{ borderTop: i === 0 ? undefined : '1px solid rgba(12, 12, 12, 0.15)' }}
>
<span className="min-w-[1.6em] font-black leading-none text-[#0C0C0C]" style={{ fontSize: 'clamp(3rem, 10vw, 140px)' }}>
{String(i + 1).padStart(2, '0')}
</span>
<div className="flex min-w-0 flex-col gap-3 text-[#0C0C0C]">
<h3 className="font-medium uppercase" style={{ fontSize: 'clamp(1rem, 2.2vw, 2.1rem)' }}>{s.name}</h3>
<p className="max-w-2xl font-light leading-relaxed opacity-60" style={{ fontSize: 'clamp(0.85rem, 1.6vw, 1.25rem)' }}>
{s.description}
</p>
</div>
</div>
</FadeIn>
))}
</div>
</section>
);
}
-1
View File
@@ -1 +0,0 @@
/// <reference types="vite/client" />
-6
View File
@@ -1,6 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{ts,tsx}'],
theme: { extend: { fontFamily: { sans: ['Kanit', 'sans-serif'] } } },
plugins: [],
};
-19
View File
@@ -1,19 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["src"]
}
-9
View File
@@ -1,9 +0,0 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
// INLINE=1 bakes every image and font into the JS/CSS (used for the one-file cloud preview).
export default defineConfig({
plugins: [react()],
base: './',
build: { assetsInlineLimit: process.env.INLINE ? 100_000_000 : 4096 },
});
+2
View File
@@ -0,0 +1,2 @@
.hyperframes/artifacts/
node_modules/
@@ -0,0 +1,233 @@
<!--
Daisy Days — caption skin (preset-local source).
This is the preset's own lower-third karaoke caption look. product-launch-video's
Step 2 copies it into the project as caption-skin.html; captions.mjs then fills the
three reserved holes, wraps the fragment in a <template>, and writes
compositions/captions.html:
· the empty GROUPS array → the engine's scene-aware word groups
· the 0 duration + 0 width/height → real total + canvas size
· the empty data-brand-tokens style → :root tokens derived from the project's frame.md
Token contract — reference ONLY this fixed vocab (captions.mjs injects it from
frame.md, so the Step-2 brand overlay flows through; each var has a daisy-days literal
fallback for standalone preview):
--cap-ink · --cap-canvas · --cap-accent · --cap-accent-2 · --font-display ·
--font-body · --cap-band-top · --cap-band-height
Hooks (do not rename): .caption-group / .caption-word + states .is-active / .is-spoken,
root data-composition-id="captions", timeline at window.__timelines["captions"].
State changes use gsap.set({className}) (applied on the engine's frame-by-frame seek);
never tl.call() callbacks (seek does not fire them → wrong state on render).
Visual: a cheerful picture-book card — a cream surface inside a 3px charcoal outline
with a hard 6px charcoal offset shadow (no blur) and a generous 24px radius, the
sticker-on-paper signature. Fredoka One glyphs, sentence case, charcoal ink. Upcoming
words sit in muted charcoal; the current word pops into a rounded sunny-pastel candy
block (coral fill, 3px charcoal outline, hard 3px offset shadow, 12px radius) with a
bouncy scale pop; spoken words settle to solid charcoal, the candy block cleared.
-->
<script src="assets/vendor/gsap.min.js"></script>
<!-- captions.mjs injects the project's :root tokens here -->
<style data-brand-tokens></style>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Fredoka One"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#captions-root {
position: absolute;
inset: 0;
pointer-events: none;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
}
/* the keep-out band, from captionBand(H) — frame content stays above it */
.caption-stage {
position: absolute;
left: 0;
right: 0;
top: var(--cap-band-top, 900px);
height: var(--cap-band-height, 180px);
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
/* every group is absolutely stacked over the SAME band — exactly one shown at a
time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
}
/* THE picture-book card — cream surface, 3px charcoal outline, generous 24px radius,
a hard 6px charcoal offset shadow (no blur): the sticker-on-paper signature */
.caption-pill {
max-width: 80%;
padding: 18px 40px 22px;
background: var(--cap-canvas, #f5f0e6);
border: 3px solid var(--cap-ink, #2d2d2d);
border-radius: 24px;
box-shadow: 6px 6px 0 var(--cap-ink, #2d2d2d);
}
.caption-line {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.18em 0.32em;
font-family: var(--font-display, "Fredoka"), sans-serif;
font-weight: 600;
font-size: clamp(38px, 4.2vw, 56px);
line-height: 1.22;
letter-spacing: 0.02em;
}
/* upcoming — muted charcoal, no candy block */
.caption-word {
display: inline-block;
padding: 0.02em 0.16em;
border-radius: 12px;
color: color-mix(in srgb, var(--cap-ink, #2d2d2d) 52%, var(--cap-canvas, #f5f0e6));
}
/* current word — THE sunny-pastel candy block: a rounded coral chip with a 3px
charcoal outline + a hard 3px charcoal offset shadow (no blur), charcoal on top */
.caption-word.is-active {
color: var(--cap-ink, #2d2d2d);
background: var(--cap-accent, #f8635f);
border-radius: 12px;
box-shadow:
0 0 0 3px var(--cap-ink, #2d2d2d),
3px 5px 0 0 var(--cap-ink, #2d2d2d);
}
/* already spoken — solid charcoal, candy block cleared */
.caption-word.is-spoken {
color: var(--cap-ink, #2d2d2d);
background: transparent;
box-shadow: none;
}
/* optional per-word accents (dormant until a word-classer tags them) */
/* brand / key term — a butter candy chip, the cheerful second pastel */
.caption-word.cap-brand {
color: var(--cap-ink, #2d2d2d);
background: var(--cap-accent-2, #fde68a);
border-radius: 12px;
box-shadow: 0 0 0 3px var(--cap-ink, #2d2d2d);
}
/* numerals / data — Quicksand, slightly recessed */
.caption-word.cap-num {
font-family: var(--font-body, "Quicksand"), system-ui, sans-serif;
font-weight: 700;
color: color-mix(in srgb, var(--cap-ink, #2d2d2d) 78%, var(--cap-canvas, #f5f0e6));
}
/* emphasis — a butter underline rule (the second sunny pastel) */
.caption-word.cap-cta {
text-decoration: underline;
text-decoration-thickness: 3px;
text-decoration-color: var(--cap-accent-2, #fde68a);
text-underline-offset: 0.12em;
}
@media (max-aspect-ratio: 9/16) {
.caption-pill {
max-width: 92%;
}
.caption-line {
font-size: clamp(32px, 5.2vw, 46px);
}
}
</style>
<div
id="captions-root"
data-composition-id="captions"
data-timeline-locked
data-start="0"
data-duration="0"
data-fps="30"
data-width="0"
data-height="0"
>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="caption-stage"></div>
</div>
</div>
<script>
// captions.mjs injects the engine's scene-aware groups + real duration here.
var GROUPS = [];
var DURATION = 0;
(function () {
var stage = document.getElementById("caption-stage");
// build DOM: one .caption-group per group, .caption-word per word
GROUPS.forEach(function (group, g) {
var groupEl = document.createElement("div");
groupEl.className = "caption-group";
groupEl.id = "caption-group-" + g;
var pill = document.createElement("div");
pill.className = "caption-pill";
var line = document.createElement("div");
line.className = "caption-line";
(group.words || []).forEach(function (w, i) {
var span = document.createElement("span");
span.className = "caption-word";
span.id = "caption-word-" + g + "-" + i;
span.textContent = String(w.text);
line.appendChild(span);
});
pill.appendChild(line);
groupEl.appendChild(pill);
stage.appendChild(groupEl);
});
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (group, g) {
var groupEl = document.getElementById("caption-group-" + g);
var words = group.words || [];
var next = GROUPS[g + 1];
var isLast = g === GROUPS.length - 1;
var start = Math.max(0, Number(group.start));
var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
if (end <= start) end = start + 0.01;
// group on/off — exactly one group visible at a time
tl.set(groupEl, { opacity: 1 }, start);
tl.set(groupEl, { opacity: 0 }, end);
// 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
// engine's frame-by-frame seek; .call callbacks would NOT fire).
words.forEach(function (w, i) {
var el = document.getElementById("caption-word-" + g + "-" + i);
var at = Math.max(start, Number(w.start));
tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
tl.set(el, { className: "caption-word is-active" }, at); // current
tl.fromTo(el, { scale: 0.82 }, { scale: 1, duration: 0.2, ease: "back.out(2.6)" }, at);
if (i + 1 < words.length) {
var nextAt = Math.max(start, Number(words[i + 1].start));
tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
}
});
if (words.length) {
var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
}
});
// full-span anchor so the sub-comp timeline spans the whole video
tl.to({}, { duration: DURATION }, 0);
window.__timelines["captions"] = tl;
})();
</script>
@@ -0,0 +1 @@
635d4927-f2e8-44dd-9cbf-1b5d4f285f53
@@ -0,0 +1 @@
{"version":1,"paths":["renders/video.mp4"]}
@@ -0,0 +1,939 @@
{
"version": 1,
"historyId": "635d4927-f2e8-44dd-9cbf-1b5d4f285f53",
"currentRevision": "6999d82e-2d3b-4026-b89c-d3107735bf7e",
"pendingChanges": false,
"files": {
".media/manifest.jsonl": {
"hash": "0619ff69adc91e800fdbfe4cd508b67d21791a1ef4eb8b043518158c0ce3483e",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/06/0619ff69adc91e800fdbfe4cd508b67d21791a1ef4eb8b043518158c0ce3483e"
},
"AGENTS.md": {
"hash": "1d4f0c8ca2507b94fd7c9df709fff01416adbd51a0f922b9d6ed2f40129147d5",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/1d/1d4f0c8ca2507b94fd7c9df709fff01416adbd51a0f922b9d6ed2f40129147d5"
},
"BRIEF.md": {
"hash": "14d649b0556246e716324f80fc2b65b27aaa4a25d5f508c9e6fceb702f29ebb4",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/14/14d649b0556246e716324f80fc2b65b27aaa4a25d5f508c9e6fceb702f29ebb4"
},
"CLAUDE.md": {
"hash": "1d4f0c8ca2507b94fd7c9df709fff01416adbd51a0f922b9d6ed2f40129147d5",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/1d/1d4f0c8ca2507b94fd7c9df709fff01416adbd51a0f922b9d6ed2f40129147d5"
},
"SCRIPT.md": {
"hash": "5fbae06748238e1717948353b077a8776b7f88221790923652363834c49fad9a",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/5f/5fbae06748238e1717948353b077a8776b7f88221790923652363834c49fad9a"
},
"STORYBOARD.md": {
"hash": "34f5c1242e964e76cf330da278bb80d9174ba52197439863a9c32f6a2bf23383",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/34/34f5c1242e964e76cf330da278bb80d9174ba52197439863a9c32f6a2bf23383"
},
"assets/fonts/fredoka-600.woff2": {
"hash": "b62f2898de150f019961f4884a49f83dcd2ce74375be5e0992c594ebb8f30294",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/b6/b62f2898de150f019961f4884a49f83dcd2ce74375be5e0992c594ebb8f30294"
},
"assets/fonts/quicksand-var.woff2": {
"hash": "2add7d60b1cd2ab84c9967e23d5ec08eb3fc9635c46855b17d59404dec6b410e",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/2a/2add7d60b1cd2ab84c9967e23d5ec08eb3fc9635c46855b17d59404dec6b410e"
},
"assets/voice/01.wav": {
"hash": "ef67616aef5fe0312c5a5a833eaba38438d7a1248057dcdd00b4d7053b064e98",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/ef/ef67616aef5fe0312c5a5a833eaba38438d7a1248057dcdd00b4d7053b064e98"
},
"assets/voice/02.wav": {
"hash": "c4b74e279af592152edbe412bc477e2fe8da05cb5add99bdbd55ed536d80cc1b",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/c4/c4b74e279af592152edbe412bc477e2fe8da05cb5add99bdbd55ed536d80cc1b"
},
"assets/voice/03.wav": {
"hash": "e8537998055079e160467161206dcc3c51d8cc98411f89f42e478f3672fe9630",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/e8/e8537998055079e160467161206dcc3c51d8cc98411f89f42e478f3672fe9630"
},
"assets/voice/04.wav": {
"hash": "e9e118731fb136fab2751e5392c47a1b89598acac4bee763962ba0e6f3ede377",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/e9/e9e118731fb136fab2751e5392c47a1b89598acac4bee763962ba0e6f3ede377"
},
"assets/voice/05.wav": {
"hash": "70c9e8aad9f6f0167e2d9e31d830a27e0aa313e1307085b73e858c8108dc83e8",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/70/70c9e8aad9f6f0167e2d9e31d830a27e0aa313e1307085b73e858c8108dc83e8"
},
"assets/voice/06.wav": {
"hash": "9441a5b1472967c809efb6ff246e25800e74fd9e6372a748c73568e4450108c3",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/94/9441a5b1472967c809efb6ff246e25800e74fd9e6372a748c73568e4450108c3"
},
"assets/voice/07.wav": {
"hash": "4fbc575c406ead86d77a06c79dcd680303c7a935147208936c83969d0b4121b3",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/4f/4fbc575c406ead86d77a06c79dcd680303c7a935147208936c83969d0b4121b3"
},
"assets/voice/08.wav": {
"hash": "7859a7ded9d7d2fe821b93162b97790c70b6929ff07211f230f9cc0f75764e95",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/78/7859a7ded9d7d2fe821b93162b97790c70b6929ff07211f230f9cc0f75764e95"
},
"assets/voice/09.wav": {
"hash": "0864df842c3d65cfb43aafd936de8bc963cd91aca56248475a8eb2c968fd4a55",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/08/0864df842c3d65cfb43aafd936de8bc963cd91aca56248475a8eb2c968fd4a55"
},
"audio_engine_meta.json": {
"hash": "4f2da6bf20a3982fb9ed326e7a457cac7e7128ef7e5e08d585f58bbf47a8c397",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/4f/4f2da6bf20a3982fb9ed326e7a457cac7e7128ef7e5e08d585f58bbf47a8c397"
},
"audio_meta.json": {
"hash": "ecc4980e89d34d9873cdc8a92f954b38dcf094112a5bab89fde7fe98a321b624",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/ec/ecc4980e89d34d9873cdc8a92f954b38dcf094112a5bab89fde7fe98a321b624"
},
"audio_request.json": {
"hash": "76765154cffa684843af5b4bc6492f4e22d7a6aa9d2b6a716351eceab241a464",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/76/76765154cffa684843af5b4bc6492f4e22d7a6aa9d2b6a716351eceab241a464"
},
"caption-overrides.json": {
"hash": "37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/37/37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570"
},
"caption_groups.json": {
"hash": "8b53722d14f329a000bcd109192eaedf8f098e16f4e553381c1a139843f8fb94",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/8b/8b53722d14f329a000bcd109192eaedf8f098e16f4e553381c1a139843f8fb94"
},
"capture/extracted/asset-descriptions.md": {
"hash": "92c2cfe647902a93e595e21cebab23e9af1a2fefbec14cf22d0a5af437dc6964",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/92/92c2cfe647902a93e595e21cebab23e9af1a2fefbec14cf22d0a5af437dc6964"
},
"capture/extracted/tokens.json": {
"hash": "2f533a1d991bdf84ad653ceb0061fbefd42a19f7b4dc22fe9b37f7156ce79c32",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/2f/2f533a1d991bdf84ad653ceb0061fbefd42a19f7b4dc22fe9b37f7156ce79c32"
},
"capture/extracted/visible-text.txt": {
"hash": "39c044a9ee527c4a857cb65b5f0dc3f855fca4a7d17beb8ffb2cf24089f5d54f",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/39/39c044a9ee527c4a857cb65b5f0dc3f855fca4a7d17beb8ffb2cf24089f5d54f"
},
"compositions/captions.html": {
"hash": "c7a724d68db626674aa77f2e4b9967b63cb606d3dd1156796d0a319e23f1e0e8",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/c7/c7a724d68db626674aa77f2e4b9967b63cb606d3dd1156796d0a319e23f1e0e8"
},
"compositions/frames/01-hook.html": {
"hash": "48852887a870cc3daa0c98e65504ee207475b7add2b4eecb79fe304b0539441b",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/48/48852887a870cc3daa0c98e65504ee207475b7add2b4eecb79fe304b0539441b"
},
"compositions/frames/02-worry.html": {
"hash": "36c53f5f5bdcbf9ebb6a7c73dc22017e80dfc64bd98470d78af8ef7846bb1d94",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/36/36c53f5f5bdcbf9ebb6a7c73dc22017e80dfc64bd98470d78af8ef7846bb1d94"
},
"compositions/frames/03-meet.html": {
"hash": "33f5f3cc5d827610300ff1198268b19f675d3adc26898e76ed11f7a89be5ef67",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/33/33f5f3cc5d827610300ff1198268b19f675d3adc26898e76ed11f7a89be5ef67"
},
"compositions/frames/04-limits.html": {
"hash": "fd4173fd65eff6f6ca43680ba4824545ae6bb57eb8710780a035c9dd120ebc37",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/fd/fd4173fd65eff6f6ca43680ba4824545ae6bb57eb8710780a035c9dd120ebc37"
},
"compositions/frames/05-apps.html": {
"hash": "5da142d1be64b6d51d5c707ac913fdf819a0419288696974c2775b1f35e3654e",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/5d/5da142d1be64b6d51d5c707ac913fdf819a0419288696974c2775b1f35e3654e"
},
"compositions/frames/06-location.html": {
"hash": "199c8b0642ea141b105b1eb7f62ff545b4b3f77dcbf1767f6581cdceac9ac019",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/19/199c8b0642ea141b105b1eb7f62ff545b4b3f77dcbf1767f6581cdceac9ac019"
},
"compositions/frames/07-balance.html": {
"hash": "6ed5fc2fb15ca1573c5ff92fc4d6c73aa89808776f0236d1934f0537b3e42a8e",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/6e/6ed5fc2fb15ca1573c5ff92fc4d6c73aa89808776f0236d1934f0537b3e42a8e"
},
"compositions/frames/08-platforms.html": {
"hash": "b0a5b3c9b1d4bd94c0b3f9f7b71e7137632d8e400add3f5e93958acb7036d99a",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/b0/b0a5b3c9b1d4bd94c0b3f9f7b71e7137632d8e400add3f5e93958acb7036d99a"
},
"compositions/frames/09-cta.html": {
"hash": "d9e539932cec2431c43f6125a26bb452abc66a2f7c616b1d1e26da5911e80e07",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/d9/d9e539932cec2431c43f6125a26bb452abc66a2f7c616b1d1e26da5911e80e07"
},
"frame.md": {
"hash": "42d827dbd279990052e171d04380b5e6bbc111ae391dc283deb7a452e70b9b97",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/42/42d827dbd279990052e171d04380b5e6bbc111ae391dc283deb7a452e70b9b97"
},
"hyperframes.json": {
"hash": "44d4e8dcefebce0a13f5afa82aa270b0b19a1d3f82e434f92616010cee775dc4",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/44/44d4e8dcefebce0a13f5afa82aa270b0b19a1d3f82e434f92616010cee775dc4"
},
"index.html": {
"hash": "7f9f086ba7fb700d1308e5f9efee068c11034983a04dfc6baed7b8096034ea32",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/7f/7f9f086ba7fb700d1308e5f9efee068c11034983a04dfc6baed7b8096034ea32"
},
"meta.json": {
"hash": "1497903acd6ced7f19d4cf6c76f104f5656593c484427d89aea883c8246ad541",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/14/1497903acd6ced7f19d4cf6c76f104f5656593c484427d89aea883c8246ad541"
},
"package.json": {
"hash": "6873ac1737fc27fd1046b92a493604be2ebc328b232d134f04f3b65c07200b4f",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/68/6873ac1737fc27fd1046b92a493604be2ebc328b232d134f04f3b65c07200b4f"
},
"assets/vendor/gsap.min.js": {
"hash": "c174bfce53a729418d57a8ad8625e7247c793a22fef8e2851e3cfa3de9cd8280",
"content": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/c1/c174bfce53a729418d57a8ad8625e7247c793a22fef8e2851e3cfa3de9cd8280"
}
},
"revisions": {
"ad80a38d-f38a-4cc1-9042-d179e2b0e6f3": {
"actor": {
"kind": "outside",
"name": "Outside"
},
"label": "Changed outside the app",
"startedAt": 1792016103184,
"endedAt": 1792016103225,
"files": {
"compositions/frames/03-meet.html": {
"before": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/96/96f8c3849528fd883b0c70f66c0fd291aa3030adaaa346a8ebdf3c95aeb38d7e",
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/33/33f5f3cc5d827610300ff1198268b19f675d3adc26898e76ed11f7a89be5ef67"
},
"compositions/frames/09-cta.html": {
"before": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/a9/a924e8f0e68f92423d70d4d8e086d81f0596cec3d6ad1120d1c7e930fd086fad",
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/35/3566b01fb9bb64e4d22e86877179c7b7cbee0a98f4bb3c354c97db2b85b13b15"
}
}
},
"2748be21-fde2-40ba-a2c0-fddfb7e47431": {
"actor": {
"kind": "outside",
"name": "Outside"
},
"label": "Changed outside the app",
"startedAt": 1792016129829,
"endedAt": 1792016129859,
"files": {
"assets/vendor/gsap.min.js": {
"before": null,
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/c1/c174bfce53a729418d57a8ad8625e7247c793a22fef8e2851e3cfa3de9cd8280"
},
"index.html": {
"before": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/cb/cb8df64b81d4e2ad46e13002a5697224c5e21c3c04e925d3302f8c4ceb0a58c3",
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/7f/7f9f086ba7fb700d1308e5f9efee068c11034983a04dfc6baed7b8096034ea32"
}
}
},
"f155d8de-7fb6-4e12-9291-60b3874a47cc": {
"actor": {
"kind": "outside",
"name": "Outside"
},
"label": "Changed outside the app",
"startedAt": 1792016156924,
"endedAt": 1792016156944,
"files": {
"compositions/captions.html": {
"before": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/d9/d9ed837d5d7a9893cf38400b360adc8d5636489ede0eaffb3f4e341faab51c04",
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/c7/c7a724d68db626674aa77f2e4b9967b63cb606d3dd1156796d0a319e23f1e0e8"
},
"compositions/frames/01-hook.html": {
"before": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/1f/1f8e5283549f95e82cfa7324067517b68a5c852ee0400e39a4b8eeadb8d2afdb",
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/f4/f4e6a9d0f2414e3f78e0b7ec499f3adf397e480ad3e3cbfc13110bd9ee6553f3"
}
}
},
"6999d82e-2d3b-4026-b89c-d3107735bf7e": {
"actor": {
"kind": "outside",
"name": "Outside"
},
"label": "Changed outside the app",
"startedAt": 1792016208793,
"endedAt": 1792016208815,
"files": {
"compositions/frames/01-hook.html": {
"before": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/f4/f4e6a9d0f2414e3f78e0b7ec499f3adf397e480ad3e3cbfc13110bd9ee6553f3",
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/48/48852887a870cc3daa0c98e65504ee207475b7add2b4eecb79fe304b0539441b"
},
"compositions/frames/02-worry.html": {
"before": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/4d/4df905219fb45cf58401646096425b66025ec56d32b12fca340e88e5a07ac264",
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/36/36c53f5f5bdcbf9ebb6a7c73dc22017e80dfc64bd98470d78af8ef7846bb1d94"
},
"compositions/frames/04-limits.html": {
"before": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/0b/0b852cb30390684e6002b762165405fabb56cad4955fc8287e84d595bd1891d3",
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/fd/fd4173fd65eff6f6ca43680ba4824545ae6bb57eb8710780a035c9dd120ebc37"
},
"compositions/frames/09-cta.html": {
"before": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/35/3566b01fb9bb64e4d22e86877179c7b7cbee0a98f4bb3c354c97db2b85b13b15",
"after": "/root/.cache/hyperframes/history/635d4927-f2e8-44dd-9cbf-1b5d4f285f53/blobs/d9/d9e539932cec2431c43f6125a26bb452abc66a2f7c616b1d1e26da5911e80e07"
}
}
}
},
"checks": {
"latest": {
"revision": "6999d82e-2d3b-4026-b89c-d3107735bf7e",
"id": "31e798e4-ad68-444f-b4c3-170cece3aeb1"
},
"revisions": {
"ad80a38d-f38a-4cc1-9042-d179e2b0e6f3": [
{
"id": "bdef997d-ad31-4453-8fb8-c432364aadaf",
"scope": "project",
"metadata": {
"ok": false,
"browserSkipped": false,
"options": {
"samples": 9,
"atTransitions": false,
"maxIssues": 80,
"collapseStatic": true,
"tolerance": 2,
"timeout": 3000,
"contrast": true,
"strict": false,
"snapshots": false,
"browserGpuMode": "auto"
}
},
"createdAt": 1792016113510,
"contentHash": "cbaddad6d2047ae01a30682fc6f678b633adb8e61cc6d7b2cd135829f2f6a67d",
"revision": "ad80a38d-f38a-4cc1-9042-d179e2b0e6f3",
"inputFingerprint": "69a931e0c93bee3efd6531a54ac48f71e88a2fc05106c9891ccb8713064b92ee",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
}
],
"2748be21-fde2-40ba-a2c0-fddfb7e47431": [
{
"id": "0e229a06-9c97-4ec5-badd-59e38d09bb91",
"scope": "project",
"metadata": {
"ok": false,
"browserSkipped": false,
"options": {
"samples": 9,
"atTransitions": false,
"maxIssues": 80,
"collapseStatic": true,
"tolerance": 2,
"timeout": 3000,
"contrast": true,
"strict": false,
"snapshots": false,
"browserGpuMode": "auto"
}
},
"createdAt": 1792016142676,
"contentHash": "cd9b82c350369ca166d5210b7192e5c1f0394570ec246f30f0a3134ed89d3e9a",
"revision": "2748be21-fde2-40ba-a2c0-fddfb7e47431",
"inputFingerprint": "86db889a4aed182e0d1dc77ef05552d96c77a15f49c3c15e3fb33051e7f719e6",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
}
],
"f155d8de-7fb6-4e12-9291-60b3874a47cc": [
{
"id": "2e9e2e50-ea4c-4ce0-ae6d-feb42f2db77c",
"scope": "project",
"metadata": {
"ok": true,
"browserSkipped": false,
"options": {
"samples": 9,
"atTransitions": false,
"maxIssues": 80,
"collapseStatic": true,
"tolerance": 2,
"timeout": 3000,
"contrast": true,
"strict": false,
"snapshots": false,
"browserGpuMode": "auto"
}
},
"createdAt": 1792016169247,
"contentHash": "3c336e043480b32bbb2ecf2a89aa5ad157bf0b6a1243806c47aeb6fba4bb5858",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
}
],
"6999d82e-2d3b-4026-b89c-d3107735bf7e": [
{
"id": "31e798e4-ad68-444f-b4c3-170cece3aeb1",
"scope": "project",
"report": "/home/user/cycle-atlas/videos/lookatu-promo/.hyperframes/artifacts/checks/31e798e4-ad68-444f-b4c3-170cece3aeb1.json",
"metadata": {
"ok": true,
"browserSkipped": false,
"options": {
"samples": 9,
"atTransitions": false,
"maxIssues": 80,
"collapseStatic": true,
"tolerance": 2,
"timeout": 3000,
"contrast": true,
"strict": false,
"snapshots": false,
"browserGpuMode": "auto"
}
},
"createdAt": 1792016221309,
"contentHash": "e4b290d5513ee41c22f25665bfab73ab61f26f0c137a9fddd861279b15f805d4",
"revision": "6999d82e-2d3b-4026-b89c-d3107735bf7e",
"inputFingerprint": "4f091d26af72cc80221f64652e53f7b6154f8b8d94c2c43bddbdaa7e7bacc2a8",
"inputScope": "local-project-files",
"attribution": "recorded-revision"
}
]
},
"unversioned": [
{
"id": "b81768cb-66dc-4b7d-8159-85d988ebc321",
"scope": "project",
"report": "/home/user/cycle-atlas/videos/lookatu-promo/.hyperframes/artifacts/checks/b81768cb-66dc-4b7d-8159-85d988ebc321.json",
"metadata": {
"ok": false,
"browserSkipped": false,
"options": {
"samples": 9,
"atTransitions": false,
"maxIssues": 80,
"collapseStatic": true,
"tolerance": 2,
"timeout": 3000,
"contrast": true,
"strict": false,
"snapshots": false,
"browserGpuMode": "auto"
}
},
"createdAt": 1792016095244,
"contentHash": "aab572c254c1e2e2a0d41cb3e69f19c969c52ea74d52d8a7b70fa33ca1a3d2e6",
"revision": null,
"inputFingerprint": "3b3a22e794c378dd99ccd728175edebfae98c1e343c6ca06027a0679d088604a",
"inputScope": "local-project-files",
"attribution": "unversioned"
}
]
},
"snapshots": {
"latest": {
"revision": "6999d82e-2d3b-4026-b89c-d3107735bf7e",
"id": "a82e082b-e292-496f-a873-daced29d123f-4"
},
"revisions": {
"f155d8de-7fb6-4e12-9291-60b3874a47cc": [
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-0",
"time": 1.5,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "b9d7d37046d37a8418b0e35047fa3c528818b9f28f321fd8c163b7d821b7b2f8",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-1",
"time": 4.8,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "8bfcfe4356e344034a588fb8eaa48d8c6a17fd1f00921172ac0452a80b1b71e4",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-2",
"time": 9,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "d7c0b4142c09d36f84c8a5b82705bd6cf46522dc372d88cef51f43192b37e42d",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-3",
"time": 12.3,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "90ca4efc8f9645e29abd5e9c1c6900404f7a53ac80294f7c6069b847fa1bccdd",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-4",
"time": 16,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "bc7a6a0347049f51793185e2867b34f9200ff93d40a06f0bffa6965696338d8c",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-5",
"time": 20.2,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "c86c03f4c948e4b51a67e7012bf1eb5898e381c173080a13261b212b5c8ce25a",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-6",
"time": 24,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "e2b216d57db98ddf973fd0134008417ff00f646888ace8fc5d3e7ce2ddef9bd0",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-7",
"time": 27.3,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "48b1ae5f592678b077b5b5ea2c166090dfc9e84c6baaabb2b1bba5f41642a735",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-8",
"time": 31,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "6b3edca25c8d8c0e7be74e31690404694ecb679d0d4348a05823c96bf891e492",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-9",
"time": 33,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182293,
"contentHash": "228695183d4db7398381a0d6d7f502ad5755dfe8601c9c68d7c73b45b44a210c",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-10",
"time": 36,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182294,
"contentHash": "d8a1ec155678c44ce8e68cde3085859a1ff031b78febf7ea5b6f4d5e45c4d0d6",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-11",
"time": 39.5,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182294,
"contentHash": "d814f09e5d907213260a03a8b6b0129ed31848abbd29de93417b6deaa5ff8871",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-12",
"time": 42,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182294,
"contentHash": "acb9108624fc79f1dcc4ecb6ddf74ee6f2c46be2c53604d8a8243b886779f9e7",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-13",
"time": 45.3,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182294,
"contentHash": "73edeff4a0be0e45c4d82b20caafd7eb154d496c5252af5c351d9349871a2dcd",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-14",
"time": 47.5,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182294,
"contentHash": "5f425b67e37447223fb533746a74d4994827e00945958068d3c6b6213f7a4938",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-15",
"time": 49,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182294,
"contentHash": "73b125a618712fb0d3e5daecffb66f3704e0b160f5844e4759971551a98c1c76",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-16",
"time": 51,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182294,
"contentHash": "83be74004821d6907afaaa93dd798169144c976855840ebaa8082069d1d7ce34",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-17",
"time": 54.2,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182294,
"contentHash": "a4293fac697e7669476d2d8668abf92a9c4aec2ed6f11672ad53f15074b43cf6",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
},
{
"scope": "full",
"id": "f15403c5-4592-4d35-ace8-a22740b42e6f-18",
"time": 53.091,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016182294,
"contentHash": "882bf50bb17bf4324156c5719de60e94835d1af72402f7af984a845c7684fd3a",
"revision": "f155d8de-7fb6-4e12-9291-60b3874a47cc",
"inputFingerprint": "9bc516d75c695a9fbcd85911f32ded85e7af79a7c0e6ef3b740f22c5908e924b",
"inputScope": "local-project-files",
"attribution": "recorded-revision",
"status": "cleaned",
"cleanedAfterRender": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3"
}
],
"6999d82e-2d3b-4026-b89c-d3107735bf7e": [
{
"scope": "full",
"id": "a82e082b-e292-496f-a873-daced29d123f-0",
"image": "/home/user/cycle-atlas/videos/lookatu-promo/.hyperframes/artifacts/snapshots/f0662448-af98-40ff-b6fd-626156d4a694/frame-00-at-1.5s.png",
"time": 1.5,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016226995,
"contentHash": "79be7032c63e7a191b47eaceb152a02bf809936a7e76071b68c388a49038f28a",
"revision": "6999d82e-2d3b-4026-b89c-d3107735bf7e",
"inputFingerprint": "4f091d26af72cc80221f64652e53f7b6154f8b8d94c2c43bddbdaa7e7bacc2a8",
"inputScope": "local-project-files",
"attribution": "recorded-revision"
},
{
"scope": "full",
"id": "a82e082b-e292-496f-a873-daced29d123f-1",
"image": "/home/user/cycle-atlas/videos/lookatu-promo/.hyperframes/artifacts/snapshots/f0662448-af98-40ff-b6fd-626156d4a694/frame-01-at-9s.png",
"time": 9,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016226996,
"contentHash": "1a9105cec34e2127ce91c60aab69a567895822f45267710285d03b2b99a1bf84",
"revision": "6999d82e-2d3b-4026-b89c-d3107735bf7e",
"inputFingerprint": "4f091d26af72cc80221f64652e53f7b6154f8b8d94c2c43bddbdaa7e7bacc2a8",
"inputScope": "local-project-files",
"attribution": "recorded-revision"
},
{
"scope": "full",
"id": "a82e082b-e292-496f-a873-daced29d123f-2",
"image": "/home/user/cycle-atlas/videos/lookatu-promo/.hyperframes/artifacts/snapshots/f0662448-af98-40ff-b6fd-626156d4a694/frame-02-at-26.5s.png",
"time": 26.5,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016226996,
"contentHash": "9132f797582916f355f507d87da8843d0fdd23f1b9c5d7f6473eb859b3fd3392",
"revision": "6999d82e-2d3b-4026-b89c-d3107735bf7e",
"inputFingerprint": "4f091d26af72cc80221f64652e53f7b6154f8b8d94c2c43bddbdaa7e7bacc2a8",
"inputScope": "local-project-files",
"attribution": "recorded-revision"
},
{
"scope": "full",
"id": "a82e082b-e292-496f-a873-daced29d123f-3",
"image": "/home/user/cycle-atlas/videos/lookatu-promo/.hyperframes/artifacts/snapshots/f0662448-af98-40ff-b6fd-626156d4a694/frame-03-at-53s.png",
"time": 53,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016226996,
"contentHash": "c8714c376f4e7f375d0c2c11766a2717127224c0927372e4ff67ee41c4d52a72",
"revision": "6999d82e-2d3b-4026-b89c-d3107735bf7e",
"inputFingerprint": "4f091d26af72cc80221f64652e53f7b6154f8b8d94c2c43bddbdaa7e7bacc2a8",
"inputScope": "local-project-files",
"attribution": "recorded-revision"
},
{
"scope": "full",
"id": "a82e082b-e292-496f-a873-daced29d123f-4",
"image": "/home/user/cycle-atlas/videos/lookatu-promo/.hyperframes/artifacts/snapshots/f0662448-af98-40ff-b6fd-626156d4a694/frame-04-at-53.091s.png",
"time": 53.091,
"metadata": {
"width": 1080,
"height": 1920,
"zoomScale": 1,
"browserGpuMode": "auto",
"warnings": [],
"partial": false
},
"createdAt": 1792016226996,
"contentHash": "5605b5c311601f318545227a90787eb6f2950613dea019cf8e0daf81353d7946",
"revision": "6999d82e-2d3b-4026-b89c-d3107735bf7e",
"inputFingerprint": "4f091d26af72cc80221f64652e53f7b6154f8b8d94c2c43bddbdaa7e7bacc2a8",
"inputScope": "local-project-files",
"attribution": "recorded-revision"
}
]
},
"unversioned": []
},
"renders": {
"latest": {
"id": "690e4ff7-bd4a-49ec-94b8-f18083c79bc3",
"completedAt": 1792016445275,
"output": "/home/user/cycle-atlas/videos/lookatu-promo/renders/video.mp4",
"status": "completed",
"revision": "6999d82e-2d3b-4026-b89c-d3107735bf7e"
},
"retainedRevisions": [
"6999d82e-2d3b-4026-b89c-d3107735bf7e"
]
},
"pendingArtifactCleanup": []
}
+12
View File
@@ -0,0 +1,12 @@
# .media · 9 assets
id type dur dims path description
voice_010 voice 3.8s — assets/voice/01.wav Where are they right now? And what are they really doing on that phone?
voice_011 voice 5.6s — assets/voice/02.wav Endless scrolling. Arguments at dinner. And that quiet worry you never say out loud.
voice_012 voice 6.1s — assets/voice/03.wav There has to be a better way. Meet lookatu. The child safety app that gives you peace of mind.
voice_013 voice 5.1s — assets/voice/04.wav Set screen time limits that actually stick. When the time is up, the phone knows.
voice_014 voice 4.2s — assets/voice/05.wav Choose which apps are okay. Allow the good ones. Block the rest.
voice_015 voice 5s — assets/voice/06.wav And when it really matters, see where they are. Location, only when you need it.
voice_016 voice 4s — assets/voice/07.wav Because kids deserve some freedom, and parents deserve some calm.
voice_017 voice 2.3s — assets/voice/08.wav Works on Android and on iPhone.
voice_018 voice 3.1s — assets/voice/09.wav lookatu. Look out for them. Download it today.
@@ -0,0 +1,18 @@
{"id":"voice_001","type":"voice","path":"assets/voice/01.wav","source":"generated","description":"Where are they right now? And what are they really doing on that phone?","duration":3.3,"provenance":{"provider":"kokoro","prompt":"Where are they right now? And what are they really doing on that phone?"}}
{"id":"voice_002","type":"voice","path":"assets/voice/02.wav","source":"generated","description":"Endless scrolling. Arguments at dinner. And that quiet worry you never say out loud.","duration":4.9,"provenance":{"provider":"kokoro","prompt":"Endless scrolling. Arguments at dinner. And that quiet worry you never say out loud."}}
{"id":"voice_003","type":"voice","path":"assets/voice/03.wav","source":"generated","description":"There has to be a better way. Meet lookatu. The child safety app that gives you peace of mind.","duration":5.4,"provenance":{"provider":"kokoro","prompt":"There has to be a better way. Meet lookatu. The child safety app that gives you peace of mind."}}
{"id":"voice_004","type":"voice","path":"assets/voice/04.wav","source":"generated","description":"Set screen time limits that actually stick. When the time is up, the phone knows.","duration":4.5,"provenance":{"provider":"kokoro","prompt":"Set screen time limits that actually stick. When the time is up, the phone knows."}}
{"id":"voice_005","type":"voice","path":"assets/voice/05.wav","source":"generated","description":"Choose which apps are okay. Allow the good ones. Block the rest.","duration":3.7,"provenance":{"provider":"kokoro","prompt":"Choose which apps are okay. Allow the good ones. Block the rest."}}
{"id":"voice_006","type":"voice","path":"assets/voice/06.wav","source":"generated","description":"And when it really matters, see where they are. Location, only when you need it.","duration":4.4,"provenance":{"provider":"kokoro","prompt":"And when it really matters, see where they are. Location, only when you need it."}}
{"id":"voice_007","type":"voice","path":"assets/voice/07.wav","source":"generated","description":"Because kids deserve some freedom, and parents deserve some calm.","duration":3.6,"provenance":{"provider":"kokoro","prompt":"Because kids deserve some freedom, and parents deserve some calm."}}
{"id":"voice_008","type":"voice","path":"assets/voice/08.wav","source":"generated","description":"Works on Android and on iPhone.","duration":2,"provenance":{"provider":"kokoro","prompt":"Works on Android and on iPhone."}}
{"id":"voice_009","type":"voice","path":"assets/voice/09.wav","source":"generated","description":"lookatu. Look out for them. Download it today.","duration":2.7,"provenance":{"provider":"kokoro","prompt":"lookatu. Look out for them. Download it today."}}
{"id":"voice_010","type":"voice","path":"assets/voice/01.wav","source":"generated","description":"Where are they right now? And what are they really doing on that phone?","duration":3.8,"provenance":{"provider":"kokoro","prompt":"Where are they right now? And what are they really doing on that phone?"}}
{"id":"voice_011","type":"voice","path":"assets/voice/02.wav","source":"generated","description":"Endless scrolling. Arguments at dinner. And that quiet worry you never say out loud.","duration":5.6,"provenance":{"provider":"kokoro","prompt":"Endless scrolling. Arguments at dinner. And that quiet worry you never say out loud."}}
{"id":"voice_012","type":"voice","path":"assets/voice/03.wav","source":"generated","description":"There has to be a better way. Meet lookatu. The child safety app that gives you peace of mind.","duration":6.1,"provenance":{"provider":"kokoro","prompt":"There has to be a better way. Meet lookatu. The child safety app that gives you peace of mind."}}
{"id":"voice_013","type":"voice","path":"assets/voice/04.wav","source":"generated","description":"Set screen time limits that actually stick. When the time is up, the phone knows.","duration":5.1,"provenance":{"provider":"kokoro","prompt":"Set screen time limits that actually stick. When the time is up, the phone knows."}}
{"id":"voice_014","type":"voice","path":"assets/voice/05.wav","source":"generated","description":"Choose which apps are okay. Allow the good ones. Block the rest.","duration":4.2,"provenance":{"provider":"kokoro","prompt":"Choose which apps are okay. Allow the good ones. Block the rest."}}
{"id":"voice_015","type":"voice","path":"assets/voice/06.wav","source":"generated","description":"And when it really matters, see where they are. Location, only when you need it.","duration":5,"provenance":{"provider":"kokoro","prompt":"And when it really matters, see where they are. Location, only when you need it."}}
{"id":"voice_016","type":"voice","path":"assets/voice/07.wav","source":"generated","description":"Because kids deserve some freedom, and parents deserve some calm.","duration":4,"provenance":{"provider":"kokoro","prompt":"Because kids deserve some freedom, and parents deserve some calm."}}
{"id":"voice_017","type":"voice","path":"assets/voice/08.wav","source":"generated","description":"Works on Android and on iPhone.","duration":2.3,"provenance":{"provider":"kokoro","prompt":"Works on Android and on iPhone."}}
{"id":"voice_018","type":"voice","path":"assets/voice/09.wav","source":"generated","description":"lookatu. Look out for them. Download it today.","duration":3.1,"provenance":{"provider":"kokoro","prompt":"lookatu. Look out for them. Download it today."}}
@@ -0,0 +1,13 @@
{
"version": 1,
"preferences": {
"style_preset.product-launch-video": {
"value": "daisy-days",
"confirmed_in": [
"lookatu-promo"
],
"updated_at": "2026-10-14T22:05:31.928Z"
}
},
"sightings": {}
}
+109
View File
@@ -0,0 +1,109 @@
# HyperFrames Composition Project
## Skills — USE THESE FIRST
**Always invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that are NOT in generic web docs. Skipping them produces broken compositions.
**Doing anything with HyperFrames?** Start at `/hyperframes` — it tells you what HyperFrames can do and which skill or workflow handles your intent (make a video, TTS / BGM, prep footage, author / animate, render, install blocks), confirms your brief up front (the intent layer), and routes every "make me a…" request (a video, a deck, a composition port) to the right workflow. Read it first, especially when there's no project context to orient you. The workflows it routes to:
- `/product-launch-video` — any **website** URL or brief / script → a product launch / SaaS / promo video, or a site tour / showcase featuring the site's own captured visuals.
- `/faceless-explainer` — arbitrary text (topic / article / notes), **no URL, no website capture** → 60-90s faceless explainer.
- `/embedded-captions` — an existing talking-head video (MP4) → the same footage with captions / subtitles added (rail + embed, or pure-cinematic embed); the footage itself is untouched.
- `/talking-head-recut` — an existing talking-head / interview / podcast video (MP4) → the same footage **packaged with designed graphic overlays** (kinetic titles, lower-thirds, data callouts, pull-quotes, side panels, pip) synced to the transcript; the clip plays unchanged underneath. (Plain captions/subtitles → `/embedded-captions`.)
- `/pr-to-video` — a GitHub PR (URL / `owner/repo#N` / "this PR") → 30-90s code-change explainer (changelog / feature reveal / fix / refactor).
- `/motion-graphics` — a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`.
- `/music-to-video` — a **music track** (audio file, video to pull audio from, or one generated from a mood brief) → beat-synced video (lyric / slideshow / kinetic promo). Music drives pacing; user-supplied images / videos are cut onto the same beat grid.
- `/slideshow` — a **presentation / pitch deck / interactive deck** — discrete slides, fragment reveals, branching, hotspot navigation, presenter mode. Output is a navigable deck, not a rendered video.
- `/general-video` — fallback for any other video (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition) and the home of **companion mode** — co-create with the full HyperFrames toolbox; the original hyperframes authoring flow, any length.
**Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) composition into HyperFrames HTML — a source migration, separate from the creation workflows above.
The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-keyframes`, `/hyperframes-creative`, `/hyperframes-cli`, `/media-use`, `/hyperframes-audio`, `/hyperframes-registry`, `/figma`) and the full capability map live inside `/hyperframes` — it is the single source of truth for which skill handles which intent.
**Changing how real footage or images look or reveal?** Load `/media-use` and read its `references/media-treatments.md` before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” It governs how footage is treated, never whether media may be used. Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays.
> **Tailwind v4 projects** (`hyperframes init --tailwind`): see `/hyperframes-core` → `references/tailwind.md`.
> **Using a HyperFrames plugin?** Load skills from that installed bundle and follow
> its `hyperframes/references/plugin-installation.md` execution rules. Update via
> the plugin manager, not the standalone commands below.
>
> **Standalone skill missing or stale?** Run `npx hyperframes skills update <name>` to install/refresh
> the specific skill you need (the `/hyperframes` router does this automatically before
> entering a workflow), or bare `npx hyperframes skills update` to refresh the core set plus
> everything already installed — neither pulls the full set. Restart the agent session so
> newly installed skills load.
## Commands
```bash
npm run dev # human-operated foreground preview (blocks until stopped)
npx hyperframes preview --background # agent-safe persistent Studio preview
npx hyperframes preview --status # verify the persistent preview is listening
npx hyperframes preview --stop # stop it when review is finished
npm run check # lint + runtime + layout + motion + contrast (one command)
npm run render # render to MP4
npm run publish # publish and get a shareable link
npx hyperframes lint --verbose # include info-level findings
npx hyperframes lint --json # machine-readable output for CI
npx hyperframes docs <topic> # reference docs in terminal
```
> **Agents must use `npx hyperframes preview --background` for Studio handoff.** Do not rely
> on a shell/tool `run_in_background` wrapper around `npm run dev`: that foreground process
> remains owned by the invoking session and can disappear while the browser stays open,
> leaving refreshes at `ERR_CONNECTION_TIMED_OUT`. Verify with `preview --status`, keep it
> alive through review, and stop it explicitly with `preview --stop` afterward.
> **Back from the desktop app.** Once this project was opened in the HyperFrames desktop app, run `npx hyperframes catch-up` before your next change here: it lists what the person asked Framey in the app and which files changed since.
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
## Documentation
**For quick reference**, use the local CLI docs command (no network required):
```bash
npx hyperframes docs <topic>
```
Topics: `data-attributes`, `gsap`, `compositions`, `rendering`, `examples`, `troubleshooting`
**For full documentation**, discover pages via the machine-readable index — do NOT guess URLs:
```
https://hyperframes.heygen.com/llms.txt
```
## Project Structure
- `index.html` — main composition (root timeline)
- `compositions/` — sub-compositions referenced via `data-composition-src`
- `meta.json` — project metadata (id, name)
- `transcript.json` — whisper word-level transcript (if generated)
## Linting — ALWAYS RUN AFTER CHANGES
After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
```bash
npm run check
```
Fix all errors before presenting the result. Warnings should be reviewed before rendering.
## Key Rules
1. Every timed element needs `data-start` and a duration. `data-start` is what marks it as timed; `data-track-index` is an optional Studio display lane the render never reads
2. Give timed visual elements `class="clip"`. The framework keys visibility off `data-start`, not the class, but the shared `.clip` CSS is what gives a scene its full-frame box, and `lint` warns without it
3. Register one paused root timeline per composition on `window.__timelines`:
```js
window.__timelines = window.__timelines || {};
window.__timelines["composition-id"] = gsap.timeline({ paused: true });
```
Scene timelines manually added to this root must not be paused. A paused
child does not advance when the root is seeked. The runtime activates
registered composition siblings, not arbitrary nested scene timelines.
4. A video with sound keeps it on the `<video>` (`data-has-audio="true"`, no `muted`). Use a separate `<audio>` for music, voiceover, replacement audio, J/L cuts, or audio detached in Studio. Silent footage and b-roll: `muted`.
5. Sub-compositions use `data-composition-src="compositions/file.html"` to reference other HTML files
6. Only deterministic logic — no `Date.now()`, no `Math.random()`, no network fetches
+36
View File
@@ -0,0 +1,36 @@
---
workflow: product-launch-video
flow: automation
storyboard: no
message: "Keep your kids safe on their phones, with peace of mind for parents"
destination: tiktok-reels-shorts
aspect: 1080x1920
language: en
audience: parents of children with smartphones
length: 60s
angle: parent peace of mind
---
## Intent
Promo for "lookatu", an Android and iOS child-safety app. Parents control how
much their kids use the phone (time limits, app control) and can track location
only when needed. Story shape: start with a worried parent, then lookatu brings
calm and safety. Warm, reassuring, trustworthy. Not scary, not spying.
## Assets
- None supplied. No website, logo, or screenshots yet. Use invented phone UI
mock-ups and a typographic "lookatu" wordmark. Real screens come in Phase 2.
## Customizations
- No-capture mode (nothing to scrape). A shipped preset supplies the design.
## Notes
- User is not a coder: explain steps in plain language.
- Only claim features the user described: usage control, location tracking
when needed, Android and iOS. No invented stats, awards, or user counts.
- Frame tracking as parent-led and transparent (no secret spying language).
- Phase 2: swap in real logo and screenshots, add real voice. Phase 3: other sizes.
+109
View File
@@ -0,0 +1,109 @@
# HyperFrames Composition Project
## Skills — USE THESE FIRST
**Always invoke the relevant skill before writing or modifying compositions.** Skills encode framework-specific patterns (e.g., `window.__timelines` registration, `data-*` attribute semantics, shader-compatible CSS rules) that are NOT in generic web docs. Skipping them produces broken compositions.
**Doing anything with HyperFrames?** Start at `/hyperframes` — it tells you what HyperFrames can do and which skill or workflow handles your intent (make a video, TTS / BGM, prep footage, author / animate, render, install blocks), confirms your brief up front (the intent layer), and routes every "make me a…" request (a video, a deck, a composition port) to the right workflow. Read it first, especially when there's no project context to orient you. The workflows it routes to:
- `/product-launch-video` — any **website** URL or brief / script → a product launch / SaaS / promo video, or a site tour / showcase featuring the site's own captured visuals.
- `/faceless-explainer` — arbitrary text (topic / article / notes), **no URL, no website capture** → 60-90s faceless explainer.
- `/embedded-captions` — an existing talking-head video (MP4) → the same footage with captions / subtitles added (rail + embed, or pure-cinematic embed); the footage itself is untouched.
- `/talking-head-recut` — an existing talking-head / interview / podcast video (MP4) → the same footage **packaged with designed graphic overlays** (kinetic titles, lower-thirds, data callouts, pull-quotes, side panels, pip) synced to the transcript; the clip plays unchanged underneath. (Plain captions/subtitles → `/embedded-captions`.)
- `/pr-to-video` — a GitHub PR (URL / `owner/repo#N` / "this PR") → 30-90s code-change explainer (changelog / feature reveal / fix / refactor).
- `/motion-graphics` — a short (typically under 10s) design-led **motion graphic**, motion-is-the-message, no narration: kinetic type, a stat / number count-up, a chart, a logo sting, a lower-third / overlay, or an animated tweet / headline / captured-page highlight; rendered to MP4 or a transparent overlay. Longer / narrated / custom → `/general-video`.
- `/music-to-video` — a **music track** (audio file, video to pull audio from, or one generated from a mood brief) → beat-synced video (lyric / slideshow / kinetic promo). Music drives pacing; user-supplied images / videos are cut onto the same beat grid.
- `/slideshow` — a **presentation / pitch deck / interactive deck** — discrete slides, fragment reveals, branching, hotspot navigation, presenter mode. Output is a navigable deck, not a rendered video.
- `/general-video` — fallback for any other video (title card, longer brand / sizzle reel, multi-scene montage, static loop, custom composition) and the home of **companion mode** — co-create with the full HyperFrames toolbox; the original hyperframes authoring flow, any length.
**Porting an existing composition?** `/remotion-to-hyperframes` translates a Remotion (React) composition into HyperFrames HTML — a source migration, separate from the creation workflows above.
The domain skills (`/hyperframes-core`, `/hyperframes-animation`, `/hyperframes-keyframes`, `/hyperframes-creative`, `/hyperframes-cli`, `/media-use`, `/hyperframes-audio`, `/hyperframes-registry`, `/figma`) and the full capability map live inside `/hyperframes` — it is the single source of truth for which skill handles which intent.
**Changing how real footage or images look or reveal?** Load `/media-use` and read its `references/media-treatments.md` before editing, even when the request only says dark, flat, boring, retro, private, or “make the reveal cooler.” It governs how footage is treated, never whether media may be used. Use canonical media treatments and seek-safe motion; do not improvise equivalent CSS/SVG filters or overlays.
> **Tailwind v4 projects** (`hyperframes init --tailwind`): see `/hyperframes-core` → `references/tailwind.md`.
> **Using a HyperFrames plugin?** Load skills from that installed bundle and follow
> its `hyperframes/references/plugin-installation.md` execution rules. Update via
> the plugin manager, not the standalone commands below.
>
> **Standalone skill missing or stale?** Run `npx hyperframes skills update <name>` to install/refresh
> the specific skill you need (the `/hyperframes` router does this automatically before
> entering a workflow), or bare `npx hyperframes skills update` to refresh the core set plus
> everything already installed — neither pulls the full set. Restart the agent session so
> newly installed skills load.
## Commands
```bash
npm run dev # human-operated foreground preview (blocks until stopped)
npx hyperframes preview --background # agent-safe persistent Studio preview
npx hyperframes preview --status # verify the persistent preview is listening
npx hyperframes preview --stop # stop it when review is finished
npm run check # lint + runtime + layout + motion + contrast (one command)
npm run render # render to MP4
npm run publish # publish and get a shareable link
npx hyperframes lint --verbose # include info-level findings
npx hyperframes lint --json # machine-readable output for CI
npx hyperframes docs <topic> # reference docs in terminal
```
> **Agents must use `npx hyperframes preview --background` for Studio handoff.** Do not rely
> on a shell/tool `run_in_background` wrapper around `npm run dev`: that foreground process
> remains owned by the invoking session and can disappear while the browser stays open,
> leaving refreshes at `ERR_CONNECTION_TIMED_OUT`. Verify with `preview --status`, keep it
> alive through review, and stop it explicitly with `preview --stop` afterward.
> **Back from the desktop app.** Once this project was opened in the HyperFrames desktop app, run `npx hyperframes catch-up` before your next change here: it lists what the person asked Framey in the app and which files changed since.
> **Pinned CLI version.** These scripts pin an exact `hyperframes@X.Y.Z` so this project re-renders identically over time. Weeks later that pin lags fixes shipped since. To move up: `npx hyperframes@latest upgrade --project . --check` (shows the delta), then `npx hyperframes@latest upgrade --project .` to rewrite the pins. Always unpinned — the pinned script re-runs the old version against itself.
## Documentation
**For quick reference**, use the local CLI docs command (no network required):
```bash
npx hyperframes docs <topic>
```
Topics: `data-attributes`, `gsap`, `compositions`, `rendering`, `examples`, `troubleshooting`
**For full documentation**, discover pages via the machine-readable index — do NOT guess URLs:
```
https://hyperframes.heygen.com/llms.txt
```
## Project Structure
- `index.html` — main composition (root timeline)
- `compositions/` — sub-compositions referenced via `data-composition-src`
- `meta.json` — project metadata (id, name)
- `transcript.json` — whisper word-level transcript (if generated)
## Linting — ALWAYS RUN AFTER CHANGES
After creating or editing any `.html` composition, **always** run the full check before considering the task complete:
```bash
npm run check
```
Fix all errors before presenting the result. Warnings should be reviewed before rendering.
## Key Rules
1. Every timed element needs `data-start` and a duration. `data-start` is what marks it as timed; `data-track-index` is an optional Studio display lane the render never reads
2. Give timed visual elements `class="clip"`. The framework keys visibility off `data-start`, not the class, but the shared `.clip` CSS is what gives a scene its full-frame box, and `lint` warns without it
3. Register one paused root timeline per composition on `window.__timelines`:
```js
window.__timelines = window.__timelines || {};
window.__timelines["composition-id"] = gsap.timeline({ paused: true });
```
Scene timelines manually added to this root must not be paused. A paused
child does not advance when the root is seeked. The runtime activates
registered composition siblings, not arbitrary nested scene timelines.
4. A video with sound keeps it on the `<video>` (`data-has-audio="true"`, no `muted`). Use a separate `<audio>` for music, voiceover, replacement audio, J/L cuts, or audio detached in Studio. Silent footage and b-roll: `muted`.
5. Sub-compositions use `data-composition-src="compositions/file.html"` to reference other HTML files
6. Only deterministic logic — no `Date.now()`, no `Math.random()`, no network fetches
+70
View File
@@ -0,0 +1,70 @@
# SCRIPT — lookatu-promo
**Voice:** af_heart (Kokoro, offline)
**Voice settings:** default
**Voice direction:** Warm, calm, reassuring. Like a friend who is also a parent.
---
## Line 1 — Where are they (Frame 1)
**Time:** 0.0 – 6.0s
**Delivery:** Gentle worry, then curious.
Where are they right now? And what are they really doing on that phone?
## Line 2 — The quiet worry (Frame 2)
**Time:** 6.0 – 13.0s
**Delivery:** Slow, a little tired, honest.
Endless scrolling. Arguments at dinner. And that quiet worry you never say out loud.
## Line 3 — Meet lookatu (Frame 3)
**Time:** 13.0 – 22.0s
**Delivery:** A breath, then relief and warmth on the name.
There has to be a better way. Meet lookatu. The child safety app that gives you peace of mind.
## Line 4 — Screen time limits (Frame 4)
**Time:** 22.0 – 30.0s
**Delivery:** Confident and clear.
Set screen time limits that actually stick. When the time is up, the phone knows.
## Line 5 — App control (Frame 5)
**Time:** 30.0 – 37.0s
**Delivery:** Crisp, three short beats.
Choose which apps are okay. Allow the good ones. Block the rest.
## Line 6 — Location when needed (Frame 6)
**Time:** 37.0 – 45.0s
**Delivery:** Soft and reassuring.
And when it really matters, see where they are. Location, only when you need it.
## Line 7 — Freedom and calm (Frame 7)
**Time:** 45.0 – 51.0s
**Delivery:** Warm, almost a smile.
Because kids deserve some freedom, and parents deserve some calm.
## Line 8 — Android and iPhone (Frame 8)
**Time:** 51.0 – 55.0s
**Delivery:** Simple and friendly.
Works on Android and on iPhone.
## Line 9 — Look out for them (Frame 9)
**Time:** 55.0 – 61.0s
**Delivery:** Warm sign-off, land on the name.
lookatu. Look out for them. Download it today.
+169
View File
@@ -0,0 +1,169 @@
---
format: 1080x1920
duration: 60s
message: "Keep your kids safe on their phones, with peace of mind for parents"
arc: Hook → Pain → Product → Time limits → App control → Location → Balance → Platforms → CTA
audience: parents of children with smartphones
mode: autonomous
music: none
---
## Video direction
Daisy Days picture-book look: cream and pastel grounds, 3px charcoal outlines, hard offset shadows,
hand-drawn ornaments (daisy, star, sun, cloud) filling the corners. Warm, reassuring, never scary.
Motion is soft and bouncy (back.out, power3.out), each piece lands when the voice names it. Phone
mock-ups are invented generic screens with no real brand logos. Captions sit in the bottom band.
## Frame 1 — Where are they
- scene: Two big question bubbles pop in over a sky-blue ground, a small phone silhouette wobbles
- voiceover: "Where are they right now? And what are they really doing on that phone?"
- duration: 5.64s
- transition_in: crossfade
- status: animated
- src: compositions/frames/01-hook.html
- type: hook
- persuasion: Pain validation
- beat: worry
- blueprint: compose
- asset_candidates:
narrativeRole: Opens on the parent's silent question in outcome language.
keyMessage: Parents worry about where their kids are and what the phone is doing.
## Frame 2 — The quiet worry
- scene: Three sticker words stack in one by one: SCROLLING, ARGUING, WORRYING
- voiceover: "Endless scrolling. Arguments at dinner. And that quiet worry you never say out loud."
- duration: 7.111s
- transition_in: crossfade
- status: animated
- src: compositions/frames/02-worry.html
- type: pain_point
- persuasion: Pain agitation
- beat: tension
- blueprint: compose
- asset_candidates:
narrativeRole: Names the everyday pain so the product feels like relief.
keyMessage: The phone brings daily friction and worry.
## Frame 3 — Meet lookatu
- scene: The lookatu wordmark builds on a turquoise ground with a shield badge and a calm sun
- voiceover: "There has to be a better way. Meet lookatu. The child safety app that gives you peace of mind."
- duration: 8.144s
- transition_in: zoom-through
- status: animated
- src: compositions/frames/03-meet.html
- type: product_intro
- persuasion: Future pacing
- beat: relief
- blueprint: compose
- asset_candidates:
narrativeRole: Lands the promise (the message) by beat 3 and names the product.
keyMessage: lookatu gives parents peace of mind.
## Frame 4 — Screen time limits
- scene: A phone mock-up with a timer ring that counts down and locks with a gentle lock icon
- voiceover: "Set screen time limits that actually stick. When the time is up, the phone knows."
- duration: 6.599s
- transition_in: push-slide LEFT
- status: animated
- src: compositions/frames/04-limits.html
- type: feature_showcase
- persuasion: Feature-to-benefit translation
- beat: control
- blueprint: compose
- asset_candidates:
narrativeRole: Feature 1 shown working, with the benefit of limits that hold.
keyMessage: Parents set the phone usage limits.
## Frame 5 — App control
- scene: A phone mock-up with a list of generic app tiles and toggles; good apps switch on, others switch off
- voiceover: "Choose which apps are okay. Allow the good ones. Block the rest."
- duration: 5.703s
- transition_in: push-slide LEFT
- status: animated
- src: compositions/frames/05-apps.html
- type: feature_showcase
- persuasion: Show-don't-tell proof
- beat: control
- blueprint: compose
- asset_candidates:
narrativeRole: Feature 2, parents decide which apps are allowed.
keyMessage: Parents allow or block apps.
## Frame 6 — Location when needed
- scene: A soft map card with a pin that drops and pulses once, sitting inside a phone mock-up
- voiceover: "And when it really matters, see where they are. Location, only when you need it."
- duration: 6.513s
- transition_in: push-slide LEFT
- status: animated
- src: compositions/frames/06-location.html
- type: feature_showcase
- persuasion: Risk reversal
- beat: reassurance
- blueprint: compose
- asset_candidates:
narrativeRole: Feature 3, location tracking available when needed.
keyMessage: Parents can check location when needed.
## Frame 7 — Freedom and calm
- scene: Two big words on a pink ground, FREEDOM and CALM, each in a sticker card joined by a heart
- voiceover: "Because kids deserve some freedom, and parents deserve some calm."
- duration: 5.468s
- transition_in: crossfade
- status: animated
- src: compositions/frames/07-balance.html
- type: benefit_highlight
- persuasion: Rule of two
- beat: peace of mind
- blueprint: compose
- asset_candidates:
narrativeRole: The emotional payoff that ties the features together.
keyMessage: Safety without a fight.
## Frame 8 — Android and iPhone
- scene: Two pill badges, ANDROID and iOS, pop in side by side on a butter ground
- voiceover: "Works on Android and on iPhone."
- duration: 3.783s
- transition_in: crossfade
- status: animated
- src: compositions/frames/08-platforms.html
- type: benefit_highlight
- persuasion: Friction reduction
- beat: ease
- blueprint: compose
- asset_candidates:
narrativeRole: Removes the "will it work on my phone" question.
keyMessage: Available for both Android and iOS.
## Frame 9 — Look out for them
- scene: Lavender ground, lookatu wordmark with the tagline, ornament wreath, gentle settle
- voiceover: "lookatu. Look out for them. Download it today."
- duration: 5.772s
- transition_in: zoom-through
- status: animated
- src: compositions/frames/09-cta.html
- type: cta
- persuasion: Clear call to action
- beat: confidence
- blueprint: compose
- asset_candidates:
narrativeRole: Closing lockup and call to action.
keyMessage: Download lookatu.
Binary file not shown.
+11
View File
@@ -0,0 +1,11 @@
/*!
* GSAP 3.14.2
* https://gsap.com
*
* @license Copyright 2025, GreenSock. All rights reserved.
* Subject to the terms at https://gsap.com/standard-license.
* @author: Jack Doyle, jack@greensock.com
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t=t||self).window=t.window||{})}(this,function(e){"use strict";function _inheritsLoose(t,e){t.prototype=Object.create(e.prototype),(t.prototype.constructor=t).__proto__=e}function _assertThisInitialized(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}function r(t){return"string"==typeof t}function s(t){return"function"==typeof t}function t(t){return"number"==typeof t}function u(t){return void 0===t}function v(t){return"object"==typeof t}function w(t){return!1!==t}function x(){return"undefined"!=typeof window}function y(t){return s(t)||r(t)}function R(t){return(i=bt(t,ht))&&Fe}function S(t,e){return console.warn("Invalid property",t,"set to",e,"Missing plugin? gsap.registerPlugin()")}function T(t,e){return!e&&console.warn(t)}function U(t,e){return t&&(ht[t]=e)&&i&&(i[t]=e)||ht}function V(){return 0}function ga(t){var e,r,i=t[0];if(v(i)||s(i)||(t=[t]),!(e=(i._gsap||{}).harness)){for(r=yt.length;r--&&!yt[r].targetTest(i););e=yt[r]}for(r=t.length;r--;)t[r]&&(t[r]._gsap||(t[r]._gsap=new Xt(t[r],e)))||t.splice(r,1);return t}function ha(t){return t._gsap||ga(Pt(t))[0]._gsap}function ia(t,e,r){return(r=t[e])&&s(r)?t[e]():u(r)&&t.getAttribute&&t.getAttribute(e)||r}function ja(t,e){return(t=t.split(",")).forEach(e)||t}function ka(t){return Math.round(1e5*t)/1e5||0}function la(t){return Math.round(1e7*t)/1e7||0}function ma(t,e){var r=e.charAt(0),i=parseFloat(e.substr(2));return t=parseFloat(t),"+"===r?t+i:"-"===r?t-i:"*"===r?t*i:t/i}function na(t,e){for(var r=e.length,i=0;t.indexOf(e[i])<0&&++i<r;);return i<r}function oa(){var t,e,r=pt.length,i=pt.slice(0);for(_t={},t=pt.length=0;t<r;t++)(e=i[t])&&e._lazy&&(e.render(e._lazy[0],e._lazy[1],!0)._lazy=0)}function pa(t){return!!(t._initted||t._startAt||t.add)}function qa(t,e,r,i){pt.length&&!I&&oa(),t.render(e,r,i||!!(I&&e<0&&pa(t))),pt.length&&!I&&oa()}function ra(t){var e=parseFloat(t);return(e||0===e)&&(t+"").match(ot).length<2?e:r(t)?t.trim():t}function sa(t){return t}function ta(t,e){for(var r in e)r in t||(t[r]=e[r]);return t}function wa(t,e){for(var r in e)"__proto__"!==r&&"constructor"!==r&&"prototype"!==r&&(t[r]=v(e[r])?wa(t[r]||(t[r]={}),e[r]):e[r]);return t}function xa(t,e){var r,i={};for(r in t)r in e||(i[r]=t[r]);return i}function ya(t){var e=t.parent||L,r=t.keyframes?function _setKeyframeDefaults(i){return function(t,e){for(var r in e)r in t||"duration"===r&&i||"ease"===r||(t[r]=e[r])}}($(t.keyframes)):ta;if(w(t.inherit))for(;e;)r(t,e.vars.defaults),e=e.parent||e._dp;return t}function Aa(t,e,r,i,n){void 0===r&&(r="_first"),void 0===i&&(i="_last");var a,s=t[i];if(n)for(a=e[n];s&&s[n]>a;)s=s._prev;return s?(e._next=s._next,s._next=e):(e._next=t[r],t[r]=e),e._next?e._next._prev=e:t[i]=e,e._prev=s,e.parent=e._dp=t,e}function Ba(t,e,r,i){void 0===r&&(r="_first"),void 0===i&&(i="_last");var n=e._prev,a=e._next;n?n._next=a:t[r]===e&&(t[r]=a),a?a._prev=n:t[i]===e&&(t[i]=n),e._next=e._prev=e.parent=null}function Ca(t,e){t.parent&&(!e||t.parent.autoRemoveChildren)&&t.parent.remove&&t.parent.remove(t),t._act=0}function Da(t,e){if(t&&(!e||e._end>t._dur||e._start<0))for(var r=t;r;)r._dirty=1,r=r.parent;return t}function Fa(t,e,r,i){return t._startAt&&(I?t._startAt.revert(ft):t.vars.immediateRender&&!t.vars.autoRevert||t._startAt.render(e,!0,i))}function Ha(t){return t._repeat?wt(t._tTime,t=t.duration()+t._rDelay)*t:0}function Ja(t,e){return(t-e._start)*e._ts+(0<=e._ts?0:e._dirty?e.totalDuration():e._tDur)}function Ka(t){return t._end=la(t._start+(t._tDur/Math.abs(t._ts||t._rts||q)||0))}function La(t,e){var r=t._dp;return r&&r.smoothChildTiming&&t._ts&&(t._start=la(r._time-(0<t._ts?e/t._ts:((t._dirty?t.totalDuration():t._tDur)-e)/-t._ts)),Ka(t),r._dirty||Da(r,t)),t}function Ma(t,e){var r;if((e._time||!e._dur&&e._initted||e._start<t._time&&(e._dur||!e.add))&&(r=Ja(t.rawTime(),e),(!e._dur||Mt(0,e.totalDuration(),r)-e._tTime>q)&&e.render(r,!0)),Da(t,e)._dp&&t._initted&&t._time>=t._dur&&t._ts){if(t._dur<t.duration())for(r=t;r._dp;)0<=r.rawTime()&&r.totalTime(r._tTime),r=r._dp;t._zTime=-q}}function Na(e,r,i,n){return r.parent&&Ca(r),r._start=la((t(i)?i:i||e!==L?Ot(e,i,r):e._time)+r._delay),r._end=la(r._start+(r.totalDuration()/Math.abs(r.timeScale())||0)),Aa(e,r,"_first","_last",e._sort?"_start":0),xt(r)||(e._recent=r),n||Ma(e,r),e._ts<0&&La(e,e._tTime),e}function Oa(t,e){return(ht.ScrollTrigger||S("scrollTrigger",e))&&ht.ScrollTrigger.create(e,t)}function Pa(t,e,r,i,n){return Qt(t,e,n),t._initted?!r&&t._pt&&!I&&(t._dur&&!1!==t.vars.lazy||!t._dur&&t.vars.lazy)&&f!==It.frame?(pt.push(t),t._lazy=[n,i],1):void 0:1}function Ua(t,e,r,i){var n=t._repeat,a=la(e)||0,s=t._tTime/t._tDur;return s&&!i&&(t._time*=a/t._dur),t._dur=a,t._tDur=n?n<0?1e10:la(a*(n+1)+t._rDelay*n):a,0<s&&!i&&La(t,t._tTime=t._tDur*s),t.parent&&Ka(t),r||Da(t.parent,t),t}function Va(t){return t instanceof Zt?Da(t)Line truncated
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,71 @@
{
"tts_provider": "kokoro",
"voice_id": "af_heart",
"bgm": null,
"bgm_pending": false,
"bgm_provider": null,
"bgm_pid": null,
"bgm_log": null,
"bgm_mode": null,
"bgm_target_duration_s": null,
"bgm_seed_duration_s": null,
"bgm_loop_count": null,
"voices": [
{
"id": "01",
"path": "assets/voice/01.wav",
"duration_s": 3.84,
"words": []
},
{
"id": "02",
"path": "assets/voice/02.wav",
"duration_s": 5.611,
"words": []
},
{
"id": "03",
"path": "assets/voice/03.wav",
"duration_s": 6.144,
"words": []
},
{
"id": "04",
"path": "assets/voice/04.wav",
"duration_s": 5.099,
"words": []
},
{
"id": "05",
"path": "assets/voice/05.wav",
"duration_s": 4.203,
"words": []
},
{
"id": "06",
"path": "assets/voice/06.wav",
"duration_s": 5.013,
"words": []
},
{
"id": "07",
"path": "assets/voice/07.wav",
"duration_s": 3.968,
"words": []
},
{
"id": "08",
"path": "assets/voice/08.wav",
"duration_s": 2.283,
"words": []
},
{
"id": "09",
"path": "assets/voice/09.wav",
"duration_s": 3.072,
"words": []
}
],
"sfx": [],
"total_duration_s": 39.233
}
+635
View File
@@ -0,0 +1,635 @@
{
"bgm": null,
"bgm_pending": false,
"voices": [
{
"frame": 1,
"path": "assets/voice/01.wav",
"duration_s": 5.64,
"words": [
{
"text": "Where",
"start": 0.8,
"end": 1.058
},
{
"text": "are",
"start": 1.08,
"end": 1.264
},
{
"text": "they",
"start": 1.28,
"end": 1.501
},
{
"text": "right",
"start": 1.52,
"end": 1.778
},
{
"text": "now",
"start": 1.8,
"end": 2.205
},
{
"text": "And",
"start": 2.24,
"end": 2.424
},
{
"text": "what",
"start": 2.44,
"end": 2.661
},
{
"text": "are",
"start": 2.68,
"end": 2.864
},
{
"text": "they",
"start": 2.88,
"end": 3.101
},
{
"text": "really",
"start": 3.12,
"end": 3.414
},
{
"text": "doing",
"start": 3.44,
"end": 3.698
},
{
"text": "on",
"start": 3.72,
"end": 3.867
},
{
"text": "that",
"start": 3.88,
"end": 4.101
},
{
"text": "phone",
"start": 4.12,
"end": 4.598
}
]
},
{
"frame": 2,
"path": "assets/voice/02.wav",
"duration_s": 7.111,
"words": [
{
"text": "Endless",
"start": 0.5,
"end": 0.908
},
{
"text": "scrolling",
"start": 0.943,
"end": 1.713
},
{
"text": "Arguments",
"start": 1.78,
"end": 2.278
},
{
"text": "at",
"start": 2.321,
"end": 2.502
},
{
"text": "dinner",
"start": 2.518,
"end": 3.152
},
{
"text": "And",
"start": 3.207,
"end": 3.433
},
{
"text": "that",
"start": 3.453,
"end": 3.725
},
{
"text": "quiet",
"start": 3.748,
"end": 4.065
},
{
"text": "worry",
"start": 4.093,
"end": 4.41
},
{
"text": "you",
"start": 4.438,
"end": 4.664
},
{
"text": "never",
"start": 4.684,
"end": 5.001
},
{
"text": "say",
"start": 5.028,
"end": 5.255
},
{
"text": "out",
"start": 5.274,
"end": 5.501
},
{
"text": "loud",
"start": 5.52,
"end": 6.064
}
]
},
{
"frame": 3,
"path": "assets/voice/03.wav",
"duration_s": 8.144,
"words": [
{
"text": "There",
"start": 0.5,
"end": 0.807
},
{
"text": "has",
"start": 0.833,
"end": 1.052
},
{
"text": "to",
"start": 1.072,
"end": 1.247
},
{
"text": "be",
"start": 1.262,
"end": 1.437
},
{
"text": "a",
"start": 1.453,
"end": 1.584
},
{
"text": "better",
"start": 1.595,
"end": 1.946
},
{
"text": "way",
"start": 1.976,
"end": 2.458
},
{
"text": "Meet",
"start": 2.5,
"end": 2.763
},
{
"text": "lookatu",
"start": 2.786,
"end": 3.443
},
{
"text": "The",
"start": 3.501,
"end": 3.72
},
{
"text": "child",
"start": 3.739,
"end": 4.045
},
{
"text": "safety",
"start": 4.072,
"end": 4.423
},
{
"text": "app",
"start": 4.453,
"end": 4.672
},
{
"text": "that",
"start": 4.691,
"end": 4.954
},
{
"text": "gives",
"start": 4.977,
"end": 5.284
},
{
"text": "you",
"start": 5.31,
"end": 5.53
},
{
"text": "peace",
"start": 5.549,
"end": 5.855
},
{
"text": "of",
"start": 5.882,
"end": 6.057
},
{
"text": "mind",
"start": 6.072,
"end": 6.598
}
]
},
{
"frame": 4,
"path": "assets/voice/04.wav",
"duration_s": 6.599,
"words": [
{
"text": "Set",
"start": 0.5,
"end": 0.715
},
{
"text": "screen",
"start": 0.734,
"end": 1.078
},
{
"text": "time",
"start": 1.108,
"end": 1.366
},
{
"text": "limits",
"start": 1.389,
"end": 1.733
},
{
"text": "that",
"start": 1.763,
"end": 2.021
},
{
"text": "actually",
"start": 2.044,
"end": 2.474
},
{
"text": "stick",
"start": 2.512,
"end": 3.071
},
{
"text": "When",
"start": 3.12,
"end": 3.378
},
{
"text": "the",
"start": 3.4,
"end": 3.616
},
{
"text": "time",
"start": 3.634,
"end": 3.892
},
{
"text": "is",
"start": 3.915,
"end": 4.087
},
{
"text": "up",
"start": 4.102,
"end": 4.403
},
{
"text": "the",
"start": 4.43,
"end": 4.645
},
{
"text": "phone",
"start": 4.663,
"end": 4.965
},
{
"text": "knows",
"start": 4.991,
"end": 5.55
}
]
},
{
"frame": 5,
"path": "assets/voice/05.wav",
"duration_s": 5.703,
"words": [
{
"text": "Choose",
"start": 0.5,
"end": 0.836
},
{
"text": "which",
"start": 0.865,
"end": 1.16
},
{
"text": "apps",
"start": 1.185,
"end": 1.437
},
{
"text": "are",
"start": 1.459,
"end": 1.67
},
{
"text": "okay",
"start": 1.688,
"end": 2.192
},
{
"text": "Allow",
"start": 2.236,
"end": 2.53
},
{
"text": "the",
"start": 2.556,
"end": 2.766
},
{
"text": "good",
"start": 2.784,
"end": 3.036
},
{
"text": "ones",
"start": 3.058,
"end": 3.563
},
{
"text": "Block",
"start": 3.607,
"end": 3.901
},
{
"text": "the",
"start": 3.926,
"end": 4.137
},
{
"text": "rest",
"start": 4.155,
"end": 4.659
}
]
},
{
"frame": 6,
"path": "assets/voice/06.wav",
"duration_s": 6.513,
"words": [
{
"text": "And",
"start": 0.5,
"end": 0.71
},
{
"text": "when",
"start": 0.728,
"end": 0.979
},
{
"text": "it",
"start": 1.001,
"end": 1.169
},
{
"text": "really",
"start": 1.184,
"end": 1.519
},
{
"text": "matters",
"start": 1.548,
"end": 2.051
},
{
"text": "see",
"start": 2.095,
"end": 2.305
},
{
"text": "where",
"start": 2.323,
"end": 2.616
},
{
"text": "they",
"start": 2.642,
"end": 2.893
},
{
"text": "are",
"start": 2.915,
"end": 3.377
},
{
"text": "Location",
"start": 3.417,
"end": 3.962
},
{
"text": "only",
"start": 4.009,
"end": 4.261
},
{
"text": "when",
"start": 4.283,
"end": 4.534
},
{
"text": "you",
"start": 4.556,
"end": 4.766
},
{
"text": "need",
"start": 4.784,
"end": 5.035
},
{
"text": "it",
"start": 5.057,
"end": 5.477
}
]
},
{
"frame": 7,
"path": "assets/voice/07.wav",
"duration_s": 5.468,
"words": [
{
"text": "Because",
"start": 0.5,
"end": 0.896
},
{
"text": "kids",
"start": 0.93,
"end": 1.194
},
{
"text": "deserve",
"start": 1.217,
"end": 1.613
},
{
"text": "some",
"start": 1.647,
"end": 1.911
},
{
"text": "freedom",
"start": 1.934,
"end": 2.462
},
{
"text": "and",
"start": 2.508,
"end": 2.728
},
{
"text": "parents",
"start": 2.747,
"end": 3.143
},
{
"text": "deserve",
"start": 3.177,
"end": 3.573
},
{
"text": "some",
"start": 3.607,
"end": 3.871
},
{
"text": "calm",
"start": 3.894,
"end": 4.422
}
]
},
{
"frame": 8,
"path": "assets/voice/08.wav",
"duration_s": 3.783,
"words": [
{
"text": "Works",
"start": 0.5,
"end": 0.842
},
{
"text": "on",
"start": 0.872,
"end": 1.067
},
{
"text": "Android",
"start": 1.084,
"end": 1.524
},
{
"text": "and",
"start": 1.562,
"end": 1.806
},
{
"text": "on",
"start": 1.827,
"end": 2.023
},
{
"text": "iPhone",
"start": 2.04,
"end": 2.724
}
]
},
{
"frame": 9,
"path": "assets/voice/09.wav",
"duration_s": 5.772,
"words": [
{
"text": "lookatu",
"start": 0.5,
"end": 1.106
},
{
"text": "Look",
"start": 1.158,
"end": 1.401
},
{
"text": "out",
"start": 1.422,
"end": 1.623
},
{
"text": "for",
"start": 1.641,
"end": 1.843
},
{
"text": "them",
"start": 1.86,
"end": 2.345
},
{
"text": "Download",
"start": 2.387,
"end": 2.791
},
{
"text": "it",
"start": 2.826,
"end": 2.987
},
{
"text": "today",
"start": 3.001,
"end": 3.526
}
]
}
],
"sfx": []
}
+46
View File
@@ -0,0 +1,46 @@
{
"provider": "kokoro",
"speed": "0.85",
"lines": [
{
"id": "01",
"text": "Where are they right now? And what are they really doing on that phone?"
},
{
"id": "02",
"text": "Endless scrolling. Arguments at dinner. And that quiet worry you never say out loud."
},
{
"id": "03",
"text": "There has to be a better way. Meet lookatu. The child safety app that gives you peace of mind."
},
{
"id": "04",
"text": "Set screen time limits that actually stick. When the time is up, the phone knows."
},
{
"id": "05",
"text": "Choose which apps are okay. Allow the good ones. Block the rest."
},
{
"id": "06",
"text": "And when it really matters, see where they are. Location, only when you need it."
},
{
"id": "07",
"text": "Because kids deserve some freedom, and parents deserve some calm."
},
{
"id": "08",
"text": "Works on Android and on iPhone."
},
{
"id": "09",
"text": "lookatu. Look out for them. Download it today."
}
],
"bgm": {
"mode": "none"
},
"voice": "af_heart"
}
@@ -0,0 +1 @@
[]
File diff suppressed because it is too large. Load diff
@@ -0,0 +1 @@
No assets were captured. No website, logo or screenshots supplied. Use invented phone UI mock-ups and a typographic lookatu wordmark.
@@ -0,0 +1 @@
{ "title": "lookatu", "description": "Child-safety app (Android and iOS) that lets parents control kids' phone usage and track location when needed.", "colors": [], "fonts": [] }
@@ -0,0 +1,4 @@
lookatu - child safety app for Android and iOS.
Parents control how their kids use the phone (time limits, app control).
Parents can track location when needed.
Message: keep your kids safe on their phones, with peace of mind for parents.
@@ -0,0 +1,235 @@
<template id="captions-template" data-composition-id="captions" data-width="1080" data-height="1920">
<script src="assets/vendor/gsap.min.js"></script>
<style data-brand-tokens>
@font-face { font-family: 'Quicksand'; src: url('assets/fonts/quicksand-var.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: block; }
:root {
--cream: #F5F0E6;
--turquoise: #7ECDC0;
--soft-pink: #F7C8D4;
--butter: #FDE68A;
--mint: #A8E6CF;
--lavender: #D4A5E8;
--peach: #FFCBA4;
--sky: #A8D8F0;
--coral: #F8635F;
--text-dark: #2D2D2D;
--text-muted: #6B6B6B;
--white: #FFFFFF;
--cap-ink: #2D2D2D;
--cap-canvas: #F5F0E6;
--cap-accent: #F8635F;
--cap-accent-2: #FDE68A;
--font-display: "Fredoka One", system-ui, serif;
--font-body: "Quicksand", system-ui, sans-serif;
--cap-band-top: 1600px;
--cap-band-height: 320px;
}
</style>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Fredoka One"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#captions-root {
position: absolute;
inset: 0;
pointer-events: none;
}
.caption-layer {
position: absolute;
inset: 0;
z-index: 20;
pointer-events: none;
}
/* the keep-out band, from captionBand(H) — frame content stays above it */
.caption-stage {
position: absolute;
left: 0;
right: 0;
top: var(--cap-band-top, 900px);
height: var(--cap-band-height, 180px);
display: flex;
align-items: center;
justify-content: center;
}
.caption-group {
/* every group is absolutely stacked over the SAME band — exactly one shown at a
time via opacity. Must be absolute: in-flow groups would lay out side-by-side. */
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
}
/* THE picture-book card — cream surface, 3px charcoal outline, generous 24px radius,
a hard 6px charcoal offset shadow (no blur): the sticker-on-paper signature */
.caption-pill {
max-width: 80%;
padding: 18px 40px 22px;
background: var(--cap-canvas, #f5f0e6);
border: 3px solid var(--cap-ink, #2d2d2d);
border-radius: 24px;
box-shadow: 6px 6px 0 var(--cap-ink, #2d2d2d);
}
.caption-line {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.18em 0.32em;
font-family: "Fredoka One", sans-serif;
font-weight: 600;
font-size: clamp(38px, 4.2vw, 56px);
line-height: 1.22;
letter-spacing: 0.02em;
}
/* upcoming — muted charcoal, no candy block */
.caption-word {
display: inline-block;
padding: 0.02em 0.16em;
border-radius: 12px;
color: color-mix(in srgb, var(--cap-ink, #2d2d2d) 52%, var(--cap-canvas, #f5f0e6));
}
/* current word — THE sunny-pastel candy block: a rounded coral chip with a 3px
charcoal outline + a hard 3px charcoal offset shadow (no blur), charcoal on top */
.caption-word.is-active {
color: var(--cap-ink, #2d2d2d);
background: var(--cap-accent, #f8635f);
border-radius: 12px;
box-shadow:
0 0 0 3px var(--cap-ink, #2d2d2d),
3px 5px 0 0 var(--cap-ink, #2d2d2d);
}
/* already spoken — solid charcoal, candy block cleared */
.caption-word.is-spoken {
color: var(--cap-ink, #2d2d2d);
background: transparent;
box-shadow: none;
}
/* optional per-word accents (dormant until a word-classer tags them) */
/* brand / key term — a butter candy chip, the cheerful second pastel */
.caption-word.cap-brand {
color: var(--cap-ink, #2d2d2d);
background: var(--cap-accent-2, #fde68a);
border-radius: 12px;
box-shadow: 0 0 0 3px var(--cap-ink, #2d2d2d);
}
/* numerals / data — Quicksand, slightly recessed */
.caption-word.cap-num {
font-family: "Quicksand", system-ui, sans-serif;
font-weight: 700;
color: color-mix(in srgb, var(--cap-ink, #2d2d2d) 78%, var(--cap-canvas, #f5f0e6));
}
/* emphasis — a butter underline rule (the second sunny pastel) */
.caption-word.cap-cta {
text-decoration: underline;
text-decoration-thickness: 3px;
text-decoration-color: var(--cap-accent-2, #fde68a);
text-underline-offset: 0.12em;
}
@media (max-aspect-ratio: 9/16) {
.caption-pill {
max-width: 92%;
}
.caption-line {
font-size: clamp(32px, 5.2vw, 46px);
}
}
</style>
<div
id="captions-root"
data-composition-id="captions"
data-timeline-locked
data-start="0"
data-duration="54.733"
data-fps="30"
data-width="1080"
data-height="1920"
>
<div class="caption-layer" aria-hidden="true">
<div id="caption-stage" class="caption-stage"></div>
</div>
</div>
<script>
// captions.mjs injects the engine's scene-aware groups + real duration here.
var GROUPS = [{"id":"caption-group-0","frame":1,"start":0.8,"end":1.28,"text":"Where are","words":[{"id":"caption-word-0-0","text":"Where","start":0.8,"end":1.058},{"id":"caption-word-0-1","text":"are","start":1.08,"end":1.264}]},{"id":"caption-group-1","frame":1,"start":1.28,"end":1.8,"text":"they right","words":[{"id":"caption-word-1-0","text":"they","start":1.28,"end":1.501},{"id":"caption-word-1-1","text":"right","start":1.52,"end":1.778}]},{"id":"caption-group-2","frame":1,"start":1.8,"end":2.44,"text":"now And","words":[{"id":"caption-word-2-0","text":"now","start":1.8,"end":2.205},{"id":"caption-word-2-1","text":"And","start":2.24,"end":2.424}]},{"id":"caption-group-3","frame":1,"start":2.44,"end":2.88,"text":"what are","words":[{"id":"caption-word-3-0","text":"what","start":2.44,"end":2.661},{"id":"caption-word-3-1","text":"are","start":2.68,"end":2.864}]},{"id":"caption-group-4","frame":1,"start":2.88,"end":3.44,"text":"they really","words":[{"id":"caption-word-4-0","text":"they","start":2.88,"end":3.101},{"id":"caption-word-4-1","text":"really","start":3.12,"end":3.414}]},{"id":"caption-group-5","frame":1,"start":3.44,"end":3.88,"text":"doing on","words":[{"id":"caption-word-5-0","text":"doing","start":3.44,"end":3.698},{"id":"caption-word-5-1","text":"on","start":3.72,"end":3.867}]},{"id":"caption-group-6","frame":1,"start":3.88,"end":4.718,"text":"that phone","words":[{"id":"caption-word-6-0","text":"that","start":3.88,"end":4.101},{"id":"caption-word-6-1","text":"phone","start":4.12,"end":4.598}]},{"id":"caption-group-7","frame":2,"start":6.14,"end":8.158,"text":"Endless scrolling Arguments at","words":[{"id":"caption-word-7-0","text":"Endless","start":6.14,"end":6.548},{"id":"caption-word-7-1","text":"scrolling","start":6.583,"end":7.353},{"id":"caption-word-7-2","text":"Arguments","start":7.42,"end":7.918},{"id":"caption-word-7-3","text":"at","start":7.961,"end":8.142}]},{"id":"caption-group-8","frame":2,"start":8.158,"end":9.388,"text":"dinner And that","words":[{"id":"caption-word-8-0","text":"dinner","start":8.158,"end":8.792},{"id":"caption-word-8-1","text":"And","start":8.847,"end":9.073},{"id":"caption-word-8-2","text":"that","start":9.093,"end":9.365}]},{"id":"caption-group-9","frame":2,"start":9.388,"end":10.078,"text":"quiet worry","words":[{"id":"caption-word-9-0","text":"quiet","start":9.388,"end":9.705},{"id":"caption-word-9-1","text":"worry","start":9.733,"end":10.05}]},{"id":"caption-group-10","frame":2,"start":10.078,"end":10.668,"text":"you never","words":[{"id":"caption-word-10-0","text":"you","start":10.078,"end":10.304},{"id":"caption-word-10-1","text":"never","start":10.324,"end":10.641}]},{"id":"caption-group-11","frame":2,"start":10.668,"end":11.824,"text":"say out loud","words":[{"id":"caption-word-11-0","text":"say","start":10.668,"end":10.895},{"id":"caption-word-11-1","text":"out","start":10.914,"end":11.141},{"id":"caption-word-11-2","text":"loud","start":11.16,"end":11.704}]},{"id":"caption-group-12","frame":3,"start":13.251,"end":13.823,"text":"There has","words":[{"id":"caption-word-12-0","text":"There","start":13.251,"end":13.558},{"id":"caption-word-12-1","text":"has","start":13.584,"end":13.803}]},{"id":"caption-group-13","frame":3,"start":13.823,"end":14.204,"text":"to be","words":[{"id":"caption-word-13-0","text":"to","start":13.823,"end":13.998},{"id":"caption-word-13-1","text":"be","start":14.013,"end":14.188}]},{"id":"caption-group-14","frame":3,"start":14.204,"end":15.251,"text":"a better way","words":[{"id":"caption-word-14-0","text":"a","start":14.204,"end":14.335},{"id":"caption-word-14-1","text":"better","start":14.346,"end":14.697},{"id":"caption-word-14-2","text":"way","start":14.727,"end":15.209}]},{"id":"caption-group-15","frame":3,"start":15.251,"end":16.823,"text":"Meet lookatu The child","words":[{"id":"caption-word-15-0","text":"Meet","start":15.251,"end":15.514},{"id":"caption-word-15-1","text":"lookatu","start":15.537,"end":16.194},{"id":"caption-word-15-2","text":"The","start":16.252,"end":16.471},{"id":"caption-word-15-3","text":"child","start":16.49,"end":16.796}]},{"id":"caption-group-16","frame":3,"start":16.823,"end":17.442,"text":"safety app","words":[{"id":"caption-word-16-0","text":"safety","start":16.823,"end":17.174},{"id":"caption-word-16-1","text":"app","start":17.204,"end":17.423}]},{"id":"caption-group-17","frame":3,"start":17.442,"end":18.061,"text":"that gives","words":[{"id":"caption-word-17-0","text":"that","start":17.442,"end":17.705},{"id":"caption-word-17-1","text":"gives","start":17.728,"end":18.035}]},{"id":"caption-group-18","frame":3,"start":18.061,"end":18.633,"text":"you peace","words":[{"id":"caption-word-18-0","text":"you","start":18.061,"end":18.281},{"id":"caption-word-18-1","text":"peace","start":18.3,"end":18.606}]},{"id":"caption-group-19","frame":3,"start":18.633,"end":19.469,"text":"of mind","words":[{"id":"caption-word-19-0","text":"of","start":18.633,"end":18.808},{"id":"caption-word-19-1","text":"mLine truncated
var DURATION = 54.733;
(function () {
var stage = document.getElementById("caption-stage");
// build DOM: one .caption-group per group, .caption-word per word
GROUPS.forEach(function (group, g) {
var groupEl = document.createElement("div");
groupEl.className = "caption-group";
groupEl.id = "caption-group-" + g;
var pill = document.createElement("div");
pill.className = "caption-pill";
var line = document.createElement("div");
line.className = "caption-line";
(group.words || []).forEach(function (w, i) {
var span = document.createElement("span");
span.className = "caption-word";
span.id = "caption-word-" + g + "-" + i;
span.textContent = String(w.text);
line.appendChild(span);
});
pill.appendChild(line);
groupEl.appendChild(pill);
stage.appendChild(groupEl);
});
window.__timelines = window.__timelines || {};
var tl = gsap.timeline({ paused: true });
GROUPS.forEach(function (group, g) {
var groupEl = document.getElementById("caption-group-" + g);
var words = group.words || [];
var next = GROUPS[g + 1];
var isLast = g === GROUPS.length - 1;
var start = Math.max(0, Number(group.start));
var end = isLast ? DURATION : Math.min(Number(next.start), Number(group.end) + 0.3);
if (end <= start) end = start + 0.01;
// group on/off — exactly one group visible at a time
tl.set(groupEl, { opacity: 1 }, start);
tl.set(groupEl, { opacity: 0 }, end);
// 3-state karaoke via className SETS — seek-safe (gsap.set applies on the
// engine's frame-by-frame seek; .call callbacks would NOT fire).
words.forEach(function (w, i) {
var el = document.getElementById("caption-word-" + g + "-" + i);
var at = Math.max(start, Number(w.start));
tl.set(el, { className: "caption-word" }, start); // upcoming (also resets on reverse-seek)
tl.set(el, { className: "caption-word is-active" }, at); // current
tl.fromTo(el, { scale: 0.82 }, { scale: 1, duration: 0.2, ease: "back.out(2.6)" }, at);
if (i + 1 < words.length) {
var nextAt = Math.max(start, Number(words[i + 1].start));
tl.set(el, { className: "caption-word is-spoken" }, nextAt); // demote when next activates
}
});
if (words.length) {
var lastEl = document.getElementById("caption-word-" + g + "-" + (words.length - 1));
var lastSpoken = Math.min(end, Number(words[words.length - 1].end) + 0.1);
tl.set(lastEl, { className: "caption-word is-spoken" }, lastSpoken);
}
});
// full-span anchor so the sub-comp timeline spans the whole video
tl.to({}, { duration: DURATION }, 0);
window.__timelines["captions"] = tl;
})();
</script>
<style>
.caption-line { line-height: 1.1 !important; }
</style>
</template>
@@ -0,0 +1,63 @@
<template>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#root { position: absolute; inset: 0; overflow: hidden; color: #2D2D2D; font-family: "Quicksand", sans-serif; font-weight: 600; }
.f1-bg { position: absolute; inset: 0; }
.f1-orn { position: absolute; z-index: 1; }
.f1-orn svg { width: 100%; height: 100%; display: block; overflow: visible; }
.f1-z { position: absolute; z-index: 2; }
.f1-d { font-family: "Fredoka", sans-serif; font-weight: 600; letter-spacing: 0.02em; line-height: 1.05; }
.f1-card { background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 40px; box-shadow: 12px 12px 0 #2D2D2D; }
.f1-pill { display: inline-flex; align-items: center; gap: 18px; border: 5px solid #2D2D2D; border-radius: 80px; box-shadow: 8px 8px 0 #2D2D2D; padding: 20px 46px; white-space: nowrap; }
.f1-phone { position: absolute; z-index: 2; left: 260px; top: 230px; width: 560px; height: 1120px; background: #FFFFFF; border: 6px solid #2D2D2D; border-radius: 80px; box-shadow: 16px 16px 0 #2D2D2D; overflow: hidden; }
.f1-notch { position: absolute; left: 50%; top: 22px; margin-left: -70px; width: 140px; height: 28px; border-radius: 20px; background: #2D2D2D; }
.f1-bub { position: absolute; z-index: 2; display: flex; align-items: center; justify-content: center; border-radius: 70px; }
.f1-bub span { font-size: 168px; color: #2D2D2D; }
.f1-b1 { left: 80px; top: 250px; width: 760px; height: 360px; }
.f1-b2 { left: 240px; top: 700px; width: 760px; height: 360px; background: #FDE68A; }
.f1-tail1 { position: absolute; z-index: 3; left: 190px; top: 584px; width: 70px; height: 70px; background: #FFFFFF; border-right: 5px solid #2D2D2D; border-bottom: 5px solid #2D2D2D; transform: rotate(45deg); }
.f1-tail2 { position: absolute; z-index: 3; left: 820px; top: 1034px; width: 70px; height: 70px; background: #FDE68A; border-right: 5px solid #2D2D2D; border-bottom: 5px solid #2D2D2D; transform: rotate(45deg); }
.f1-ph { left: 390px; top: 1160px; width: 300px; height: 420px; background: #2D2D2D; border: 6px solid #2D2D2D; border-radius: 56px; box-shadow: 12px 12px 0 rgba(45,45,45,0.25); display: flex; align-items: center; justify-content: center; }
.f1-ph b { font-family: "Fredoka", sans-serif; font-weight: 600; font-size: 170px; color: #FDE68A; }
</style>
<div id="root" data-composition-id="01-hook" data-width="1080" data-height="1920" data-duration="6.14">
<div id="scene1-bg" class="clip f1-bg" data-start="0" data-duration="6.14" data-track-index="0" style="background:#A8D8F0"></div>
<div class="f1-orn" style="left:-70px;top:90px;width:300px;height:300px"><svg viewBox="0 0 100 100" style="transform:rotate(-4deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f1-orn" style="left:800px;top:40px;width:260px;height:260px"><svg viewBox="0 0 100 100" style="transform:rotate(10deg)" xmlns="http://www.w3.org/2000/svg"><line x1="50" y1="9" x2="50" y2="22" transform="rotate(0 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(30 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(60 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(90 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(120 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(150 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(180 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(210 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(240 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(270 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(300 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(330 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><circle cx="50" cy="50" r="24" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f1-orn" style="left:900px;top:1150px;width:150px;height:150px"><svg viewBox="0 0 100 100" style="transform:rotate(14deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f1-orn" style="left:-50px;top:1330px;width:230px;height:230px"><svg viewBox="0 0 100 100" style="transform:rotate(8deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f1-orn" style="left:760px;top:1380px;width:260px;height:260px"><svg viewBox="0 0 100 100" style="transform:rotate(5deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f1-bub f1-card f1-b1"><span class="f1-d">WHERE?</span></div>
<div class="f1-tail1"></div>
<div class="f1-bub f1-card f1-b2"><span class="f1-d">WHAT?</span></div>
<div class="f1-tail2"></div>
<div class="f1-z f1-ph"><b>?</b></div>
</div>
<script>
(function () {
const DUR = 5.64;
const tl = gsap.timeline({ paused: true });
const orns = document.querySelectorAll(".f1-orn");
tl.fromTo(orns, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.55, ease: "back.out(1.8)", stagger: 0.07 }, 0.05);
orns.forEach((el, i) => {
const t0 = 0.9 + i * 0.12, half = Math.max(1.2, (DUR - 1.6) / 2), amp = i % 2 ? -14 : 14;
tl.to(el, { y: amp, duration: half, ease: "sine.inOut" }, t0).to(el, { y: 0, duration: half, ease: "sine.inOut" }, t0 + half);
});
tl.fromTo(".f1-b1", { scale: 0.2, opacity: 0, rotation: -8 }, { scale: 1, opacity: 1, rotation: -2, duration: 0.55, ease: "back.out(1.9)" }, 0.3);
tl.fromTo(".f1-tail1", { opacity: 0 }, { opacity: 1, duration: 0.2 }, 0.6499999999999999);
tl.fromTo(".f1-b2", { scale: 0.2, opacity: 0, rotation: 8 }, { scale: 1, opacity: 1, rotation: 2, duration: 0.55, ease: "back.out(1.9)" }, 2.12);
tl.fromTo(".f1-tail2", { opacity: 0 }, { opacity: 1, duration: 0.2 }, 2.47);
tl.fromTo(".f1-ph", { scale: 0, rotation: 12 }, { scale: 1, rotation: 0, duration: 0.6, ease: "back.out(1.7)" }, 3);
tl.to(".f1-ph", { rotation: -5, duration: 0.18, ease: "sine.inOut" }, 3.6)
.to(".f1-ph", { rotation: 5, duration: 0.3, ease: "sine.inOut" }, ">")
.to(".f1-ph", { rotation: -3, duration: 0.3, ease: "sine.inOut" }, ">")
.to(".f1-ph", { rotation: 0, duration: 0.25, ease: "sine.inOut" }, ">");
window.__timelines["01-hook"] = tl;
})();
</script>
</template>
@@ -0,0 +1,49 @@
<template>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#root { position: absolute; inset: 0; overflow: hidden; color: #2D2D2D; font-family: "Quicksand", sans-serif; font-weight: 600; }
.f2-bg { position: absolute; inset: 0; }
.f2-orn { position: absolute; z-index: 1; }
.f2-orn svg { width: 100%; height: 100%; display: block; overflow: visible; }
.f2-z { position: absolute; z-index: 2; }
.f2-d { font-family: "Fredoka", sans-serif; font-weight: 600; letter-spacing: 0.02em; line-height: 1.05; }
.f2-card { background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 40px; box-shadow: 12px 12px 0 #2D2D2D; }
.f2-pill { display: inline-flex; align-items: center; gap: 18px; border: 5px solid #2D2D2D; border-radius: 80px; box-shadow: 8px 8px 0 #2D2D2D; padding: 20px 46px; white-space: nowrap; }
.f2-phone { position: absolute; z-index: 2; left: 260px; top: 230px; width: 560px; height: 1120px; background: #FFFFFF; border: 6px solid #2D2D2D; border-radius: 80px; box-shadow: 16px 16px 0 #2D2D2D; overflow: hidden; }
.f2-notch { position: absolute; left: 50%; top: 22px; margin-left: -70px; width: 140px; height: 28px; border-radius: 20px; background: #2D2D2D; }
.f2-row { position: absolute; z-index: 2; left: 100px; width: 880px; height: 320px; display: flex; align-items: center; padding: 0 44px; gap: 34px; }
.f2-row svg { width: 130px; height: 130px; flex: none; background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 50%; padding: 22px; box-sizing: border-box; }
.f2-row span { font-size: 92px; color: #2D2D2D; }
</style>
<div id="root" data-composition-id="02-worry" data-width="1080" data-height="1920" data-duration="7.511">
<div id="scene2-bg" class="clip f2-bg" data-start="0" data-duration="7.511" data-track-index="0" style="background:#F5F0E6"></div>
<div class="f2-orn" style="left:880px;top:90px;width:170px;height:170px"><svg viewBox="0 0 100 100" style="transform:rotate(12deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f2-orn" style="left:-60px;top:150px;width:220px;height:220px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f2-orn" style="left:780px;top:1360px;width:300px;height:300px"><svg viewBox="0 0 100 100" style="transform:rotate(-6deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f2-orn" style="left:50px;top:1420px;width:130px;height:130px"><svg viewBox="0 0 100 100" style="transform:rotate(-14deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f2-orn" style="left:900px;top:700px;width:170px;height:170px"><svg viewBox="0 0 100 100" style="transform:rotate(20deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f2-row f2-card f2-r0" style="top:250px;background:#FDE68A"><svg viewBox="0 0 100 100"><path d="M20 34 L50 62 L80 34" fill="none" stroke="#2D2D2D" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/><path d="M20 12 L50 40 L80 12" fill="none" stroke="#2D2D2D" stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/></svg><span class="f2-d">SCROLLING</span></div>
<div class="f2-row f2-card f2-r1" style="top:650px;background:#F7C8D4"><svg viewBox="0 0 100 100"><polygon points="58,6 24,54 48,54 40,94 76,42 52,42" fill="#F8635F" stroke="#2D2D2D" stroke-width="5" stroke-linejoin="round"/></svg><span class="f2-d">ARGUING</span></div>
<div class="f2-row f2-card f2-r2" style="top:1050px;background:#D4A5E8"><svg viewBox="0 0 100 100"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="5" stroke-linejoin="round"/></svg><span class="f2-d">WORRYING</span></div>
</div>
<script>
(function () {
const DUR = 7.111;
const tl = gsap.timeline({ paused: true });
const orns = document.querySelectorAll(".f2-orn");
tl.fromTo(orns, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.55, ease: "back.out(1.8)", stagger: 0.07 }, 0.05);
orns.forEach((el, i) => {
const t0 = 0.9 + i * 0.12, half = Math.max(1.2, (DUR - 1.6) / 2), amp = i % 2 ? -14 : 14;
tl.to(el, { y: amp, duration: half, ease: "sine.inOut" }, t0).to(el, { y: 0, duration: half, ease: "sine.inOut" }, t0 + half);
});
tl.fromTo(".f2-r0", { x: -260, opacity: 0, rotation: -12 }, { x: 0, opacity: 1, rotation: -3, duration: 0.6, ease: "back.out(1.5)" }, 0.38);
tl.fromTo(".f2-r1", { x: 260, opacity: 0, rotation: 10 }, { x: 0, opacity: 1, rotation: 2.5, duration: 0.6, ease: "back.out(1.5)" }, 1.66);
tl.fromTo(".f2-r2", { x: -260, opacity: 0, rotation: -8 }, { x: 0, opacity: 1, rotation: -2, duration: 0.6, ease: "back.out(1.5)" }, 3.09);
window.__timelines["02-worry"] = tl;
})();
</script>
</template>
@@ -0,0 +1,71 @@
<template>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#root { position: absolute; inset: 0; overflow: hidden; color: #2D2D2D; font-family: "Quicksand", sans-serif; font-weight: 600; }
.f3-bg { position: absolute; inset: 0; }
.f3-orn { position: absolute; z-index: 1; }
.f3-orn svg { width: 100%; height: 100%; display: block; overflow: visible; }
.f3-z { position: absolute; z-index: 2; }
.f3-d { font-family: "Fredoka", sans-serif; font-weight: 600; letter-spacing: 0.02em; line-height: 1.05; }
.f3-card { background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 40px; box-shadow: 12px 12px 0 #2D2D2D; }
.f3-pill { display: inline-flex; align-items: center; gap: 18px; border: 5px solid #2D2D2D; border-radius: 80px; box-shadow: 8px 8px 0 #2D2D2D; padding: 20px 46px; white-space: nowrap; }
.f3-phone { position: absolute; z-index: 2; left: 260px; top: 230px; width: 560px; height: 1120px; background: #FFFFFF; border: 6px solid #2D2D2D; border-radius: 80px; box-shadow: 16px 16px 0 #2D2D2D; overflow: hidden; }
.f3-notch { position: absolute; left: 50%; top: 22px; margin-left: -70px; width: 140px; height: 28px; border-radius: 20px; background: #2D2D2D; }
.f3-eye { display: block; position: relative; width: 128.8px; height: 128.8px; border-radius: 50%; background: #FFFFFF; border: 6.8999999999999995px solid #2D2D2D; box-sizing: border-box; text-shadow: none; }
.f3-pup { position: absolute; left: 50%; top: 50%; width: 50.6px; height: 50.6px; margin: -25.3px 0 0 -25.3px; border-radius: 50%; background: #2D2D2D; }
.f3-badge { left: 50%; top: 250px; margin-left: -330px; width: 660px; justify-content: center; background: #FDE68A; }
.f3-badge span { font-size: 62px; }
.f3-wmwrap { left: 0; right: 0; top: 700px; }
.f3-shield { left: 50%; top: 380px; margin-left: -115px; width: 230px; height: 260px; }
.f3-peace { left: 50%; top: 1130px; margin-left: -400px; width: 800px; height: 200px; display: flex; align-items: center; justify-content: center; gap: 36px; }
.f3-peace span { font-size: 78px; }
.f3-peace svg { width: 100px; height: 100px; }
</style>
<div id="root" data-composition-id="03-meet" data-width="1080" data-height="1920" data-duration="8.644">
<div id="scene3-bg" class="clip f3-bg" data-start="0" data-duration="8.644" data-track-index="0" style="background:#7ECDC0"></div>
<div class="f3-orn" style="left:790px;top:60px;width:280px;height:280px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><line x1="50" y1="9" x2="50" y2="22" transform="rotate(0 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(30 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(60 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(90 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(120 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(150 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(180 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(210 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(240 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(270 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(300 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(330 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><circle cx="50" cy="50" r="24" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f3-orn" style="left:-80px;top:200px;width:330px;height:330px"><svg viewBox="0 0 100 100" style="transform:rotate(-3deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f3-orn" style="left:-40px;top:1250px;width:260px;height:260px"><svg viewBox="0 0 100 100" style="transform:rotate(10deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f3-orn" style="left:820px;top:1330px;width:230px;height:230px"><svg viewBox="0 0 100 100" style="transform:rotate(-12deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f3-orn" style="left:130px;top:1010px;width:120px;height:120px"><svg viewBox="0 0 100 100" style="transform:rotate(8deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f3-orn" style="left:830px;top:1020px;width:140px;height:140px"><svg viewBox="0 0 100 100" style="transform:rotate(-10deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f3-z f3-pill f3-badge"><span class="f3-d">A BETTER WAY</span></div>
<svg class="f3-z f3-shield" viewBox="0 0 100 112"><path d="M50 4 L92 18 V54 C92 82 70 100 50 108 C30 100 8 82 8 54 V18 Z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="4.5" stroke-linejoin="round"/><path d="M30 56 L45 71 L72 40" fill="none" stroke="#F8635F" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/></svg>
<div class="f3-z f3-wmwrap">
<div class="f3-wm f3-d" style="display:flex;align-items:flex-end;justify-content:center;font-size:230px;color:#2D2D2D;text-shadow:8px 8px 0 #FFFFFF;line-height:1">
<span>l</span>
<span class="f3-eye" style="margin:0 6.8999999999999995px 13.799999999999999px 11.5px"><i class="f3-pup"></i></span>
<span class="f3-eye" style="margin:0 11.5px 13.799999999999999px 6.8999999999999995px"><i class="f3-pup"></i></span>
<span>katu</span>
</div></div>
<div class="f3-z f3-card f3-peace"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><path d="M50 86 C8 56 10 18 32 18 C42 18 48 25 50 32 C52 25 58 18 68 18 C90 18 92 56 50 86 Z" fill="#F8635F" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg><span class="f3-d">PEACE OF MIND</span></div>
</div>
<script>
(function () {
const DUR = 8.144;
const tl = gsap.timeline({ paused: true });
const orns = document.querySelectorAll(".f3-orn");
tl.fromTo(orns, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.55, ease: "back.out(1.8)", stagger: 0.07 }, 0.05);
orns.forEach((el, i) => {
const t0 = 0.9 + i * 0.12, half = Math.max(1.2, (DUR - 1.6) / 2), amp = i % 2 ? -14 : 14;
tl.to(el, { y: amp, duration: half, ease: "sine.inOut" }, t0).to(el, { y: 0, duration: half, ease: "sine.inOut" }, t0 + half);
});
tl.fromTo(".f3-badge", { y: -80, opacity: 0, scale: 0.7 }, { y: 0, opacity: 1, scale: 1, duration: 0.55, ease: "back.out(1.8)" }, 0.38);
tl.fromTo(".f3-shield", { scale: 0, rotation: -20, opacity: 0 }, { scale: 1, rotation: 0, opacity: 1, duration: 0.65, ease: "back.out(1.9)" }, 1.48);
tl.fromTo(".f3-wm", { scale: 0.3, opacity: 0, y: 60 }, { scale: 1, opacity: 1, y: 0, duration: 0.75, ease: "back.out(1.6)" }, 2.38);
const pups = document.querySelectorAll(".f3-pup"), r = 23;
tl.fromTo(pups, { x: 0, y: 0 }, { x: -r, y: -r * 0.4, duration: 0.35, ease: "power2.out" }, 2.9699999999999998)
.to(pups, { x: r, y: r * 0.3, duration: 0.5, ease: "power2.inOut" }, 3.57)
.to(pups, { x: 0, y: 0, duration: 0.35, ease: "power2.out" }, 4.32);
tl.fromTo(".f3-peace", { y: 100, opacity: 0, rotation: -3 }, { y: 0, opacity: 1, rotation: 0, duration: 0.6, ease: "back.out(1.6)" }, 5.43);
tl.to(".f3-peace svg", { scale: 1.2, duration: 0.3, ease: "sine.inOut" }, 6.13).to(".f3-peace svg", { scale: 1, duration: 0.3, ease: "sine.inOut" }, ">");
window.__timelines["03-meet"] = tl;
})();
</script>
</template>
@@ -0,0 +1,78 @@
<template>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#root { position: absolute; inset: 0; overflow: hidden; color: #2D2D2D; font-family: "Quicksand", sans-serif; font-weight: 600; }
.f4-bg { position: absolute; inset: 0; }
.f4-orn { position: absolute; z-index: 1; }
.f4-orn svg { width: 100%; height: 100%; display: block; overflow: visible; }
.f4-z { position: absolute; z-index: 2; }
.f4-d { font-family: "Fredoka", sans-serif; font-weight: 600; letter-spacing: 0.02em; line-height: 1.05; }
.f4-card { background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 40px; box-shadow: 12px 12px 0 #2D2D2D; }
.f4-pill { display: inline-flex; align-items: center; gap: 18px; border: 5px solid #2D2D2D; border-radius: 80px; box-shadow: 8px 8px 0 #2D2D2D; padding: 20px 46px; white-space: nowrap; }
.f4-phone { position: absolute; z-index: 2; left: 260px; top: 230px; width: 560px; height: 1120px; background: #FFFFFF; border: 6px solid #2D2D2D; border-radius: 80px; box-shadow: 16px 16px 0 #2D2D2D; overflow: hidden; }
.f4-notch { position: absolute; left: 50%; top: 22px; margin-left: -70px; width: 140px; height: 28px; border-radius: 20px; background: #2D2D2D; }
.f4-label { position: absolute; left: 0; right: 0; top: 130px; text-align: center; }
.f4-label .f4-pill { background: #FDE68A; padding: 14px 44px; } .f4-label span { font-size: 56px; }
.f4-ringbox { position: absolute; left: 90px; top: 250px; width: 380px; height: 380px; }
.f4-ringbox svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.f4-time { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 104px; }
.f4-lock { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.f4-lock svg { position: static; width: 170px; height: 170px; }
.f4-state { position: absolute; left: 50px; top: 740px; width: 460px; text-align: center; }
.f4-state .f4-pill { padding: 14px 0; width: 100%; justify-content: center; box-shadow: 6px 6px 0 #2D2D2D; } .f4-state span { font-size: 56px; }
.f4-s2 { position: absolute; left: 0; top: 0; background: #F8635F; color: #FFFFFF; opacity: 0; }
.f4-bar { position: absolute; left: 70px; top: 930px; width: 420px; height: 26px; border: 4px solid #2D2D2D; border-radius: 20px; background: #F5F0E6; overflow: hidden; }
.f4-bar i { display: block; height: 100%; width: 100%; background: #A8E6CF; transform-origin: left center; }
</style>
<div id="root" data-composition-id="04-limits" data-width="1080" data-height="1920" data-duration="7.099">
<div id="scene4-bg" class="clip f4-bg" data-start="0" data-duration="7.099" data-track-index="0" style="background:#F5F0E6"></div>
<div class="f4-orn" style="left:-70px;top:120px;width:240px;height:240px"><svg viewBox="0 0 100 100" style="transform:rotate(5deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f4-orn" style="left:840px;top:90px;width:230px;height:230px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><line x1="50" y1="9" x2="50" y2="22" transform="rotate(0 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(30 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(60 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(90 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(120 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(150 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(180 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(210 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(240 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(270 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(300 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(330 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><circle cx="50" cy="50" r="24" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f4-orn" style="left:30px;top:1050px;width:140px;height:140px"><svg viewBox="0 0 100 100" style="transform:rotate(-8deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f4-orn" style="left:770px;top:1180px;width:300px;height:300px"><svg viewBox="0 0 100 100" style="transform:rotate(4deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f4-orn" style="left:40px;top:1400px;width:190px;height:190px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f4-label f4-z"><span class="f4-pill"><span class="f4-d">SCREEN TIME</span></span></div>
<div class="f4-phone">
<div class="f4-notch"></div>
<div class="f4-ringbox">
<svg viewBox="0 0 380 380"><circle cx="190" cy="190" r="150" fill="none" stroke="#E8E0D4" stroke-width="40"/><circle class="f4-arc" cx="190" cy="190" r="150" fill="none" stroke="#7ECDC0" stroke-width="40" stroke-linecap="round" stroke-dasharray="942.5" stroke-dashoffset="0" transform="rotate(-90 190 190)"/></svg>
<div class="f4-time f4-d">1:00</div>
<div class="f4-lock"><svg viewBox="0 0 100 100"><rect x="20" y="42" width="60" height="46" rx="10" fill="#F8635F" stroke="#2D2D2D" stroke-width="5"/><path d="M33 42 V30 a17 17 0 0 1 34 0 V42" fill="none" stroke="#2D2D2D" stroke-width="6" stroke-linecap="round"/><circle cx="50" cy="65" r="6" fill="#2D2D2D"/></svg></div>
</div>
<div class="f4-state"><div class="f4-pill f4-s1" style="background:#A8E6CF"><span class="f4-d">LIMIT SET</span></div><div class="f4-pill f4-s2"><span class="f4-d">TIME'S UP</span></div></div>
<div class="f4-bar"><i></i></div>
</div>
</div>
<script>
(function () {
const DUR = 6.599;
const tl = gsap.timeline({ paused: true });
const orns = document.querySelectorAll(".f4-orn");
tl.fromTo(orns, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.55, ease: "back.out(1.8)", stagger: 0.07 }, 0.05);
orns.forEach((el, i) => {
const t0 = 0.9 + i * 0.12, half = Math.max(1.2, (DUR - 1.6) / 2), amp = i % 2 ? -14 : 14;
tl.to(el, { y: amp, duration: half, ease: "sine.inOut" }, t0).to(el, { y: 0, duration: half, ease: "sine.inOut" }, t0 + half);
});
const t0 = 0.38, tEnd = 3.79;
tl.fromTo(".f4-label", { y: -60, opacity: 0 }, { y: 0, opacity: 1, duration: 0.5, ease: "back.out(1.6)" }, t0);
tl.fromTo(".f4-phone", { y: 160, opacity: 0, rotation: 4 }, { y: 0, opacity: 1, rotation: 0, duration: 0.7, ease: "power3.out" }, t0);
tl.set(".f4-lock", { opacity: 0 }, 0);
tl.fromTo(".f4-state", { scale: 0.6, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.45, ease: "back.out(1.8)" }, 0.61);
tl.to(".f4-arc", { strokeDashoffset: 942.5, duration: tEnd - 0.99, ease: "none" }, 0.99);
tl.to(".f4-bar i", { scaleX: 0, duration: tEnd - 0.99, ease: "none" }, 0.99);
const cd = { v: 60 }, tm = document.querySelector(".f4-time");
tl.to(cd, { v: 0, duration: tEnd - 0.99, ease: "none", onUpdate: () => { const s = Math.ceil(cd.v); tm.textContent = Math.floor(s / 60) + ":" + String(s % 60).padStart(2, "0"); } }, 0.99);
tl.to(".f4-time", { opacity: 0, duration: 0.2 }, tEnd);
tl.to(".f4-arc", { opacity: 0, duration: 0.1 }, tEnd - 0.05);
tl.fromTo(".f4-lock", { opacity: 0, scale: 0.3, rotation: -25 }, { opacity: 1, scale: 1, rotation: 0, duration: 0.55, ease: "back.out(2)" }, tEnd);
tl.to(".f4-s1", { opacity: 0, duration: 0.15 }, tEnd).to(".f4-s2", { opacity: 1, duration: 0.2 }, tEnd);
tl.to(".f4-phone", { rotation: -2, duration: 0.12, ease: "sine.inOut" }, tEnd + 0.1).to(".f4-phone", { rotation: 2, duration: 0.16, ease: "sine.inOut" }, ">").to(".f4-phone", { rotation: 0, duration: 0.14, ease: "sine.inOut" }, ">");
window.__timelines["04-limits"] = tl;
})();
</script>
</template>
@@ -0,0 +1,65 @@
<template>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#root { position: absolute; inset: 0; overflow: hidden; color: #2D2D2D; font-family: "Quicksand", sans-serif; font-weight: 600; }
.f5-bg { position: absolute; inset: 0; }
.f5-orn { position: absolute; z-index: 1; }
.f5-orn svg { width: 100%; height: 100%; display: block; overflow: visible; }
.f5-z { position: absolute; z-index: 2; }
.f5-d { font-family: "Fredoka", sans-serif; font-weight: 600; letter-spacing: 0.02em; line-height: 1.05; }
.f5-card { background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 40px; box-shadow: 12px 12px 0 #2D2D2D; }
.f5-pill { display: inline-flex; align-items: center; gap: 18px; border: 5px solid #2D2D2D; border-radius: 80px; box-shadow: 8px 8px 0 #2D2D2D; padding: 20px 46px; white-space: nowrap; }
.f5-phone { position: absolute; z-index: 2; left: 260px; top: 230px; width: 560px; height: 1120px; background: #FFFFFF; border: 6px solid #2D2D2D; border-radius: 80px; box-shadow: 16px 16px 0 #2D2D2D; overflow: hidden; }
.f5-notch { position: absolute; left: 50%; top: 22px; margin-left: -70px; width: 140px; height: 28px; border-radius: 20px; background: #2D2D2D; }
.f5-row { position: absolute; left: 36px; width: 488px; height: 170px; display: flex; align-items: center; gap: 22px; padding: 0 24px; background: #FFFFFF; border: 4px solid #2D2D2D; border-radius: 34px; box-shadow: 6px 6px 0 #2D2D2D; }
.f5-ic { flex: none; width: 92px; height: 92px; border-radius: 50%; border: 4px solid #2D2D2D; display: flex; align-items: center; justify-content: center; font-size: 52px; }
.f5-nm { font-size: 46px; flex: 1; }
.f5-tg { flex: none; position: relative; width: 120px; height: 66px; border-radius: 40px; border: 4px solid #2D2D2D; background: #D9D4C7; }
.f5-kn { position: absolute; left: 4px; top: 4px; width: 50px; height: 50px; border-radius: 50%; background: #FFFFFF; border: 4px solid #2D2D2D; box-sizing: border-box; width: 50px; }
.f5-blk { position: absolute; right: -12px; top: -22px; background: #F8635F; color: #FFFFFF; border: 4px solid #2D2D2D; border-radius: 30px; padding: 4px 16px; font-size: 30px; opacity: 0; }
.f5-head { position: absolute; left: 0; right: 0; top: 80px; text-align: center; font-size: 56px; }
</style>
<div id="root" data-composition-id="05-apps" data-width="1080" data-height="1920" data-duration="6.203">
<div id="scene5-bg" class="clip f5-bg" data-start="0" data-duration="6.203" data-track-index="0" style="background:#A8D8F0"></div>
<div class="f5-orn" style="left:-90px;top:90px;width:320px;height:320px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f5-orn" style="left:880px;top:150px;width:170px;height:170px"><svg viewBox="0 0 100 100" style="transform:rotate(14deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f5-orn" style="left:-50px;top:1180px;width:250px;height:250px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f5-orn" style="left:800px;top:1250px;width:260px;height:260px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><line x1="50" y1="9" x2="50" y2="22" transform="rotate(0 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(30 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(60 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(90 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(120 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(150 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(180 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(210 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(240 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(270 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(300 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(330 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><circle cx="50" cy="50" r="24" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f5-orn" style="left:900px;top:760px;width:110px;height:110px"><svg viewBox="0 0 100 100" style="transform:rotate(-12deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f5-phone">
<div class="f5-notch"></div>
<div class="f5-head f5-d">APPS</div>
<div class="f5-row f5-r0" style="top:190px"><div class="f5-ic f5-d" style="background:#A8E6CF">L</div><div class="f5-nm f5-d">Learn</div><div class="f5-tg f5-t0"><div class="f5-kn f5-k0"></div></div></div>
<div class="f5-row f5-r1" style="top:400px"><div class="f5-ic f5-d" style="background:#A8D8F0">D</div><div class="f5-nm f5-d">Draw</div><div class="f5-tg f5-t1"><div class="f5-kn f5-k1"></div></div></div>
<div class="f5-row f5-r2" style="top:610px"><div class="f5-ic f5-d" style="background:#FFCBA4">G</div><div class="f5-nm f5-d">Games</div><div class="f5-tg f5-t2"><div class="f5-kn f5-k2"></div></div><div class="f5-blk f5-d f5-bl2">BLOCKED</div></div>
<div class="f5-row f5-r3" style="top:820px"><div class="f5-ic f5-d" style="background:#D4A5E8">V</div><div class="f5-nm f5-d">Videos</div><div class="f5-tg f5-t3"><div class="f5-kn f5-k3"></div></div><div class="f5-blk f5-d f5-bl3">BLOCKED</div></div>
</div>
</div>
<script>
(function () {
const DUR = 5.703;
const tl = gsap.timeline({ paused: true });
const orns = document.querySelectorAll(".f5-orn");
tl.fromTo(orns, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.55, ease: "back.out(1.8)", stagger: 0.07 }, 0.05);
orns.forEach((el, i) => {
const t0 = 0.9 + i * 0.12, half = Math.max(1.2, (DUR - 1.6) / 2), amp = i % 2 ? -14 : 14;
tl.to(el, { y: amp, duration: half, ease: "sine.inOut" }, t0).to(el, { y: 0, duration: half, ease: "sine.inOut" }, t0 + half);
});
tl.fromTo(".f5-phone", { y: 160, opacity: 0, rotation: -4 }, { y: 0, opacity: 1, rotation: 0, duration: 0.7, ease: "power3.out" }, 0.38);
tl.fromTo(".f5-r0", { x: 90, opacity: 0 }, { x: 0, opacity: 1, duration: 0.45, ease: "power3.out" }, 0.88);
tl.fromTo(".f5-r1", { x: 90, opacity: 0 }, { x: 0, opacity: 1, duration: 0.45, ease: "power3.out" }, 1.16);
tl.fromTo(".f5-r2", { x: 90, opacity: 0 }, { x: 0, opacity: 1, duration: 0.45, ease: "power3.out" }, 1.44);
tl.fromTo(".f5-r3", { x: 90, opacity: 0 }, { x: 0, opacity: 1, duration: 0.45, ease: "power3.out" }, 1.72);
tl.to(".f5-k0", { x: 54, duration: 0.3, ease: "back.out(1.7)" }, 2.12).to(".f5-t0", { backgroundColor: "#A8E6CF", duration: 0.3 }, 2.12);
tl.to(".f5-k1", { x: 54, duration: 0.3, ease: "back.out(1.7)" }, 2.57).to(".f5-t1", { backgroundColor: "#A8E6CF", duration: 0.3 }, 2.57);
tl.to(".f5-r2", { opacity: 0.55, duration: 0.3 }, 3.49).fromTo(".f5-bl2", { opacity: 0, scale: 0.3, rotation: -10 }, { opacity: 1, scale: 1, rotation: -4, duration: 0.4, ease: "back.out(2)" }, 3.49);
tl.to(".f5-r3", { opacity: 0.55, duration: 0.3 }, 3.79).fromTo(".f5-bl3", { opacity: 0, scale: 0.3, rotation: -10 }, { opacity: 1, scale: 1, rotation: -4, duration: 0.4, ease: "back.out(2)" }, 3.79);
window.__timelines["05-apps"] = tl;
})();
</script>
</template>
@@ -0,0 +1,74 @@
<template>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#root { position: absolute; inset: 0; overflow: hidden; color: #2D2D2D; font-family: "Quicksand", sans-serif; font-weight: 600; }
.f6-bg { position: absolute; inset: 0; }
.f6-orn { position: absolute; z-index: 1; }
.f6-orn svg { width: 100%; height: 100%; display: block; overflow: visible; }
.f6-z { position: absolute; z-index: 2; }
.f6-d { font-family: "Fredoka", sans-serif; font-weight: 600; letter-spacing: 0.02em; line-height: 1.05; }
.f6-card { background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 40px; box-shadow: 12px 12px 0 #2D2D2D; }
.f6-pill { display: inline-flex; align-items: center; gap: 18px; border: 5px solid #2D2D2D; border-radius: 80px; box-shadow: 8px 8px 0 #2D2D2D; padding: 20px 46px; white-space: nowrap; }
.f6-phone { position: absolute; z-index: 2; left: 260px; top: 230px; width: 560px; height: 1120px; background: #FFFFFF; border: 6px solid #2D2D2D; border-radius: 80px; box-shadow: 16px 16px 0 #2D2D2D; overflow: hidden; }
.f6-notch { position: absolute; left: 50%; top: 22px; margin-left: -70px; width: 140px; height: 28px; border-radius: 20px; background: #2D2D2D; }
.f6-btn { position: absolute; left: 70px; top: 100px; width: 420px; justify-content: center; padding: 18px 0; background: #7ECDC0; box-shadow: 6px 6px 0 #2D2D2D; }
.f6-btn span { font-size: 50px; }
.f6-map { position: absolute; left: 36px; top: 260px; width: 488px; height: 780px; border: 5px solid #2D2D2D; border-radius: 40px; background: #A8E6CF; overflow: hidden; }
.f6-map svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.f6-pin { position: absolute; left: 244px; top: 360px; width: 120px; margin-left: -60px; height: 150px; margin-top: -150px; }
.f6-pin svg { width: 100%; height: 100%; }
.f6-pulse { position: absolute; left: 244px; top: 360px; width: 200px; height: 200px; margin: -100px 0 0 -100px; border: 6px solid #F8635F; border-radius: 50%; opacity: 0; }
.f6-note { left: 0; right: 0; top: 1430px; text-align: center; }
.f6-note .f6-pill { background: #FDE68A; } .f6-note span { font-size: 62px; }
</style>
<div id="root" data-composition-id="06-location" data-width="1080" data-height="1920" data-duration="7.013">
<div id="scene6-bg" class="clip f6-bg" data-start="0" data-duration="7.013" data-track-index="0" style="background:#FFCBA4"></div>
<div class="f6-orn" style="left:-80px;top:90px;width:280px;height:280px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><line x1="50" y1="9" x2="50" y2="22" transform="rotate(0 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(30 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(60 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(90 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(120 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(150 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(180 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(210 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(240 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(270 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(300 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(330 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><circle cx="50" cy="50" r="24" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f6-orn" style="left:890px;top:120px;width:150px;height:150px"><svg viewBox="0 0 100 100" style="transform:rotate(10deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f6-orn" style="left:880px;top:1230px;width:220px;height:220px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f6-orn" style="left:-90px;top:1180px;width:300px;height:300px"><svg viewBox="0 0 100 100" style="transform:rotate(4deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f6-orn" style="left:40px;top:700px;width:100px;height:100px"><svg viewBox="0 0 100 100" style="transform:rotate(-12deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f6-phone">
<div class="f6-notch"></div>
<div class="f6-pill f6-d f6-btn"><span class="f6-d">CHECK NOW</span></div>
<div class="f6-map">
<svg viewBox="0 0 488 780">
<rect x="300" y="0" width="188" height="780" fill="#A8D8F0"/>
<rect x="30" y="40" width="190" height="150" rx="26" fill="#8ADBB6" stroke="#2D2D2D" stroke-width="3"/>
<rect x="30" y="520" width="230" height="200" rx="26" fill="#8ADBB6" stroke="#2D2D2D" stroke-width="3"/>
<path d="M0 250 H488 M150 0 V780 M0 470 H300 M360 60 L120 780" fill="none" stroke="#FFFFFF" stroke-width="22" stroke-linecap="round"/>
<path d="M0 250 H488 M150 0 V780 M0 470 H300 M360 60 L120 780" fill="none" stroke="#E8E0D4" stroke-width="3" stroke-dasharray="14 18"/>
</svg>
<div class="f6-pulse f6-pu1"></div><div class="f6-pulse f6-pu2"></div>
<div class="f6-pin"><svg viewBox="0 0 100 125"><path d="M50 118 C14 74 12 54 12 42 a38 38 0 0 1 76 0 C88 54 86 74 50 118 Z" fill="#F8635F" stroke="#2D2D2D" stroke-width="5" stroke-linejoin="round"/><circle cx="50" cy="42" r="14" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="5"/></svg></div>
</div>
</div>
<div class="f6-z f6-note"><span class="f6-pill"><span class="f6-d">ONLY WHEN NEEDED</span></span></div>
</div>
<script>
(function () {
const DUR = 6.513;
const tl = gsap.timeline({ paused: true });
const orns = document.querySelectorAll(".f6-orn");
tl.fromTo(orns, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.55, ease: "back.out(1.8)", stagger: 0.07 }, 0.05);
orns.forEach((el, i) => {
const t0 = 0.9 + i * 0.12, half = Math.max(1.2, (DUR - 1.6) / 2), amp = i % 2 ? -14 : 14;
tl.to(el, { y: amp, duration: half, ease: "sine.inOut" }, t0).to(el, { y: 0, duration: half, ease: "sine.inOut" }, t0 + half);
});
tl.fromTo(".f6-phone", { y: 160, opacity: 0, rotation: 4 }, { y: 0, opacity: 1, rotation: 0, duration: 0.7, ease: "power3.out" }, 0.38);
tl.fromTo(".f6-map", { opacity: 0, scale: 0.9 }, { opacity: 1, scale: 1, duration: 0.5, ease: "power3.out" }, 0.61);
tl.set(".f6-pin", { opacity: 0 }, 0);
tl.to(".f6-btn", { scale: 0.9, duration: 0.12, ease: "power2.in" }, 1.8).to(".f6-btn", { scale: 1, duration: 0.25, ease: "back.out(2.5)" }, ">");
tl.fromTo(".f6-pin", { y: -520, opacity: 1, scaleY: 1.1 }, { y: 0, opacity: 1, scaleY: 1, duration: 0.7, ease: "bounce.out" }, 2.2);
tl.fromTo(".f6-pu1", { scale: 0.3, opacity: 0.9 }, { scale: 2.6, opacity: 0, duration: 1.1, ease: "power2.out" }, 2.95);
tl.fromTo(".f6-pu2", { scale: 0.3, opacity: 0.9 }, { scale: 2.6, opacity: 0, duration: 1.1, ease: "power2.out" }, 3.45);
tl.fromTo(".f6-note", { y: 80, opacity: 0, scale: 0.8 }, { y: 0, opacity: 1, scale: 1, duration: 0.55, ease: "back.out(1.8)" }, 3.89);
window.__timelines["06-location"] = tl;
})();
</script>
</template>
@@ -0,0 +1,53 @@
<template>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#root { position: absolute; inset: 0; overflow: hidden; color: #2D2D2D; font-family: "Quicksand", sans-serif; font-weight: 600; }
.f7-bg { position: absolute; inset: 0; }
.f7-orn { position: absolute; z-index: 1; }
.f7-orn svg { width: 100%; height: 100%; display: block; overflow: visible; }
.f7-z { position: absolute; z-index: 2; }
.f7-d { font-family: "Fredoka", sans-serif; font-weight: 600; letter-spacing: 0.02em; line-height: 1.05; }
.f7-card { background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 40px; box-shadow: 12px 12px 0 #2D2D2D; }
.f7-pill { display: inline-flex; align-items: center; gap: 18px; border: 5px solid #2D2D2D; border-radius: 80px; box-shadow: 8px 8px 0 #2D2D2D; padding: 20px 46px; white-space: nowrap; }
.f7-phone { position: absolute; z-index: 2; left: 260px; top: 230px; width: 560px; height: 1120px; background: #FFFFFF; border: 6px solid #2D2D2D; border-radius: 80px; box-shadow: 16px 16px 0 #2D2D2D; overflow: hidden; }
.f7-notch { position: absolute; left: 50%; top: 22px; margin-left: -70px; width: 140px; height: 28px; border-radius: 20px; background: #2D2D2D; }
.f7-big { position: absolute; z-index: 2; left: 110px; width: 860px; height: 400px; display: flex; align-items: center; justify-content: center; gap: 30px; }
.f7-big span { font-size: 138px; }
.f7-big svg { width: 120px; height: 120px; flex: none; }
.f7-heart { position: absolute; z-index: 3; left: 50%; top: 720px; margin-left: -110px; width: 220px; height: 220px; }
.f7-heart svg { width: 100%; height: 100%; }
</style>
<div id="root" data-composition-id="07-balance" data-width="1080" data-height="1920" data-duration="5.968">
<div id="scene7-bg" class="clip f7-bg" data-start="0" data-duration="5.968" data-track-index="0" style="background:#F7C8D4"></div>
<div class="f7-orn" style="left:-60px;top:100px;width:240px;height:240px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f7-orn" style="left:880px;top:120px;width:160px;height:160px"><svg viewBox="0 0 100 100" style="transform:rotate(12deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f7-orn" style="left:800px;top:1230px;width:280px;height:280px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f7-orn" style="left:-80px;top:1210px;width:260px;height:260px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><line x1="50" y1="9" x2="50" y2="22" transform="rotate(0 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(30 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(60 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(90 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(120 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(150 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(180 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(210 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(240 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(270 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(300 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(330 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><circle cx="50" cy="50" r="24" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f7-orn" style="left:60px;top:760px;width:110px;height:110px"><svg viewBox="0 0 100 100" style="transform:rotate(-10deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f7-big f7-card f7-c1" style="top:240px;background:#A8D8F0"><svg viewBox="0 0 100 100" style="transform:rotate(8deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg><span class="f7-d">FREEDOM</span></div>
<div class="f7-heart f7-h"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><path d="M50 86 C8 56 10 18 32 18 C42 18 48 25 50 32 C52 25 58 18 68 18 C90 18 92 56 50 86 Z" fill="#F8635F" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f7-big f7-card f7-c2" style="top:1000px;background:#A8E6CF"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><line x1="50" y1="9" x2="50" y2="22" transform="rotate(0 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(30 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(60 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(90 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(120 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(150 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(180 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(210 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(240 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(270 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(300 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(330 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><circle cx="50" cy="50" r="24" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg><span class="f7-d">CALM</span></div>
</div>
<script>
(function () {
const DUR = 5.468;
const tl = gsap.timeline({ paused: true });
const orns = document.querySelectorAll(".f7-orn");
tl.fromTo(orns, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.55, ease: "back.out(1.8)", stagger: 0.07 }, 0.05);
orns.forEach((el, i) => {
const t0 = 0.9 + i * 0.12, half = Math.max(1.2, (DUR - 1.6) / 2), amp = i % 2 ? -14 : 14;
tl.to(el, { y: amp, duration: half, ease: "sine.inOut" }, t0).to(el, { y: 0, duration: half, ease: "sine.inOut" }, t0 + half);
});
tl.fromTo(".f7-c1", { x: -300, opacity: 0, rotation: -10 }, { x: 0, opacity: 1, rotation: -2, duration: 0.65, ease: "back.out(1.5)" }, 0.81);
tl.fromTo(".f7-c2", { x: 300, opacity: 0, rotation: 10 }, { x: 0, opacity: 1, rotation: 2, duration: 0.65, ease: "back.out(1.5)" }, 2.63);
tl.fromTo(".f7-h", { scale: 0, opacity: 0, rotation: -30 }, { scale: 1, opacity: 1, rotation: 0, duration: 0.7, ease: "back.out(2.2)" }, 3.49);
tl.to(".f7-h", { scale: 1.18, duration: 0.3, ease: "sine.inOut" }, 4.29).to(".f7-h", { scale: 1, duration: 0.3, ease: "sine.inOut" }, ">");
window.__timelines["07-balance"] = tl;
})();
</script>
</template>
@@ -0,0 +1,47 @@
<template>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#root { position: absolute; inset: 0; overflow: hidden; color: #2D2D2D; font-family: "Quicksand", sans-serif; font-weight: 600; }
.f8-bg { position: absolute; inset: 0; }
.f8-orn { position: absolute; z-index: 1; }
.f8-orn svg { width: 100%; height: 100%; display: block; overflow: visible; }
.f8-z { position: absolute; z-index: 2; }
.f8-d { font-family: "Fredoka", sans-serif; font-weight: 600; letter-spacing: 0.02em; line-height: 1.05; }
.f8-card { background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 40px; box-shadow: 12px 12px 0 #2D2D2D; }
.f8-pill { display: inline-flex; align-items: center; gap: 18px; border: 5px solid #2D2D2D; border-radius: 80px; box-shadow: 8px 8px 0 #2D2D2D; padding: 20px 46px; white-space: nowrap; }
.f8-phone { position: absolute; z-index: 2; left: 260px; top: 230px; width: 560px; height: 1120px; background: #FFFFFF; border: 6px solid #2D2D2D; border-radius: 80px; box-shadow: 16px 16px 0 #2D2D2D; overflow: hidden; }
.f8-notch { position: absolute; left: 50%; top: 22px; margin-left: -70px; width: 140px; height: 28px; border-radius: 20px; background: #2D2D2D; }
.f8-big { position: absolute; z-index: 2; left: 110px; width: 860px; height: 400px; display: flex; align-items: center; justify-content: center; gap: 36px; }
.f8-big span { font-size: 124px; }
</style>
<div id="root" data-composition-id="08-platforms" data-width="1080" data-height="1920" data-duration="4.183">
<div id="scene8-bg" class="clip f8-bg" data-start="0" data-duration="4.183" data-track-index="0" style="background:#FDE68A"></div>
<div class="f8-orn" style="left:800px;top:90px;width:250px;height:250px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><line x1="50" y1="9" x2="50" y2="22" transform="rotate(0 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(30 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(60 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(90 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(120 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(150 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(180 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(210 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(240 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(270 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(300 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(330 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><circle cx="50" cy="50" r="24" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f8-orn" style="left:-60px;top:130px;width:230px;height:230px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f8-orn" style="left:-70px;top:1230px;width:290px;height:290px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f8-orn" style="left:880px;top:1300px;width:150px;height:150px"><svg viewBox="0 0 100 100" style="transform:rotate(12deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f8-orn" style="left:520px;top:1440px;width:100px;height:100px"><svg viewBox="0 0 100 100" style="transform:rotate(-8deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f8-big f8-card f8-c1" style="top:290px;background:#A8E6CF"><svg viewBox="0 0 100 100" style="width:150px;height:150px;flex:none"><rect x="26" y="6" width="48" height="88" rx="12" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="5"/><rect x="34" y="18" width="32" height="56" rx="4" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3"/><circle cx="50" cy="83" r="4" fill="#2D2D2D"/></svg><span class="f8-d">ANDROID</span></div>
<div class="f8-big f8-card f8-c2" style="top:790px;background:#A8D8F0"><svg viewBox="0 0 100 100" style="width:150px;height:150px;flex:none"><rect x="26" y="6" width="48" height="88" rx="12" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="5"/><rect x="34" y="18" width="32" height="56" rx="4" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3"/><circle cx="50" cy="83" r="4" fill="#2D2D2D"/></svg><span class="f8-d">iOS</span></div>
</div>
<script>
(function () {
const DUR = 3.783;
const tl = gsap.timeline({ paused: true });
const orns = document.querySelectorAll(".f8-orn");
tl.fromTo(orns, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.55, ease: "back.out(1.8)", stagger: 0.07 }, 0.05);
orns.forEach((el, i) => {
const t0 = 0.9 + i * 0.12, half = Math.max(1.2, (DUR - 1.6) / 2), amp = i % 2 ? -14 : 14;
tl.to(el, { y: amp, duration: half, ease: "sine.inOut" }, t0).to(el, { y: 0, duration: half, ease: "sine.inOut" }, t0 + half);
});
tl.fromTo(".f8-c1", { y: 160, opacity: 0, rotation: -6, scale: 0.8 }, { y: 0, opacity: 1, rotation: -2, scale: 1, duration: 0.6, ease: "back.out(1.6)" }, 0.96);
tl.fromTo(".f8-c2", { y: 160, opacity: 0, rotation: 6, scale: 0.8 }, { y: 0, opacity: 1, rotation: 2, scale: 1, duration: 0.6, ease: "back.out(1.6)" }, 1.92);
window.__timelines["08-platforms"] = tl;
})();
</script>
</template>
@@ -0,0 +1,68 @@
<template>
<style>
@font-face { font-family: "Fredoka"; src: url("assets/fonts/fredoka-600.woff2") format("woff2"); font-weight: 600; font-style: normal; }
@font-face { font-family: "Quicksand"; src: url("assets/fonts/quicksand-var.woff2") format("woff2"); font-weight: 500 700; font-style: normal; }
#root { position: absolute; inset: 0; overflow: hidden; color: #2D2D2D; font-family: "Quicksand", sans-serif; font-weight: 600; }
.f9-bg { position: absolute; inset: 0; }
.f9-orn { position: absolute; z-index: 1; }
.f9-orn svg { width: 100%; height: 100%; display: block; overflow: visible; }
.f9-z { position: absolute; z-index: 2; }
.f9-d { font-family: "Fredoka", sans-serif; font-weight: 600; letter-spacing: 0.02em; line-height: 1.05; }
.f9-card { background: #FFFFFF; border: 5px solid #2D2D2D; border-radius: 40px; box-shadow: 12px 12px 0 #2D2D2D; }
.f9-pill { display: inline-flex; align-items: center; gap: 18px; border: 5px solid #2D2D2D; border-radius: 80px; box-shadow: 8px 8px 0 #2D2D2D; padding: 20px 46px; white-space: nowrap; }
.f9-phone { position: absolute; z-index: 2; left: 260px; top: 230px; width: 560px; height: 1120px; background: #FFFFFF; border: 6px solid #2D2D2D; border-radius: 80px; box-shadow: 16px 16px 0 #2D2D2D; overflow: hidden; }
.f9-notch { position: absolute; left: 50%; top: 22px; margin-left: -70px; width: 140px; height: 28px; border-radius: 20px; background: #2D2D2D; }
.f9-eye { display: block; position: relative; width: 140px; height: 140px; border-radius: 50%; background: #FFFFFF; border: 7.5px solid #2D2D2D; box-sizing: border-box; text-shadow: none; }
.f9-pup { position: absolute; left: 50%; top: 50%; width: 55px; height: 55px; margin: -27.5px 0 0 -27.5px; border-radius: 50%; background: #2D2D2D; }
.f9-wmwrap { left: 0; right: 0; top: 400px; }
.f9-tag { left: 50%; top: 840px; margin-left: -400px; width: 800px; height: 190px; display: flex; align-items: center; justify-content: center; background: #FFFFFF; }
.f9-tag span { font-size: 64px; }
.f9-chips { left: 0; right: 0; top: 1130px; display: flex; justify-content: center; gap: 40px; }
.f9-chips .f9-pill { padding: 16px 40px; } .f9-chips span { font-size: 56px; }
</style>
<div id="root" data-composition-id="09-cta" data-width="1080" data-height="1920">
<div id="scene9-bg" class="clip f9-bg" data-start="0" data-duration="5.772" data-track-index="0" style="background:#D4A5E8"></div>
<div class="f9-orn" style="left:780px;top:70px;width:300px;height:300px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><line x1="50" y1="9" x2="50" y2="22" transform="rotate(0 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(30 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(60 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(90 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(120 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(150 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(180 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(210 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(240 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(270 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(300 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><line x1="50" y1="9" x2="50" y2="22" transform="rotate(330 50 50)" stroke="#2D2D2D" stroke-width="3.2" stroke-linecap="round"/><circle cx="50" cy="50" r="24" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f9-orn" style="left:-80px;top:160px;width:260px;height:260px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f9-orn" style="left:850px;top:1250px;width:250px;height:250px"><svg viewBox="0 0 100 100" style="transform:rotate(0deg)" xmlns="http://www.w3.org/2000/svg"><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(0 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(45 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(90 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(135 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(180 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(225 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(270 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><ellipse cx="50" cy="21" rx="11" ry="19" transform="rotate(315 50 50)" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2"/><circle cx="50" cy="50" r="14" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2"/></svg></div>
<div class="f9-orn" style="left:-90px;top:1220px;width:330px;height:330px"><svg viewBox="0 0 100 100" style="transform:rotate(3deg)" xmlns="http://www.w3.org/2000/svg"><path d="M24 72 a17 17 0 0 1 3-33 a22 22 0 0 1 43-3 a17 17 0 0 1 6 36 z" fill="#FFFFFF" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f9-orn" style="left:140px;top:1010px;width:130px;height:130px"><svg viewBox="0 0 100 100" style="transform:rotate(-10deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f9-orn" style="left:810px;top:1000px;width:120px;height:120px"><svg viewBox="0 0 100 100" style="transform:rotate(12deg)" xmlns="http://www.w3.org/2000/svg"><polygon points="50,6 61,38 95,38 67,58 78,92 50,71 22,92 33,58 5,38 39,38" fill="#FDE68A" stroke="#2D2D2D" stroke-width="3.2" stroke-linejoin="round"/></svg></div>
<div class="f9-z f9-wmwrap">
<div class="f9-wm f9-d" style="display:flex;align-items:flex-end;justify-content:center;font-size:250px;color:#2D2D2D;text-shadow:9px 9px 0 #FFFFFF;line-height:1">
<span>l</span>
<span class="f9-eye" style="margin:0 7.5px 15px 12.5px"><i class="f9-pup"></i></span>
<span class="f9-eye" style="margin:0 12.5px 15px 7.5px"><i class="f9-pup"></i></span>
<span>katu</span>
</div></div>
<div class="f9-z f9-card f9-tag"><span class="f9-d">LOOK OUT FOR THEM</span></div>
<div class="f9-z f9-chips"><span class="f9-pill f9-ch1" style="background:#A8E6CF"><span class="f9-d">ANDROID</span></span><span class="f9-pill f9-ch2" style="background:#A8D8F0"><span class="f9-d">iOS</span></span></div>
</div>
<script>
(function () {
const DUR = 5.772;
const tl = gsap.timeline({ paused: true });
const orns = document.querySelectorAll(".f9-orn");
tl.fromTo(orns, { scale: 0, opacity: 0 }, { scale: 1, opacity: 1, duration: 0.55, ease: "back.out(1.8)", stagger: 0.07 }, 0.05);
orns.forEach((el, i) => {
const t0 = 0.9 + i * 0.12, half = Math.max(1.2, (DUR - 1.6) / 2), amp = i % 2 ? -14 : 14;
tl.to(el, { y: amp, duration: half, ease: "sine.inOut" }, t0).to(el, { y: 0, duration: half, ease: "sine.inOut" }, t0 + half);
});
tl.fromTo(".f9-wm", { scale: 0.4, opacity: 0, y: 70 }, { scale: 1, opacity: 1, y: 0, duration: 0.75, ease: "back.out(1.6)" }, 0.12);
const pups = document.querySelectorAll(".f9-pup"), r = 25;
tl.fromTo(pups, { x: 0, y: 0 }, { x: -r, y: -r * 0.4, duration: 0.35, ease: "power2.out" }, 0.7)
.to(pups, { x: r, y: r * 0.3, duration: 0.5, ease: "power2.inOut" }, 1.2999999999999998)
.to(pups, { x: 0, y: 0, duration: 0.35, ease: "power2.out" }, 2.05);
tl.fromTo(".f9-tag", { y: 90, opacity: 0, rotation: -4 }, { y: 0, opacity: 1, rotation: 0, duration: 0.6, ease: "back.out(1.6)" }, 1.04);
tl.fromTo(".f9-ch1", { y: 70, opacity: 0, scale: 0.7 }, { y: 0, opacity: 1, scale: 1, duration: 0.5, ease: "back.out(1.8)" }, 2.27);
tl.fromTo(".f9-ch2", { y: 70, opacity: 0, scale: 0.7 }, { y: 0, opacity: 1, scale: 1, duration: 0.5, ease: "back.out(1.8)" }, 2.45);
tl.to(".f9-wm", { scale: 1.04, duration: 0.5, ease: "sine.inOut" }, 3.28).to(".f9-wm", { scale: 1, duration: 0.5, ease: "sine.inOut" }, ">");
window.__timelines["09-cta"] = tl;
})();
</script>
</template>
+274
View File
@@ -0,0 +1,274 @@
---
version: alpha
name: Daisy Days — Frame (video / frame layer)
description: >
Video-first companion to Daisy Days' design.md. The unit is the frame (1920×1080). Atoms are
identical and sacred — the sunny-garden pastel palette (cream + turquoise/pink/butter/mint/
lavender/peach/sky + coral accent), charcoal 3px outlines, hard offset shadows (6/4px, no blur),
the Fredoka + Quicksand pairing, generous radii, headline text-shadow on color, dot bullets, and
the hand-drawn SVG ornament layer. Composition + frame scale rewritten. Motion out of scope.
unit: the frame — 1920×1080 primary; 9:16 and 1:1 documented
principle: atoms are sacred · composition is free · numbers come from the script
colors:
cream: "#F5F0E6"
turquoise: "#7ECDC0"
soft-pink: "#F7C8D4"
butter: "#FDE68A"
mint: "#A8E6CF"
lavender: "#D4A5E8"
peach: "#FFCBA4"
sky: "#A8D8F0"
coral: "#F8635F"
text-dark: "#2D2D2D"
text-muted: "#6B6B6B"
white: "#FFFFFF"
borders: { primary: "3px solid text-dark", thin: "2px solid text-dark" }
shadows: { default: "6px 6px 0 text-dark", small: "4px 4px 0 text-dark", text-headline: "3px 3px 0 text-dark", text-headline-soft: "3px 3px 0 rgba(0,0,0,0.2)" }
typography:
# — reading ramp (Quicksand) —
body: { fontFamily: "Quicksand", cqw: 0.95, weight: 500, lineHeight: 1.6 }
body-strong:{ fontFamily: "Quicksand", cqw: 0.95, weight: 600, lineHeight: 1.5 }
meta: { fontFamily: "Quicksand", cqw: 0.78, weight: 600, lineHeight: 1.45 }
# — display ramp (Fredoka One / Fredoka 600 — single weight, never italic) —
badge: { fontFamily: "Fredoka One", cqw: 0.9, tracking: "0.02em" }
label-display:{ fontFamily: "Fredoka One", cqw: 1.3, lineHeight: 1.3, tracking: "0.02em" }
subtitle:{ fontFamily: "Fredoka One", cqw: 1.8, lineHeight: 1.2, tracking: "0.02em" }
quote: { fontFamily: "Fredoka One", cqw: 2.6, lineHeight: 1.35 }
title: { fontFamily: "Fredoka One", cqw: 3.0, lineHeight: 1.15, tracking: "0.02em" }
headline:{ fontFamily: "Fredoka One", cqw: 4.5, lineHeight: 1.1, tracking: "0.02em" }
display: { fontFamily: "Fredoka One", cqw: 6.5, lineHeight: 1.1, tracking: "0.02em" }
spacing:
pad-slide: "3cqw"
radius: "20px"
radius-lg: "28px"
radius-pill: "50px"
radius-round: "50%"
components:
card:
backgroundColor: "{colors.white}"
border: "3px solid {colors.text-dark}"
rounded: "{spacing.radius} (28px featured)"
shadow: "6px 6px 0 {colors.text-dark}"
description: "The universal container; white-on-pastel is standard."
framed-header:
backgroundColor: "pastel cap + {colors.white} body"
border: "3px solid {colors.text-dark} (one continuous)"
rounded: "{spacing.radius-lg}"
shadow: "6px 6px 0 {colors.text-dark}"
description: "Pastel header strip flush above a white body — one unit, one shadow."
badge-pill:
backgroundColor: "{colors.butter}"
border: "3px solid {colors.text-dark}"
rounded: "{spacing.radius-pill}"
typography: "{typography.badge}"
shadow: "4px 4px 0 {colors.text-dark}"
description: "Section tag. white-space:nowrap."
circle-marker:
backgroundColor: "any pastel"
textColor: "{colors.white} (dark on butter)"
border: "3px solid {colors.text-dark}"
rounded: "50%"
size: "bullet 20 / icon 44 / dot 48 / step 90"
typography: "Fredoka numeral"
description: "Steps carry a small shadow."
bullet-dot:
backgroundColor: "{colors.butter}"
border: "2px solid {colors.text-dark}"
rounded: "50%"
size: "20px (::before, 4px from line top)"
description: "Lists never use glyph bullets."
ornament:
type: "hand-drawn SVG (daisy, star, sun, cloud, rainbow)"
stroke: "2.1px {colors.text-dark}"
placement: "z-index:1 behind content (z-index:2), cropping past the frame edge"
description: "3–7 per frame, clustered at corners/edges."
quote-mark:
typography: "oversized Fredoka quote glyph"
color: "{colors.soft-pink} (charcoal stroke)"
description: "Above a quote body."
text-headline-shadow:
shadow: "3px 3px 0 {colors.text-dark} (soft 20% variant on pink/mint)"
appliesTo: "Fredoka headlines on saturated surfaces (cream headlines sit flat)"
description: "Makes the headline read 'outlined' like the shapes."
---
# Daisy Days — Frame (video / frame layer)
## Overview
Daisy Days at frame scale is a **cheerful, childlike system** — picture-book illustration meets
sticker-sheet kawaii. Every shape carries a **3px charcoal outline**, every elevated element a
**solid hard offset shadow** (no blur), every surface a sunny-garden pastel. The voice is one
pairing: **Fredoka One** (chunky rounded, single weight) for every headline, **Quicksand** for
every body and meta line. The signature is the **hand-drawn SVG ornament layer** — daisies, stars,
suns, clouds, rainbows clustering at corners and cropping past the edge.
The palette is **multi-pastel with one warm pop**: cream canvas, seven pastel surfaces, and
`{colors.coral}` reserved for small high-attention markers only. Headlines on a saturated surface
get a 3px charcoal text-shadow (so they read "outlined" like the shapes) and switch to white;
headlines on cream sit flat in charcoal. Depth is 2D and graphic — thick outline + hard offset =
sticker-on-paper.
**Key characteristics at frame scale:**
- **Cream default + rotating pastel surfaces**; `{colors.coral}` is a marker accent, never a surface.
- **Fredoka One** headlines + **Quicksand 500/600** body — strict by role, never crossed.
- **3px charcoal outline + hard offset shadow** (6/4px, zero blur) on every elevated shape.
- **Generous radii** — 20px cards, 28px featured, pill badges, full-circle markers; no square corners.
- **Headline text-shadow on saturated surfaces** (white text); flat charcoal on cream.
- **Dot bullets** (outlined butter discs, never glyphs) + a **3–7 ornament wreath** per frame.
## The Frame
### Frame Craft Bar
Three eyeball tests gate every frame before any structural check:
- **Squint** — one Fredoka headline or content card dominates at 3–6× its neighbor.
- **Silence** — **one content container per frame** surrounded by an ornament wreath; the **info-card grid is the one dense exception**. Empty corners read as broken.
- **Restraint** — cream or **one** pastel surface; coral is a small-marker accent (never a surface); charcoal borders + hard offset shadows only; no ninth color.
- **Reference** — aim at a **children's picture-book / sticker-sheet kawaii zine**; failure looks like a **flat, square-cornered, blurred-shadow corporate slide**.
- **Primary:** 1920×1080 (16:9). Display authored in **`cqw`** (`px ÷ 1920 × 100 = cqw`).
- **Vertical:** 1080×1920 (9:16). **Square:** 1080×1080 (1:1).
- **Safe area:** `pad-slide` ~3cqw; ornaments deliberately bleed past the edge.
**The container law (load-bearing).** Every frame ground sets `container-type: size`; ALL
frame-relative units are `cqw`/`cqh` against it — never `vw`. Borders stay 3px/2px; radii stay
20/28/50px; shadow offsets scale in `cqw` so the sticker offset holds proportionally.
## Colors
Tokens identical to the source. Default ground `{colors.cream}`; rotate saturated pastels
(turquoise / soft-pink / butter / mint / lavender / peach / sky) for tonal mood. **Cards are white**
on any surface. **Borders + shadows are always `{colors.text-dark}` charcoal** — never colored,
never blurred, never `rgba` (save the soft text-shadow variant). `{colors.coral}` is the lone
high-saturation accent — small markers (step circles, dots, headers) **only**, never a surface.
Body is charcoal/muted; pastels carry **no semantic meaning**. No ninth color.
## Typography
Two ramps. The **reading ramp** (Quicksand 500 body 0.95cqw, 600 emphasis, meta) carries copy; the
**display ramp** (Fredoka One `label-display` 1.3cqw → `display` 6.5cqw, single weight) carries
every headline, title, quote, and marker numeral.
- **Legibility floor:** any load-bearing line ≥ **1.4cqw**; meta is chrome only.
- **Fit-to-measure:** size the headline to its length. Cap the block at **≤ 78cqw**; ≤3 words → `display`; 4–6 → `headline`; 7+ → `title`.
- **Fredoka One for all display, Quicksand for all body — never crossed.** Fredoka is single-weight (no italic, no underline, no alt weights); Quicksand stays 500/600/700. Fredoka tracking 0.02em; Quicksand body never uppercase.
## Depth & Surface
2D graphic depth — hard offset shadow, solid charcoal, zero blur, bottom-right:
- **`shadows.default` 6px** cards, frames, badges, chart containers.
- **`shadows.small` 4px** small cards, step circles, avatars.
- **Text-headline shadow** — 3px charcoal on Fredoka headlines over saturated surfaces (20% soft variant on pink/mint); cream headlines flat.
- **Outline + offset** together are the sticker-on-paper signature.
**Ceiling:** no blurred shadow, no `rgba` (except the soft text-shadow), no gradient, no glow; an element either casts a hard charcoal offset or none.
## Shapes
- **20px** cards, **28px** featured, **50px** pill (badges, counter), **50%** all circles, **4px** legend swatch. Zero square corners — every region is rounded.
## Components
- **card / framed-header** — the white-fill bordered containers (one shadow). **badge-pill** — butter section tag.
- **circle-marker** family (bullet 20 / icon 44 / dot 48 / step 90) + **bullet-dot** — outlined pastel discs, Fredoka numerals.
- **ornament** — the hand-drawn SVG sticker layer (3–7 per frame). **quote-mark** — soft-pink Fredoka anchor. **text-headline-shadow** — the on-color headline treatment.
## Frame Treatments
> Recipe: ground · container · composes · focal · chrome · accent · silence · Fixed/Free · density.
> One content container per frame + a 3–7 ornament wreath; empty corners read as broken.
### 1 · Cover (identity · move: ornament wreath · saturated · centered)
**Ground** a saturated pastel (e.g. `{colors.turquoise}`). **Composes** badge-pill, display, body sub, 3–7 ornaments, counter. **Focal** a 1–2 line Fredoka `display` in **white with the 3px charcoal text-shadow**, centered, under a butter badge-pill. **Chrome** counter pill. **Accent** the ornament wreath (daisies + stars at corners, cropping past edges). **Silence** content centered; ornaments fill the edges. **Fixed** text-shadow on color, 3px outlines, charcoal shadows. **Free** surface color, ornament mix/positions, copy. **Density** full-but-not-crowded.
### 2 · Info Cards (catalog · move: 3-up white cards · cream · the dense frame)
**Ground** `{colors.cream}`, `pad-slide`. **Composes** headline (flat charcoal), 3× card (circle-icon + Fredoka title + Quicksand body), a couple ornaments. **Focal** three white 3px-bordered cards with 6px shadows. **Chrome** flat headline. **Accent** the pastel circle-icons (rotate turquoise/coral/lavender). **Silence** tight — the density exception (fewer ornaments here). **Fixed** white cards, 3px + 6px, 20px radius. **Free** card content, icon hues. **Density** dense-exception.
### 3 · Process Steps (sequence · move: rotating circle markers · peach · centered)
**Ground** `{colors.peach}` (or another pastel). **Composes** headline (white + text-shadow), 3–4 step circles + `→` arrows, ornaments. **Focal** a row of 90px outlined step circles, fills **rotating coral → mint → sky → lavender**, Fredoka white numerals, linked by Fredoka arrows. **Chrome** white headline with text-shadow. **Accent** the rotating circle fills. **Silence** moderate. **Fixed** 3px circles + small shadow, rotating fills, arrow glyphs. **Free** step count, labels. **Density** standard.
### 4 · Quote (quote · move: quote-mark anchor · soft-pink · centered)
**Ground** `{colors.soft-pink}`. **Composes** a white quote card (28px radius, 6px shadow), quote-mark, Fredoka quote, Quicksand attribution, ornaments. **Focal** a Fredoka `quote` in charcoal inside the white card, under an oversized soft-pink quote-mark. **Chrome** Quicksand 700 attribution. **Accent** the quote-mark + a star or two. **Silence** card centered, ornaments at corners. **Fixed** quote-mark anchor, white card. **Free** quote, attribution. **Density** moderate.
### 5 · Framed Section (feature · move: cap+body card · cream)
**Ground** `{colors.cream}`. **Composes** framed-header (pastel cap + white body), bullet-dot list, ornaments. **Focal** a framed-header — a pastel cap strip (Fredoka title, optional text-shadow) above a white body with a butter-dot bullet list. **Accent** the cap color + bullet dots. **Silence** moderate. **Fixed** one continuous 3px border + one shadow, butter dot bullets. **Free** cap color, list. **Density** standard.
### 6 · Closing (closer · move: ornament wreath · saturated · centered)
**Ground** a saturated pastel (e.g. `{colors.lavender}`). **Composes** badge-pill, display (white + text-shadow), 3–7 ornaments. **Focal** a Fredoka `display` sign-off in white with the charcoal text-shadow, centered. **Accent** the ornament wreath. **Silence** content centered. **Fixed** text-shadow on color, ornaments fill corners. **Free** sign-off, surface, ornaments. **Density** full.
## Composition Rules
### Do
- Pair **Fredoka One headlines + Quicksand body** strictly by role.
- Outline every shape **3px charcoal + a hard offset shadow** (6/4px, no blur); white card fills on any surface.
- Give Fredoka headlines on **saturated surfaces a 3px charcoal text-shadow + white text**; flat charcoal on cream.
- Use **outlined butter-disc bullets** (never glyphs); cluster **3–7 hand-drawn ornaments** per frame (corners, cropping past edges).
- Keep **one content container per frame**; rotate marker colors (coral → mint → sky → lavender → butter); reserve coral for small markers.
- Center most frames; lean cream for content-dense frames, saturated for cover/closer/quote.
### Don't
- No square corners; no blurred or `rgba` shadows (save the soft text-shadow).
- No colored borders (charcoal only); no coral surface; no ninth color.
- No third font; no Quicksand headline or Fredoka body; no italic/underline Fredoka; no uppercase Quicksand body.
- No glyph bullets; no empty corners (ornaments fill them); no two competing content panels.
- Don't blow a headline edge-to-edge — fit to measure.
## Aspect-Ratio Behavior
| Treatment | 16:9 | 9:16 | 1:1 |
| -------------- | -------------------------- | -------------------------------- | ---------- |
| Cover | centered, corner ornaments | centered, taller, more ornaments | centered |
| Info Cards | 3 across | stacked | 2+1 |
| Process Steps | horizontal + arrows | vertical, arrows rotate down | 2×2 |
| Quote | centered card | centered, taller | centered |
| Framed Section | cap + body | cap + body taller | cap + body |
| Closing | centered, wreath | centered, wreath | centered |
`pad-slide` holds on the short edge; re-step display above the 1.4cqw floor. On tighter ratios keep
the ornament count toward the upper end (5–7) so corners never read empty.
## Approved Real Entities
No real customers, logos, or vendors are defined in the source — render any such mark as a
placeholder. Ornaments and pastels are content-agnostic; counters/badges carry per-deck text.
## Numerals & Claims (hard rule)
Never invent figures, dates, or counts at frame scale. Render slots as `— figure —`, `{metric}`,
`N`. Step numbers and any chart values carry placeholders until the script supplies them; the slide
counter is decorative chrome.
## Pre-Render Self-Audit
- **Squint** — one Fredoka headline or content card dominates per frame.
- **Silence** — one container per frame surrounded by an ornament wreath; only the info-card grid runs dense.
- **Color** — cream or one pastel surface; coral markers only; charcoal borders/shadows; no ninth hue.
- **Type** — Fredoka headlines (text-shadow + white on saturated, flat charcoal on cream), Quicksand body; ≥1.4cqw floor.
- **Depth** — 3px outline + hard offset (no blur, no rgba save soft text-shadow); rounded corners only.
- **Ornaments** — 3–7 per frame, cropping past edges; no empty corners. **Bullets** — outlined discs, never glyphs.
- **Fabrication** — every numeral traces to the script, else placeholder.
## Known Gaps
- **Motion intentionally out of scope.** frame.md specifies composition only; the source uses scroll-snap nav, no transition spec.
- **Fonts:** the source names _Fredoka One_; Google now serves the **Fredoka** variable family — request `Fredoka:wght@500;600;700` and set display weight 600 (visually equal to Fredoka One), with `Fredoka One` kept first in the stack for environments that still serve it. Quicksand loads normally. CJK: ZCOOL XiaoWei (display) / Yozai (body).
- **9:16 / 1:1 are guidance**; keep ornament count high so corners stay filled per ratio.
- Ornaments (daisy/star/sun/cloud/rainbow), markers, and framed headers are CSS/SVG-only; recoloring SVG ornaments requires editing their stroke values.
- **Contrast:** keep `{colors.text-muted}` off pastel surfaces (cream/white cards only); small text on saturated grounds should be charcoal or white.
+13
View File
@@ -0,0 +1,13 @@
{
"$schema": "https://hyperframes.heygen.com/schema/hyperframes.json",
"registry": "https://raw.githubusercontent.com/heygen-com/hyperframes/main/registry",
"paths": {
"blocks": "compositions",
"components": "compositions/components",
"assets": "assets"
},
"media": {
"autoProxy": true
},
"authoringSkill": "product-launch-video"
}
+245
View File
@@ -0,0 +1,245 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1080, height=1920" />
<script src="assets/vendor/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: 1080px;
height: 1920px;
overflow: hidden;
background: #000;
}
#root {
position: relative;
width: 1080px;
height: 1920px;
overflow: hidden;
background: #F5F0E6;
}
.scene {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="main"
data-start="0"
data-duration="54.733"
data-width="1080"
data-height="1920"
>
<div
id="el-01-hook"
class="scene"
data-composition-id="01-hook"
data-composition-src="compositions/frames/01-hook.html"
data-start="0"
data-duration="6.14"
data-track-index="0"
></div>
<audio
id="el-01-hook-voice"
src="assets/voice/01.wav"
data-start="0"
data-duration="5.64"
data-track-index="10"
data-volume="1"
></audio>
<div
id="el-02-worry"
class="scene"
data-composition-id="02-worry"
data-composition-src="compositions/frames/02-worry.html"
data-start="5.64"
data-duration="7.511"
data-track-index="1"
></div>
<audio
id="el-02-worry-voice"
src="assets/voice/02.wav"
data-start="5.64"
data-duration="7.111"
data-track-index="10"
data-volume="1"
></audio>
<div
id="el-03-meet"
class="scene"
data-composition-id="03-meet"
data-composition-src="compositions/frames/03-meet.html"
data-start="12.751"
data-duration="8.644"
data-track-index="0"
></div>
<audio
id="el-03-meet-voice"
src="assets/voice/03.wav"
data-start="12.751"
data-duration="8.144"
data-track-index="10"
data-volume="1"
></audio>
<div
id="el-04-limits"
class="scene"
data-composition-id="04-limits"
data-composition-src="compositions/frames/04-limits.html"
data-start="20.895"
data-duration="7.099"
data-track-index="1"
></div>
<audio
id="el-04-limits-voice"
src="assets/voice/04.wav"
data-start="20.895"
data-duration="6.599"
data-track-index="10"
data-volume="1"
></audio>
<div
id="el-05-apps"
class="scene"
data-composition-id="05-apps"
data-composition-src="compositions/frames/05-apps.html"
data-start="27.494"
data-duration="6.203"
data-track-index="0"
></div>
<audio
id="el-05-apps-voice"
src="assets/voice/05.wav"
data-start="27.494"
data-duration="5.703"
data-track-index="10"
data-volume="1"
></audio>
<div
id="el-06-location"
class="scene"
data-composition-id="06-location"
data-composition-src="compositions/frames/06-location.html"
data-start="33.197"
data-duration="7.013"
data-track-index="1"
></div>
<audio
id="el-06-location-voice"
src="assets/voice/06.wav"
data-start="33.197"
data-duration="6.513"
data-track-index="10"
data-volume="1"
></audio>
<div
id="el-07-balance"
class="scene"
data-composition-id="07-balance"
data-composition-src="compositions/frames/07-balance.html"
data-start="39.71"
data-duration="5.968"
data-track-index="0"
></div>
<audio
id="el-07-balance-voice"
src="assets/voice/07.wav"
data-start="39.71"
data-duration="5.468"
data-track-index="10"
data-volume="1"
></audio>
<div
id="el-08-platforms"
class="scene"
data-composition-id="08-platforms"
data-composition-src="compositions/frames/08-platforms.html"
data-start="45.178"
data-duration="4.183"
data-track-index="1"
></div>
<audio
id="el-08-platforms-voice"
src="assets/voice/08.wav"
data-start="45.178"
data-duration="3.783"
data-track-index="10"
data-volume="1"
></audio>
<div
id="el-09-cta"
class="scene"
data-composition-id="09-cta"
data-composition-src="compositions/frames/09-cta.html"
data-start="48.961"
data-duration="5.772"
data-track-index="0"
></div>
<audio
id="el-09-cta-voice"
src="assets/voice/09.wav"
data-start="48.961"
data-duration="5.772"
data-track-index="10"
data-volume="1"
></audio>
<!-- captions -->
<div
id="el-captions"
data-track-kind="captions"
class="scene"
data-composition-id="captions"
data-composition-src="compositions/captions.html"
data-start="0"
data-duration="54.733"
data-track-index="2"
></div>
</div>
<script>
window.__timelines = window.__timelines || {};
window.__timelines["main"] = gsap.timeline({ paused: true });
// ── frame transitions (injected by transitions.mjs) ──
(function () { var tl = window.__timelines["main"];
tl.to("#el-01-hook", { opacity: 0, duration: 0.5, ease: "power2.inOut" }, 5.64);
tl.fromTo("#el-02-worry", { opacity: 0 }, { opacity: 1, duration: 0.5, ease: "power2.inOut" }, 5.64);
tl.to("#el-02-worry", { scale: 2.5, opacity: 0, filter: "blur(8px)", duration: 0.4, ease: "power3.in" }, 12.751);
tl.fromTo("#el-03-meet", { scale: 0.5, opacity: 0, filter: "blur(8px)" }, { scale: 1, opacity: 1, filter: "blur(0px)", duration: 0.4, ease: "power3.out" }, 12.751);
tl.to("#el-03-meet", { x: -1080, duration: 0.5, ease: "power3.inOut" }, 20.895);
tl.fromTo("#el-04-limits", { x: 1080, opacity: 1 }, { x: 0, opacity: 1, duration: 0.5, ease: "power3.inOut" }, 20.895);
tl.to("#el-04-limits", { x: -1080, duration: 0.5, ease: "power3.inOut" }, 27.494);
tl.fromTo("#el-05-apps", { x: 1080, opacity: 1 }, { x: 0, opacity: 1, duration: 0.5, ease: "power3.inOut" }, 27.494);
tl.to("#el-05-apps", { x: -1080, duration: 0.5, ease: "power3.inOut" }, 33.197);
tl.fromTo("#el-06-location", { x: 1080, opacity: 1 }, { x: 0, opacity: 1, duration: 0.5, ease: "power3.inOut" }, 33.197);
tl.to("#el-06-location", { opacity: 0, duration: 0.5, ease: "power2.inOut" }, 39.71);
tl.fromTo("#el-07-balance", { opacity: 0 }, { opacity: 1, duration: 0.5, ease: "power2.inOut" }, 39.71);
tl.to("#el-07-balance", { opacity: 0, duration: 0.5, ease: "power2.inOut" }, 45.178);
tl.fromTo("#el-08-platforms", { opacity: 0 }, { opacity: 1, duration: 0.5, ease: "power2.inOut" }, 45.178);
tl.to("#el-08-platforms", { scale: 2.5, opacity: 0, filter: "blur(8px)", duration: 0.4, ease: "power3.in" }, 48.961);
tl.fromTo("#el-09-cta", { scale: 0.5, opacity: 0, filter: "blur(8px)" }, { scale: 1, opacity: 1, filter: "blur(0px)", duration: 0.4, ease: "power3.out" }, 48.961);
tl.to({}, { duration: 54.733 }, 0); // full-span anchor — main.duration() == composition total (Studio master duration)
})();
</script>
</body>
</html>
+5
View File
@@ -0,0 +1,5 @@
{
"id": "lookatu-promo",
"name": "lookatu-promo",
"createdAt": "2026-10-14T22:04:45.006Z"
}
+11
View File
@@ -0,0 +1,11 @@
{
"name": "lookatu-promo",
"private": true,
"type": "module",
"scripts": {
"dev": "npx --yes hyperframes@0.8.158 preview",
"check": "npx --yes hyperframes@0.8.158 check",
"render": "npx --yes hyperframes@0.8.158 render",
"publish": "npx --yes hyperframes@0.8.158 publish"
}
}
Binary file not shown.