/* Embarc explainer video — "From Struggling to Scaling" Scenes composed on a 1280x720 canvas. Brand: navy + orange. */ const HEAD = '"Schibsted Grotesk", system-ui, sans-serif'; const BODY = '"IBM Plex Sans", system-ui, sans-serif'; const MONO = '"IBM Plex Mono", ui-monospace, monospace'; const C = { navy: '#0b2545', navyDeep: '#07182f', navySoft: '#12305a', orange: '#fa8e08', orangeBright: '#ffa83a', green: '#3ddc84', red: '#ff6b6b', white: '#ffffff', mute: 'rgba(255,255,255,0.58)', faint: 'rgba(255,255,255,0.32)', }; /* ---------- shared backdrop (constant across scenes for continuity) ---------- */ function VideoBG() { const t = useTime(); // very slow drift on the glow for "always in motion" const gx = 60 + Math.sin(t * 0.18) * 8; const gy = -10 + Math.cos(t * 0.15) * 6; return (
{/* faint grid */}
); } /* ---------- logo mark (orange rounded square + white hex) ---------- */ function LogoMark({ size = 64, x, y, scale = 1, opacity = 1 }) { return (
); } /* ---------- eyebrow label ---------- */ function Eyebrow({ children, color = C.orangeBright }) { return (
{children}
); } /* ---------- progressive line chart (clip-reveal) ---------- */ function LineChart({ points, progress, color, fill, w = 560, h = 250, glowDot = true }) { const poly = points.map(p => `${p[0]},${p[1]}`).join(' '); const area = `0,${h} ` + poly + ` ${w},${h}`; const revealW = w * progress; // dot at current end const idx = clamp(progress, 0, 1) * (points.length - 1); const i0 = Math.floor(idx), i1 = Math.min(points.length - 1, i0 + 1); const f = idx - i0; const dotX = points[i0][0] + (points[i1][0] - points[i0][0]) * f; const dotY = points[i0][1] + (points[i1][1] - points[i0][1]) * f; const cid = 'clip' + color.replace(/[^a-z0-9]/gi, ''); return ( {/* baseline */} {glowDot && progress > 0.02 && progress < 0.999 && ( )} ); } /* ---------- pill stat (for problem flicker) ---------- */ function MiniStat({ label, value, danger, delay, x, y }) { const { localTime } = useSprite(); const a = clamp((localTime - delay) / 0.4, 0, 1); const ease = Easing.easeOutBack(a); return (
{label} {value}
); } /* ========================================================================= SCENE 1 — THE PROBLEM (0 – 4.4s) ========================================================================= */ function SceneProblem() { const { localTime, progress } = useSprite(); // declining line const pts = [[0,70],[95,86],[190,104],[285,128],[380,150],[475,176],[560,206]]; const draw = animate({ from: 0, to: 1, start: 0.3, end: 1.8, ease: Easing.easeInOutCubic })(localTime); const headA = clamp((localTime - 0.15) / 0.5, 0, 1); return (
The starting point
Stalled.
Bleeding spend.
Written off.
{/* chart on right */}
); } /* ========================================================================= SCENE 2 — ENTER EMBARC (4.4 – 7.8s) ========================================================================= */ function SceneEnter() { const { localTime } = useSprite(); const logoS = animate({ from: 0.3, to: 1, start: 0.1, end: 0.9, ease: Easing.easeOutBack })(localTime); const logoO = clamp(localTime / 0.5, 0, 1); const nameA = clamp((localTime - 0.6) / 0.5, 0, 1); const tagA = clamp((localTime - 1.0) / 0.6, 0, 1); const tags = ['10 years experience', 'Private label only', 'SPN registered']; return (
Then Embarc steps in.
{tags.map((tg, i) => { const a = clamp((localTime - 1.0 - i * 0.18) / 0.5, 0, 1); return (
{tg}
); })}
); } /* ========================================================================= SCENE 3 — THE PROCESS (7.8 – 16.6s) ========================================================================= */ const STEPS = [ { n: '01', t: 'Audit', s: 'Find what everyone else missed' }, { n: '02', t: 'Stabilize', s: 'Fix account health & risk' }, { n: '03', t: 'Optimize', s: 'Listings & SEO that convert' }, { n: '04', t: 'Advertise', s: 'PPC rebuilt for profit' }, { n: '05', t: 'Scale', s: 'Launch & grow with momentum' }, ]; function SceneProcess() { const { localTime } = useSprite(); const headA = clamp(localTime / 0.5, 0, 1); const colW = 210, gap = 18; const totalW = STEPS.length * colW + (STEPS.length - 1) * gap; const startX = (1280 - totalW) / 2; const cardY = 300; // connecting progress line const lineStart = 1.0, lineEnd = 7.4; const lineP = clamp((localTime - lineStart) / (lineEnd - lineStart), 0, 1); return (
The process
A decade of judgment, applied in order.
{/* connecting line behind cards */}
{STEPS.map((st, i) => { const appear = 0.7 + i * 1.25; const a = clamp((localTime - appear) / 0.55, 0, 1); const ease = Easing.easeOutBack(a); const active = localTime >= appear && localTime < appear + 1.6; return (
{st.n}
{st.t}
{st.s}
{/* node dot on the line */}
); })}
); } /* ========================================================================= SCENE 4 — THE TURNAROUND (16.6 – 21.8s) ========================================================================= */ function StatTurn({ label, before, after, fmt, delta, x, y, startAt }) { const { localTime } = useSprite(); const a = clamp((localTime - startAt) / 0.4, 0, 1); const val = interpolate([startAt + 0.5, startAt + 2.1], [before, after], Easing.easeOutCubic)(localTime); const deltaA = clamp((localTime - startAt - 2.0) / 0.4, 0, 1); return (
{label}
{fmt(before)} {fmt(val)}
{delta}
); } function SceneTurn() { const { localTime } = useSprite(); const headA = clamp(localTime / 0.5, 0, 1); const pts = [[0,210],[95,206],[190,196],[285,170],[380,128],[475,74],[560,20]]; const draw = animate({ from: 0, to: 1, start: 0.6, end: 2.6, ease: Easing.easeInOutCubic })(localTime); return (
The turnaround
The same account —
now scaling.
`$${Math.round(v)}K`} delta="▲ 234%" x={100} y={340} startAt={0.8} /> `${v.toFixed(1)}%`} delta="▼ 17.2 pts" x={100} y={470} startAt={1.1} /> `${Math.round(v)}%`} delta="▲ 37 pts" x={380} y={470} startAt={1.4} /> {/* growth chart on right */}
Illustrative example — not a specific client
); } /* ========================================================================= SCENE 5 — CLOSE / CTA (21.8 – 25s) ========================================================================= */ function SceneClose() { const { localTime, duration } = useSprite(); const logoS = animate({ from: 0.5, to: 1, start: 0.05, end: 0.7, ease: Easing.easeOutBack })(localTime); const a = clamp((localTime - 0.4) / 0.5, 0, 1); const ctaA = clamp((localTime - 0.9) / 0.5, 0, 1); // gentle fade out at very end for clean loop const outA = 1 - clamp((localTime - (duration - 0.5)) / 0.5, 0, 1); return (
We grow the brands
everyone else gave up on.
Book a free audit
embarcconsulting.com
); } /* ========================================================================= ROOT ========================================================================= */ function EmbarcVideo() { const t = useTime(); React.useEffect(() => { const root = document.getElementById('vroot'); if (root) root.setAttribute('data-screen-label', `t=${t.toFixed(0)}s`); }, [Math.floor(t)]); return (
); } Object.assign(window, { EmbarcVideo });