/* 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 (
);
}
/* ---------- 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 (
);
}
/* ---------- 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 (
{/* 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 });