diff --git a/README.md b/README.md index 9b1a6e3..47f59cf 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - tablint.cachevector.com + tablint.cachevector.org

diff --git a/pyproject.toml b/pyproject.toml index 594d823..d3f199b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,9 +54,9 @@ parquet = [ version = { attr = "tablint.__version__" } [project.urls] -Homepage = "https://tablint.cachevector.com" +Homepage = "https://tablint.cachevector.org" Repository = "https://github.com/cachevector/tablint" -Documentation = "https://tablint.cachevector.com/docs" +Documentation = "https://tablint.cachevector.org/docs" Issues = "https://github.com/cachevector/tablint/issues" [tool.setuptools.packages.find] diff --git a/web/src/app.css b/web/src/app.css index eec61ff..a4ee290 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -1,138 +1,73 @@ /* ============================================ - Tablint Design System - Primary accent: #b16286 (rose/mauve from logo) - Typography: Epilogue (300-600) + DM Mono (300-500) + Tablint — built on the CacheVector design tokens (static/cv.css). + Tablint only swaps the accent: gruvbox purple, matching the logo. ============================================ */ :root { - /* Brand colors derived from #b16286 */ - --brand-50: #fdf2f8; - --brand-100: #fce7f3; - --brand-200: #f5d0e6; - --brand-300: #e8a9cd; - --brand-400: #d47eaf; - --brand-500: #b16286; - --brand-600: #9d4f72; - --brand-700: #843f5e; - --brand-800: #6e354e; - --brand-900: #5c2f44; + --cv-accent-light: #b16286; + --cv-accent-dark: #d3869b; - /* Base sizing */ - --max-width: 1200px; - --nav-height: 64px; + --nav-height: var(--cv-nav-height); + --max-width: var(--cv-max-width); --radius-sm: 0px; --radius-md: 0px; --radius-lg: 0px; --radius-full: 9999px; - /* Typography */ - --font-sans: 'Epilogue', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --font-mono: 'DM Mono', 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', - 'Courier New', monospace; + --font-sans: var(--cv-font-sans); + --font-mono: var(--cv-font-mono); - /* Transitions */ --transition-fast: 150ms ease; --transition-base: 250ms ease; } -/* ---- Light Theme (default) ---- */ -:root, [data-theme="light"] { - color-scheme: light; - --bg-primary: #fdfbff; - --bg-secondary: #f9f4fb; - --bg-tertiary: #f2e9f4; - --bg-elevated: #ffffff; - --bg-code: #fafafa; - - --text-primary: #111827; - --text-secondary: #4b5563; - --text-tertiary: #9ca3af; - --text-inverse: #ffffff; - - --border-primary: rgba(15, 23, 42, 0.12); - --border-secondary: rgba(148, 163, 184, 0.4); - --border-accent: var(--brand-200); - - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04); - --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.06); - --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.08); - --shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.10); - - --accent: var(--brand-500); - --accent-hover: var(--brand-600); - --accent-light: var(--brand-50); - --accent-muted: var(--brand-100); - - --nav-bg: rgba(253, 251, 255, 0.9); - --nav-border: var(--border-secondary); - - --code-bg: #fafafa; - --code-text: var(--text-primary); - --code-border: var(--border-primary); - - --hero-gradient: radial-gradient(circle at top, rgba(177, 98, 134, 0.16), transparent 55%), - radial-gradient(circle at bottom, rgba(148, 163, 184, 0.12), transparent 65%); - --section-alt-bg: var(--bg-secondary); - - --grid-color: rgba(148, 163, 184, 0.15); - - --hl-comment: #64748b; - --hl-arg: #15803d; - --hl-operator:#6b7280; - --hl-value: #a16207; - --hl-keyword: #7c3aed; - --hl-string: #c2410c; - --hl-builtin: #0369a1; +/* Component-facing names, resolved from the shared tokens per theme. */ +:root, +[data-theme="light"], +[data-theme="dark"] { + --bg-primary: var(--cv-bg); + --bg-secondary: var(--cv-bg-sunken); + --bg-tertiary: var(--cv-bg-sunken); + --bg-elevated: var(--cv-bg-raised); + --bg-code: var(--cv-bg-sunken); + + --text-primary: var(--cv-fg); + --text-secondary: var(--cv-fg-muted); + --text-tertiary: var(--cv-fg-subtle); + --text-inverse: var(--cv-bg); + + --border-primary: var(--cv-line-strong); + --border-secondary: var(--cv-line); + + --accent: var(--cv-accent); + --accent-hover: var(--cv-fg); + --accent-light: var(--cv-accent-soft); + + --code-bg: var(--cv-bg-sunken); + --code-text: var(--cv-fg); + --code-border: var(--cv-line); +} + +/* Syntax colors: gruvbox, so code reads the same here and on cachevector.org. */ +:root, +[data-theme="light"] { + --hl-comment: var(--cv-fg-subtle); + --hl-arg: #79740e; + --hl-operator: var(--cv-fg-subtle); + --hl-value: #b57614; + --hl-keyword: #076678; + --hl-string: #af3a03; + --hl-builtin: #427b58; + --term-warn: #b57614; } -/* ---- Dark Theme ---- */ [data-theme="dark"] { - color-scheme: dark; - --bg-primary: #0f1117; - --bg-secondary: #151821; - --bg-tertiary: #1a1d28; - --bg-elevated: #1f2330; - --bg-code: #151821; - - --text-primary: #f9fafb; - --text-secondary: #d1d5db; - --text-tertiary: #9ca3af; - --text-inverse: #0f1117; - - --border-primary: rgba(148, 163, 184, 0.2); - --border-secondary: rgba(148, 163, 184, 0.12); - --border-accent: var(--brand-800); - - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); - --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4); - --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5); - --shadow-xl: 0 16px 48px rgba(0, 0, 0, 0.6); - - --accent: var(--brand-300); - --accent-hover: var(--brand-200); - --accent-light: rgba(177, 98, 134, 0.2); - --accent-muted: rgba(177, 98, 134, 0.12); - - --nav-bg: rgba(15, 17, 23, 0.94); - --nav-border: var(--border-secondary); - - --code-bg: #151821; - --code-text: var(--text-secondary); - --code-border: var(--border-primary); - - --hero-gradient: radial-gradient(circle at top, rgba(177, 98, 134, 0.25), transparent 55%), - radial-gradient(circle at bottom, rgba(15, 23, 42, 0.4), transparent 70%); - --section-alt-bg: var(--bg-secondary); - - --grid-color: rgba(31, 41, 55, 0.25); - - --hl-comment: #94a3b8; - --hl-arg: #22c55e; - --hl-operator:#e5e7eb; - --hl-value: #eab308; - --hl-keyword: #a855f7; - --hl-string: #f97316; - --hl-builtin: #0ea5e9; + --hl-arg: #b8bb26; + --hl-value: #fabd2f; + --hl-keyword: #83a598; + --hl-string: #fe8019; + --hl-builtin: #8ec07c; + --term-warn: #fabd2f; } /* ---- Reset & Base ---- */ @@ -144,6 +79,7 @@ html { scroll-behavior: smooth; + scroll-padding-top: calc(var(--nav-height) + 24px); -webkit-text-size-adjust: 100%; } @@ -155,23 +91,7 @@ body { background-color: var(--bg-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - transition: color var(--transition-base); - position: relative; - overflow-x: hidden; - max-width: 100vw; -} - -body::before { - content: ''; - position: fixed; - inset: 0; - pointer-events: none; - background-image: - linear-gradient(to right, var(--grid-color) 1px, transparent 1px), - linear-gradient(to bottom, var(--grid-color) 1px, transparent 1px); - background-size: 80px 80px; - opacity: 0.3; - z-index: -1; + overflow-x: clip; } a { @@ -189,75 +109,72 @@ button { cursor: pointer; border: none; background: none; + color: inherit; } code, pre { font-family: var(--font-mono); } +ul { + list-style: none; +} + /* ---- Utility ---- */ .container { width: 100%; max-width: var(--max-width); margin: 0 auto; - padding: 0 24px; - box-sizing: border-box; -} - -@media (max-width: 640px) { - .container { - padding: 0 16px; - } -} - -/* Prevent horizontal overflow */ -html, body { - overflow-x: hidden; - max-width: 100vw; + padding: 0 var(--cv-gutter); } -main { - overflow-x: hidden; - max-width: 100%; -} - -/* ---- Typography Scale ---- */ +/* ---- Typography Scale (matches cachevector.org) ---- */ h1, h2, h3, h4, h5, h6 { - line-height: 1.1; + font-family: var(--font-sans); + font-weight: 600; + line-height: 1.15; + letter-spacing: -0.025em; color: var(--text-primary); } h1 { - font-size: clamp(2.5rem, 8vw, 5rem); - font-weight: 300; - letter-spacing: -0.05em; - line-height: 0.95; + font-size: clamp(2.25rem, 5vw, 3.5rem); + letter-spacing: -0.035em; } h2 { - font-size: clamp(1.5rem, 4vw, 2.5rem); - font-weight: 300; - letter-spacing: -0.04em; - line-height: 1.05; + font-size: clamp(1.5rem, 3vw, 2rem); } h3 { - font-size: clamp(1rem, 1.5vw, 1.125rem); - font-weight: 500; - letter-spacing: -0.02em; - text-transform: uppercase; + font-size: 1.125rem; } p { color: var(--text-secondary); - line-height: 1.7; - font-size: 0.875rem; + line-height: 1.65; +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; } -/* Selection */ ::selection { - background: var(--brand-500); - color: white; + background: var(--accent); + color: var(--cv-on-accent); +} + +/* ---- Shared section rhythm ---- */ +.section { + padding: 64px 0; + border-top: 1px solid var(--border-secondary); +} + +@media (min-width: 641px) { + .section { + padding: 88px 0; + } } /* ---- Docs syntax highlighting ---- */ @@ -273,20 +190,8 @@ p { .hl-string { color: var(--hl-string); } .hl-builtin { color: var(--hl-builtin); } -/* Scrollbar */ -::-webkit-scrollbar { - width: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: var(--border-primary); - border-radius: 3px; -} - -::-webkit-scrollbar-thumb:hover { - background: var(--text-tertiary); +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } } diff --git a/web/src/app.html b/web/src/app.html index d12a77c..2598e65 100644 --- a/web/src/app.html +++ b/web/src/app.html @@ -5,19 +5,24 @@ + + + diff --git a/web/src/lib/components/CTA.svelte b/web/src/lib/components/CTA.svelte index 93d0bd9..735f6f9 100644 --- a/web/src/lib/components/CTA.svelte +++ b/web/src/lib/components/CTA.svelte @@ -1,24 +1,22 @@ -

+
+
+

Get started

Stop debugging datasets manually.

Install Tablint and get your first dataset audit in under a minute.

-
- pip install tablint -
+
+
@@ -26,106 +24,74 @@
diff --git a/web/src/lib/components/Checks.svelte b/web/src/lib/components/Checks.svelte index 3a498bd..c4700b4 100644 --- a/web/src/lib/components/Checks.svelte +++ b/web/src/lib/components/Checks.svelte @@ -1,4 +1,6 @@ -
+
-
-

13 built-in checks.
Every issue caught.

-

- From basic data quality to ML-specific concerns, - Tablint runs a comprehensive audit on your dataset. -

-
+
{#each checks as check} @@ -95,27 +96,11 @@
diff --git a/web/src/lib/components/CodeExample.svelte b/web/src/lib/components/CodeExample.svelte index 4e28e37..de6133d 100644 --- a/web/src/lib/components/CodeExample.svelte +++ b/web/src/lib/components/CodeExample.svelte @@ -2,10 +2,11 @@ let activeTab = $state<'cli' | 'python'>('cli'); -
+
+

03 / Usage

Use it your way

Quick terminal scan or deep Python integration — Tablint fits into any ML workflow. @@ -13,27 +14,19 @@

- - - + 13 built-in dataset checks
- - - + HTML, PDF, Markdown, JSON reports
- - - + Auto-generated fix scripts
- - - + Drift detection between datasets
@@ -42,6 +35,7 @@
diff --git a/web/src/lib/components/Features.svelte b/web/src/lib/components/Features.svelte index b521b1d..e43adea 100644 --- a/web/src/lib/components/Features.svelte +++ b/web/src/lib/components/Features.svelte @@ -1,4 +1,6 @@ -
+
-
-

Profile, debug, and fix
your datasets in seconds

-

- Tablint catches critical issues before they derail your ML pipeline, - explains the problems, and suggests context-aware fixes. -

-
+
{#each features as feature} @@ -82,82 +82,45 @@
diff --git a/web/src/lib/components/Footer.svelte b/web/src/lib/components/Footer.svelte index 85092e1..0c960b4 100644 --- a/web/src/lib/components/Footer.svelte +++ b/web/src/lib/components/Footer.svelte @@ -2,148 +2,151 @@ const currentYear = new Date().getFullYear(); + diff --git a/web/src/lib/components/Hero.svelte b/web/src/lib/components/Hero.svelte index 9b69f12..44595e6 100644 --- a/web/src/lib/components/Hero.svelte +++ b/web/src/lib/components/Hero.svelte @@ -1,42 +1,48 @@
-
- v0.1.0b1 -
- -

- Dataset Profiler & Debugger
for Machine Learning. -

- -

- Catch critical dataset issues before they derail your ML pipeline. - Profile, debug, and fix — all in one command. -

- -
- - - Get Started - - - View on GitHub - +
+

+ Dataset profiler & debugger + v0.1.0b1 +

+ +

Lint your datasets before they reach training.

+ +

+ Catch critical dataset issues before they derail your ML pipeline. + Profile, debug, and fix — all in one command. +

+ +
+ Get started + +
-
-
-
- - - +
+ +
+
+ ~/project + tablint
- terminal -
-
-
$ pip install tablint
+        
+
$ pip install tablint
 Successfully installed tablint-0.1.0b1
 
 $ tablint scan train.csv --target Survived
@@ -51,6 +57,7 @@
   ✗ outliers             Fare: 116 outliers
   ⚠ high_cardinality    Ticket: 681 unique
   ⚠ feature_correlation SibSp ↔ Parch: 0.41
+
@@ -58,150 +65,107 @@ diff --git a/web/src/lib/components/HowItWorks.svelte b/web/src/lib/components/HowItWorks.svelte index 94317d3..87fcffa 100644 --- a/web/src/lib/components/HowItWorks.svelte +++ b/web/src/lib/components/HowItWorks.svelte @@ -1,4 +1,6 @@ -
+
-
-

Three commands.
Zero guesswork.

-

- No need to change your workflow — bring your own tools, - frameworks, and datasets. -

-
+
{#each steps as step}
-
-
{step.step}
-

{step.title}

-
- {step.command} -
-
+ {step.step} +

{step.title}

{step.description}

+
+ {step.command} +
{/each}
@@ -49,140 +47,58 @@
diff --git a/web/src/lib/components/Integrations.svelte b/web/src/lib/components/Integrations.svelte index 480dc75..6d3405a 100644 --- a/web/src/lib/components/Integrations.svelte +++ b/web/src/lib/components/Integrations.svelte @@ -9,38 +9,38 @@
-

Works with your ML stack

+

Works with your ML stack

{#each integrations as item}
{#if item.icon === 'python'} - - + + {:else if item.icon === 'pandas'} - - - - - - - + + + + + + + {:else if item.icon === 'sklearn'} - - - - - + + + + + {:else if item.icon === 'numpy'} - - - + + + {/if} {item.name} @@ -52,70 +52,46 @@ diff --git a/web/src/lib/components/Logo.svelte b/web/src/lib/components/Logo.svelte index 621d0c1..57ac634 100644 --- a/web/src/lib/components/Logo.svelte +++ b/web/src/lib/components/Logo.svelte @@ -1,36 +1,66 @@ - diff --git a/web/src/lib/components/Navbar.svelte b/web/src/lib/components/Navbar.svelte index bbcecae..7326769 100644 --- a/web/src/lib/components/Navbar.svelte +++ b/web/src/lib/components/Navbar.svelte @@ -1,4 +1,5 @@ -{#if mobileOpen} -
-
+ {#if mobileOpen} +
{#each navLinks as link} {link.label} {/each} -
- - Get Started - + GitHub ↗ + Get started
-
-{/if} + {/if} + diff --git a/web/src/lib/components/SectionHeader.svelte b/web/src/lib/components/SectionHeader.svelte new file mode 100644 index 0000000..1be7534 --- /dev/null +++ b/web/src/lib/components/SectionHeader.svelte @@ -0,0 +1,72 @@ + + +
+
+ {index} + +
+
+
+

{title}

+ {#if intro} +

{intro}

+ {/if} +
+ {#if link} + {link.label} + {/if} +
+
+ + diff --git a/web/src/lib/components/ThemeToggle.svelte b/web/src/lib/components/ThemeToggle.svelte index ccd2247..a92f739 100644 --- a/web/src/lib/components/ThemeToggle.svelte +++ b/web/src/lib/components/ThemeToggle.svelte @@ -32,15 +32,13 @@ display: flex; align-items: center; justify-content: center; - width: 36px; - height: 36px; - border-radius: var(--radius-sm); + min-width: 44px; + min-height: 44px; color: var(--text-secondary); - transition: color var(--transition-fast), background-color var(--transition-fast); + transition: color var(--transition-fast); } .theme-toggle:hover { color: var(--text-primary); - background-color: var(--bg-tertiary); } diff --git a/web/src/lib/components/Themes.svelte b/web/src/lib/components/Themes.svelte index aa95433..6678a7c 100644 --- a/web/src/lib/components/Themes.svelte +++ b/web/src/lib/components/Themes.svelte @@ -1,15 +1,14 @@ -
+
-
-

Beautiful reports.
Your choice of style.

-

- Generate professional HTML reports with two built-in themes. - Every report includes interactive visualizations and detailed diagnostics. -

-
+
@@ -64,38 +63,24 @@
diff --git a/web/src/lib/site.ts b/web/src/lib/site.ts index 5a666f0..6a8401f 100644 --- a/web/src/lib/site.ts +++ b/web/src/lib/site.ts @@ -1 +1 @@ -export const siteOrigin = 'https://tablint.cachevector.com'; +export const siteOrigin = 'https://tablint.cachevector.org'; diff --git a/web/src/lib/theme.svelte.ts b/web/src/lib/theme.svelte.ts index 9c1fe38..b5b7668 100644 --- a/web/src/lib/theme.svelte.ts +++ b/web/src/lib/theme.svelte.ts @@ -6,10 +6,24 @@ type Theme = 'light' | 'dark'; // Safari doesn't repaint body.backgroundColor when CSS vars change on html, // so we set it directly in JS with the known values. const BG: Record = { - light: '#fdfbff', - dark: '#0f1117', + light: '#fbfaf7', + dark: '#0f0f0d', }; +// Shared with cachevector.org and the other project subdomains. +const COOKIE = 'cv-theme'; + +function readCookie(): Theme | null { + const match = document.cookie.match(/(?:^|; )cv-theme=(light|dark)/); + return match ? (match[1] as Theme) : null; +} + +function writeCookie(value: Theme) { + const host = window.location.hostname; + const domain = host === 'cachevector.org' || host.endsWith('.cachevector.org') ? '; domain=.cachevector.org' : ''; + document.cookie = `${COOKIE}=${value}; path=/; max-age=31536000; SameSite=Lax${domain}`; +} + function safeLocalStorage(action: 'get' | 'set', key: string, value?: string): string | null { try { if (action === 'get') return localStorage.getItem(key); @@ -27,7 +41,7 @@ function getSystemTheme(): Theme { function getStoredPreference(): 'light' | 'dark' | 'system' { if (!browser) return 'system'; - return (safeLocalStorage('get', 'tablint-theme') as 'light' | 'dark' | 'system') || 'system'; + return readCookie() || (safeLocalStorage('get', 'tablint-theme') as 'light' | 'dark' | 'system') || 'system'; } let preference = $state<'light' | 'dark' | 'system'>(getStoredPreference()); @@ -48,6 +62,7 @@ function setPreference(pref: 'light' | 'dark' | 'system') { preference = pref; if (browser) { safeLocalStorage('set', 'tablint-theme', pref); + if (pref !== 'system') writeCookie(pref); } apply(pref === 'system' ? getSystemTheme() : pref); } diff --git a/web/src/routes/docs/+page.svelte b/web/src/routes/docs/+page.svelte index 952940d..f6c2c46 100644 --- a/web/src/routes/docs/+page.svelte +++ b/web/src/routes/docs/+page.svelte @@ -1,18 +1,56 @@
-
-
Documentation
+
+

Documentation v0.1.0b1

Learn Tablint in minutes, not hours.

-

- A focused guide to installing, running, and integrating Tablint into your ML workflows. Built to match the - minimal, calm UI of the rest of the site. +

+ A focused guide to installing, running, and integrating Tablint into your ML workflows.

@@ -22,23 +60,23 @@ -
+

Overview

@@ -402,7 +440,7 @@ summary = analyzer.analyze()

Config files can be written in YAML (.yaml / .yml), TOML (.toml), or JSON (.json). Only the keys you specify are changed; everything else falls back to defaults.

-
# tablint.yaml
+            
# tablint.yaml
 missing_values:
   warning: 0.3
   critical: 0.6
@@ -458,43 +496,40 @@ summary = analyzer.analyze()
diff --git a/web/static/CNAME b/web/static/CNAME index 05c1a03..e394635 100644 --- a/web/static/CNAME +++ b/web/static/CNAME @@ -1 +1 @@ -tablint.cachevector.com +tablint.cachevector.org diff --git a/web/static/cachevector.svg b/web/static/cachevector.svg new file mode 100644 index 0000000..637b698 --- /dev/null +++ b/web/static/cachevector.svg @@ -0,0 +1,57 @@ + + + + diff --git a/web/static/cv.css b/web/static/cv.css new file mode 100644 index 0000000..32dab22 --- /dev/null +++ b/web/static/cv.css @@ -0,0 +1,173 @@ +/* Vendored from cachevector.org/public/cv.css — keep in sync; edit upstream first. */ +/* + * CacheVector shared design tokens — https://cachevector.org/cv.css + * + * One stylesheet for cachevector.org and every project subdomain + * (tablint.cachevector.org, docs, ...). Link it before the site's own CSS: + * + * + * + * A project keeps the shared neutrals, type, and halftone texture, and only + * swaps its accent hue. Accents come from the gruvbox palette the logo uses: + * + * :root { --cv-accent-light: #b16286; --cv-accent-dark: #d3869b; } (tablint) + * + * aqua #427b58 / #8ec07c (cachevector) + * purple #b16286 / #d3869b blue #076678 / #83a598 + * orange #af3a03 / #fe8019 yellow #b57614 / #fabd2f + * + * Theme: dark mode is [data-theme="dark"] on . The choice is stored in a + * `cv-theme` cookie on .cachevector.org so it carries across subdomains. + * + * Fonts: Epilogue (400-700) and DM Mono (400-500) from Google Fonts. + */ + +:root { + --cv-accent-light: #427b58; + --cv-accent-dark: #8ec07c; + + --cv-font-sans: "Epilogue", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; + --cv-font-mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; + + --cv-max-width: 1200px; + --cv-nav-height: 60px; + --cv-gutter: 16px; + + --cv-bg: #fbfaf7; + --cv-bg-sunken: #f3f1eb; + --cv-bg-raised: #ffffff; + --cv-fg: #17160f; + --cv-fg-muted: #46443b; + --cv-fg-subtle: #75726a; + --cv-line: #e2ded4; + --cv-line-strong: #17160f; + --cv-dot: #17160f; + --cv-accent: var(--cv-accent-light); + --cv-on-accent: #ffffff; + + color-scheme: light; +} + +[data-theme="dark"] { + --cv-bg: #0f0f0d; + --cv-bg-sunken: #161613; + --cv-bg-raised: #1b1b18; + --cv-fg: #edece6; + --cv-fg-muted: #c2bfb5; + --cv-fg-subtle: #8a877e; + --cv-line: #2b2a26; + --cv-line-strong: #edece6; + --cv-dot: #edece6; + --cv-accent: var(--cv-accent-dark); + --cv-on-accent: #0f0f0d; + + color-scheme: dark; +} + +:root { + --cv-accent-soft: color-mix(in srgb, var(--cv-accent) 12%, transparent); +} + +@media (min-width: 641px) { + :root { + --cv-gutter: 24px; + } +} + +/* ---- Halftone --------------------------------------------------------- + * Dot-screen texture. Set --cv-ht-color, --cv-ht-size (dot radius) and + * --cv-ht-gap (cell size); fade it with a mask, e.g. .cv-halftone-fade. + */ + +.cv-halftone { + --cv-ht-color: var(--cv-dot); + --cv-ht-size: 1px; + --cv-ht-gap: 6px; + background-image: radial-gradient( + circle, + var(--cv-ht-color) var(--cv-ht-size), + transparent calc(var(--cv-ht-size) + 0.6px) + ); + background-size: var(--cv-ht-gap) var(--cv-ht-gap); +} + +.cv-halftone-fade { + -webkit-mask-image: linear-gradient(to right, #000, transparent); + mask-image: linear-gradient(to right, #000, transparent); +} + +/* ---- Small shared primitives ------------------------------------------ */ + +.cv-container { + width: 100%; + max-width: var(--cv-max-width); + margin: 0 auto; + padding: 0 var(--cv-gutter); +} + +.cv-eyebrow { + font-family: var(--cv-font-mono); + font-size: 0.75rem; + font-weight: 400; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--cv-fg-subtle); +} + +.cv-chip { + display: inline-flex; + align-items: center; + gap: 6px; + font-family: var(--cv-font-mono); + font-size: 0.6875rem; + letter-spacing: 0.04em; + text-transform: uppercase; + line-height: 1; + padding: 5px 8px; + border: 1px solid var(--cv-line); + color: var(--cv-fg-subtle); + white-space: nowrap; +} + +.cv-chip-accent { + border-color: color-mix(in srgb, var(--cv-accent) 45%, transparent); + color: var(--cv-accent); + background: var(--cv-accent-soft); +} + +.cv-btn { + display: inline-flex; + align-items: center; + gap: 8px; + height: 44px; + padding: 0 20px; + font-family: var(--cv-font-sans); + font-size: 0.875rem; + font-weight: 500; + line-height: 1; + border: 1px solid var(--cv-line-strong); + background: var(--cv-fg); + color: var(--cv-bg); + text-decoration: none; + transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease; +} + +.cv-btn:hover { + background: var(--cv-accent); + border-color: var(--cv-accent); + color: var(--cv-on-accent); +} + +.cv-btn-ghost { + background: transparent; + color: var(--cv-fg); + border-color: var(--cv-line); + font-family: var(--cv-font-mono); + font-weight: 400; +} + +.cv-btn-ghost:hover { + background: transparent; + color: var(--cv-fg); + border-color: var(--cv-line-strong); +}