Session 34: Design system Phase C — app shell, nav, routing, auth gate, footer, 404 (1818 tests)

VYNDR 2.0 conversion, Phase C (the frame every page sits inside). Frontend-only;
zero backend changes.

- Nav rewritten: new .wm Wordmark, mono uppercase links, More dropdown, search/
  bell/read-meter/avatar, Ticker under the bar. layout main paddingTop 64 -> 96.
- Routing: web/src/lib/routes.js (GATED/OPEN/HASH_ALIASES, isGatedRoute,
  resolveHashAlias). Client AuthGate bounces signed-out users off personal
  routes to /login?next=. HashRedirect maps #scan/#terminal to real routes.
- Footer rewritten to system voice + Detroit signature; mounted globally in
  layout (removed per-page dup).
- 404 converted to the north star (scanlines, crt-sweep, glitch wordmark, amber).
- Stub pages for terminal/compare/invite/help/about/notifications via RouteStub.

Honest reconciliations: auth gate is client-side (no auth-helpers pkg; session is
client-side Supabase); GATED narrowed to protect the free-scan funnel; did not
stub over existing real pages; redirect param is ?next= (what /login reads).

26 new tests. Backend 1792 -> 1818, 142 suites, zero regressions. Web build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Kev
2026-06-15 23:27:58 -04:00
parent a74b5dd1ed
commit 907c7b17c1
19 changed files with 1020 additions and 430 deletions
+72 -129
View File
@@ -1,151 +1,94 @@
const PRIMARY_LINKS = [
{ label: 'Read', href: '/scan' },
{ label: 'Tracker', href: '/tracker' },
{ label: 'Ledger', href: '/ledger' },
{ label: 'Pricing', href: '/#pricing' },
{ label: 'Blog', href: '/blog' },
import { Wordmark } from '@/components/vyndr';
// VYNDR 2.0 footer (§C.4). System language, not marketing — mono, uppercase
// labels, the Detroit signature, and the legal/21+ line. Server component
// (no interactivity); link hover is handled by the global .footer-link rule.
const COLUMNS: { head: string; links: { label: string; href: string }[] }[] = [
{
head: 'PRODUCT',
links: [
{ label: 'Slate', href: '/dashboard' },
{ label: 'The Terminal', href: '/terminal' },
{ label: 'Pricing', href: '/pricing' },
{ label: 'Invite friends', href: '/invite' },
],
},
{
head: 'COMPANY',
links: [
{ label: 'About', href: '/about' },
{ label: 'The Report', href: '/blog' },
{ label: 'Help & FAQ', href: '/help' },
],
},
{
head: 'LEGAL',
links: [
{ label: 'Responsible Play', href: '/responsible-gambling' },
{ label: 'Terms', href: '/terms' },
{ label: 'Privacy', href: '/privacy' },
],
},
];
const LEGAL_LINKS = [
{ label: 'Terms', href: '/terms' },
{ label: 'Privacy', href: '/privacy' },
{ label: 'Responsible Gambling', href: '/responsible-gambling' },
// Session 17 — support contact in the legal column. Audit found
// the platform had no support surface at all.
{ label: 'Support', href: 'mailto:support@vyndr.app' },
];
import Wordmark from '@/components/Wordmark';
const SOCIAL = [
{ label: 'Twitter', href: 'https://twitter.com/getvyndr' },
{ label: 'Discord', href: 'https://discord.gg/getvyndr' },
];
const mono = { fontFamily: 'var(--mono)' } as const;
export default function Footer() {
return (
<footer
style={{
borderTop: '1px solid var(--border)',
padding: '64px 24px 32px',
marginTop: 64,
}}
>
<div style={{ maxWidth: 1200, margin: '0 auto' }}>
<div
style={{
display: 'grid',
gap: 48,
marginBottom: 48,
}}
className="footer-top"
>
<div style={{ maxWidth: 400 }}>
<a
href="/"
style={{ color: 'var(--text-0)', textDecoration: 'none', display: 'inline-flex', alignItems: 'center' }}
aria-label="VYNDR — home"
>
<Wordmark size={24} />
<footer style={{ borderTop: '1px solid var(--border)', marginTop: 40, padding: '34px 24px' }}>
<div style={{ maxWidth: 1320, margin: '0 auto' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', flexWrap: 'wrap', gap: 28 }}>
<div style={{ maxWidth: 300 }}>
<a href="/" aria-label="VYNDR — home" style={{ display: 'inline-flex' }}>
<Wordmark size="sm" beta />
</a>
<p
style={{
marginTop: 12,
fontSize: 14,
color: 'var(--text-secondary)',
lineHeight: 1.6,
}}
>
The books have every advantage. We built this to give it back.
</p>
<p className="mono" style={{ marginTop: 16, fontSize: 12, color: 'var(--text-tertiary)' }}>
Built in Detroit.
</p>
<div style={{ ...mono, fontSize: 11.5, color: 'var(--text-2)', lineHeight: 1.7, marginTop: 14 }}>
Prop intelligence the books don&apos;t want you to have. Analytics tool, not a sportsbook.
</div>
</div>
<FooterColumn title="Product" links={PRIMARY_LINKS} />
<FooterColumn title="Legal" links={LEGAL_LINKS} />
<FooterColumn title="Community" links={SOCIAL} external />
<div style={{ display: 'flex', gap: 48, flexWrap: 'wrap' }}>
{COLUMNS.map((col) => (
<div key={col.head}>
<div className="label" style={{ fontSize: 9.5, marginBottom: 12 }}>{col.head}</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 9 }}>
{col.links.map((l) => (
<a key={l.label} href={l.href} className="footer-link" style={{ ...mono, fontSize: 12, color: 'var(--text-1)', textDecoration: 'none' }}>
{l.label}
</a>
))}
</div>
</div>
))}
</div>
</div>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
flexWrap: 'wrap',
gap: 12,
marginTop: 30,
paddingTop: 20,
borderTop: '1px solid var(--border)',
paddingTop: 24,
display: 'grid',
gap: 16,
}}
>
<p style={{ fontSize: 12, color: 'var(--text-tertiary)', lineHeight: 1.6 }}>
VYNDR is an analytics tool, not a sportsbook. We don&apos;t accept wagers. Gamble responsibly.
If you or someone you know has a gambling problem, call <strong style={{ color: 'var(--text-secondary)' }}>1-800-522-4700</strong>{' '}
or visit <a href="https://www.ncpgambling.org" target="_blank" rel="noopener noreferrer" style={{ color: 'var(--grade-a)' }}>ncpgambling.org</a>.
</p>
<p className="mono" style={{ fontSize: 11, color: 'var(--text-tertiary)' }}>
© 2026 VYNDR. All rights reserved.
</p>
<div style={{ ...mono, fontSize: 11, color: 'var(--text-2)' }}>
VYNDR is an analytics tool, not a sportsbook. Not financial advice. Gamble responsibly. 21+ ·{' '}
<a href="tel:18005224700" style={{ color: 'var(--amber)', textDecoration: 'none' }}>1-800-522-4700</a>
</div>
<div style={{ ...mono, fontSize: 11, color: 'var(--text-2)' }}>
BUILT BY KEVON BUTLER · DETROIT · © 2026 VYNDR
</div>
</div>
</div>
<style jsx>{`
:global(.footer-top) {
grid-template-columns: 1fr;
}
@media (min-width: 768px) {
:global(.footer-top) {
grid-template-columns: 2fr 1fr 1fr 1fr;
}
}
<style>{`
.footer-link { transition: color .15s; }
.footer-link:hover { color: var(--text-0); }
`}</style>
</footer>
);
}
function FooterColumn({
title,
links,
external,
}: {
title: string;
links: { label: string; href: string }[];
external?: boolean;
}) {
return (
<div>
<h4
className="mono"
style={{
fontSize: 11,
fontWeight: 700,
letterSpacing: '0.08em',
textTransform: 'uppercase',
color: 'var(--text-tertiary)',
marginBottom: 16,
}}
>
{title}
</h4>
<ul style={{ display: 'grid', gap: 8 }}>
{links.map((l) => (
<li key={l.label}>
<a
href={l.href}
target={external ? '_blank' : undefined}
rel={external ? 'noopener noreferrer' : undefined}
style={{
color: 'var(--text-secondary)',
textDecoration: 'none',
fontSize: 14,
transition: 'color 200ms ease',
}}
onMouseEnter={(e) => (e.currentTarget.style.color = 'var(--text-primary)')}
onMouseLeave={(e) => (e.currentTarget.style.color = 'var(--text-secondary)')}
>
{l.label}
</a>
</li>
))}
</ul>
</div>
);
}