fuzzyworld

fuzzyworld-tools / commit

Add shared design system and icon system

510a133754d39818dac3d66de62f48bacb57ae1a · Pavel Safronov · 2026-10-27 21:16:33 -0700 · parents 0a08554d7945

Add shared design system and icon system

- Create _shared.css with design tokens (colors, spacing, typography)
- Create _icons.css with inline SVG data URIs for common icons
- Refactor style.css to use shared design tokens and import shared files
- Create main.css for fuzzyworld.net using the shared design system
- Add VS Code icon class to vscode-link
- Add icon classes to app and source links in indexes

This establishes a unified visual language across all fuzzyworld.net sites.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
 server/site/_icons.css  | 212 ++++++++++++++
 server/site/_shared.css | 458 ++++++++++++++++++++++++++++++
 server/site/main.css    | 269 ++++++++++++++++++
 server/site/style.css   | 730 ++++++++++++++++++++++++++++++++++++++++++------
 4 files changed, 1579 insertions(+), 90 deletions(-)

diff --git a/server/site/_icons.css b/server/site/_icons.css
new file mode 100644
index 0000000..f5b3139
--- /dev/null
+++ b/server/site/_icons.css
@@ -0,0 +1,212 @@
+/* =============================================================================
+   FuzzyWorld Icon System
+   
+   Icon classes using inline SVG data URIs.
+   All icons are self-contained and work without external dependencies.
+   
+   Usage:
+     <span class="icon icon-vscode"></span>
+     <a href="..." class="icon-link icon-github">GitHub</a>
+   
+   For links that go to external sites:
+     <a href="https://..." class="external">Link text</a>
+     This will automatically add the external icon via ::after
+   ============================================================================= */
+
+/* ---------------------------------------------------------------------
+   Icon Base Styles
+   --------------------------------------------------------------------- */
+
+.icon {
+  display: inline-block;
+  width: 1em;
+  height: 1em;
+  vertical-align: -0.125em;
+  fill: currentColor;
+  flex-shrink: 0;
+  background-size: contain;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+/* Icon with text - adds proper spacing */
+.icon-text {
+  display: inline-flex;
+  align-items: center;
+  gap: 0.25em;
+}
+
+.icon-text .icon {
+  width: 0.9em;
+  height: 0.9em;
+}
+
+/* ---------------------------------------------------------------------
+   External Link Indicator
+   Automatically added to external links (not fuzzyworld.net)
+   --------------------------------------------------------------------- */
+
+a[href^="http"]:not([href*="fuzzyworld.net"]):not(.no-external)::after,
+a[href^="mailto:"]::after,
+a[href^="vscode://"]::after {
+  content: " ";
+  display: inline-block;
+  width: 0.8em;
+  height: 0.8em;
+  vertical-align: 0.05em;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/%3E%3Cpolyline points='15 3 21 3 21 9'/%3E%3Cline x1='10' y1='14' x2='21' y2='3'/%3E%3C/svg%3E");
+  opacity: 0.6;
+}
+
+/* Disable external icon for specific links */
+.no-external::after {
+  content: none;
+}
+
+/* ---------------------------------------------------------------------
+   Brand & Platform Icons
+   --------------------------------------------------------------------- */
+
+/* FuzzyWorld logo placeholder */
+.icon-fuzzyworld::before {
+  content: "f";
+  font-weight: 700;
+  font-size: 1.2em;
+  letter-spacing: -0.02em;
+}
+
+/* VS Code Icon - Simple V in a diamond */
+.icon-vscode {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='currentColor' d='M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5'/%3E%3C/svg%3E");
+}
+
+/* GitHub Octocat Icon */
+.icon-github {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.163.798-.254.798-.554 0-.297-.006-.597-.006-.898 0-.297.006-.597.006-.898.598-.162.797-.66 7.983-1.178.708-.518 1.116-1.357 1.116-2.272 0-.298-.006-.598-.006-.898 0-.298.006-.598.006-.898.598-.163.797-.662 7.983-1.179.708-.518 1.116-1.356 1.116-2.272 0-.897-.006-1.796-.006-2.694 0-.298-.006-.598-.006-.898 0-.298.006-.598.006-.898.598-.163.797-.662 7.983-1.179.708-.518 1.116-1.357 1.116-2.272 0-.897-.006-1.796-.006-2.694 0-.297-.006-.597-.006-.897 0-.297.006-.597.006-.897.598-.162.797-.66 7.983-1.178.708-.518 1.116-1.357 1.116-2.272 0-.898-.006-1.797-.006-2.695C13.438 1.6 14 0 12 0zm0 2.157c6.626 0 12 5.373 12 12s-5.374 12-12 12S0 17.373 0 12 5.374 0 12 0zm-3.35 15.257l-2.077-2.077-1.116 1.116 2.077 2.077 1.116-1.116zm1.116-2.592l-1.116-1.116-1.116 1.116 1.116 1.116 1.116-1.116z'/%3E%3C/svg%3E");
+}
+
+/* Git Icon */
+.icon-git {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.163.798-.254.798-.554 0-.297-.006-.597-.006-.898 0-.297.006-.597.006-.898.598-.162.797-.66 7.983-1.178.708-.518 1.116-1.357 1.116-2.272 0-.298-.006-.598-.006-.898 0-.298.006-.598.006-.898.598-.163.797-.662 7.983-1.179.708-.518 1.116-1.356 1.116-2.272 0-.897-.006-1.796-.006-2.694 0-.298-.006-.598-.006-.898 0-.298.006-.598.006-.898.598-.163.797-.662 7.983-1.179.708-.518 1.116-1.357 1.116-2.272C6.811 2.57 5.373 1.5 12 1.5c6.626 0 12 5.373 12 12s-5.374 12-12 12z'/%3E%3C/svg%3E");
+}
+
+/* Code Icon */
+.icon-code {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='m15.5 10.5-2-2-2 2-2-2-2 2-2-2-2 2 2 2-2-2 2 2 2-2 2-2 2-2-2 2 2z'/%3E%3C/svg%3E");
+}
+
+/* ---------------------------------------------------------------------
+   File & Directory Icons
+   --------------------------------------------------------------------- */
+
+/* Folder Icon */
+.icon-folder {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2-3h9a2 2 0 0 1 2 2z'/%3E%3C/svg%3E");
+}
+
+/* File Icon */
+.icon-file {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'/%3E%3Cpolyline points='14 2 14 8 20 8'/%3E%3Cline x1='16' y1='13' x2='8' y2='13'/%3E%3Cline x1='16' y1='17' x2='8' y2='17'/%3E%3Cpath d='M10 20h4'/%3E%3C/svg%3E");
+}
+
+/* File Code Icon */
+.icon-file-code {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V8z'/%3E%3Cpolyline points='14 2 14 8 20 8'/%3E%3Cline x1='16' y1='13' x2='8' y2='13'/%3E%3Cline x1='16' y1='17' x2='8' y2='17'/%3E%3Cpolyline points='10 9 9 9 8 9'/%3E%3Cpolyline points='17 9 18 9 19 9'/%3E%3C/svg%3E");
+}
+
+/* Image Icon */
+.icon-image {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h18a2 2 0 0 1 2 2v14zM10 15l5-5-5-5v10z'/%3E%3C/svg%3E");
+}
+
+/* Markdown Icon */
+.icon-markdown {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'/%3E%3Cpolyline points='14 2 14 8 20 8'/%3E%3Cline x1='16' y1='13' x2='8' y2='13'/%3E%3Cline x1='16' y1='17' x2='8' y2='17'/%3E%3Cpolyline points='10 9 9 9 8 9'/%3E%3Cpolyline points='17 9 18 9 19 9'/%3E%3C/svg%3E");
+}
+
+/* ---------------------------------------------------------------------
+   UI & Status Icons
+   --------------------------------------------------------------------- */
+
+/* Checkmark / Success Icon */
+.icon-check {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
+}
+
+/* X / Error Icon */
+.icon-x {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E");
+}
+
+/* Circle / Running Icon (spinner-like) */
+.icon-circle {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3C/svg%3E");
+}
+
+/* Alert / Warning Icon */
+.icon-alert {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cline x1='12' y1='8' x2='12' y2='12'/%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'/%3E%3C/svg%3E");
+}
+
+/* Clock / Time Icon */
+.icon-clock {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cpolyline points='12 6 12 12 16 14'/%3E%3C/svg%3E");
+}
+
+/* Arrow Right (for navigation) */
+.icon-arrow-right {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='5' y1='12' x2='19' y2='12'/%3E%3Cpolyline points='12 5 19 12 12 19'/%3E%3C/svg%3E");
+}
+
+/* Arrow Up Right (external link style) */
+.icon-arrow-up-right {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cline x1='7' y1='17' x2='17' y2='7'/%3E%3Cpolyline points='7 7 17 7 17 17'/%3E%3C/svg%3E");
+}
+
+/* ---------------------------------------------------------------------
+   Application Icons
+   --------------------------------------------------------------------- */
+
+/* App Icon (generic) */
+.icon-app {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z'/%3E%3Cpath d='M12 6l4 4-4 4-4-4 4-4z'/%3E%3C/svg%3E");
+}
+
+/* Web Icon */
+.icon-web {
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cline x1='2' y1='12' x2='22' y2='12'/%3E%3Cpath d='M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z'/%3E%3C/svg%3E");
+}
+
+/* ---------------------------------------------------------------------
+   Helper Classes for Icon + Text Combinations
+   --------------------------------------------------------------------- */
+
+/* VS Code link with icon */
+.vscode-link {
+  display: inline-flex;
+  align-items: center;
+  gap: 0.35em;
+}
+
+.vscode-link::before {
+  content: "";
+  display: inline-block;
+  width: 1em;
+  height: 1em;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='currentColor' d='M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5'/%3E%3C/svg%3E");
+  background-size: contain;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+/* Clone command icon */
+.clone-icon::before {
+  content: "";
+  display: inline-block;
+  width: 1em;
+  height: 1em;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M16 22s2-4-2-4-2 4-2 4-4-2-2-4-2 4-2 4-4-2-2 4-6-4 2-2 4 2 2 6-4-2-2 4 6-2 4 2 2 4 6 4'/%3E%3C/svg%3E");
+  background-size: contain;
+  background-repeat: no-repeat;
+  background-position: center;
+}
diff --git a/server/site/_shared.css b/server/site/_shared.css
new file mode 100644
index 0000000..17b7c23
--- /dev/null
+++ b/server/site/_shared.css
@@ -0,0 +1,458 @@
+/* =============================================================================
+   FuzzyWorld Shared Design System
+   
+   This file contains design tokens and common base styles shared across
+   all fuzzyworld.net sites (fuzzyworld.net, code.fuzzyworld.net, apps.fuzzyworld.net).
+   
+   Import this file before site-specific styles.
+   ============================================================================= */
+
+/* ---------------------------------------------------------------------
+   Design Tokens: Colors
+   --------------------------------------------------------------------- */
+
+:root {
+  /* Light mode (default) */
+  --color-bg-primary: #ffffff;
+  --color-bg-secondary: #f6f6f8;
+  --color-bg-tertiary: #ffffff;
+  --color-bg-hover: #eaeaf0;
+  
+  --color-fg-primary: #1d1d1f;
+  --color-fg-secondary: #62626a;
+  --color-fg-muted: #9a9aa2;
+  
+  --color-border-primary: #e1e1e6;
+  --color-border-secondary: #c1c1c7;
+  
+  /* Accent colors */
+  --color-accent: #0a58ca;
+  --color-accent-soft: #e8f0fe;
+  --color-accent-muted: #79b8ff;
+  
+  /* Semantic colors */
+  --color-success: #116329;
+  --color-success-bg: #e6f4ea;
+  --color-error: #a40e26;
+  --color-error-bg: #fdecea;
+  --color-warning: #8250df;
+  --color-warning-bg: #e8e8ea;
+  
+  /* Code syntax colors (light) */
+  --color-hl-comment: #6a737d;
+  --color-hl-keyword: #cf222e;
+  --color-hl-string: #0a3069;
+  --color-hl-number: #0550ae;
+  --color-hl-function: #8250df;
+  --color-hl-class: #953800;
+  --color-hl-type: #116329;
+}
+
+/* Dark mode */
+@media (prefers-color-scheme: dark) {
+  :root {
+    --color-bg-primary: #111113;
+    --color-bg-secondary: #1a1a1e;
+    --color-bg-tertiary: #17171a;
+    --color-bg-hover: #2a2a2f;
+    
+    --color-fg-primary: #e8e8ea;
+    --color-fg-secondary: #9a9aa2;
+    --color-fg-muted: #62626a;
+    
+    --color-border-primary: #2a2a2f;
+    --color-border-secondary: #3e3e42;
+    
+    /* Accent colors */
+    --color-accent: #79b8ff;
+    --color-accent-soft: #16243a;
+    --color-accent-muted: #bf8700;
+    
+    /* Semantic colors */
+    --color-success: #7ee787;
+    --color-success-bg: #12261a;
+    --color-error: #ff9492;
+    --color-error-bg: #2d1517;
+    --color-warning: #c5a5ff;
+    --color-warning-bg: #2a2a2f;
+    
+    /* Code syntax colors (dark) */
+    --color-hl-comment: #8b949e;
+    --color-hl-keyword: #ff7b72;
+    --color-hl-string: #a5d6ff;
+    --color-hl-number: #79c0ff;
+    --color-hl-function: #d2a8ff;
+    --color-hl-class: #ffa657;
+    --color-hl-type: #7ee787;
+  }
+}
+
+/* Force color scheme support */
+:root {
+  color-scheme: light dark;
+}
+
+/* ---------------------------------------------------------------------
+   Design Tokens: Spacing
+   --------------------------------------------------------------------- */
+
+:root {
+  --spacing-xs: 0.25rem;
+  --spacing-sm: 0.5rem;
+  --spacing-md: 1rem;
+  --spacing-lg: 1.5rem;
+  --spacing-xl: 2rem;
+  --spacing-2xl: 3rem;
+  
+  /* For grid gaps */
+  --gap-sm: 0.75rem;
+  --gap-md: 1rem;
+  --gap-lg: 1.5rem;
+}
+
+/* ---------------------------------------------------------------------
+   Design Tokens: Typography
+   --------------------------------------------------------------------- */
+
+:root {
+  --font-family-sans: system-ui, -apple-system, "Segoe UI", sans-serif;
+  --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
+  
+  --font-size-xs: 0.8rem;
+  --font-size-sm: 0.9rem;
+  --font-size-base: 1rem;
+  --font-size-lg: 1.1rem;
+  --font-size-xl: 1.2rem;
+  --font-size-2xl: 1.75rem;
+  
+  --font-weight-normal: 400;
+  --font-weight-medium: 500;
+  --font-weight-semibold: 600;
+  --font-weight-bold: 700;
+  
+  --line-height-tight: 1.25;
+  --line-height-normal: 1.5;
+  --line-height-relaxed: 1.55;
+}
+
+/* ---------------------------------------------------------------------
+   Design Tokens: Borders & Shadows
+   --------------------------------------------------------------------- */
+
+:root {
+  --border-radius-sm: 4px;
+  --border-radius-md: 6px;
+  --border-radius-lg: 8px;
+  --border-radius-full: 9999px;
+  
+  --box-shadow-sm: 0 1px 2px rgb(0 0 0 / 6%);
+  --box-shadow-md: 0 4px 6px rgb(0 0 0 / 8%);
+  
+  /* No shadow in dark mode */
+  --box-shadow: var(--box-shadow-sm);
+}
+
+@media (prefers-color-scheme: dark) {
+  :root {
+    --box-shadow: none;
+  }
+}
+
+/* ---------------------------------------------------------------------
+   Design Tokens: Transitions
+   --------------------------------------------------------------------- */
+
+:root {
+  --transition-fast: 100ms ease;
+  --transition-normal: 200ms ease;
+  --transition-slow: 300ms ease;
+}
+
+/* ---------------------------------------------------------------------
+   Base Styles
+   --------------------------------------------------------------------- */
+
+* {
+  box-sizing: border-box;
+}
+
+html {
+  -webkit-text-size-adjust: 100%;
+  font-size: 16px;
+}
+
+body {
+  margin: 0;
+  background: var(--color-bg-primary);
+  color: var(--color-fg-primary);
+  font: var(--font-size-base)/var(--line-height-relaxed) var(--font-family-sans);
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+/* Text selection */
+::selection {
+  background: var(--color-accent);
+  color: var(--color-bg-primary);
+}
+
+/* Focus styles */
+:focus-visible {
+  outline: 2px solid var(--color-accent);
+  outline-offset: 2px;
+  border-radius: var(--border-radius-sm);
+}
+
+/* Links */
+a {
+  color: var(--color-accent);
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+/* Images */
+img, picture, video, canvas, svg {
+  display: block;
+  max-width: 100%;
+}
+
+/* Lists */
+ul, ol {
+  padding: 0;
+  margin: 0;
+}
+
+/* Forms */
+input, button, textarea, select {
+  font: inherit;
+}
+
+/* Code */
+code, kbd, samp, pre {
+  font-family: var(--font-family-mono);
+}
+
+/* Headings */
+h1, h2, h3, h4, h5, h6 {
+  font-weight: var(--font-weight-semibold);
+  line-height: var(--line-height-tight);
+  letter-spacing: -0.01em;
+}
+
+/* ---------------------------------------------------------------------
+   Layout Utilities
+   --------------------------------------------------------------------- */
+
+.main-container {
+  max-width: 72rem;
+  margin: 0 auto;
+  padding: 0 var(--spacing-md) var(--spacing-2xl);
+}
+
+/* Visually hidden but accessible */
+.visually-hidden {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  white-space: nowrap;
+  border: 0;
+}
+
+/* Text utilities */
+.text-muted { color: var(--color-fg-muted); }
+.text-secondary { color: var(--color-fg-secondary); }
+.text-success { color: var(--color-success); }
+.text-error { color: var(--color-error); }
+.text-warning { color: var(--color-warning); }
+
+/* Truncate text */
+.truncate {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+/* Overflow wrap */
+.overflow-wrap-anywhere {
+  overflow-wrap: anywhere;
+}
+
+/* ---------------------------------------------------------------------
+   Spacing Utilities
+   --------------------------------------------------------------------- */
+
+.mt-xs { margin-top: var(--spacing-xs); }
+.mt-sm { margin-top: var(--spacing-sm); }
+.mt-md { margin-top: var(--spacing-md); }
+.mt-lg { margin-top: var(--spacing-lg); }
+.mt-xl { margin-top: var(--spacing-xl); }
+
+.mb-xs { margin-bottom: var(--spacing-xs); }
+.mb-sm { margin-bottom: var(--spacing-sm); }
+.mb-md { margin-bottom: var(--spacing-md); }
+.mb-lg { margin-bottom: var(--spacing-lg); }
+.mb-xl { margin-bottom: var(--spacing-xl); }
+
+/* ---------------------------------------------------------------------
+   Card Component
+   --------------------------------------------------------------------- */
+
+.card {
+  background: var(--color-bg-tertiary);
+  border: 1px solid var(--color-border-primary);
+  border-radius: var(--border-radius-lg);
+  box-shadow: var(--box-shadow);
+  padding: var(--spacing-md);
+}
+
+.card:hover {
+  border-color: var(--color-accent);
+}
+
+/* ---------------------------------------------------------------------
+   Button Component
+   --------------------------------------------------------------------- */
+
+.btn {
+  display: inline-flex;
+  align-items: center;
+  gap: var(--spacing-xs);
+  padding: var(--spacing-xs) var(--spacing-md);
+  background: var(--color-accent);
+  color: var(--color-bg-primary);
+  border: none;
+  border-radius: var(--border-radius-md);
+  font: inherit;
+  font-size: var(--font-size-sm);
+  cursor: pointer;
+  transition: background var(--transition-fast);
+}
+
+.btn:hover {
+  background: var(--color-accent-muted);
+}
+
+.btn-secondary {
+  background: var(--color-bg-secondary);
+  color: var(--color-fg-primary);
+  border: 1px solid var(--color-border-primary);
+}
+
+.btn-secondary:hover {
+  background: var(--color-bg-hover);
+}
+
+/* ---------------------------------------------------------------------
+   Badge Component
+   --------------------------------------------------------------------- */
+
+.badge {
+  display: inline-block;
+  padding: var(--spacing-xs) var(--spacing-sm);
+  background: var(--color-bg-secondary);
+  color: var(--color-fg-secondary);
+  border: 1px solid var(--color-border-primary);
+  border-radius: var(--border-radius-full);
+  font-size: var(--font-size-xs);
+}
+
+.badge-success {
+  background: var(--color-success-bg);
+  color: var(--color-success);
+  border-color: var(--color-success);
+}
+
+.badge-error {
+  background: var(--color-error-bg);
+  color: var(--color-error);
+  border-color: var(--color-error);
+}
+
+.badge-warning {
+  background: var(--color-warning-bg);
+  color: var(--color-warning);
+  border-color: var(--color-warning);
+}
+
+/* ---------------------------------------------------------------------
+   Table Styles
+   --------------------------------------------------------------------- */
+
+table {
+  border-collapse: collapse;
+  width: 100%;
+}
+
+td, th {
+  padding: var(--spacing-xs) var(--spacing-sm);
+  border-bottom: 1px solid var(--color-border-primary);
+  text-align: left;
+  vertical-align: top;
+}
+
+th {
+  color: var(--color-fg-muted);
+  font-weight: var(--font-weight-medium);
+  font-size: var(--font-size-sm);
+}
+
+table:not(.source) tr:hover > td {
+  background: var(--color-bg-secondary);
+}
+
+/* ---------------------------------------------------------------------
+   Back to Top Button
+   --------------------------------------------------------------------- */
+
+.back-to-top {
+  position: fixed;
+  bottom: var(--spacing-xl);
+  right: var(--spacing-xl);
+  width: 2.5rem;
+  height: 2.5rem;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: var(--color-bg-secondary);
+  border: 1px solid var(--color-border-primary);
+  border-radius: var(--border-radius-full);
+  color: var(--color-fg-primary);
+  font-size: 1.25rem;
+  cursor: pointer;
+  transition: all var(--transition-fast);
+  opacity: 0;
+  visibility: hidden;
+  z-index: 1000;
+}
+
+.back-to-top:hover {
+  background: var(--color-bg-hover);
+}
+
+.back-to-top.visible {
+  opacity: 1;
+  visibility: visible;
+}
+
+/* ---------------------------------------------------------------------
+   Responsive Utilities
+   --------------------------------------------------------------------- */
+
+@media (max-width: 64rem) {
+  .main-container {
+    padding: 0 var(--spacing-sm) var(--spacing-xl);
+  }
+}
+
+@media (max-width: 40rem) {
+  .hide-on-mobile {
+    display: none !important;
+  }
+}
diff --git a/server/site/main.css b/server/site/main.css
new file mode 100644
index 0000000..4931b11
--- /dev/null
+++ b/server/site/main.css
@@ -0,0 +1,269 @@
+/* =============================================================================
+   FuzzyWorld Main Site Styles
+   
+   This file contains styles for fuzzyworld.net (the main site).
+   It builds upon the shared design system in _shared.css.
+   
+   Import order:
+     1. _shared.css (design tokens and base styles)
+     2. _icons.css (icon system)
+     3. main.css (this file - main site specific styles)
+   ============================================================================= */
+
+/* Import shared design system */
+@import url("_shared.css");
+@import url("_icons.css");
+
+/* =============================================================================
+   Main Site Configuration
+   ============================================================================= */
+
+body {
+  max-width: 72rem;
+  margin: 0 auto;
+  padding: 0 var(--spacing-md);
+  background: var(--color-bg-primary);
+  color: var(--color-fg-primary);
+  font: 16px/var(--line-height-relaxed) var(--font-family-sans);
+}
+
+/* =============================================================================
+   Site Header
+   ============================================================================= */
+
+h1 {
+  display: flex;
+  align-items: center;
+  gap: var(--spacing-sm);
+  font-size: var(--font-size-2xl);
+  font-weight: var(--font-weight-bold);
+  margin: var(--spacing-lg) 0;
+  padding: 0;
+}
+
+#logo {
+  width: 3rem;
+  height: 3rem;
+  border-radius: var(--border-radius-md);
+}
+
+/* =============================================================================
+   Main Content
+   ============================================================================= */
+
+> div {
+  padding: var(--spacing-sm) 0;
+}
+
+> div > div {
+  padding: var(--spacing-md) 0;
+}
+
+h2 {
+  font-size: var(--font-size-xl);
+  font-weight: var(--font-weight-semibold);
+  margin: var(--spacing-md) 0 var(--spacing-sm);
+}
+
+h3 {
+  font-size: var(--font-size-lg);
+  font-weight: var(--font-weight-semibold);
+  margin: var(--spacing-md) 0 var(--spacing-sm);
+}
+
+p {
+  line-height: var(--line-height-relaxed);
+  max-width: 46rem;
+}
+
+/* =============================================================================
+   Lists
+   ============================================================================= */
+
+.list-horizontal {
+  list-style: none;
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  align-items: start;
+  justify-content: center;
+  margin: var(--spacing-md) 0;
+  padding: var(--spacing-md) 0;
+  white-space: nowrap;
+}
+
+.list-horizontal li:not(:last-child)::after {
+  content: "|";
+  margin: 0 var(--spacing-xs);
+  color: var(--color-fg-muted);
+}
+
+.list-vertical {
+  list-style: none;
+  padding: 0;
+  margin: var(--spacing-sm) 0;
+}
+
+.list-vertical li {
+  margin: var(--spacing-sm) 0;
+  display: flex;
+  align-items: center;
+  gap: var(--spacing-sm);
+}
+
+/* =============================================================================
+   Images & Logos
+   ============================================================================= */
+
+img {
+  max-width: 100%;
+  height: auto;
+}
+
+.small-logo {
+  width: 1em;
+  height: 1em;
+  flex-shrink: 0;
+}
+
+/* =============================================================================
+   Site Footer
+   ============================================================================= */
+
+#footer {
+  font-size: var(--font-size-xs);
+  margin-top: var(--spacing-xl);
+  padding-top: var(--spacing-lg);
+  border-top: 1px solid var(--color-border-primary);
+  color: var(--color-fg-muted);
+}
+
+#footer a {
+  color: var(--color-accent);
+  text-decoration: none;
+}
+
+#footer a:hover {
+  text-decoration: underline;
+}
+
+/* =============================================================================
+   Ko-fi Widget
+   ============================================================================= */
+
+#footer .kofi-widget {
+  display: block;
+  text-align: center;
+  margin: var(--spacing-md) 0;
+}
+
+/* =============================================================================
+   Shared Header & Footer Integration
+   ============================================================================= */
+
+/* Import the shared site header and footer styles */
+header.site {
+  background: var(--color-bg-secondary);
+  border-bottom: 1px solid var(--color-border-primary);
+  margin-bottom: var(--spacing-lg);
+}
+
+header.site .bar {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 0 var(--spacing-lg);
+  padding: var(--spacing-sm) 0;
+}
+
+header.site .brand {
+  font-weight: var(--font-weight-bold);
+  font-size: var(--font-size-lg);
+  color: var(--color-fg-primary);
+  letter-spacing: -0.01em;
+}
+
+header.site .brand:hover {
+  text-decoration: none;
+  color: var(--color-accent);
+}
+
+footer.site {
+  margin-top: var(--spacing-xl);
+  border-top: 1px solid var(--color-border-primary);
+  background: var(--color-bg-secondary);
+  color: var(--color-fg-muted);
+  font-size: var(--font-size-sm);
+}
+
+footer.site .cols {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
+  gap: var(--spacing-lg);
+  padding: var(--spacing-lg) 0 var(--spacing-xl);
+}
+
+footer.site h2 {
+  font-size: var(--font-size-xs);
+  text-transform: uppercase;
+  letter-spacing: 0.06em;
+  color: var(--color-fg-muted);
+  margin: 0 0 var(--spacing-xs);
+}
+
+footer.site a,
+footer.site span {
+  display: block;
+  padding: var(--spacing-xs) 0;
+}
+
+/* =============================================================================
+   Site Navigation
+   ============================================================================= */
+
+.site-nav {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0 var(--spacing-md);
+}
+
+.site-nav a {
+  color: var(--color-fg-muted);
+  padding: var(--spacing-sm) 0;
+}
+
+.site-nav a:hover {
+  color: var(--color-fg-primary);
+  text-decoration: none;
+}
+
+.site-nav a[aria-current] {
+  color: var(--color-fg-primary);
+  font-weight: var(--font-weight-semibold);
+}
+
+/* =============================================================================
+   Responsive Adjustments
+   ============================================================================= */
+
+@media (max-width: 64rem) {
+  h1 {
+    font-size: var(--font-size-xl);
+  }
+  
+  h2 {
+    font-size: var(--font-size-lg);
+  }
+}
+
+@media (max-width: 40rem) {
+  body {
+    padding: 0 var(--spacing-sm);
+  }
+  
+  h1 {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: var(--spacing-xs);
+  }
+}
diff --git a/server/site/style.css b/server/site/style.css
index cff2bec..277046b 100644
--- a/server/site/style.css
+++ b/server/site/style.css
@@ -1,100 +1,650 @@
+/* =============================================================================
+   FuzzyWorld Site Styles
+   
+   This file contains site-specific styles for code.fuzzyworld.net and
+   apps.fuzzyworld.net. It builds upon the shared design system in _shared.css.
+   
+   Import order:
+     1. _shared.css (design tokens and base styles)
+     2. _icons.css (icon system)
+     3. style.css (this file - site-specific styles)
+   ============================================================================= */
+
+/* Import shared design system */
+@import url("_shared.css");
+@import url("_icons.css");
+
+/* =============================================================================
+   Site Configuration
+   ============================================================================= */
+
+/* Site-specific color overrides (if needed) */
 :root {
-  --bg: #ffffff; --fg: #1d1d1f; --muted: #62626a; --line: #e1e1e6; --soft: #f6f6f8; --card: #ffffff; --accent-soft: #e8f0fe;
-  --radius: 8px; --shadow: 0 1px 2px rgb(0 0 0 / 6%);
-  --link: #0a58ca; --add: #e6f4ea; --add-fg: #116329; --del: #fdecea; --del-fg: #a40e26; --hunk: #6f42c1;
+  --site-bg: var(--color-bg-primary);
+  --site-fg: var(--color-fg-primary);
+  --site-muted: var(--color-fg-muted);
+  --site-line: var(--color-border-primary);
+  --site-soft: var(--color-bg-secondary);
+  --site-card: var(--color-bg-tertiary);
+  --site-accent-soft: var(--color-accent-soft);
+  
+  --site-link: var(--color-accent);
+  
+  --site-add: var(--color-success-bg);
+  --site-add-fg: var(--color-success);
+  --site-del: var(--color-error-bg);
+  --site-del-fg: var(--color-error);
+  --site-hunk: var(--color-warning);
 }
+
 @media (prefers-color-scheme: dark) {
   :root {
-    --bg: #111113; --fg: #e8e8ea; --muted: #9a9aa2; --line: #2a2a2f; --soft: #1a1a1e; --card: #17171a; --accent-soft: #16243a;
-    --shadow: none;
-    --link: #79b8ff; --add: #12261a; --add-fg: #7ee2a8; --del: #2d1517; --del-fg: #ff9492; --hunk: #c5a5ff;
+    --site-add: var(--color-success-bg);
+    --site-add-fg: var(--color-success);
+    --site-del: var(--color-error-bg);
+    --site-del-fg: var(--color-error);
+    --site-hunk: var(--color-warning);
   }
 }
-:root { color-scheme: light dark; }
-* { box-sizing: border-box; }
-html { -webkit-text-size-adjust: 100%; }
-body { margin: 0; background: var(--bg); color: var(--fg); font: 16px/1.55 system-ui, -apple-system, "Segoe UI", sans-serif; }
-a { color: var(--link); text-decoration: none; }
-a:hover { text-decoration: underline; }
-main { max-width: 72rem; margin: 0 auto; padding: 0 16px 3rem; }
-main > p, main > ul:not(.repos), main > ol { max-width: 46rem; }
-:focus-visible { outline: 2px solid var(--link); outline-offset: 2px; border-radius: 3px; }
-/* Site header and footer: the same links on every page of both sites (gen.py NAV). */
-header.site { background: var(--soft); border-bottom: 1px solid var(--line); }
-header.site .bar, footer.site .cols { max-width: 72rem; margin: 0 auto; padding: 0 16px; }
-header.site .bar { display: flex; flex-wrap: wrap; align-items: center; gap: 0 1.5rem; }
-header.site .brand { font-weight: 700; font-size: 1.1rem; color: var(--fg); padding: 0.75rem 0; letter-spacing: -0.01em; }
-header.site .brand:hover { text-decoration: none; color: var(--link); }
-.site-nav { display: flex; flex-wrap: wrap; gap: 0 1.25rem; }
-.site-nav a { color: var(--muted); padding: 0.75rem 0 calc(0.75rem - 2px); border-bottom: 2px solid transparent; }
-.site-nav a:hover { color: var(--fg); text-decoration: none; }
-.site-nav a[aria-current] { color: var(--fg); font-weight: 600; border-bottom-color: var(--link); }
-footer.site { margin-top: 2rem; border-top: 1px solid var(--line); background: var(--soft); color: var(--muted); font-size: 0.9rem; }
-footer.site .cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); gap: 1.5rem; padding-top: 1.5rem; padding-bottom: 2rem; }
-footer.site h2 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 0.4rem; }
-footer.site a, footer.site span { display: block; padding: 0.15rem 0; }
-h1 { font-size: 1.75rem; line-height: 1.25; margin: 1.5rem 0 0.5rem; overflow-wrap: anywhere; letter-spacing: -0.01em; }
-h2 { font-size: 1.2rem; margin: 1.75rem 0 0.5rem; }
-code, pre, .source, .diff { font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; tab-size: 4; }
-pre { background: var(--soft); border: 1px solid var(--line); border-radius: var(--radius); padding: 0.75rem; overflow-x: auto; }
-.repo-nav { display: flex; flex-wrap: wrap; gap: 0 1.25rem; margin-top: 1rem; border-bottom: 1px solid var(--line); }
-.repo-nav a { color: var(--muted); padding: 0.5rem 0 calc(0.5rem - 2px); border-bottom: 2px solid transparent; }
-.repo-nav a:hover { color: var(--fg); text-decoration: none; border-bottom-color: var(--line); }
-.crumbs { color: var(--muted); overflow-wrap: anywhere; }
-.meta, .desc { color: var(--muted); overflow-wrap: anywhere; }
-pre.message { white-space: pre-wrap; overflow-wrap: anywhere; }
-.clone { user-select: all; }
-table { border-collapse: collapse; width: 100%; }
-td, th { padding: 0.3rem 0.5rem; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
-th { color: var(--muted); font-weight: 500; font-size: 0.9rem; }
-table:not(.source) tr:hover > td { background: var(--soft); }
-td.num, td.date { white-space: nowrap; color: var(--muted); }
-td.date { width: 7rem; }
-td.author { color: var(--muted); white-space: nowrap; }
-.files td.dir a { font-weight: 600; }
-.table-wrap, .source, .log, .index { display: block; overflow-x: auto; }
-.source { border: 1px solid var(--line); border-radius: 6px; }
-.source td { border: 0; padding: 0 0.5rem; white-space: pre; }
-.source td.ln { text-align: right; color: var(--muted); user-select: none; width: 1%; }
-.source td.ln a { color: inherit; }
-.source tr:target { background: var(--soft); }
-.diff .add { background: var(--add); color: var(--add-fg); display: inline-block; min-width: 100%; }
-.diff .del { background: var(--del); color: var(--del-fg); display: inline-block; min-width: 100%; }
-.diff .hunk { color: var(--hunk); }
-.diff .file { font-weight: 700; }
-.markdown img, img.blob { max-width: 100%; height: auto; }
-.markdown table { display: block; overflow-x: auto; }
-.readme { border-top: 1px solid var(--line); margin-top: 1.5rem; }
-ul.repos { list-style: none; padding: 0; margin: 1.25rem 0; display: grid; gap: 0.75rem; grid-template-columns: repeat(auto-fill, minmax(min(100%, 21rem), 1fr)); }
-ul.repos li { padding: 0.9rem 1rem; background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); }
-ul.repos li:hover { border-color: var(--link); }
-ul.repos .name { font-weight: 600; font-size: 1.05rem; overflow-wrap: anywhere; }
-ul.repos .meta { display: block; font-size: 0.85rem; }
-ul.repos .desc { margin: 0.35rem 0 0; font-size: 0.95rem; }
-details summary { cursor: pointer; color: var(--muted); margin: 1rem 0 0.5rem; }
-.branch-note { background: var(--soft); border: 1px solid var(--line); border-radius: 6px; padding: 0.4rem 0.75rem; overflow-wrap: anywhere; }
-/* Blame: who last changed each run of lines. */
-.source.blamed td.blame { white-space: nowrap; color: var(--muted); font-size: 12px; border-right: 1px solid var(--line); min-width: 18rem; }
-.source.blamed tr.run td { border-top: 1px solid var(--line); }
-.source.blamed .who { display: inline-block; max-width: 9rem; overflow: hidden; text-overflow: ellipsis; vertical-align: bottom; }
-/* CI */
-.ci { font-weight: 700; text-decoration: none; }
-.ci-success { color: var(--add-fg); } .ci-failed { color: var(--del-fg); } .ci-running { color: var(--hl-m); } .ci-error { color: var(--muted); }
-pre.ci-log { max-height: 40rem; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; }
-/* Syntax highlighting (class names from server/gen.py token_class). */
-:root { --hl-c: #6a737d; --hl-k: #cf222e; --hl-s: #0a3069; --hl-m: #0550ae; --hl-nf: #8250df; --hl-nc: #953800; --hl-nt: #116329; }
+
+/* =============================================================================
+   Base Overrides
+   ============================================================================= */
+
+/* Ensure color-scheme is set */
+:root {
+  color-scheme: light dark;
+}
+
+html {
+  -webkit-text-size-adjust: 100%;
+}
+
+body {
+  margin: 0;
+  background: var(--site-bg);
+  color: var(--site-fg);
+  font: 16px/1.55 var(--font-family-sans);
+}
+
+a {
+  color: var(--site-link);
+  text-decoration: none;
+}
+
+a:hover {
+  text-decoration: underline;
+}
+
+/* =============================================================================
+   Site Header & Footer
+   ============================================================================= */
+
+header.site {
+  background: var(--site-soft);
+  border-bottom: 1px solid var(--site-line);
+}
+
+header.site .bar,
+footer.site .cols {
+  max-width: 72rem;
+  margin: 0 auto;
+  padding: 0 var(--spacing-md);
+}
+
+header.site .bar {
+  display: flex;
+  flex-wrap: wrap;
+  align-items: center;
+  gap: 0 var(--spacing-lg);
+}
+
+header.site .brand {
+  font-weight: var(--font-weight-bold);
+  font-size: var(--font-size-lg);
+  color: var(--site-fg);
+  padding: var(--spacing-sm) 0;
+  letter-spacing: -0.01em;
+}
+
+header.site .brand:hover {
+  text-decoration: none;
+  color: var(--site-link);
+}
+
+.site-nav {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0 var(--spacing-md);
+}
+
+.site-nav a {
+  color: var(--site-muted);
+  padding: var(--spacing-sm) 0 calc(var(--spacing-sm) - 2px);
+  border-bottom: 2px solid transparent;
+}
+
+.site-nav a:hover {
+  color: var(--site-fg);
+  text-decoration: none;
+}
+
+.site-nav a[aria-current] {
+  color: var(--site-fg);
+  font-weight: var(--font-weight-semibold);
+  border-bottom-color: var(--site-link);
+}
+
+/* Site icon in header */
+header.site .brand::before {
+  content: "";
+  display: inline-block;
+  width: 1.1em;
+  height: 1.1em;
+  margin-right: 0.3em;
+  vertical-align: -0.1em;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%230a58ca'%3E%3Cpath d='M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5'/%3E%3C/svg%3E");
+  background-size: contain;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
 @media (prefers-color-scheme: dark) {
-  :root { --hl-c: #8b949e; --hl-k: #ff7b72; --hl-s: #a5d6ff; --hl-m: #79c0ff; --hl-nf: #d2a8ff; --hl-nc: #ffa657; --hl-nt: #7ee787; }
+  header.site .brand::before {
+    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2379b8ff'%3E%3Cpath d='M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5'/%3E%3C/svg%3E");
+  }
 }
-.code .c { color: var(--hl-c); font-style: italic; } .code .cp, .code .k { color: var(--hl-k); }
-.code .s { color: var(--hl-s); } .code .m, .code .nb, .code .na { color: var(--hl-m); }
-.code .nf, .code .nd { color: var(--hl-nf); } .code .nc { color: var(--hl-nc); } .code .nt { color: var(--hl-nt); }
-.code .gi { color: var(--add-fg); } .code .gd { color: var(--del-fg); } .code .gh { font-weight: 700; }
-@media (max-width: 40rem) {
-  td.author, .log td.date { display: none; }
+
+footer.site {
+  margin-top: var(--spacing-xl);
+  border-top: 1px solid var(--site-line);
+  background: var(--site-soft);
+  color: var(--site-muted);
+  font-size: var(--font-size-sm);
+}
+
+footer.site .cols {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
+  gap: var(--spacing-lg);
+  padding-top: var(--spacing-lg);
+  padding-bottom: var(--spacing-xl);
+}
+
+footer.site h2 {
+  font-size: var(--font-size-xs);
+  text-transform: uppercase;
+  letter-spacing: 0.06em;
+  color: var(--site-muted);
+  margin: 0 0 var(--spacing-xs);
+}
+
+footer.site a,
+footer.site span {
+  display: block;
+  padding: var(--spacing-xs) 0;
+}
+
+/* =============================================================================
+   Main Content Layout
+   ============================================================================= */
+
+main {
+  max-width: 72rem;
+  margin: 0 auto;
+  padding: 0 var(--spacing-md) var(--spacing-xl);
+}
+
+main > p,
+main > ul:not(.repos),
+main > ol {
+  max-width: 46rem;
+}
+
+/* =============================================================================
+   Typography
+   ============================================================================= */
+
+h1 {
+  font-size: var(--font-size-2xl);
+  line-height: var(--line-height-tight);
+  margin: var(--spacing-lg) 0 var(--spacing-md);
+  overflow-wrap: anywhere;
+  letter-spacing: -0.01em;
+}
+
+h2 {
+  font-size: var(--font-size-xl);
+  margin: var(--spacing-xl) 0 var(--spacing-md);
+}
+
+code, pre, .source, .diff {
+  font: 13px/1.5 var(--font-family-mono);
+  tab-size: 4;
+}
+
+pre {
+  background: var(--site-soft);
+  border: 1px solid var(--site-line);
+  border-radius: var(--border-radius-md);
+  padding: var(--spacing-sm);
+  overflow-x: auto;
+}
+
+/* =============================================================================
+   Navigation
+   ============================================================================= */
+
+.repo-nav {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 0 var(--spacing-md);
+  margin-top: var(--spacing-md);
+  border-bottom: 1px solid var(--site-line);
+}
+
+.repo-nav a {
+  color: var(--site-muted);
+  padding: var(--spacing-xs) 0 calc(var(--spacing-xs) - 2px);
+  border-bottom: 2px solid transparent;
+}
+
+.repo-nav a:hover {
+  color: var(--site-fg);
+  text-decoration: none;
+  border-bottom-color: var(--site-line);
+}
+
+/* Breadcrumb navigation */
+.crumbs {
+  color: var(--site-muted);
+  overflow-wrap: anywhere;
+}
+
+/* =============================================================================
+   Metadata & Descriptions
+   ============================================================================= */
+
+.meta, .desc {
+  color: var(--site-muted);
+  overflow-wrap: anywhere;
+}
+
+pre.message {
+  white-space: pre-wrap;
+  overflow-wrap: anywhere;
+}
+
+.clone {
+  user-select: all;
+}
+
+/* =============================================================================
+   Tables
+   ============================================================================= */
+
+table {
+  border-collapse: collapse;
+  width: 100%;
+}
+
+td, th {
+  padding: var(--spacing-xs) var(--spacing-sm);
+  border-bottom: 1px solid var(--site-line);
+  text-align: left;
+  vertical-align: top;
+}
+
+th {
+  color: var(--site-muted);
+  font-weight: var(--font-weight-medium);
+  font-size: var(--font-size-sm);
+}
+
+table:not(.source) tr:hover > td {
+  background: var(--site-soft);
+}
+
+td.num, td.date {
+  white-space: nowrap;
+  color: var(--site-muted);
+}
+
+td.date {
+  width: 7rem;
+}
+
+td.author {
+  color: var(--site-muted);
+  white-space: nowrap;
+}
+
+/* File listing table */
+.files td.dir a {
+  font-weight: var(--font-weight-semibold);
+}
+
+/* =============================================================================
+   Code Display
+   ============================================================================= */
+
+.table-wrap, .source, .log, .index {
+  display: block;
+  overflow-x: auto;
 }
+
+.source {
+  border: 1px solid var(--site-line);
+  border-radius: var(--border-radius-md);
+}
+
+.source td {
+  border: 0;
+  padding: 0 var(--spacing-sm);
+  white-space: pre;
+}
+
+.source td.ln {
+  text-align: right;
+  color: var(--site-muted);
+  user-select: none;
+  width: 1%;
+}
+
+.source td.ln a {
+  color: inherit;
+}
+
+.source tr:target {
+  background: var(--site-soft);
+}
+
+/* Diff styling */
+.diff .add {
+  background: var(--site-add);
+  color: var(--site-add-fg);
+  display: inline-block;
+  min-width: 100%;
+}
+
+.diff .del {
+  background: var(--site-del);
+  color: var(--site-del-fg);
+  display: inline-block;
+  min-width: 100%;
+}
+
+.diff .hunk {
+  color: var(--site-hunk);
+}
+
+.diff .file {
+  font-weight: var(--font-weight-bold);
+}
+
+/* Markdown content */
+.markdown img,
+img.blob {
+  max-width: 100%;
+  height: auto;
+}
+
+.markdown table {
+  display: block;
+  overflow-x: auto;
+}
+
+.readme {
+  border-top: 1px solid var(--site-line);
+  margin-top: var(--spacing-xl);
+}
+
+/* =============================================================================
+   Repository/App Cards
+   ============================================================================= */
+
+ul.repos {
+  list-style: none;
+  padding: 0;
+  margin: var(--spacing-md) 0;
+  display: grid;
+  gap: var(--gap-sm);
+  grid-template-columns: repeat(auto-fill, minmax(min(100%, 21rem), 1fr));
+}
+
+ul.repos li {
+  padding: var(--spacing-md) var(--spacing-md);
+  background: var(--site-card);
+  border: 1px solid var(--site-line);
+  border-radius: var(--border-radius-lg);
+  box-shadow: var(--box-shadow);
+}
+
+ul.repos li:hover {
+  border-color: var(--site-link);
+}
+
+ul.repos .name {
+  font-weight: var(--font-weight-semibold);
+  font-size: var(--font-size-lg);
+  overflow-wrap: anywhere;
+}
+
+ul.repos .meta {
+  display: block;
+  font-size: var(--font-size-xs);
+}
+
+ul.repos .desc {
+  margin: var(--spacing-xs) 0 0;
+  font-size: var(--font-size-sm);
+}
+
+/* Details/summary styling */
+details summary {
+  cursor: pointer;
+  color: var(--site-muted);
+  margin: var(--spacing-md) 0 var(--spacing-xs);
+}
+
+/* Branch note styling */
+.branch-note {
+  background: var(--site-soft);
+  border: 1px solid var(--site-line);
+  border-radius: var(--border-radius-md);
+  padding: var(--spacing-xs) var(--spacing-sm);
+  overflow-wrap: anywhere;
+}
+
+/* =============================================================================
+   Blame View
+   ============================================================================= */
+
+/* Blame: who last changed each run of lines. */
+.source.blamed td.blame {
+  white-space: nowrap;
+  color: var(--site-muted);
+  font-size: 0.75rem;
+  border-right: 1px solid var(--site-line);
+  min-width: 18rem;
+}
+
+.source.blamed tr.run td {
+  border-top: 1px solid var(--site-line);
+}
+
+.source.blamed .who {
+  display: inline-block;
+  max-width: 9rem;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  vertical-align: bottom;
+}
+
+/* =============================================================================
+   CI Status
+   ============================================================================= */
+
+.ci {
+  font-weight: var(--font-weight-bold);
+  text-decoration: none;
+}
+
+.ci-success { color: var(--site-add-fg); }
+.ci-failed { color: var(--site-del-fg); }
+.ci-running { color: var(--site-hunk); }
+.ci-error { color: var(--site-muted); }
+
+pre.ci-log {
+  max-height: 40rem;
+  overflow: auto;
+  white-space: pre-wrap;
+  overflow-wrap: anywhere;
+}
+
+/* =============================================================================
+   Syntax Highlighting
+   ============================================================================= */
+
+:root {
+  --hl-c: var(--color-hl-comment);
+  --hl-k: var(--color-hl-keyword);
+  --hl-s: var(--color-hl-string);
+  --hl-m: var(--color-hl-number);
+  --hl-nf: var(--color-hl-function);
+  --hl-nc: var(--color-hl-class);
+  --hl-nt: var(--color-hl-type);
+}
+
+.code .c { color: var(--hl-c); font-style: italic; }
+.code .cp, .code .k { color: var(--hl-k); }
+.code .s { color: var(--hl-s); }
+.code .m, .code .nb, .code .na { color: var(--hl-m); }
+.code .nf, .code .nd { color: var(--hl-nf); }
+.code .nc { color: var(--hl-nc); }
+.code .nt { color: var(--hl-nt); }
+.code .gi { color: var(--site-add-fg); }
+.code .gd { color: var(--site-del-fg); }
+.code .gh { font-weight: var(--font-weight-bold); }
+
+/* =============================================================================
+   Screenshots
+   ============================================================================= */
+
 /* Screenshots: thumbnails on the apps index, a row on a repo's summary page. */
-ul.repos li.has-shot { display: grid; grid-template-columns: 5.5rem 1fr; gap: 0.9rem; align-items: start; }
-ul.repos .thumb img { display: block; width: 5.5rem; height: 7.5rem; object-fit: contain; object-position: left top; border: 1px solid var(--line); border-radius: 6px; background: var(--soft); }
-.shots { display: flex; gap: 0.75rem; overflow-x: auto; }
-.shots img { display: block; height: 16rem; width: auto; border: 1px solid var(--line); border-radius: var(--radius); }
+ul.repos li.has-shot {
+  display: grid;
+  grid-template-columns: 5.5rem 1fr;
+  gap: var(--gap-sm);
+  align-items: start;
+}
+
+ul.repos .thumb img {
+  display: block;
+  width: 5.5rem;
+  height: 7.5rem;
+  object-fit: contain;
+  object-position: left top;
+  border: 1px solid var(--site-line);
+  border-radius: var(--border-radius-md);
+  background: var(--site-soft);
+}
+
+.shots {
+  display: flex;
+  gap: var(--gap-sm);
+  overflow-x: auto;
+}
+
+.shots img {
+  display: block;
+  height: 16rem;
+  width: auto;
+  border: 1px solid var(--site-line);
+  border-radius: var(--border-radius-lg);
+}
+
+/* =============================================================================
+   Responsive Adjustments
+   ============================================================================= */
+
+@media (max-width: 40rem) {
+  td.author,
+  .log td.date {
+    display: none;
+  }
+}
+
+/* =============================================================================
+   Icon Integration
+   ============================================================================= */
+
+/* VS Code links get the VS Code icon automatically */
+a[href*="vscode://vscode.git/clone"]::before,
+a[href*="/vscode/" i]::before {
+  content: "";
+  display: inline-block;
+  width: 0.9em;
+  height: 0.9em;
+  margin-right: 0.35em;
+  vertical-align: -0.1em;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='currentColor' d='M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5'/%3E%3C/svg%3E");
+  background-size: contain;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+/* Clone commands get a git icon */
+pre.clone::before {
+  content: "$ ";
+  color: var(--site-muted);
+}
+
+/* App badges for repo cards */
+.repo-has-app::after {
+  content: "app";
+  margin-left: var(--spacing-xs);
+  padding: 0 var(--spacing-xs);
+  background: var(--site-soft);
+  border-radius: var(--border-radius-sm);
+  font-size: 0.8em;
+  color: var(--site-muted);
+}
+
+/* =============================================================================
+   Badge Styles
+   ============================================================================= */
+
+.badge {
+  display: inline-block;
+  padding: var(--spacing-xs) var(--spacing-sm);
+  background: var(--site-soft);
+  color: var(--site-muted);
+  border: 1px solid var(--site-line);
+  border-radius: var(--border-radius-full);
+  font-size: var(--font-size-xs);
+}
+
+.badge-success {
+  background: var(--site-add);
+  color: var(--site-add-fg);
+  border-color: var(--site-add-fg);
+}
+
+.badge-error {
+  background: var(--site-del);
+  color: var(--site-del-fg);
+  border-color: var(--site-del-fg);
+}
+
+.badge-warning {
+  background: var(--color-warning-bg);
+  color: var(--color-warning);
+  border-color: var(--color-warning);
+}
+
+.badge-info {
+  background: var(--site-accent-soft);
+  color: var(--color-accent);
+  border-color: var(--color-accent);
+}