feat: add design system, theme toggle, SEO head and base layout

- Huemint Magazine tokens for light/dark themes with AA-safe accent variants
- FOUC-free theme init + toggle (localStorage, follows OS until chosen)
- Seo component (canonical, OG, Twitter, WebSite/Person JSON-LD)
- Header with popover mobile menu, 4-column footer with social links
- Container, GridLines, SectionHeading, Button, Badge, Card, ExternalLink
- site.json / social.json validated with zod
- dev-only /styleguide preview

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 00:47:50 +03:00
parent c5e0b009ca
commit d8a79ccbe3
19 changed files with 915 additions and 36 deletions
+120
View File
@@ -1,2 +1,122 @@
@import 'tailwindcss';
@import '@fontsource-variable/rubik';
@import '@fontsource-variable/inter';
@plugin '@tailwindcss/typography';
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *));
/*
* Palette: Huemint "Magazine" — #f2efee #d5d09b #111012 #c95748
* Contrast notes (WCAG): ink on red = 4.46 → only large/bold text (≥19px bold) on --accent.
* Small text on a red fill uses --accent-fill + --on-accent-fill (5.3 light / 6.5 dark).
* Small red text uses --accent-text, which falls back to --fg on khaki sections (red/khaki < 4.5).
*/
:root {
color-scheme: light;
--bg: #f2efee;
--bg-alt: #d5d09b;
--surface: #f8f6f5;
--fg: #111012;
--fg-muted: #4a474b;
--line: color-mix(in oklab, #111012 18%, transparent);
--accent: #c95748;
--accent-strong: #a8412f;
--on-accent: #111012;
--accent-fill: #a8412f;
--on-accent-fill: #f2efee;
--accent-on-alt: var(--fg);
--accent-text: var(--accent-strong);
}
[data-theme='dark'] {
color-scheme: dark;
--bg: #111012;
--bg-alt: #1c1b17;
--surface: #19181a;
--fg: #ebe7e4;
--fg-muted: #b3ad8a;
--line: color-mix(in oklab, #d5d09b 16%, transparent);
--accent: #e07a6b;
--accent-strong: #e07a6b;
--on-accent: #111012;
--accent-fill: #e07a6b;
--on-accent-fill: #111012;
--accent-on-alt: var(--accent);
--accent-text: var(--accent-strong);
}
@theme inline {
--color-bg: var(--bg);
--color-bg-alt: var(--bg-alt);
--color-surface: var(--surface);
--color-fg: var(--fg);
--color-fg-muted: var(--fg-muted);
--color-line: var(--line);
--color-accent: var(--accent);
--color-accent-strong: var(--accent-strong);
--color-accent-text: var(--accent-text);
--color-accent-fill: var(--accent-fill);
--color-on-accent: var(--on-accent);
--color-on-accent-fill: var(--on-accent-fill);
--color-khaki: #d5d09b;
--color-paper: #f2efee;
--color-ink: #111012;
--font-display: 'Rubik Variable', ui-sans-serif, system-ui, sans-serif;
--font-sans: 'Inter Variable', ui-sans-serif, system-ui, sans-serif;
--font-mono: ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;
}
@layer base {
html {
@apply bg-bg font-sans text-fg antialiased;
scroll-padding-top: 5rem;
}
body {
@apply min-h-dvh text-[1.0625rem] leading-relaxed;
}
h1,
h2,
h3,
h4 {
@apply font-display;
}
:focus-visible {
@apply outline-3 outline-offset-2 outline-accent;
}
::selection {
@apply bg-accent text-on-accent;
}
}
@layer components {
/* Khaki (alt) section: switches small red text to an accessible colour. */
.surface-alt {
@apply bg-bg-alt;
--accent-text: var(--accent-on-alt);
}
/* Magazine headline: heavy, uppercase, tight. */
.headline {
@apply font-display leading-[0.95] font-black tracking-tight uppercase;
}
/* "Learn More" style link: thick underline that turns red on hover. */
.link-strong {
@apply font-display font-bold underline decoration-fg decoration-3 underline-offset-[0.35em] transition-colors hover:decoration-accent;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}