// Our Skills — Apple-style vertical panels with rich, smooth WOW animations.
const RED = '#ff2e4d';
const RED_GLOW = '#ff4d6a';
const CYAN = '#5cf0ff';
const GREEN = '#38e29a';
const AMBER = '#ffb547';
function useTick(ms = 1000) {
const [t, setT] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => setT((x) => x + 1), ms);
return () => clearInterval(id);
}, [ms]);
return t;
}
function useRaf() {
const [t, setT] = React.useState(0);
React.useEffect(() => {
let raf, start;
const loop = (ts) => { if (!start) start = ts; setT((ts - start) / 1000); raf = requestAnimationFrame(loop); };
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, []);
return t;
}
const easeInOut = (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
const lerp = (a, b, t) => a + (b - a) * t;
// Smooth Catmull-Rom -> Bezier path through points
function smoothPath(pts) {
if (pts.length < 2) return '';
let d = `M${pts[0][0].toFixed(1)},${pts[0][1].toFixed(1)}`;
for (let i = 0; i < pts.length - 1; i++) {
const p0 = pts[i - 1] || pts[i];
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = pts[i + 2] || p2;
const cp1x = p1[0] + (p2[0] - p0[0]) / 6;
const cp1y = p1[1] + (p2[1] - p0[1]) / 6;
const cp2x = p2[0] - (p3[0] - p1[0]) / 6;
const cp2y = p2[1] - (p3[1] - p1[1]) / 6;
d += ` C${cp1x.toFixed(1)},${cp1y.toFixed(1)} ${cp2x.toFixed(1)},${cp2y.toFixed(1)} ${p2[0].toFixed(1)},${p2[1].toFixed(1)}`;
}
return d;
}
/* ============================================================
1. ANALYTICS — multi-series, sweeping cursor, ticker, axis labels
============================================================ */
function DemoAnalytics() {
const t = useRaf();
const N = 32;
// Three "datasets" — we lerp between them on a slow loop for live feel
const datasets = React.useMemo(() => [
Array.from({ length: N }, (_, i) => 0.35 + Math.sin(i * 0.4 + 0.5) * 0.18 + i * 0.012),
Array.from({ length: N }, (_, i) => 0.5 + Math.sin(i * 0.32 + 1.6) * 0.22 + Math.sin(i * 0.6) * 0.08 + i * 0.014),
Array.from({ length: N }, (_, i) => 0.42 + Math.sin(i * 0.5 + 2.4) * 0.2 + i * 0.018),
], []);
const cost = React.useMemo(() => [
Array.from({ length: N }, (_, i) => 0.25 + Math.sin(i * 0.35) * 0.1 + i * 0.006),
Array.from({ length: N }, (_, i) => 0.32 + Math.sin(i * 0.28 + 1.0) * 0.12 + i * 0.008),
Array.from({ length: N }, (_, i) => 0.28 + Math.sin(i * 0.42 + 2.0) * 0.11 + i * 0.007),
], []);
const cycleLen = 4.5;
const phase = (t / cycleLen) % datasets.length;
const a = Math.floor(phase);
const b = (a + 1) % datasets.length;
const lp = easeInOut(phase - a);
const cur = datasets[a].map((v, i) => lerp(v, datasets[b][i], lp));
const curCost = cost[a].map((v, i) => lerp(v, cost[b][i], lp));
// Sweeping cursor
const sweep = (Math.sin(t * 0.55) + 1) / 2;
const ixF = easeInOut(sweep) * (N - 1);
const ix = Math.round(ixF);
const W = 900, H = 480, padX = 50, padY = 110, padB = 100;
const innerW = W - padX * 2, innerH = H - padY - padB;
const yFor = (v) => padY + innerH - v * innerH * 0.95;
const xFor = (i) => padX + (i / (N - 1)) * innerW;
const ptsR = cur.map((v, i) => [xFor(i), yFor(v)]);
const ptsC = curCost.map((v, i) => [xFor(i), yFor(v)]);
const pathR = smoothPath(ptsR);
const pathC = smoothPath(ptsC);
const areaR = pathR + ` L ${xFor(N-1)},${H-padB} L ${xFor(0)},${H-padB} Z`;
const areaC = pathC + ` L ${xFor(N-1)},${H-padB} L ${xFor(0)},${H-padB} Z`;
// Interp cursor on the curve
const cx = lerp(ptsR[Math.floor(ixF)][0], ptsR[Math.ceil(ixF)][0], ixF % 1);
const cy = lerp(ptsR[Math.floor(ixF)][1], ptsR[Math.ceil(ixF)][1], ixF % 1);
const cyC = lerp(ptsC[Math.floor(ixF)][1], ptsC[Math.ceil(ixF)][1], ixF % 1);
// Big number — smoothly count
const sumR = cur.reduce((s, v) => s + v, 0) / N;
const value = sumR * 2.6; // in millions
const delta = (8.4 + (sumR - 0.5) * 18 + Math.sin(t * 0.4) * 0.3);
// Bars at bottom (7 days)
const bars = Array.from({ length: 7 }, (_, i) => {
const ph = (t * 0.6 + i * 0.7);
return 0.45 + Math.sin(ph) * 0.22 + Math.sin(ph * 0.5) * 0.1;
});
const peakBar = bars.indexOf(Math.max(...bars));
// Period highlight
const months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const visMonths = months.slice(0, 12);
const tooltipVal = (sumR * (0.7 + cur[ix]) * 220).toFixed(0);
return (
Revenue · this quarter
${value.toFixed(2)}M
↗ {delta.toFixed(1)}% vs last quarter
Active
{(12400 + Math.floor(sumR * 800)).toLocaleString()}
Conv.
{(3.4 + Math.sin(t*0.5)*0.2).toFixed(2)}%
);
}
/* ============================================================
2. MOBILE — phone with floating UI cards + screen morphing
============================================================ */
function DemoMobile() {
const t = useRaf();
const dishes = [
{ name: 'Nasi Lemak', cn: '椰浆饭', tag: 'Chef pick', price: '18.00', rating: '4.9', cook: '12 min', cat: 'Rice', img: 'https://images.unsplash.com/photo-1626804475297-41608ea09aeb?w=400&q=80&auto=format&fit=crop' },
{ name: 'Char Kway Teow', cn: '炒粿条', tag: 'Spicy', price: '16.00', rating: '4.8', cook: '9 min', cat: 'Noodle', img: 'https://images.unsplash.com/photo-1645177628172-a94c1f96e6db?w=400&q=80&auto=format&fit=crop' },
{ name: 'Hainanese Chicken Rice', cn: '海南鸡饭', tag: 'Top pick', price: '15.00', rating: '4.9', cook: '8 min', cat: 'Rice', img: 'https://images.unsplash.com/photo-1569058242253-92a9c755a0ec?w=400&q=80&auto=format&fit=crop' },
{ name: 'Satay Ayam', cn: '沙嗲鸡', tag: '10 sticks', price: '14.00', rating: '4.7', cook: '15 min', cat: 'Grill', img: 'https://images.unsplash.com/photo-1529193591184-b1d58069ecdd?w=400&q=80&auto=format&fit=crop' },
{ name: 'Roti Canai', cn: '印度煎饼', tag: 'Best seller', price: '4.50', rating: '4.8', cook: '6 min', cat: 'Snack', img: 'https://images.unsplash.com/photo-1631292784640-2b24be784d5d?w=400&q=80&auto=format&fit=crop' },
{ name: 'Teh Tarik', cn: '拉茶', tag: 'Classic', price: '3.50', rating: '4.9', cook: '3 min', cat: 'Drink', img: 'https://images.unsplash.com/photo-1572490122747-3968b75cc699?w=400&q=80&auto=format&fit=crop' },
];
// Featured rotates through first three dishes; "Add" pulse comes at end of each beat.
const cycle = 4.0;
const idx = Math.floor((t / cycle) % 3);
const phase = (t / cycle) % 1;
const added = phase > 0.74;
const featured = dishes[idx];
const tick = Math.floor(t / cycle);
// Menu list = the other 3 dishes (Satay, Roti, Teh Tarik)
const menu = dishes.slice(3);
// Cart line items grow with the cycle
const cartCount = 2 + (added ? 1 : 0);
const cartTotal = (42 + (added ? Number(featured.price) : 0)).toFixed(2);
// Animated floating-card numbers
const orders = 247 + Math.floor(t * 0.3);
const rating = (4.8 + Math.sin(t * 0.6) * 0.05).toFixed(1);
const cats = [
{ k: 'All', n: 24 },
{ k: 'Rice', n: 6 },
{ k: 'Noodle', n: 5 },
{ k: 'Grill', n: 4 },
{ k: 'Drink', n: 9 },
];
const catActive = featured.cat === 'Rice' ? 1 : featured.cat === 'Noodle' ? 2 : 0;
return (
{/* Floating cards */}
Order paid
Table 14 · RM 42.00
{orders}
Orders
today
{[0,1,2,3,4].map(i => ★)}
{rating}
1,284 reviews
{/* Status bar */}
{/* Header */}
{/* Search */}
{/* Category chips */}
{cats.map((c, i) => (
{c.k}{c.n}
))}
{/* Featured card */}
Featured today
{dishes.slice(0,3).map((_, i) => )}
{featured.tag}
{featured.name}
{featured.cn} · {featured.cook}
RM
{featured.price}
{/* Section header */}
Popular nearby
See all →
{/* Menu list */}
{menu.map((d, i) => (
{d.name}
★ {d.rating}
·
{d.cook}
))}
{/* Floating cart bar */}
View cart
{cartCount} items · est. 12 min
RM {cartTotal}
{/* Tab bar */}
{/* fly-to-cart particle */}
{added && (
)}
);
}
/* ============================================================
3. COMMERCE — real product detail page (browser chrome, thumbs,
breadcrumb, rating, sale price, size, trust row)
============================================================ */
function shadeHex(hex, p) {
const n = parseInt(hex.slice(1), 16);
const r = Math.max(0, Math.min(255, ((n >> 16) & 255) + Math.round(p * 255)));
const g = Math.max(0, Math.min(255, ((n >> 8) & 255) + Math.round(p * 255)));
const b = Math.max(0, Math.min(255, (n & 255) + Math.round(p * 255)));
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function DemoCommerce() {
const t = useRaf();
const tick = Math.floor(t / 2.8);
const variants = [
{ name: 'Sand', color: '#c9b896', price: 459, img: 'https://images.unsplash.com/photo-1539533018447-63fcce2678e3?w=600&h=760&fit=crop&q=80&auto=format' },
{ name: 'Charcoal', color: '#2a2c33', price: 459, img: 'https://images.unsplash.com/photo-1544022613-e87ca75a784a?w=600&h=760&fit=crop&q=80&auto=format' },
{ name: 'Rust', color: '#a8472c', price: 489, img: 'https://images.unsplash.com/photo-1591047139829-d91aecb6caea?w=600&h=760&fit=crop&q=80&auto=format' },
{ name: 'Bone', color: '#e8e2d2', price: 459, img: 'https://images.unsplash.com/photo-1539109136881-3be0616acf4b?w=600&h=760&fit=crop&q=80&auto=format' },
];
const sizes = ['XS', 'S', 'M', 'L', 'XL'];
const soldOut = new Set(['XS']); // shows realistic stock state
const sel = tick % variants.length;
const sizeIdx = 2; // M selected (realistic — stays still)
const v = variants[sel];
const oldPrice = 689;
const discount = Math.round((1 - v.price / oldPrice) * 100);
// live viewer count (drifts slowly)
const viewers = 12 + Math.floor((Math.sin(t * 0.4) + 1) * 6);
return (
{/* Browser chrome */}
maison.shop/coats/wool-overcoat
{/* Product viewer: vertical thumbnail rail + hero image */}
{variants.map((vr, i) => (
))}
+4
−{discount}%
NEW IN

{ e.currentTarget.style.display = 'none'; }}
/>
22 SS
{v.name.toUpperCase()}
MAISON
{viewers} viewing now
1 / 5
{/* price tag fly-in */}
RM
{v.price}
Coats › Wool overcoats › 22 SS
MAISON
Wool overcoat
{[0,1,2,3,4].map(i => )}
4.8
· 124 reviews
RM {v.price}
RM {oldPrice}
or 3× RM {Math.round(v.price/3)} interest-free
Color · {v.name}
{variants.map((vr, i) => (
))}
{sizes.map((s, i) => (
))}
Free shipping
30-day returns
Authentic
In stock · ships in 24h from KL
);
}
/* ============================================================
4. SCHEDULING — wave-fill calendar + animated booking detail
============================================================ */
function DemoCalendar() {
const t = useRaf();
const tick = Math.floor(t / 1.2);
const cols = 7, rows = 4;
const total = cols * rows;
// Wave-fill from upper-left
const waveProg = ((t * 0.6) % 6) / 6;
const filled = new Set();
const filledPattern = [3, 9, 15, 8, 11, 18, 6, 17, 20, 13, 5, 10, 22, 25, 1, 19];
const cutoff = Math.floor(waveProg * (filledPattern.length + 3));
filledPattern.slice(0, cutoff).forEach(i => filled.add(i));
const selected = filledPattern[tick % filledPattern.length];
const slots = [
{ t: '09:00', n: 'Dr. Tan', s: 'Consult' },
{ t: '10:30', n: 'Dr. Lim', s: 'Follow-up' },
{ t: '11:15', n: 'Dr. Wong', s: 'Scan' },
{ t: '14:00', n: 'Dr. Tan', s: 'Consult' },
{ t: '15:45', n: 'Dr. Lim', s: 'Follow-up' },
];
const slotIdx = tick % slots.length;
const utilization = Math.min(100, Math.floor((filled.size / total) * 100));
return (
May 2026
Week 20 · Showroom KL
{['M','T','W','T','F','S','S'].map((d,i) => {d})}
{Array.from({ length: total }).map((_, i) => {
const isFilled = filled.has(i);
const isSel = i === selected;
return (
{i + 1}
{isFilled && }
);
})}
{slots.map((sl, i) => (
{sl.t}
{i === slotIdx ? 'Selecting…' : sl.n}
))}
);
}
/* ============================================================
5. DEVOPS — full production deploy dashboard
============================================================ */
function DemoCode() {
const t = useRaf();
const CYCLE_LEN = 18; // seconds
const cycle = (t % CYCLE_LEN) / CYCLE_LEN;
// Pipeline stages — realistic CI/CD with elapsed times
const stages = [
{ id: 'install', l: 'Install dependencies', from: 0.00, to: 0.07, dur: 12.4 },
{ id: 'lint', l: 'Lint & typecheck', from: 0.07, to: 0.16, dur: 9.1 },
{ id: 'test', l: 'Unit + integration', from: 0.16, to: 0.34, dur: 24.7 },
{ id: 'build', l: 'Build & containerize', from: 0.34, to: 0.62, dur: 38.2 },
{ id: 'deploy', l: 'Rolling deploy · 6 regions', from: 0.62, to: 0.95, dur: 47.3 },
];
let activeIdx = stages.findIndex((s) => cycle >= s.from && cycle < s.to);
const done = cycle >= 0.95;
if (done) activeIdx = stages.length;
if (activeIdx === -1 && !done) activeIdx = 0;
const stageProgress = (i) => {
const s = stages[i];
if (cycle < s.from) return 0;
if (cycle >= s.to) return 1;
return (cycle - s.from) / (s.to - s.from);
};
const overall = Math.min(1, cycle / 0.95);
// Regions roll out in sequence
const regions = [
{ loc: 'SIN', city: 'Singapore' },
{ loc: 'TYO', city: 'Tokyo' },
{ loc: 'HKG', city: 'Hong Kong' },
{ loc: 'SYD', city: 'Sydney' },
{ loc: 'FRA', city: 'Frankfurt' },
{ loc: 'IAD', city: 'N. Virginia'},
];
const dStart = 0.62, dEnd = 0.95;
const dSpan = dEnd - dStart;
const regionStatus = (i) => {
const w = dSpan / regions.length;
const start = dStart + w * i;
const end = start + w * 0.85;
if (cycle < start) return { s: 'queued', p: 0 };
if (cycle >= end) return { s: 'live', p: 1 };
return { s: 'rolling', p: (cycle - start) / (end - start) };
};
// Live log stream — appears as cycle progresses
const allLogs = React.useMemo(() => [
{ c: 0.005, l: '$ pnpm install --frozen-lockfile', k: 'cmd' },
{ c: 0.030, l: 'Lockfile is up to date · 1,834 packages · cached', k: 'mute' },
{ c: 0.060, l: 'done in 11.8s', k: 'ok' },
{ c: 0.080, l: '$ pnpm lint && tsc --noEmit', k: 'cmd' },
{ c: 0.110, l: 'eslint src/** · 0 errors 0 warnings', k: 'mute' },
{ c: 0.145, l: 'tsc · checked 4,212 files · 0 errors', k: 'ok' },
{ c: 0.170, l: '$ vitest run --coverage --reporter=dot', k: 'cmd' },
{ c: 0.230, l: '· · · · · · · · · · · · · · · · · ·', k: 'mute' },
{ c: 0.295, l: '✓ 412 tests passed · branch cov 96.2%', k: 'ok' },
{ c: 0.340, l: '$ next build && docker buildx build --platform linux/arm64,linux/amd64 .', k: 'cmd' },
{ c: 0.420, l: '○ Compiled successfully · 247 routes · 1.18 MB gzip', k: 'mute' },
{ c: 0.495, l: 'Pushed → gcr.io/six6/web@sha256:a3f9b4c2…d7e1', k: 'mute' },
{ c: 0.580, l: 'Image scan · 0 critical · 2 low · ok', k: 'ok' },
{ c: 0.625, l: '$ kubectl rollout · canary 5% → 100% surge', k: 'cmd' },
{ c: 0.665, l: '→ sin1 pulling image · 4/4 ready · healthy', k: 'deploy' },
{ c: 0.720, l: '→ tyo1 pulling image · 4/4 ready · healthy', k: 'deploy' },
{ c: 0.775, l: '→ hkg1 pulling image · 4/4 ready · healthy', k: 'deploy' },
{ c: 0.830, l: '→ syd1 pulling image · 4/4 ready · healthy', k: 'deploy' },
{ c: 0.875, l: '→ fra1 pulling image · 4/4 ready · healthy', k: 'deploy' },
{ c: 0.920, l: '→ iad1 pulling image · 4/4 ready · healthy', k: 'deploy' },
{ c: 0.955, l: '✓ shipped v2026.5.17 · 2m 31s · 0 downtime · 24/24 pods', k: 'ok' },
], []);
const shownLogs = allLogs.filter((L) => cycle >= L.c);
const tailRef = React.useRef(null);
React.useEffect(() => {
if (tailRef.current) tailRef.current.scrollTop = tailRef.current.scrollHeight;
}, [shownLogs.length]);
return (
{/* Header — branch, commit, status */}
{done ? 'Deployed' : 'Deploying'}
main
←
fix/checkout-poll
8f3a2c1d
·
v2026.5.17
{/* Commit message */}
fix(checkout): debounce inventory polling on quantity step
@nadia · 2 files · +47 −12
{/* Body — pipeline + terminal */}
{stages.map((s, i) => {
const isAct = i === activeIdx && !done;
const isDone = (i < activeIdx) || done;
const p = stageProgress(i);
const elapsed = isDone ? s.dur : isAct ? p * s.dur : 0;
return (
{isDone ? (
) : isAct ? (
) : (
)}
{s.l}
{isDone ? `${s.dur.toFixed(1)}s` :
isAct ? `${elapsed.toFixed(1)}s` :
'—'}
{isAct && (
)}
);
})}
runner · ubuntu-22.04 · 8 cpu
{shownLogs.slice(-14).map((L, i) => (
{L.l}
))}
{!done &&
▍
}
{/* Overall progress */}
{Math.round(overall * 100)}%
{/* Region rollout */}
{regions.map((r, i) => {
const rs = regionStatus(i);
return (
{r.loc}
{r.city}
{rs.s === 'queued' && queued}
{rs.s === 'rolling' && (
{Math.round(rs.p * 100)}%
)}
{rs.s === 'live' && v2026.5.17}
);
})}
);
}
/* ============================================================
SECTION
============================================================ */
function SkillPanel({ n, label, titleKey, desc, flip, Comp }) {
return (
);
}
function Work() {
const { t } = useLang();
const skills = [
{ n: '01', label: t('work.1.label'), titleKey: 'work.1.title.html', desc: t('work.1.desc'), Comp: DemoAnalytics, flip: false },
{ n: '02', label: t('work.2.label'), titleKey: 'work.2.title.html', desc: t('work.2.desc'), Comp: DemoMobile, flip: true },
{ n: '03', label: t('work.3.label'), titleKey: 'work.3.title.html', desc: t('work.3.desc'), Comp: DemoCommerce, flip: false },
{ n: '04', label: t('work.4.label'), titleKey: 'work.4.title.html', desc: t('work.4.desc'), Comp: DemoCalendar, flip: true },
{ n: '05', label: t('work.5.label'), titleKey: 'work.5.title.html', desc: t('work.5.desc'), Comp: DemoCode, flip: false },
];
return (
{t('work.kicker')}{t('work.kicker.right')}
{skills.map((s) => ())}
);
}
window.Work = Work;