Add storefront component migration: cards, badges, progress bars, animations, and React wrapper package

Major feature migration from dt-shopify-storefront into the design system:

- Card system: per-card color modes (normal/emphasis/warning/success/other),
  progress bars, full-bleed card body (card-body-flush), image bevel clip-paths,
  and visible structural borders at bevel corners
- Badge system: dt-card-badge with position-aware offsets for cards vs media frames
- Media frames: dt-bevel-media with inner surface fill, image clipping, and
  "MISSING MEDIA" placeholder for empty containers
- Interactive bevel buttons: dt-btn with hover/selected states and pulse animation
- Menu items: dt-menu-item with active states and level indentation
- Animations: scale-in, fade-in, slide-up, pulse, ping, spin keyframes
- Feature legend: CSS grid for product feature icons with state colors
- Scrollbar styling for DT brand
- New @dangerousthings/react package wrapping web CSS components
- New @dangerousthings/tailwind-preset package
- New @dangerousthings/hex-background package
- Desktop showcase rewritten in React with Tailwind CSS
- Mobile showcase updated with new screens (animations, filters, advanced cards)
- Tokens: added mode color tokens, RGB variants, and selected-state tokens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-05 13:26:03 -08:00
parent 47e8085581
commit e74c285b70
109 changed files with 7196 additions and 1026 deletions

View File

@@ -0,0 +1,100 @@
import { useState, useEffect } from 'react';
import { themes } from '@dangerousthings/tokens';
import type { ThemeBrand, ThemeMode } from '@dangerousthings/tokens';
import { HomePage } from './pages/HomePage';
import { BevelsPage } from './pages/BevelsPage';
import { GlowsPage } from './pages/GlowsPage';
import { FormsPage } from './pages/FormsPage';
import { AnimationsPage } from './pages/AnimationsPage';
import { CardsAdvancedPage } from './pages/CardsAdvancedPage';
import { TokensPage } from './pages/TokensPage';
const navPages = [
{ hash: '', label: 'Home' },
{ hash: 'bevels', label: 'Bevels' },
{ hash: 'glows', label: 'Glows' },
{ hash: 'forms', label: 'Forms' },
{ hash: 'animations', label: 'Animations' },
{ hash: 'cards-advanced', label: 'Advanced Cards' },
{ hash: 'tokens', label: 'Tokens' },
];
export function App() {
const [brand, setBrand] = useState<ThemeBrand>('dt');
const [theme, setTheme] = useState<ThemeMode>('dark');
const [currentHash, setCurrentHash] = useState('');
// Sync brand/theme to <html> so CSS custom properties cascade everywhere
useEffect(() => {
document.documentElement.setAttribute('data-brand', brand);
document.documentElement.setAttribute('data-theme', theme);
}, [brand, theme]);
// Hash-based routing
useEffect(() => {
function onHashChange() {
setCurrentHash(window.location.hash.replace('#/', '').replace('#', ''));
}
window.addEventListener('hashchange', onHashChange);
onHashChange();
return () => window.removeEventListener('hashchange', onHashChange);
}, []);
function renderPage() {
switch (currentHash) {
case 'bevels': return <BevelsPage />;
case 'glows': return <GlowsPage />;
case 'forms': return <FormsPage />;
case 'animations': return <AnimationsPage />;
case 'cards-advanced': return <CardsAdvancedPage />;
case 'tokens': return <TokensPage brand={brand} />;
default: return <HomePage />;
}
}
return (
<>
<nav id="sidebar">
<div className="nav-title">DT DESIGN SYSTEM</div>
<div className="brand-switcher-container">
{themes.map(t => (
<button
key={t.id}
className={`brand-btn${t.id === brand ? ' active' : ''}`}
onClick={() => setBrand(t.id)}
type="button"
>
{t.name}
</button>
))}
<div className="mode-switcher">
{(['dark', 'light', 'auto'] as ThemeMode[]).map(m => (
<button
key={m}
className={`mode-btn${m === theme ? ' active' : ''}`}
onClick={() => setTheme(m)}
type="button"
>
{m}
</button>
))}
</div>
</div>
<div className="nav-links">
{navPages.map(p => (
<a
key={p.hash}
href={`#/${p.hash}`}
className={`nav-link${p.hash === currentHash ? ' active' : ''}`}
>
{p.label}
</a>
))}
</div>
</nav>
<main id="content">
{renderPage()}
</main>
</>
);
}

View File

@@ -1,77 +0,0 @@
import { themes } from '@dangerousthings/tokens';
import type { ThemeBrand, ThemeMode } from '@dangerousthings/tokens';
let currentBrand: ThemeBrand = 'dt';
let currentMode: ThemeMode = 'dark';
export function initBrandSwitcher(container: HTMLElement) {
// Brand buttons
for (const theme of themes) {
const btn = document.createElement('button');
btn.textContent = theme.name;
btn.className = 'brand-btn';
btn.dataset.brand = theme.id;
if (theme.id === currentBrand) btn.classList.add('active');
btn.addEventListener('click', () => switchBrand(theme.id));
container.appendChild(btn);
}
// Mode switcher
const modeContainer = document.createElement('div');
modeContainer.className = 'mode-switcher';
const modes: ThemeMode[] = ['dark', 'light', 'auto'];
for (const mode of modes) {
const btn = document.createElement('button');
btn.textContent = mode;
btn.className = 'mode-btn';
btn.dataset.mode = mode;
if (mode === currentMode) btn.classList.add('active');
btn.addEventListener('click', () => switchMode(mode));
modeContainer.appendChild(btn);
}
container.appendChild(modeContainer);
// Set initial state
applyTheme();
}
function switchBrand(id: ThemeBrand) {
currentBrand = id;
applyTheme();
updateBrandButtons();
}
function switchMode(mode: ThemeMode) {
currentMode = mode;
applyTheme();
updateModeButtons();
}
function applyTheme() {
document.documentElement.setAttribute('data-brand', currentBrand);
document.documentElement.setAttribute('data-theme', currentMode);
}
function updateBrandButtons() {
document.querySelectorAll('.brand-btn').forEach((btn) => {
const el = btn as HTMLButtonElement;
el.classList.toggle('active', el.dataset.brand === currentBrand);
});
}
function updateModeButtons() {
document.querySelectorAll('.mode-btn').forEach((btn) => {
const el = btn as HTMLButtonElement;
el.classList.toggle('active', el.dataset.mode === currentMode);
});
}
export function getCurrentBrand(): ThemeBrand {
return currentBrand;
}
export function getCurrentMode(): ThemeMode {
return currentMode;
}

View File

@@ -0,0 +1,23 @@
import type { ReactNode, CSSProperties } from 'react';
export function Section({ title, description, children }: { title: string; description: string; children: ReactNode }) {
return (
<div className="demo-section">
<h2>{title}</h2>
<p className="demo-description">{description}</p>
{children}
</div>
);
}
export function Row({ children, style }: { children: ReactNode; style?: CSSProperties }) {
return <div className="demo-row" style={style}>{children}</div>;
}
export function CodeLabel({ text }: { text: string }) {
return <div className="code-label">{text}</div>;
}
export function DemoLabel({ text }: { text: string }) {
return <div className="demo-label">{text}</div>;
}

View File

@@ -6,10 +6,7 @@
<title>DT Design System — Desktop Showcase</title>
</head>
<body>
<div id="app">
<nav id="sidebar"></nav>
<main id="content"></main>
</div>
<script type="module" src="./main.ts"></script>
<div id="app"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>

View File

@@ -1,56 +0,0 @@
import '@dangerousthings/web/index.css';
import './style.css';
import { initRouter } from './utils/router';
import { initBrandSwitcher } from './brand-switcher';
import { renderHomePage } from './pages/home';
import { renderBevelsPage } from './pages/bevels';
import { renderGlowsPage } from './pages/glows';
import { renderFormsPage } from './pages/forms';
import { renderTokensPage } from './pages/tokens';
const routes: Record<string, (container: HTMLElement) => void> = {
'': renderHomePage,
'bevels': renderBevelsPage,
'glows': renderGlowsPage,
'forms': renderFormsPage,
'tokens': renderTokensPage,
};
const sidebar = document.getElementById('sidebar')!;
const content = document.getElementById('content')!;
// Build sidebar navigation
const navTitle = document.createElement('div');
navTitle.className = 'nav-title';
navTitle.textContent = 'DT DESIGN SYSTEM';
sidebar.appendChild(navTitle);
const brandContainer = document.createElement('div');
brandContainer.className = 'brand-switcher-container';
sidebar.appendChild(brandContainer);
initBrandSwitcher(brandContainer);
const navLinks = document.createElement('div');
navLinks.className = 'nav-links';
const pages = [
{ hash: '', label: 'Home' },
{ hash: 'bevels', label: 'Bevels' },
{ hash: 'glows', label: 'Glows' },
{ hash: 'forms', label: 'Forms' },
{ hash: 'tokens', label: 'Tokens' },
];
for (const page of pages) {
const link = document.createElement('a');
link.href = `#/${page.hash}`;
link.textContent = page.label;
link.className = 'nav-link';
navLinks.appendChild(link);
}
sidebar.appendChild(navLinks);
// Initialize router
initRouter(routes, content);

View File

@@ -0,0 +1,6 @@
import '@dangerousthings/web/index.css';
import './style.css';
import { createRoot } from 'react-dom/client';
import { App } from './App';
createRoot(document.getElementById('app')!).render(<App />);

View File

@@ -0,0 +1,150 @@
import { useState } from 'react';
import { DTCard, DTStaggerContainer } from '@dangerousthings/react';
import { Section, Row, CodeLabel } from '../components/Section';
function AnimBox({ className, label }: { className: string; label: string }) {
return (
<div style={{ textAlign: 'center' }}>
<div
className={className}
style={{
background: 'var(--color-primary)',
width: 80,
height: 80,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 700,
color: 'var(--color-bg)',
fontSize: '0.7rem',
}}
>
{label}
</div>
</div>
);
}
export function AnimationsPage() {
const [entranceKey, setEntranceKey] = useState(0);
const [staggerKey, setStaggerKey] = useState(0);
const [accordionExpanded, setAccordionExpanded] = useState(false);
return (
<>
<h1 className="page-title">Animations</h1>
<p className="page-subtitle">Entrance animations, interactive effects, stagger containers, and transition utilities.</p>
<Section title="Entrance Animations" description="One-shot animations for elements entering the viewport.">
<Row key={entranceKey}>
<AnimBox className="dt-animate-scale-in" label="SCALE IN" />
<AnimBox className="dt-animate-fade-in" label="FADE IN" />
<AnimBox className="dt-animate-slide-up" label="SLIDE UP" />
</Row>
<button className="btn-secondary" style={{ marginTop: 'var(--space-4)' }} onClick={() => setEntranceKey(k => k + 1)} type="button">
REPLAY
</button>
<CodeLabel text=".dt-animate-scale-in | .dt-animate-fade-in | .dt-animate-slide-up" />
</Section>
<Section title="Interactive Animations" description="Looping animations for active/loading states.">
<Row>
<AnimBox className="dt-animate-pulse" label="PULSE" />
<AnimBox className="dt-animate-ping" label="PING" />
<AnimBox className="dt-animate-spin" label="SPIN" />
</Row>
<CodeLabel text=".dt-animate-pulse | .dt-animate-ping | .dt-animate-spin" />
</Section>
<Section title="Stagger Container" description="Automatic staggered scale-in animation for child elements. Customizable via --dt-stagger-duration and --dt-stagger-interval.">
<DTStaggerContainer
key={staggerKey}
className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-dt-3"
>
{Array.from({ length: 12 }, (_, i) => (
<DTCard key={i} title={`CARD ${i + 1}`} style={{ textAlign: 'center' }}>
<div className="card-body" style={{ fontSize: '0.75rem' }}>Item #{i + 1}</div>
</DTCard>
))}
</DTStaggerContainer>
<button
className="btn-secondary"
style={{ marginTop: 'var(--space-4)' }}
onClick={() => setStaggerKey(k => k + 1)}
type="button"
>
REPLAY STAGGER
</button>
<CodeLabel text="<DTStaggerContainer> — nth-child delays up to 24 children" />
</Section>
<Section title="Transition Utilities" description="Accordion expand, chevron rotation, and progress bar transitions.">
<div style={{ maxWidth: 400 }}>
<button
className="dt-accent-top"
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
padding: 'var(--space-3) var(--space-4)',
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
cursor: 'pointer',
color: 'var(--color-text)',
fontWeight: 600,
textTransform: 'uppercase',
}}
aria-expanded={accordionExpanded}
onClick={() => setAccordionExpanded(!accordionExpanded)}
type="button"
>
Click to expand
<span style={{
display: 'inline-block',
transition: 'transform 250ms ease-in-out',
transform: accordionExpanded ? 'rotate(180deg)' : undefined,
}}>
&#x25BC;
</span>
</button>
<div style={{
maxHeight: accordionExpanded ? 200 : 0,
overflow: 'hidden',
transition: 'max-height 250ms ease-in-out',
}}>
<div style={{
padding: 'var(--space-4)',
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
borderTop: 'none',
}}>
This content expands with a smooth max-height transition. The chevron rotates 180 degrees.
</div>
</div>
</div>
<CodeLabel text=".dt-transition-accordion | .dt-transition-chevron | .dt-transition-progress" />
</Section>
<Section title="Scrollbar Styling" description="Thin neon scrollbar scoped under [data-brand='dt'].">
<div
className="dt-scrollbar"
style={{
maxHeight: 120,
overflowY: 'auto',
background: 'var(--color-surface)',
border: '1px solid var(--color-border)',
padding: 'var(--space-3)',
}}
>
{Array.from({ length: 20 }, (_, i) => (
<div key={i} style={{ padding: '4px 0', color: 'var(--color-text)' }}>
Scrollbar line {i + 1} thin neon scrollbar styled with --color-primary
</div>
))}
</div>
<CodeLabel text=".dt-scrollbar — scrollbar-color: var(--color-primary)" />
</Section>
</>
);
}

View File

@@ -0,0 +1,103 @@
import { DTCard, DTMediaFrame } from '@dangerousthings/react';
import type { DTVariant } from '@dangerousthings/tokens';
import { Section, Row, CodeLabel } from '../components/Section';
const modes: DTVariant[] = ['normal', 'emphasis', 'warning', 'success', 'other'];
export function BevelsPage() {
return (
<>
<h1 className="page-title">Bevels</h1>
<p className="page-subtitle">Angular clip-path patterns from the DT design system. Active on the DT brand.</p>
<Section title="Card Bevels" description="Dual bottom bevels using clip-path. Outer bg = border color, ::before = surface fill.">
<DTCard title="CARD TITLE">
<div className="card-body">Dual bottom bevels bottom-right (bevel-md) and bottom-left (bevel-sm). Uses the dual-element technique with ::before pseudo-element.</div>
</DTCard>
<CodeLabel text="<DTCard title='...'> or .card" />
<DTCard>
<div className="card-body">Card without title no header bar, just the beveled container.</div>
</DTCard>
<CodeLabel text="<DTCard> (no title prop)" />
</Section>
<Section title="Button Bevels" description="Top-right corner cut. Filled buttons use direct clip-path, outline uses dual-element.">
<Row>
<button className="btn-primary" type="button">PRIMARY</button>
<button className="btn-secondary" type="button">SECONDARY</button>
<button className="btn-danger" type="button">DANGER</button>
</Row>
<CodeLabel text=".btn-primary | .btn-secondary | .btn-danger" />
</Section>
<Section title="Badge Bevels" description="Top-right bevel with dual-element technique. Color variants via CSS custom properties.">
<Row>
<span className="badge">Default</span>
<span className="badge badge-success">Success</span>
<span className="badge badge-error">Error</span>
<span className="badge badge-warning">Warning</span>
<span className="badge badge-info">Info</span>
</Row>
<CodeLabel text=".badge | .badge-success | .badge-error | .badge-warning | .badge-info" />
</Section>
<Section title="Media Frame Bevels" description="Diagonal opposite corners (top-left + bottom-right).">
<DTMediaFrame>
<div style={{ background: 'var(--color-primary)', width: '100%', aspectRatio: '16/9', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span style={{ color: 'var(--color-bg)', fontWeight: 700 }}>MEDIA FRAME</span>
</div>
</DTMediaFrame>
<CodeLabel text="<DTMediaFrame> or .dt-bevel-media" />
</Section>
<Section title="Modal Bevels" description="Dual bottom bevels at bevel-lg scale.">
<div className="dt-bevel-modal" style={{ background: 'var(--color-primary)', padding: 'var(--space-8)', textAlign: 'center' }}>
<div style={{ background: 'var(--color-surface)', padding: 'var(--space-6)' }}>
<div style={{ fontWeight: 700, textTransform: 'uppercase' }}>Modal Content</div>
<div style={{ color: 'var(--color-text-muted)', marginTop: 'var(--space-2)' }}>Large dual bottom bevels</div>
</div>
</div>
<CodeLabel text=".dt-bevel-modal" />
</Section>
<Section title="Drawer Bevels" description="Exposed-edge bevels for sliding panels.">
<Row>
<div className="dt-bevel-drawer-right" style={{ background: 'var(--color-primary)', padding: 'var(--space-6)', width: 200, height: 120, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span style={{ color: 'var(--color-bg)', fontWeight: 700 }}>RIGHT</span>
</div>
<div className="dt-bevel-drawer-left" style={{ background: 'var(--color-primary)', padding: 'var(--space-6)', width: 200, height: 120, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span style={{ color: 'var(--color-bg)', fontWeight: 700 }}>LEFT</span>
</div>
</Row>
<CodeLabel text=".dt-bevel-drawer-right | .dt-bevel-drawer-left" />
</Section>
<Section title="Small Bevel Utility" description="For compact elements — arrows, stepper buttons.">
<Row>
<div className="dt-bevel-sm" style={{ background: 'var(--color-primary)', width: 60, height: 60, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span style={{ color: 'var(--color-bg)', fontWeight: 700, fontSize: '0.75rem' }}>SM</span>
</div>
</Row>
<CodeLabel text=".dt-bevel-sm" />
</Section>
<Section title="Accent Top" description="Used on accordion headers and menu items.">
<div className="dt-accent-top" style={{ padding: 'var(--space-4)', background: 'var(--color-surface)', border: '1px solid var(--color-border)' }}>
<span style={{ fontWeight: 600, textTransform: 'uppercase' }}>Thick Top Border Accent</span>
</div>
<CodeLabel text=".dt-accent-top" />
</Section>
<Section title="Card Color Modes" description="Per-card mode coloring — see Advanced Cards page for full demos.">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3">
{modes.map(mode => (
<DTCard key={mode} variant={mode} title={mode.toUpperCase()}>
<div className="card-body" style={{ fontSize: '0.75rem' }}>mode-{mode}</div>
</DTCard>
))}
</div>
<CodeLabel text="<DTCard variant='normal'> | 'emphasis' | 'warning' | 'success' | 'other'" />
</Section>
</>
);
}

View File

@@ -0,0 +1,210 @@
import { createElement, useState, type CSSProperties } from 'react';
import {
DTCard,
DTButton,
DTStaggerContainer,
DTFeatureLegend,
} from '@dangerousthings/react';
import type { DTFeatureItem } from '@dangerousthings/react';
import type { DTVariant } from '@dangerousthings/tokens';
import { Section, Row, CodeLabel } from '../components/Section';
// Feature icons from dt-shopify-storefront
import {
MdOutlinePhonelinkRing,
MdOutlineVpnKey,
MdOutlineMobileScreenShare,
MdOutlineCreditCard,
MdOutlineCopyAll,
MdOutlineLightbulb,
MdOutlineThermostat,
MdOutlineSensors,
MdOutlineFitbit,
MdOutlineVibration,
MdOutlineExplore,
MdLightbulbOutline,
MdOutlineVisibility,
} from 'react-icons/md';
import { FaUserShield } from 'react-icons/fa';
import { LuBinary } from 'react-icons/lu';
const modes: DTVariant[] = ['normal', 'emphasis', 'warning', 'success', 'other'];
// Wrap react-icons to avoid IconType/ReactNode type mismatch
const ico = (C: any) => createElement(C, { style: { fontSize: '2rem' } });
// Full chip feature legend (9 features — from storefront UseCaseLegend)
const chipFeatures: DTFeatureItem[] = [
{ key: 'smartphone', name: 'Smartphone', icon: ico(MdOutlinePhonelinkRing), state: 'supported' },
{ key: 'access_control', name: 'Access Control', icon: ico(MdOutlineVpnKey), state: 'supported' },
{ key: 'digital_security', name: 'Digital Security', icon: ico(FaUserShield), state: 'supported' },
{ key: 'cryptography', name: 'Cryptography', icon: ico(LuBinary), state: 'unsupported' },
{ key: 'data_sharing', name: 'Data Sharing', icon: ico(MdOutlineMobileScreenShare), state: 'supported' },
{ key: 'payment', name: 'Payment', icon: ico(MdOutlineCreditCard), state: 'disabled' },
{ key: 'magic', name: 'UID Magic', icon: ico(MdOutlineCopyAll), state: 'supported' },
{ key: 'illumination', name: 'Illumination', icon: ico(MdOutlineLightbulb), state: 'unsupported' },
{ key: 'temperature', name: 'Temperature', icon: ico(MdOutlineThermostat), state: 'unsupported' },
];
// Full biomagnet feature legend (4 features — from storefront MagnetUseCaseLegend)
const magnetFeatures: DTFeatureItem[] = [
{ key: 'sensing', name: 'Sensing', icon: ico(MdOutlineSensors), state: 'supported' },
{ key: 'lifting', name: 'Lifting', icon: ico(MdOutlineFitbit), state: 'supported' },
{ key: 'haptics', name: 'Haptics', icon: ico(MdOutlineVibration), state: 'supported' },
{ key: 'polarity', name: 'Polarity Detection', icon: ico(MdOutlineExplore), state: 'unsupported' },
];
// Full aesthetic feature legend (2 features — from storefront AestheticUseCaseLegend)
const aestheticFeatures: DTFeatureItem[] = [
{ key: 'illumination', name: 'Illumination', icon: ico(MdLightbulbOutline), state: 'supported' },
{ key: 'prominence', name: 'Prominence', icon: ico(MdOutlineVisibility), state: 'supported' },
];
export function CardsAdvancedPage() {
const [staggerKey, setStaggerKey] = useState(0);
return (
<>
<h1 className="page-title">Advanced Cards</h1>
<p className="page-subtitle">Card color modes, progress bars, badge overlays, and interactive bevel buttons.</p>
<Section title="Card Color Modes" description="Per-card color via variant prop. Sets --dt-card-color, glow color, and accent.">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3">
{modes.map(mode => (
<DTCard key={mode} variant={mode} title={mode.toUpperCase()}>
<div className="card-body" style={{ fontSize: '0.8rem' }}>mode-{mode}</div>
</DTCard>
))}
</div>
<CodeLabel text="<DTCard variant='normal'> | 'emphasis' | 'warning' | 'success' | 'other'" />
</Section>
<Section title="Card Progress Bar" description="Vertical left-edge bar driven by progress prop (0-100).">
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3">
{[0, 25, 50, 75, 100].map((val, i) => (
<DTCard key={val} variant={modes[i % modes.length]} title={`${val}%`} progress={val}>
<div className="card-body" style={{ fontSize: '0.8rem' }}>Progress: {val}%</div>
</DTCard>
))}
</div>
<CodeLabel text="<DTCard progress={50} /> — height driven by --dt-card-progress" />
</Section>
<Section title="Card Badges" description="Bottom-right chip badge on product image area. Inherits card mode color.">
<div className="grid grid-cols-1 sm:grid-cols-3 gap-dt-4">
{[
{ label: 'LAB', mode: 'warning' as DTVariant, img: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=400&h=400&fit=crop' },
{ label: 'BUNDLE', mode: 'other' as DTVariant, img: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&h=400&fit=crop' },
{ label: 'NFC', mode: 'normal' as DTVariant, img: 'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=400&h=400&fit=crop' },
].map(badge => (
<DTCard key={badge.label} variant={badge.mode} title={badge.label + ' PRODUCT'}>
<div className="card-body-flush dt-badge-parent" style={{ aspectRatio: '1' }}>
<img src={badge.img} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }} />
<span className="dt-card-badge">{badge.label}</span>
</div>
</DTCard>
))}
</div>
<CodeLabel text=".dt-badge-parent > .dt-card-badge — badge positioned on image container" />
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.75rem', margin: 'var(--space-4) 0 var(--space-2)' }}>
Also works on beveled media frames:
</p>
<div className="grid grid-cols-3 gap-dt-4">
{[
{ label: 'LAB', mode: 'warning' as DTVariant, img: 'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=400&h=400&fit=crop' },
{ label: 'BUNDLE', mode: 'other' as DTVariant, img: 'https://images.unsplash.com/photo-1518770660439-4636190af475?w=400&h=400&fit=crop' },
{ label: 'NFC', mode: 'normal' as DTVariant, img: 'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=400&h=400&fit=crop' },
].map(badge => (
<div key={badge.label} className={`dt-bevel-media mode-${badge.mode}`} style={{ aspectRatio: '1', overflow: 'hidden' }}>
<img src={badge.img} alt="" style={{ width: '100%', height: '100%', objectFit: 'cover' }} />
<span className="dt-card-badge">{badge.label}</span>
</div>
))}
</div>
<CodeLabel text=".dt-bevel-media.mode-warning > .dt-card-badge" />
<p style={{ color: 'var(--color-text-muted)', fontSize: '0.75rem', margin: 'var(--space-4) 0 var(--space-2)' }}>
Missing image placeholder (no img element):
</p>
<div className="grid grid-cols-3 gap-dt-4">
{(['warning', 'other', 'normal'] as DTVariant[]).map(mode => (
<div key={mode} className={`dt-bevel-media mode-${mode}`} style={{ aspectRatio: '1' }} />
))}
</div>
<CodeLabel text=".dt-bevel-media without child img — shows 'MISSING MEDIA' placeholder" />
</Section>
<Section title="Interactive Bevel Buttons" description="Outlined rectangle by default. Bottom-right bevel appears on hover/select, fills with mode color.">
<Row>
{modes.map(mode => (
<DTButton key={mode} variant={mode}>{mode.toUpperCase()}</DTButton>
))}
</Row>
<CodeLabel text="<DTButton variant='normal'> — hover for bevel + fill" />
<Row style={{ marginTop: 'var(--space-4)' }}>
{modes.map(mode => (
<DTButton key={mode} variant={mode} selected>{mode.toUpperCase()} SEL</DTButton>
))}
</Row>
<CodeLabel text="<DTButton variant='emphasis' selected /> — persistent bevel + fill" />
</Section>
<Section title="Menu Items" description="Filter-style beveled menu items with active/selected states and level indentation.">
<div style={{ maxWidth: 300 }}>
{['All Products', 'NFC Implants', 'RFID Tags', 'Accessories', 'Lab Products'].map((item, i) => (
<button
key={item}
className={`dt-menu-item${i === 0 ? ' active' : ''}${i === 4 ? ' mode-warning' : ''}`}
style={i > 1 && i < 4 ? { '--dt-menu-level': '1' } as CSSProperties : undefined}
type="button"
>
{item}
</button>
))}
</div>
<CodeLabel text=".dt-menu-item | .dt-menu-item.active | --dt-menu-level: 1" />
</Section>
<Section title="Feature Legends" description="Product feature grids from the storefront. Icons indicate feature capabilities; color indicates state.">
<DTFeatureLegend features={chipFeatures} title="NFC CHIP FEATURES" variant="normal" columns={5} />
<div style={{ height: 'var(--space-6)' }} />
<DTFeatureLegend features={magnetFeatures} title="BIOMAGNET FEATURES" variant="emphasis" columns={4} />
<div style={{ height: 'var(--space-6)' }} />
<DTFeatureLegend features={aestheticFeatures} title="AESTHETIC FEATURES" variant="other" columns={2} />
<div style={{ height: 'var(--space-4)' }} />
<CodeLabel text="<DTFeatureLegend features={chipFeatures} title='NFC CHIP FEATURES' variant='normal' />" />
<CodeLabel text="<DTFeatureLegend features={magnetFeatures} variant='emphasis' columns={4} />" />
<CodeLabel text="<DTFeatureLegend features={aestheticFeatures} variant='other' columns={2} />" />
<div style={{ marginTop: 'var(--space-3)', display: 'flex', gap: 'var(--space-4)', fontSize: '0.75rem' }}>
<span><span style={{ color: 'var(--mode-normal)' }}></span> Supported</span>
<span><span style={{ color: 'var(--mode-emphasis)' }}></span> Disabled</span>
<span><span style={{ color: 'var(--mode-warning)' }}></span> Unsupported</span>
</div>
</Section>
<Section title="Stagger + Modes" description="Mode-colored cards in a stagger container.">
<DTStaggerContainer
key={staggerKey}
className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-dt-3"
>
{Array.from({ length: 10 }, (_, i) => (
<DTCard key={i} variant={modes[i % modes.length]} title={modes[i % modes.length].toUpperCase()} style={{ textAlign: 'center' }}>
{null}
</DTCard>
))}
</DTStaggerContainer>
<button
className="btn-secondary"
style={{ marginTop: 'var(--space-4)' }}
onClick={() => setStaggerKey(k => k + 1)}
type="button"
>
REPLAY
</button>
<CodeLabel text="<DTStaggerContainer> with <DTCard variant='...' />" />
</Section>
</>
);
}

View File

@@ -0,0 +1,122 @@
import { useState } from 'react';
import {
DTTextInput,
DTCheckbox,
DTSwitch,
DTRadioGroup,
DTProgressBar,
DTAccordion,
DTQuantityStepper,
} from '@dangerousthings/react';
import type { DTAccordionSection } from '@dangerousthings/react';
import { Section, Row, CodeLabel, DemoLabel } from '../components/Section';
export function FormsPage() {
const [checks, setChecks] = useState({ a: false, b: true, c: true, d: false });
const [switches, setSwitches] = useState({ a: false, b: true, c: true, d: false });
const [radioValue, setRadioValue] = useState('nfc');
const [stepperValue, setStepperValue] = useState(1);
const accordionSections: DTAccordionSection[] = [
{ key: 'size', title: 'Size', children: <div style={{ padding: 'var(--space-4)' }}>Small, Medium, Large options available.</div> },
{ key: 'chip', title: 'Chip Type', children: <div style={{ padding: 'var(--space-4)' }}>NTAG, DESFire, MIFARE Classic.</div> },
{ key: 'freq', title: 'Frequency', children: <div style={{ padding: 'var(--space-4)' }}>13.56 MHz (HF), 125 kHz (LF).</div> },
];
return (
<>
<h1 className="page-title">Forms</h1>
<p className="page-subtitle">DT-branded form components using @dangerousthings/react.</p>
<Section title="Text Input" description="Sharp corners + focus glow bar. 2px solid border, no border-radius.">
<DTTextInput placeholder="Normal input — click to focus" style={{ maxWidth: 400 }} />
<CodeLabel text="<DTTextInput /> — focus glow bar" />
<br />
<DTTextInput error placeholder="Error state input" style={{ maxWidth: 400 }} />
<CodeLabel text="<DTTextInput error /> — red focus bar" />
<br />
<textarea placeholder="Textarea element" style={{ maxWidth: 400, minHeight: 80 }} />
<CodeLabel text="textarea — same styling" />
</Section>
<Section title="Checkbox" description="Beveled hexagon shape via clip-path. 30% corner cuts.">
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<DTCheckbox checked={checks.a} onChange={v => setChecks(p => ({ ...p, a: v }))} label="Unchecked checkbox" />
<DTCheckbox checked={checks.b} onChange={v => setChecks(p => ({ ...p, b: v }))} label="Checked checkbox" />
<DTCheckbox checked={checks.c} onChange={() => {}} disabled label="Disabled checked" />
<DTCheckbox checked={checks.d} onChange={() => {}} disabled label="Disabled unchecked" />
</div>
<CodeLabel text="<DTCheckbox checked onChange label />" />
</Section>
<Section title="Switch / Toggle" description="Angular track + sliding square thumb. 48x26 track, 20x20 thumb.">
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
<DTSwitch checked={switches.a} onChange={v => setSwitches(p => ({ ...p, a: v }))} label="Off" />
<DTSwitch checked={switches.b} onChange={v => setSwitches(p => ({ ...p, b: v }))} label="On" />
<DTSwitch checked={switches.c} onChange={() => {}} disabled label="Disabled (on)" />
<DTSwitch checked={switches.d} onChange={() => {}} disabled label="Disabled (off)" />
</div>
<CodeLabel text="<DTSwitch checked onChange label />" />
</Section>
<Section title="Radio Button" description="Hexagonal indicator via clip-path. Flat-top hexagon shape.">
<DTRadioGroup
options={[
{ value: 'nfc', label: 'NFC (Near Field Communication)' },
{ value: 'rfid', label: 'RFID (Radio Frequency ID)' },
{ value: 'ble', label: 'BLE (Bluetooth Low Energy)' },
]}
value={radioValue}
onChange={setRadioValue}
/>
<CodeLabel text="<DTRadioGroup options value onChange />" />
</Section>
<Section title="Progress Bar" description="Angular, no border-radius. 4px default height.">
<DemoLabel text="25%" />
<DTProgressBar value={0.25} />
<DemoLabel text="50% WITH LABEL" />
<DTProgressBar value={0.5} label="50%" />
<DemoLabel text="75%" />
<DTProgressBar value={0.75} />
<DemoLabel text="100%" />
<DTProgressBar value={1.0} label="100%" />
<CodeLabel text="<DTProgressBar value={0.5} label='50%' />" />
</Section>
<Section title="Accordion" description="Click headers to expand. 5px top border, chevron rotation.">
<DTAccordion sections={accordionSections} />
<CodeLabel text="<DTAccordion sections={[{ key, title, children }]} />" />
</Section>
<Section title="Quantity Stepper" description="Beveled +/- buttons with center display.">
<DTQuantityStepper value={stepperValue} onChange={setStepperValue} min={0} max={10} />
<CodeLabel text="<DTQuantityStepper value onChange min max />" />
</Section>
<Section title="Filter Header" description="Thick top border accent. Active state switches to secondary color.">
<div style={{ maxWidth: 400 }}>
<button className="dt-filter-header" type="button">FILTER CATEGORY</button>
<button className="dt-filter-header active" type="button">ACTIVE FILTER</button>
</div>
<CodeLabel text=".dt-filter-header | .dt-filter-header.active" />
</Section>
<Section title="Menu Items" description="Beveled filter menu items with active state and level indentation.">
<div style={{ maxWidth: 300 }}>
{['All Products', 'NFC Implants', 'RFID Tags', 'Accessories', 'Lab Products'].map((name, i) => (
<button
key={name}
className={`dt-menu-item${i === 0 ? ' active' : ''}${i === 4 ? ' mode-warning' : ''}`}
style={(i === 2 || i === 3) ? { '--dt-menu-level': '1' } as React.CSSProperties : undefined}
type="button"
>
{name}
</button>
))}
</div>
<CodeLabel text=".dt-menu-item | .active | --dt-menu-level: 1" />
</Section>
</>
);
}

View File

@@ -0,0 +1,81 @@
import { DTCard } from '@dangerousthings/react';
import { Section, Row, CodeLabel } from '../components/Section';
export function GlowsPage() {
return (
<>
<h1 className="page-title">Glows</h1>
<p className="page-subtitle">Neon drop-shadow and text-shadow effects. Active on the DT brand.</p>
<Section title="Button Glows" description="filter: drop-shadow() follows clip-path shape. Hover for enhanced glow.">
<Row>
<button className="btn-primary" type="button">PRIMARY GLOW</button>
<button className="btn-danger" type="button">DANGER GLOW</button>
</Row>
<CodeLabel text={'.btn-primary / .btn-danger — automatic on [data-brand="dt"]'} />
<br />
<Row>
<button className="btn-secondary" type="button">SECONDARY (HOVER)</button>
</Row>
<CodeLabel text=".btn-secondary — glow on hover" />
</Section>
<Section title="Link Glow" description="text-shadow on hover. Respects --dt-glow-color when set by a parent mode.">
<Row>
<a href="#" style={{ color: 'var(--color-primary)', fontWeight: 600, fontSize: '1.125rem' }}>
Hover this link for text glow
</a>
</Row>
<CodeLabel text="a:hover — uses var(--dt-glow-color, var(--color-primary))" />
</Section>
<Section title="Mode-Aware Link Glow" description="Links inside mode containers glow with that mode's color.">
<Row>
{(['normal', 'emphasis', 'warning', 'success', 'other'] as const).map(mode => (
<div key={mode} className={`mode-${mode}`} style={{ padding: 'var(--space-3)' }}>
<a href="#" style={{ fontWeight: 600, fontSize: '1rem' }}>{mode.toUpperCase()}</a>
</div>
))}
</Row>
<CodeLabel text=".mode-emphasis a:hover — glows yellow via --dt-glow-color" />
</Section>
<Section title="Terminal Inset Glow" description="Inset + outer box-shadow. No clip-path so box-shadow works directly.">
<div className="terminal dt-accent-top">
<code>{'$ dt-web-theme --install\n> Installing design tokens...\n> Applying cyberpunk aesthetic...\n> Done. Welcome to the future.'}</code>
</div>
<CodeLabel text={'.terminal — automatic on [data-brand="dt"]'} />
</Section>
<Section title="Card Hover Glow" description="filter: drop-shadow() respects the beveled clip-path.">
<DTCard title="HOVER ME" style={{ cursor: 'pointer' }}>
<div className="card-body">Cards get a drop-shadow glow on hover.</div>
</DTCard>
<CodeLabel text={'.card:hover — automatic on [data-brand="dt"]'} />
</Section>
<Section title="Input Focus Glow" description="box-shadow: 0 4px 0 1px — bright bar beneath the input.">
<input
type="text"
className="input"
placeholder="Click to focus — see the glow bar"
style={{ maxWidth: 400 }}
/>
<CodeLabel text={'.input:focus — automatic on [data-brand="dt"]'} />
</Section>
<Section title="Glow Utilities" description="Generic utility classes for applying glow effects to any element.">
<Row>
<div className="dt-glow" style={{ background: 'var(--color-primary)', padding: 'var(--space-4) var(--space-6)', display: 'inline-block', fontWeight: 700, color: 'var(--color-bg)' }}>.dt-glow</div>
<div className="dt-glow-strong" style={{ background: 'var(--color-primary)', padding: 'var(--space-4) var(--space-6)', display: 'inline-block', fontWeight: 700, color: 'var(--color-bg)' }}>.dt-glow-strong</div>
</Row>
<CodeLabel text=".dt-glow | .dt-glow-strong" />
<Row>
<div className="dt-glow-inset" style={{ padding: 'var(--space-4) var(--space-6)', display: 'inline-block', fontWeight: 700, border: '1px solid var(--color-border)' }}>.dt-glow-inset</div>
<div className="dt-text-glow" style={{ fontSize: '1.25rem', fontWeight: 700, color: 'var(--color-primary)', display: 'inline-block' }}>.dt-text-glow</div>
</Row>
<CodeLabel text=".dt-glow-inset | .dt-text-glow" />
</Section>
</>
);
}

View File

@@ -0,0 +1,103 @@
import { DTCard, DTStaggerContainer } from '@dangerousthings/react';
import type { DTVariant } from '@dangerousthings/tokens';
import { Section, CodeLabel } from '../components/Section';
const categories: { hash: string; title: string; desc: string; mode: DTVariant; count: number }[] = [
{ hash: 'bevels', title: 'Bevels', desc: 'Angular clip-path patterns — cards, buttons, badges, media frames, modals, drawers', mode: 'normal', count: 8 },
{ hash: 'glows', title: 'Glows', desc: 'Neon drop-shadow and text-shadow effects for the DT brand', mode: 'emphasis', count: 6 },
{ hash: 'forms', title: 'Forms', desc: 'Text inputs, checkboxes, switches, radios, progress bars, accordions, steppers', mode: 'success', count: 7 },
{ hash: 'animations', title: 'Animations', desc: 'Entrance animations, stagger containers, transition utilities, scrollbar styling', mode: 'other', count: 5 },
{ hash: 'cards-advanced', title: 'Advanced Cards', desc: 'Card color modes, progress bars, badge overlays, interactive bevel buttons, feature legend', mode: 'warning', count: 6 },
{ hash: 'tokens', title: 'Tokens', desc: 'Color palette, typography, spacing, and shape values for the active brand', mode: 'normal', count: 3 },
];
function HexDecor({ color, size = 14, opacity = 0.5 }: { color: string; size?: number; opacity?: number }) {
return (
<svg width={size * 2} height={size * 2} viewBox="0 0 28 28" style={{ opacity }}>
<polygon
points="14,1 25.5,7.5 25.5,20.5 14,27 2.5,20.5 2.5,7.5"
fill={color}
/>
</svg>
);
}
export function HomePage() {
return (
<>
<div style={{ textAlign: 'center', marginBottom: 'var(--space-8)' }}>
<h1 style={{
color: 'var(--color-primary)',
fontSize: '2.5rem',
fontWeight: 900,
letterSpacing: '0.15em',
marginBottom: '0.25rem',
textTransform: 'uppercase',
}}>
DANGEROUS THINGS
</h1>
<p style={{
color: 'var(--mode-emphasis, var(--color-secondary))',
fontSize: '1.25rem',
fontWeight: 700,
letterSpacing: '0.2em',
textTransform: 'uppercase',
marginTop: 0,
}}>
DESIGN SYSTEM
</p>
<p style={{
color: 'var(--color-text-muted)',
fontSize: '0.8rem',
marginTop: 'var(--space-2)',
}}>
React component showcase switch brands and modes in the sidebar
</p>
<div style={{
display: 'flex',
justifyContent: 'center',
gap: 12,
marginTop: 'var(--space-6)',
}}>
<HexDecor color="var(--mode-normal, var(--color-primary))" opacity={0.4} />
<HexDecor color="var(--mode-emphasis, var(--color-secondary))" opacity={0.6} />
<HexDecor color="var(--mode-warning, var(--color-error))" opacity={0.4} />
<HexDecor color="var(--mode-success, var(--color-accent))" opacity={0.6} />
<HexDecor color="var(--mode-other, var(--color-other))" opacity={0.4} />
</div>
</div>
<p style={{
color: 'var(--color-text-muted)',
fontSize: '0.7rem',
letterSpacing: '0.15em',
textTransform: 'uppercase',
marginBottom: 'var(--space-4)',
}}>
COMPONENT CATALOG
</p>
<DTStaggerContainer className="grid grid-cols-1 md:grid-cols-2 gap-dt-4">
{categories.map(cat => (
<a key={cat.hash} href={`#/${cat.hash}`} style={{ textDecoration: 'none', color: 'inherit' }}>
<DTCard variant={cat.mode} title={cat.title.toUpperCase()}>
<div className="card-body">{cat.desc}</div>
{cat.count > 0 && (
<div style={{ color: 'var(--color-text-muted)', fontSize: '0.7rem', marginTop: 'var(--space-2)' }}>
{cat.count} components
</div>
)}
</DTCard>
</a>
))}
</DTStaggerContainer>
<Section title="Quick Start" description="Import the React components and CSS to get started.">
<div className="terminal dt-accent-top">
<code>{`import { DTCard, DTButton } from '@dangerousthings/react';\nimport '@dangerousthings/web/index.css';`}</code>
</div>
</Section>
</>
);
}

View File

@@ -0,0 +1,99 @@
import { brands } from '@dangerousthings/tokens';
import type { ThemeBrand } from '@dangerousthings/tokens';
import { Section, CodeLabel } from '../components/Section';
interface TokensPageProps {
brand: ThemeBrand;
}
export function TokensPage({ brand: brandId }: TokensPageProps) {
const brand = brands[brandId];
if (!brand) return null;
const darkColors = brand.dark;
const darkEntries = Object.entries(darkColors) as [string, string][];
const lightColors = brand.light;
const lightEntries = Object.entries(lightColors) as [string, string][];
return (
<>
<h1 className="page-title">Tokens</h1>
<p className="page-subtitle">Design token values for the active brand. Switch brands in the sidebar.</p>
<Section title={`Color Palette — ${brand.name} (Dark Mode)`} description={brand.description}>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: 'var(--space-3)' }}>
{darkEntries.map(([name, hex]) => (
<div key={name} style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-3)' }}>
<div style={{ width: 32, height: 32, background: hex, border: '1px solid rgba(255,255,255,0.15)', flexShrink: 0 }} />
<div>
<div style={{ fontWeight: 600, fontSize: '0.8125rem' }}>{name}</div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.6875rem', opacity: 0.5 }}>{hex}</div>
</div>
</div>
))}
</div>
</Section>
<Section title={`Color Palette — ${brand.name} (Light Mode)`} description="Light mode color tokens.">
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: 'var(--space-3)' }}>
{lightEntries.map(([name, hex]) => (
<div key={name} style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-3)' }}>
<div style={{ width: 32, height: 32, background: hex, border: '1px solid rgba(255,255,255,0.15)', flexShrink: 0 }} />
<div>
<div style={{ fontWeight: 600, fontSize: '0.8125rem' }}>{name}</div>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.6875rem', opacity: 0.5 }}>{hex}</div>
</div>
</div>
))}
</div>
</Section>
<Section title="Typography" description="Font family tokens for headings, body text, and monospace.">
<div className="terminal" style={{ marginBottom: 'var(--space-4)' }}>
<code>{`heading: ${brand.typography.heading}\nbody: ${brand.typography.body}\nmono: ${brand.typography.mono}`}</code>
</div>
</Section>
<Section title="Typography Scale" description="Live rendering with the current brand's font families.">
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-3)' }}>
<h1 style={{ fontFamily: 'var(--font-heading)', fontSize: '2rem', fontWeight: 900 }}>Heading 1</h1>
<h2 style={{ fontFamily: 'var(--font-heading)', fontSize: '1.5rem', fontWeight: 700 }}>Heading 2</h2>
<h3 style={{ fontFamily: 'var(--font-heading)', fontSize: '1.25rem', fontWeight: 600 }}>Heading 3</h3>
<p style={{ fontFamily: 'var(--font-body)', fontSize: '1rem' }}>Body text the quick brown fox jumps over the lazy dog</p>
<code style={{ fontFamily: 'var(--font-mono)', fontSize: '0.875rem' }}>{'const monospace = "code";'}</code>
</div>
</Section>
<Section title="Shape Tokens" description="Bevel and radius values determine the visual language — angular (DT) vs rounded (Classic).">
<div className="terminal" style={{ marginBottom: 'var(--space-4)' }}>
<code>{`bevelSm: ${brand.shape.bevelSm}\nbevelMd: ${brand.shape.bevelMd}\nbevelLg: ${brand.shape.bevelLg}\nradiusSm: ${brand.shape.radiusSm}\nradius: ${brand.shape.radius}\nradiusLg: ${brand.shape.radiusLg}`}</code>
</div>
</Section>
<Section title="Spacing Scale" description="Shared spacing tokens across all brands.">
<div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--space-2)' }}>
{[
{ name: '--space-1', value: '0.25rem (4px)', width: '1.5rem' },
{ name: '--space-2', value: '0.5rem (8px)', width: '3rem' },
{ name: '--space-3', value: '0.75rem (12px)', width: '4.5rem' },
{ name: '--space-4', value: '1rem (16px)', width: '6rem' },
{ name: '--space-6', value: '1.5rem (24px)', width: '9rem' },
{ name: '--space-8', value: '2rem (32px)', width: '12rem' },
].map(sp => (
<div key={sp.name} style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-3)' }}>
<div style={{ width: sp.width, height: 12, background: 'var(--color-primary)', opacity: 0.6 }} />
<span style={{ fontFamily: 'var(--font-mono)', fontSize: '0.75rem', opacity: 0.6 }}>{sp.name}: {sp.value}</span>
</div>
))}
</div>
</Section>
<Section title="CSS Custom Properties" description="These are generated from TypeScript tokens and applied per-brand via data-brand attribute.">
<div className="terminal">
<code>{`/* Generated CSS custom properties */\n--color-bg: ${darkColors.bg};\n--color-primary: ${darkColors.primary};\n--color-secondary: ${darkColors.secondary};\n--color-error: ${darkColors.error};\n--color-success: ${darkColors.success};\n--bevel-sm: ${brand.shape.bevelSm};\n--bevel-md: ${brand.shape.bevelMd};\n--font-heading: ${brand.typography.heading};`}</code>
</div>
<CodeLabel text="See packages/tokens/src/scripts/generate-css.ts" />
</Section>
</>
);
}

View File

@@ -1,126 +0,0 @@
import { el, section, row, label, codeLabel } from '../utils/dom';
export function renderBevelsPage(container: HTMLElement) {
container.appendChild(el('h1', { className: 'page-title' }, 'Bevels'));
container.appendChild(el('p', { className: 'page-subtitle' }, 'Angular clip-path patterns from the DT design system. Active on the DT brand.'));
// Card Bevels
const card1 = el('div', { className: 'card', style: 'padding: var(--space-6);' });
card1.appendChild(el('div', { className: 'card-title' }, 'CARD TITLE'));
card1.appendChild(el('div', { className: 'card-body' }, 'Dual bottom bevels — bottom-right (bevel-md) and bottom-left (bevel-sm). Uses the dual-element technique with ::before pseudo-element.'));
const card2 = el('div', { className: 'card', style: 'padding: var(--space-6);' });
card2.appendChild(el('div', { className: 'card-body' }, 'Card without title — no header bar, just the beveled container.'));
container.appendChild(
section('Card Bevels', 'Dual bottom bevels using clip-path. Outer bg = border color, ::before = surface fill.',
card1,
codeLabel('.card or .dt-bevel-card'),
card2,
codeLabel('.card (no .card-title child)'),
),
);
// Button Bevels
container.appendChild(
section('Button Bevels', 'Top-right corner cut. Filled buttons use direct clip-path, outline uses dual-element.',
row(
el('button', { className: 'btn-primary' }, 'PRIMARY'),
el('button', { className: 'btn-secondary' }, 'SECONDARY'),
el('button', { className: 'btn-danger' }, 'DANGER'),
),
codeLabel('.btn-primary | .btn-secondary | .btn-danger'),
),
);
// Badge Bevels
container.appendChild(
section('Badge Bevels', 'Top-right bevel with dual-element technique. Color variants via CSS custom properties.',
row(
el('span', { className: 'badge' }, 'Default'),
el('span', { className: 'badge badge-success' }, 'Success'),
el('span', { className: 'badge badge-error' }, 'Error'),
el('span', { className: 'badge badge-warning' }, 'Warning'),
el('span', { className: 'badge badge-info' }, 'Info'),
),
codeLabel('.badge | .badge-success | .badge-error | .badge-warning | .badge-info'),
),
);
// Media Frame
const mediaFrame = el('div', {
className: 'dt-bevel-media',
style: 'background: var(--color-primary); width: 100%; aspect-ratio: 16/9; display: flex; align-items: center; justify-content: center;',
});
mediaFrame.appendChild(el('span', { style: 'color: var(--color-bg); font-weight: 700;' }, 'MEDIA FRAME'));
container.appendChild(
section('Media Frame Bevels', 'Diagonal opposite corners (top-left + bottom-right).',
mediaFrame,
codeLabel('.dt-bevel-media'),
),
);
// Modal Bevel
const modalDemo = el('div', {
className: 'dt-bevel-modal',
style: 'background: var(--color-primary); padding: var(--space-8); text-align: center;',
});
const modalInner = el('div', { style: 'background: var(--color-surface); padding: var(--space-6);' });
modalInner.appendChild(el('div', { style: 'font-weight: 700; text-transform: uppercase;' }, 'Modal Content'));
modalInner.appendChild(el('div', { style: 'color: var(--color-text-muted); margin-top: var(--space-2);' }, 'Large dual bottom bevels'));
modalDemo.appendChild(modalInner);
container.appendChild(
section('Modal Bevels', 'Dual bottom bevels at bevel-lg scale.',
modalDemo,
codeLabel('.dt-bevel-modal'),
),
);
// Drawer Bevels
const drawerRight = el('div', {
className: 'dt-bevel-drawer-right',
style: 'background: var(--color-primary); padding: var(--space-6); width: 200px; height: 120px; display: flex; align-items: center; justify-content: center;',
});
drawerRight.appendChild(el('span', { style: 'color: var(--color-bg); font-weight: 700;' }, 'RIGHT'));
const drawerLeft = el('div', {
className: 'dt-bevel-drawer-left',
style: 'background: var(--color-primary); padding: var(--space-6); width: 200px; height: 120px; display: flex; align-items: center; justify-content: center;',
});
drawerLeft.appendChild(el('span', { style: 'color: var(--color-bg); font-weight: 700;' }, 'LEFT'));
container.appendChild(
section('Drawer Bevels', 'Exposed-edge bevels for sliding panels.',
row(drawerRight, drawerLeft),
codeLabel('.dt-bevel-drawer-right | .dt-bevel-drawer-left'),
),
);
// Small Bevel Utility
const smallBevel = el('div', {
className: 'dt-bevel-sm',
style: 'background: var(--color-primary); width: 60px; height: 60px; display: flex; align-items: center; justify-content: center;',
});
smallBevel.appendChild(el('span', { style: 'color: var(--color-bg); font-weight: 700; font-size: 0.75rem;' }, 'SM'));
container.appendChild(
section('Small Bevel Utility', 'For compact elements — arrows, stepper buttons.',
row(smallBevel),
codeLabel('.dt-bevel-sm'),
),
);
// Accent Top
const accentDemo = el('div', {
className: 'dt-accent-top',
style: 'padding: var(--space-4); background: var(--color-surface); border: 1px solid var(--color-border);',
});
accentDemo.appendChild(el('span', { style: 'font-weight: 600; text-transform: uppercase;' }, 'Thick Top Border Accent'));
container.appendChild(
section('Accent Top', 'Used on accordion headers and menu items.',
accentDemo,
codeLabel('.dt-accent-top'),
),
);
}

View File

@@ -1,199 +0,0 @@
import { el, section, row, label, codeLabel } from '../utils/dom';
export function renderFormsPage(container: HTMLElement) {
container.appendChild(el('h1', { className: 'page-title' }, 'Forms'));
container.appendChild(el('p', { className: 'page-subtitle' }, 'DT-branded form components. Angular styling with focus effects.'));
// Text Inputs
const normalInput = el('input', { type: 'text', placeholder: 'Normal input — click to focus', style: 'max-width: 400px;' }) as HTMLInputElement;
const errorInput = el('input', { type: 'text', className: 'error', placeholder: 'Error state input', style: 'max-width: 400px;' }) as HTMLInputElement;
const textarea = el('textarea', { placeholder: 'Textarea element', style: 'max-width: 400px; min-height: 80px;' }) as HTMLTextAreaElement;
container.appendChild(
section('Text Input', 'Sharp corners + focus glow bar. 2px solid border, no border-radius.',
normalInput,
codeLabel('input[type="text"] — focus glow bar on [data-brand="dt"]'),
el('br'),
errorInput,
codeLabel('input.error — red focus bar on [data-brand="dt"]'),
el('br'),
textarea,
codeLabel('textarea — same styling'),
),
);
// Checkboxes
function checkbox(labelText: string, checked: boolean, disabled = false): HTMLElement {
const wrapper = el('label', { style: 'display: flex; align-items: center; gap: 12px; cursor: pointer; padding: 4px 0;' });
const input = document.createElement('input');
input.type = 'checkbox';
if (checked) input.checked = true;
if (disabled) input.disabled = true;
wrapper.appendChild(input);
wrapper.appendChild(el('span', {}, labelText));
return wrapper;
}
container.appendChild(
section('Checkbox', 'Beveled hexagon shape via clip-path. 30% corner cuts.',
checkbox('Unchecked checkbox', false),
checkbox('Checked checkbox', true),
checkbox('Disabled checked', true, true),
checkbox('Disabled unchecked', false, true),
codeLabel('input[type="checkbox"] — hex bevel on [data-brand="dt"]'),
),
);
// Switch / Toggle
function switchEl(labelText: string, checked: boolean, disabled = false): HTMLElement {
const wrapper = el('div', { style: 'display: flex; align-items: center; gap: 12px; margin-bottom: 8px;' });
const sw = el('label', { className: 'dt-switch' });
const input = document.createElement('input');
input.type = 'checkbox';
if (checked) input.checked = true;
if (disabled) input.disabled = true;
sw.appendChild(input);
sw.appendChild(el('div', { className: 'dt-switch-track' }));
sw.appendChild(el('div', { className: 'dt-switch-thumb' }));
wrapper.appendChild(sw);
wrapper.appendChild(el('span', {}, labelText));
return wrapper;
}
container.appendChild(
section('Switch / Toggle', 'Angular track + sliding square thumb. 48x26 track, 20x20 thumb.',
switchEl('Off', false),
switchEl('On', true),
switchEl('Disabled (on)', true, true),
switchEl('Disabled (off)', false, true),
codeLabel('.dt-switch > input + .dt-switch-track + .dt-switch-thumb'),
),
);
// Radio Buttons
function radioGroup(name: string, options: { value: string; label: string; checked?: boolean; disabled?: boolean }[]): HTMLElement {
const group = el('div', { style: 'display: flex; flex-direction: column; gap: 4px;' });
for (const opt of options) {
const wrapper = el('label', { className: `dt-radio-option${opt.checked ? ' selected' : ''}`, style: 'cursor: pointer;' });
const input = document.createElement('input');
input.type = 'radio';
input.name = name;
input.value = opt.value;
if (opt.checked) input.checked = true;
if (opt.disabled) input.disabled = true;
input.addEventListener('change', () => {
group.querySelectorAll('.dt-radio-option').forEach((o) => o.classList.remove('selected'));
if (input.checked) wrapper.classList.add('selected');
});
wrapper.appendChild(input);
wrapper.appendChild(el('span', {}, opt.label));
group.appendChild(wrapper);
}
return group;
}
container.appendChild(
section('Radio Button', 'Hexagonal indicator via clip-path. Flat-top hexagon shape.',
radioGroup('demo-radio', [
{ value: 'nfc', label: 'NFC (Near Field Communication)', checked: true },
{ value: 'rfid', label: 'RFID (Radio Frequency ID)' },
{ value: 'ble', label: 'BLE (Bluetooth Low Energy)' },
]),
codeLabel('input[type="radio"] — hex indicator on [data-brand="dt"]'),
),
);
// Progress Bar
function progressBar(value: number, showLabel = false): HTMLElement {
const wrapper = el('div');
const bar = el('div', { className: 'dt-progress' });
const fill = el('div', { className: 'dt-progress-fill', style: `width: ${value * 100}%;` });
bar.appendChild(fill);
wrapper.appendChild(bar);
if (showLabel) {
wrapper.appendChild(el('div', { className: 'dt-progress-label' }, `${Math.round(value * 100)}%`));
}
return wrapper;
}
container.appendChild(
section('Progress Bar', 'Angular, no border-radius. 4px default height.',
label('25%'),
progressBar(0.25),
label('50% WITH LABEL'),
progressBar(0.5, true),
label('75%'),
progressBar(0.75),
label('100%'),
progressBar(1.0, true),
codeLabel('.dt-progress > .dt-progress-fill'),
),
);
// Accordion
function accordion(items: { title: string; content: string }[]): HTMLElement {
const acc = el('div', { className: 'dt-accordion' });
for (const item of items) {
const header = el('button', { className: 'dt-accordion-header', 'aria-expanded': 'false' });
header.appendChild(document.createTextNode(item.title));
header.appendChild(el('span', { className: 'dt-accordion-chevron' }));
const content = el('div', { className: 'dt-accordion-content', 'data-open': 'false' });
content.appendChild(el('div', { style: 'padding: var(--space-4);' }, item.content));
header.addEventListener('click', () => {
const expanded = header.getAttribute('aria-expanded') === 'true';
header.setAttribute('aria-expanded', String(!expanded));
content.setAttribute('data-open', String(!expanded));
});
acc.appendChild(header);
acc.appendChild(content);
}
return acc;
}
container.appendChild(
section('Accordion', 'Click headers to expand. 5px top border, chevron rotation.',
accordion([
{ title: 'Size', content: 'Small, Medium, Large options available.' },
{ title: 'Chip Type', content: 'NTAG, DESFire, MIFARE Classic.' },
{ title: 'Frequency', content: '13.56 MHz (HF), 125 kHz (LF).' },
]),
codeLabel('.dt-accordion > .dt-accordion-header + .dt-accordion-content'),
),
);
// Quantity Stepper
function stepper(initialValue: number, min: number, max: number): HTMLElement {
let value = initialValue;
const wrapper = el('div', { className: 'dt-stepper' });
const minusBtn = el('button', { className: 'dt-stepper-btn' }, '\u2212') as HTMLButtonElement;
const valueEl = el('span', { className: 'dt-stepper-value' }, String(value));
const plusBtn = el('button', { className: 'dt-stepper-btn' }, '+') as HTMLButtonElement;
function update() {
valueEl.textContent = String(value);
minusBtn.disabled = value <= min;
plusBtn.disabled = value >= max;
}
minusBtn.addEventListener('click', () => { value = Math.max(min, value - 1); update(); });
plusBtn.addEventListener('click', () => { value = Math.min(max, value + 1); update(); });
wrapper.appendChild(minusBtn);
wrapper.appendChild(valueEl);
wrapper.appendChild(plusBtn);
update();
return wrapper;
}
container.appendChild(
section('Quantity Stepper', 'Beveled +/- buttons with center display.',
stepper(1, 0, 10),
codeLabel('.dt-stepper > .dt-stepper-btn + .dt-stepper-value + .dt-stepper-btn'),
),
);
}

View File

@@ -1,97 +0,0 @@
import { el, section, row, codeLabel } from '../utils/dom';
export function renderGlowsPage(container: HTMLElement) {
container.appendChild(el('h1', { className: 'page-title' }, 'Glows'));
container.appendChild(el('p', { className: 'page-subtitle' }, 'Neon drop-shadow and text-shadow effects. Active on the DT brand.'));
// Button Glows
container.appendChild(
section('Button Glows', 'filter: drop-shadow() follows clip-path shape. Hover for enhanced glow.',
row(
el('button', { className: 'btn-primary' }, 'PRIMARY GLOW'),
el('button', { className: 'btn-danger' }, 'DANGER GLOW'),
),
codeLabel('.btn-primary / .btn-danger — automatic on [data-brand="dt"]'),
el('br'),
row(
el('button', { className: 'btn-secondary' }, 'SECONDARY (HOVER)'),
),
codeLabel('.btn-secondary — glow on hover'),
),
);
// Link Glow
const link = el('a', { href: '#', style: 'color: var(--color-primary); font-weight: 600; font-size: 1.125rem;' }, 'Hover this link for text glow');
container.appendChild(
section('Link Glow', 'text-shadow on hover for anchor elements.',
row(link),
codeLabel('a:hover — automatic on [data-brand="dt"]'),
),
);
// Terminal Inset
const terminal = el('div', { className: 'terminal dt-accent-top' });
terminal.appendChild(el('code', {}, '$ dt-web-theme --install\n> Installing design tokens...\n> Applying cyberpunk aesthetic...\n> Done. Welcome to the future.'));
container.appendChild(
section('Terminal Inset Glow', 'Inset + outer box-shadow. No clip-path so box-shadow works directly.',
terminal,
codeLabel('.terminal — automatic on [data-brand="dt"]'),
),
);
// Card Hover Glow
const card = el('div', { className: 'card', style: 'padding: var(--space-6); cursor: pointer;' });
card.appendChild(el('div', { className: 'card-title' }, 'HOVER ME'));
card.appendChild(el('div', { className: 'card-body' }, 'Cards get a drop-shadow glow on hover.'));
container.appendChild(
section('Card Hover Glow', 'filter: drop-shadow() respects the beveled clip-path.',
card,
codeLabel('.card:hover — automatic on [data-brand="dt"]'),
),
);
// Input Focus Glow
const input = el('input', {
type: 'text',
className: 'input',
placeholder: 'Click to focus — see the glow bar',
style: 'max-width: 400px;',
}) as HTMLInputElement;
container.appendChild(
section('Input Focus Glow', 'box-shadow: 0 4px 0 1px — bright bar beneath the input.',
input,
codeLabel('.input:focus — automatic on [data-brand="dt"]'),
),
);
// Generic Glow Utilities
const glowBox = el('div', {
className: 'dt-glow',
style: 'background: var(--color-primary); padding: var(--space-4) var(--space-6); display: inline-block; font-weight: 700; color: var(--color-bg);',
}, '.dt-glow');
const glowStrong = el('div', {
className: 'dt-glow-strong',
style: 'background: var(--color-primary); padding: var(--space-4) var(--space-6); display: inline-block; font-weight: 700; color: var(--color-bg);',
}, '.dt-glow-strong');
const glowInset = el('div', {
className: 'dt-glow-inset',
style: 'padding: var(--space-4) var(--space-6); display: inline-block; font-weight: 700; border: 1px solid var(--color-border);',
}, '.dt-glow-inset');
const textGlow = el('div', {
className: 'dt-text-glow',
style: 'font-size: 1.25rem; font-weight: 700; color: var(--color-primary); display: inline-block;',
}, '.dt-text-glow');
container.appendChild(
section('Glow Utilities', 'Generic utility classes for applying glow effects to any element.',
row(glowBox, glowStrong),
codeLabel('.dt-glow | .dt-glow-strong'),
row(glowInset, textGlow),
codeLabel('.dt-glow-inset | .dt-text-glow'),
),
);
}

View File

@@ -1,33 +0,0 @@
import { el, section } from '../utils/dom';
export function renderHomePage(container: HTMLElement) {
container.appendChild(el('h1', { className: 'page-title' }, 'DT Design System'));
container.appendChild(el('p', { className: 'page-subtitle' }, 'Web CSS component showcase — switch brands and modes in the sidebar.'));
const categories = [
{ hash: 'bevels', title: 'Bevels', desc: 'Angular clip-path patterns — cards, buttons, badges, media frames, modals, drawers' },
{ hash: 'glows', title: 'Glows', desc: 'Neon drop-shadow and text-shadow effects for the DT brand' },
{ hash: 'forms', title: 'Forms', desc: 'Text inputs, checkboxes, switches, radios, progress bars, accordions, steppers' },
{ hash: 'tokens', title: 'Tokens', desc: 'Color palette, typography, spacing, and shape values for the active brand' },
];
for (const cat of categories) {
const link = el('a', { href: `#/${cat.hash}`, className: 'home-card-link' });
const card = el('div', { className: 'card' });
card.appendChild(el('div', { className: 'card-title' }, cat.title.toUpperCase()));
card.appendChild(el('div', { className: 'card-body' }, cat.desc));
link.appendChild(card);
container.appendChild(link);
}
// Quick overview terminal
container.appendChild(
section('Quick Start', 'Include the CSS and set data attributes on your HTML element.',
el('div', { className: 'terminal dt-accent-top' },
el('code', {},
'<link rel="stylesheet" href="@dangerousthings/web">\n<html data-brand="dt" data-theme="dark">'
),
),
),
);
}

View File

@@ -1,141 +0,0 @@
import { brands } from '@dangerousthings/tokens';
import type { ThemeBrand } from '@dangerousthings/tokens';
import { el, section, codeLabel } from '../utils/dom';
import { getCurrentBrand } from '../brand-switcher';
export function renderTokensPage(container: HTMLElement) {
container.appendChild(el('h1', { className: 'page-title' }, 'Tokens'));
container.appendChild(el('p', { className: 'page-subtitle' }, 'Design token values for the active brand. Switch brands in the sidebar.'));
const brandId = getCurrentBrand();
const brand = brands[brandId];
if (!brand) return;
// Color Palette — Dark Mode
const darkColors = brand.dark;
const colorEntries: [string, string][] = Object.entries(darkColors);
const colorsGrid = el('div', { style: 'display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--space-3);' });
for (const [name, hex] of colorEntries) {
const swatch = el('div', { style: 'display: flex; align-items: center; gap: var(--space-3);' });
const colorBox = el('div', { style: `width: 32px; height: 32px; background: ${hex}; border: 1px solid rgba(255,255,255,0.15); flex-shrink: 0;` });
const info = el('div');
info.appendChild(el('div', { style: 'font-weight: 600; font-size: 0.8125rem;' }, name));
info.appendChild(el('div', { style: 'font-family: var(--font-mono); font-size: 0.6875rem; opacity: 0.5;' }, hex));
swatch.appendChild(colorBox);
swatch.appendChild(info);
colorsGrid.appendChild(swatch);
}
container.appendChild(
section(`Color Palette — ${brand.name} (Dark Mode)`, brand.description,
colorsGrid,
),
);
// Light Mode
const lightColors = brand.light;
const lightEntries: [string, string][] = Object.entries(lightColors);
const lightGrid = el('div', { style: 'display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--space-3);' });
for (const [name, hex] of lightEntries) {
const swatch = el('div', { style: 'display: flex; align-items: center; gap: var(--space-3);' });
const colorBox = el('div', { style: `width: 32px; height: 32px; background: ${hex}; border: 1px solid rgba(255,255,255,0.15); flex-shrink: 0;` });
const info = el('div');
info.appendChild(el('div', { style: 'font-weight: 600; font-size: 0.8125rem;' }, name));
info.appendChild(el('div', { style: 'font-family: var(--font-mono); font-size: 0.6875rem; opacity: 0.5;' }, hex));
swatch.appendChild(colorBox);
swatch.appendChild(info);
lightGrid.appendChild(swatch);
}
container.appendChild(
section(`Color Palette — ${brand.name} (Light Mode)`, 'Light mode color tokens.',
lightGrid,
),
);
// Typography
const typo = el('div', { className: 'terminal', style: 'margin-bottom: var(--space-4);' });
typo.appendChild(el('code', {},
`heading: ${brand.typography.heading}\nbody: ${brand.typography.body}\nmono: ${brand.typography.mono}`
));
container.appendChild(
section('Typography', 'Font family tokens for headings, body text, and monospace.',
typo,
),
);
// Typography Scale Demo
const scaleItems = [
{ tag: 'h1', label: 'Heading 1', style: 'font-family: var(--font-heading); font-size: 2rem; font-weight: 900;' },
{ tag: 'h2', label: 'Heading 2', style: 'font-family: var(--font-heading); font-size: 1.5rem; font-weight: 700;' },
{ tag: 'h3', label: 'Heading 3', style: 'font-family: var(--font-heading); font-size: 1.25rem; font-weight: 600;' },
{ tag: 'p', label: 'Body text — the quick brown fox jumps over the lazy dog', style: 'font-family: var(--font-body); font-size: 1rem;' },
{ tag: 'code', label: 'const monospace = "code";', style: 'font-family: var(--font-mono); font-size: 0.875rem;' },
];
const scaleDemo = el('div', { style: 'display: flex; flex-direction: column; gap: var(--space-3);' });
for (const item of scaleItems) {
scaleDemo.appendChild(el(item.tag, { style: item.style }, item.label));
}
container.appendChild(
section('Typography Scale', 'Live rendering with the current brand\'s font families.',
scaleDemo,
),
);
// Shape Tokens
const shape = el('div', { className: 'terminal', style: 'margin-bottom: var(--space-4);' });
shape.appendChild(el('code', {},
`bevelSm: ${brand.shape.bevelSm}\nbevelMd: ${brand.shape.bevelMd}\nbevelLg: ${brand.shape.bevelLg}\nradiusSm: ${brand.shape.radiusSm}\nradius: ${brand.shape.radius}\nradiusLg: ${brand.shape.radiusLg}`
));
container.appendChild(
section('Shape Tokens', 'Bevel and radius values determine the visual language — angular (DT) vs rounded (Classic).',
shape,
),
);
// Spacing Scale
const spacings = [
{ name: '--space-1', value: '0.25rem (4px)' },
{ name: '--space-2', value: '0.5rem (8px)' },
{ name: '--space-3', value: '0.75rem (12px)' },
{ name: '--space-4', value: '1rem (16px)' },
{ name: '--space-6', value: '1.5rem (24px)' },
{ name: '--space-8', value: '2rem (32px)' },
];
const spacingDemo = el('div', { style: 'display: flex; flex-direction: column; gap: var(--space-2);' });
for (const sp of spacings) {
const row = el('div', { style: 'display: flex; align-items: center; gap: var(--space-3);' });
row.appendChild(el('div', { style: `width: ${sp.name.replace('--', 'var(--') + ')'}; width: calc(${sp.value.split(' ')[0]} * 6); height: 12px; background: var(--color-primary); opacity: 0.6;` }));
row.appendChild(el('span', { style: 'font-family: var(--font-mono); font-size: 0.75rem; opacity: 0.6;' }, `${sp.name}: ${sp.value}`));
spacingDemo.appendChild(row);
}
container.appendChild(
section('Spacing Scale', 'Shared spacing tokens across all brands.',
spacingDemo,
),
);
// CSS Custom Properties reference
const cssRef = el('div', { className: 'terminal' });
cssRef.appendChild(el('code', {},
`/* Generated CSS custom properties */\n--color-bg: ${darkColors.bg};\n--color-primary: ${darkColors.primary};\n--color-secondary: ${darkColors.secondary};\n--color-error: ${darkColors.error};\n--color-success: ${darkColors.success};\n--bevel-sm: ${brand.shape.bevelSm};\n--bevel-md: ${brand.shape.bevelMd};\n--font-heading: ${brand.typography.heading};`
));
container.appendChild(
section('CSS Custom Properties', 'These are generated from TypeScript tokens and applied per-brand via data-brand attribute.',
cssRef,
codeLabel('See packages/tokens/src/scripts/generate-css.ts'),
),
);
}

View File

@@ -1,3 +1,6 @@
@tailwind components;
@tailwind utilities;
/* Showcase App Layout */
* {
@@ -228,12 +231,16 @@ body {
opacity: 0.9;
}
/* Cards */
/* Cards — DT brand padding is set in bevels.css via --_card-pad */
.card {
padding: var(--space-6);
margin-bottom: var(--space-4);
}
/* Classic brand: cards need explicit padding (no bevel CSS provides it) */
[data-brand="classic"] .card {
padding: var(--space-6);
}
.card-title {
font-weight: 900;
font-size: 1rem;

View File

@@ -1,46 +0,0 @@
export function el(
tag: string,
attrs?: Record<string, string>,
...children: (string | HTMLElement)[]
): HTMLElement {
const element = document.createElement(tag);
if (attrs) {
for (const [key, value] of Object.entries(attrs)) {
if (key === 'className') {
element.className = value;
} else {
element.setAttribute(key, value);
}
}
}
for (const child of children) {
if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
} else {
element.appendChild(child);
}
}
return element;
}
export function section(title: string, description: string, ...children: HTMLElement[]): HTMLElement {
const container = el('div', { className: 'demo-section' });
container.appendChild(el('h2', {}, title));
container.appendChild(el('p', { className: 'demo-description' }, description));
for (const child of children) {
container.appendChild(child);
}
return container;
}
export function row(...children: HTMLElement[]): HTMLElement {
return el('div', { className: 'demo-row' }, ...children);
}
export function label(text: string): HTMLElement {
return el('div', { className: 'demo-label' }, text);
}
export function codeLabel(text: string): HTMLElement {
return el('div', { className: 'code-label' }, text);
}

View File

@@ -1,28 +0,0 @@
export function initRouter(
routes: Record<string, (container: HTMLElement) => void>,
container: HTMLElement,
) {
function navigate() {
const hash = window.location.hash.replace('#/', '').replace('#', '');
const render = routes[hash] || routes[''];
// Update active nav link
document.querySelectorAll('.nav-link').forEach((link) => {
const href = (link as HTMLAnchorElement).getAttribute('href') || '';
const linkHash = href.replace('#/', '').replace('#', '');
link.classList.toggle('active', linkHash === hash);
});
// Clear content safely
while (container.firstChild) {
container.removeChild(container.firstChild);
}
if (render) {
render(container);
}
}
window.addEventListener('hashchange', navigate);
navigate();
}