// DS5 (Design v2) — Pricing (Desk-as-hero) + Motion (ticker → punctuated // stillness) + Empty/Error unification (the 404 bar) + archetype propagation. // Source-grep locks (plain-JS Jest, no TS transform) — same pattern as // vyndrParityQA / ds4Billboards. Every assertion targets a DS5 artifact that // did NOT exist on the base commit, so the suite fails before and passes after. const fs = require('fs'); const path = require('path'); const ROOT = path.join(__dirname, '..', '..'); const WEB = path.join(ROOT, 'web', 'src'); const read = (rel) => fs.readFileSync(path.join(WEB, rel), 'utf8'); const readCss = () => fs.readFileSync(path.join(WEB, 'app', 'globals.css'), 'utf8'); // Extract a single TIERS object block by its id. function tierBlock(src, id) { const m = src.match(new RegExp(`id: '${id}'[\\s\\S]*?highlight: (true|false)`)); return m ? m[1] : null; } // ── 1. Desk $44.99 is the HERO tier (#8, Part 6) ────────────────────────── describe('Pricing — Desk is the hero, real prices, single primary CTA', () => { const pricing = read('components/Pricing.tsx'); const showcase = read('app/pricing/DeskShowcase.tsx'); const page = read('app/pricing/page.tsx'); test('Desk is highlighted (the hero); Analyst is NOT (no two competing green CTAs, #9)', () => { expect(tierBlock(pricing, 'desk')).toBe('true'); expect(tierBlock(pricing, 'analyst')).toBe('false'); }); test('Desk CTA is at least as strong as Analyst — Desk drives the primary button', () => { // highlight:true → btn-primary (the strong CTA); highlight:false → btn-ghost. expect(pricing).toContain("className={tier.highlight ? 'btn-primary' : 'btn-ghost'}"); // Desk is the highlighted tier, so it owns the primary; Analyst is secondary. expect(tierBlock(pricing, 'desk')).toBe('true'); }); test('real prices only — Desk $34.99 founder / $44.99 regular, Analyst $14.99/$19.99, Free 5 reads', () => { expect(pricing).toMatch(/id: 'desk'[\s\S]*?price: '\$34\.99'/); expect(pricing).toMatch(/id: 'desk'[\s\S]*?originalPrice: '\$44\.99'/); expect(pricing).toMatch(/id: 'analyst'[\s\S]*?price: '\$14\.99'/); expect(pricing).toMatch(/id: 'analyst'[\s\S]*?originalPrice: '\$19\.99'/); expect(pricing).toContain('5 reads to try the model'); // no fabricated legacy price expect(pricing).not.toContain("originalPrice: '$24.99'"); expect(pricing).not.toContain("originalPrice: '$49.99'"); }); test('the Desk story leads with deadpan value-showing copy (no "$1M" brag), above the grid, with a real feature ladder', () => { expect(page).toContain('import DeskShowcase'); expect(page).toContain(' { expect(require('../../web/src/lib/checkout').checkoutUrl('desk')).toBe('/api/checkout?tier=desk'); expect(pricing).toContain("fetch('/api/checkout'"); }); }); // ── 2. Ticker → punctuated stillness (#7, Part 4) ───────────────────────── describe('Ticker + header motion — mostly still, meaningful pulses', () => { const ticker = read('components/vyndr/Ticker.tsx'); const css = readCss(); test('motion is tokenized — no one-off durations (--ticker-hold + --motion-*)', () => { expect(css).toMatch(/--ticker-hold:\s*\d+ms/); expect(css).toContain('--motion-transition:'); expect(css).toContain('--motion-data:'); expect(css).toContain('--motion-idle:'); }); test('the ticker RESTS ≥4s on each item (hold token + JS constant both ≥4000ms, in sync)', () => { const holdMs = Number((css.match(/--ticker-hold:\s*(\d+)ms/) || [])[1]); const jsMs = Number((ticker.match(/TICKER_HOLD_MS\s*=\s*(\d+)/) || [])[1]); expect(holdMs).toBeGreaterThanOrEqual(4000); expect(jsMs).toBeGreaterThanOrEqual(4000); expect(jsMs).toBe(holdMs); }); test('the continuous marquee is retired — the ticker no longer scrolls forever', () => { // The resting strip is static; the old infinite .ticker-track marquee is gone // from the component (constant motion reads cheap — Part 0 law #1). expect(ticker).not.toContain('ticker-track'); expect(ticker).toContain('ticker-rest'); }); test('ONE animated element in the header zone — the ticker no longer renders a competing live-dot', () => { // The single idle proof-of-life is the heartbeat live-dot; the ticker drops // its own pulsing dot so the header is not ticker + heartbeat + counter. expect(ticker).not.toMatch(/className="[^"]*\blive-dot\b/); const live = read('components/vyndr/LiveLayer.tsx'); expect((live.match(/live-dot/g) || []).length).toBe(1); }); test('the EKG heartbeat is a STATIC readout (no idle scroll animation)', () => { // .ekg-track keeps its class (vyndrSystems locks the string) but no longer // carries an infinite scroll — proof-of-life is the one live-dot. expect(/\.ekg-track\s*\{[^}]*animation/.test(css)).toBe(false); expect(read('components/vyndr/LiveLayer.tsx')).toContain('ekg-track'); }); test('prefers-reduced-motion kills the motion entirely', () => { expect(ticker).toContain('prefers-reduced-motion'); // new ticker motion classes are in the global reduced-motion kill list expect(css).toMatch(/prefers-reduced-motion[\s\S]*?\.ticker-item-enter/); expect(css).toMatch(/prefers-reduced-motion[\s\S]*?\.ticker-pulse/); }); test('the polling contract is preserved (tickerLive lock stays green)', () => { expect(ticker).toContain("fetch('/api/ticker'"); expect(ticker).toContain('pollMs = 30_000'); expect(ticker).toContain('feed && feed.length > 0 ? feed : items'); }); }); // ── 3. Unify empty/error to the 404 bar (#20, Part 8) ───────────────────── describe('EmptyState — one designed empty/error system, modeled on the 404', () => { const es = read('components/vyndr/EmptyState.tsx'); test('carries the 404 north-star grammar (scanlines + glitch wordmark + amber system voice)', () => { expect(es).toContain('scanlines'); expect(es).toContain('crt-sweep'); expect(es).toContain('amber-glow'); expect(es).toContain('Wordmark'); }); test('CTA hierarchy — at most one primary (color contract #9)', () => { expect(es).toContain('primary'); expect(es).toContain("a.primary ? 'btn-primary' : 'btn-ghost'"); }); test('reused at the "Team not found" offender — no bare-red line', () => { const hub = read('app/team/[abbr]/TeamHub.tsx'); expect(hub).toContain(' { const game = read('app/game/[id]/page.tsx'); expect(game).toContain(' { const ledger = read('app/ledger/page.tsx'); expect(ledger).toContain(''); }); test('EmptyState is exported from the vyndr barrel (one component, everywhere)', () => { expect(read('components/vyndr/index.ts')).toContain("export { default as EmptyState }"); }); }); // ── 4. Propagate archetype glyphs everywhere (Part 5) ───────────────────── describe('Archetype glyph+chip — the ONE component, propagated', () => { test('the grade reveal renders the archetype (glyph+chip via ArchetypeBlend)', () => { const card = read('components/vyndr/GradeResultCard.tsx'); expect(card).toContain('ArchetypeBlend'); expect(card).toContain(' { const panel = read('components/StreaksPanel.tsx'); expect(panel).toContain("import ArchetypeBadge"); expect(panel).toContain(' { const ledger = read('app/ledger/page.tsx'); expect(ledger).toContain('ArchetypeBadge'); expect(ledger).toContain(' { expect(read('components/StreaksPanel.tsx')).toContain('showDesc'); expect(read('app/ledger/page.tsx')).toContain('showDesc'); }); });