From 34c3a31e73e4c872969cb43b241c14daf636d8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ullrich=20Sch=C3=A4fer?= Date: Wed, 15 Jul 2026 23:00:28 +0200 Subject: [PATCH] =?UTF-8?q?feat(ui):=20design-system=20foundation=20?= =?UTF-8?q?=E2=80=94=20shared=20token=20layer=20+=20fonts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Establish the visual-redesign foundation (task group 1): a single shared source of design tokens both apps consume, so Planner and Journal read from one palette/typography layer instead of drifting per-app. - New @trails-cool/ui package exporting theme.css: Tailwind v4 @theme tokens (warm off-white surfaces, one sage accent, earthy overnight tones, elevation-gradient colors, shadows) extracted from the visual-redesign mockup. - Self-hosted Outfit (body) + Geist Mono (stats) via @fontsource-variable — privacy-first, no Google Fonts CDN. - Both apps import the theme and set the base canvas to the warm off-white token + Outfit as the default font. Foundation only: tokens are now available as Tailwind utilities (bg-bg-raised, text-accent, font-mono, …) and raw CSS vars. Wiring them into specific surfaces (topbar, sidebar, markers, elevation chart) is later task groups. Co-Authored-By: Claude Opus 4.8 --- apps/journal/app/root.tsx | 2 +- apps/journal/app/styles.css | 1 + apps/journal/package.json | 1 + apps/planner/app/root.tsx | 2 +- apps/planner/app/styles.css | 1 + apps/planner/package.json | 1 + openspec/changes/visual-redesign/tasks.md | 8 +-- packages/ui/package.json | 12 ++++ packages/ui/src/theme.css | 71 +++++++++++++++++++++++ pnpm-lock.yaml | 31 ++++++++++ pnpm-workspace.yaml | 2 + 11 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 packages/ui/package.json create mode 100644 packages/ui/src/theme.css diff --git a/apps/journal/app/root.tsx b/apps/journal/app/root.tsx index dd74a92..e75dabd 100644 --- a/apps/journal/app/root.tsx +++ b/apps/journal/app/root.tsx @@ -41,7 +41,7 @@ export function Layout({ children }: { children: React.ReactNode }) { - + {children} diff --git a/apps/journal/app/styles.css b/apps/journal/app/styles.css index 2fa790e..8eff5ef 100644 --- a/apps/journal/app/styles.css +++ b/apps/journal/app/styles.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +@import "@trails-cool/ui/theme.css"; @keyframes slide { 0% { transform: translateX(-100%); } diff --git a/apps/journal/package.json b/apps/journal/package.json index 50e0ea7..659169b 100644 --- a/apps/journal/package.json +++ b/apps/journal/package.json @@ -28,6 +28,7 @@ "@trails-cool/i18n": "workspace:*", "@trails-cool/jobs": "workspace:*", "@trails-cool/map-core": "workspace:*", + "@trails-cool/ui": "workspace:*", "@trails-cool/sentry-config": "workspace:*", "@trails-cool/types": "workspace:*", "drizzle-orm": "catalog:", diff --git a/apps/planner/app/root.tsx b/apps/planner/app/root.tsx index b116079..4a670b2 100644 --- a/apps/planner/app/root.tsx +++ b/apps/planner/app/root.tsx @@ -20,7 +20,7 @@ export function Layout({ children }: { children: React.ReactNode }) { - + {children} diff --git a/apps/planner/app/styles.css b/apps/planner/app/styles.css index 2fa790e..8eff5ef 100644 --- a/apps/planner/app/styles.css +++ b/apps/planner/app/styles.css @@ -1,4 +1,5 @@ @import "tailwindcss"; +@import "@trails-cool/ui/theme.css"; @keyframes slide { 0% { transform: translateX(-100%); } diff --git a/apps/planner/package.json b/apps/planner/package.json index 5b46319..bf4c80f 100644 --- a/apps/planner/package.json +++ b/apps/planner/package.json @@ -28,6 +28,7 @@ "@trails-cool/i18n": "workspace:*", "@trails-cool/jobs": "workspace:*", "@trails-cool/map-core": "workspace:*", + "@trails-cool/ui": "workspace:*", "@trails-cool/sentry-config": "workspace:*", "@trails-cool/types": "workspace:*", "codemirror": "^6.0.2", diff --git a/openspec/changes/visual-redesign/tasks.md b/openspec/changes/visual-redesign/tasks.md index c6ee3c7..40ce4f3 100644 --- a/openspec/changes/visual-redesign/tasks.md +++ b/openspec/changes/visual-redesign/tasks.md @@ -1,9 +1,9 @@ ## 1. Design Tokens & Typography -- [ ] 1.1 Add CSS custom properties (`:root` vars) for colors, shadows, borders -- [ ] 1.2 Extend Tailwind config with project color tokens and font families -- [ ] 1.3 Add Outfit + Geist Mono fonts (Google Fonts or self-hosted) -- [ ] 1.4 Update base styles: body background, default text color, font-family +- [x] 1.1 Add CSS custom properties (`:root` vars) for colors, shadows, borders +- [x] 1.2 Extend Tailwind config with project color tokens and font families +- [x] 1.3 Add Outfit + Geist Mono fonts (Google Fonts or self-hosted) +- [x] 1.4 Update base styles: body background, default text color, font-family - [ ] 1.5 Update elevation gradient colors in ColoredRoute + ElevationChart to use tokens ## 2. Topbar Redesign diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..fb62fe5 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,12 @@ +{ + "name": "@trails-cool/ui", + "version": "0.0.1", + "type": "module", + "exports": { + "./theme.css": "./src/theme.css" + }, + "dependencies": { + "@fontsource-variable/geist-mono": "catalog:", + "@fontsource-variable/outfit": "catalog:" + } +} diff --git a/packages/ui/src/theme.css b/packages/ui/src/theme.css new file mode 100644 index 0000000..b3135d4 --- /dev/null +++ b/packages/ui/src/theme.css @@ -0,0 +1,71 @@ +/* + * trails.cool design tokens — the single source of truth for both apps. + * + * Direction: "Trail Worn warmth + Nordic Precision lightness" — warm + * off-whites, one muted sage-forest accent, earthy overnight tones, Outfit + * for text and Geist Mono for stats. Values come from the design exploration + * in openspec/changes/visual-redesign/mockup.html. + * + * Consume the tokens as Tailwind utilities (bg-bg-raised, text-text-md, + * text-accent, shadow-sm, font-mono, …) or as raw CSS vars (var(--color-accent), + * var(--color-eg-lo), …) where a utility can't reach — e.g. canvas/SVG fills. + * + * Fonts are self-hosted (privacy-first — no Google Fonts CDN). + */ + +@import "@fontsource-variable/outfit"; +@import "@fontsource-variable/geist-mono"; + +@theme { + /* ── Surface ── */ + --color-bg: #f5f2eb; /* warm off-white — like uncoated paper */ + --color-bg-subtle: #edeae1; /* slightly deeper — card backgrounds */ + --color-bg-raised: #faf8f4; /* topbar, sidebar, elevated surfaces */ + --color-map-tint: #e4dfd2; /* map background tone */ + + /* ── Text ── */ + --color-text-hi: #1a1916; /* near-black — primary */ + --color-text-md: #5c5847; /* secondary labels */ + --color-text-lo: #9a9484; /* tertiary, placeholders */ + --color-text-inv: #faf8f4; /* on dark surfaces */ + + /* ── Accent: one muted sage-forest green ── */ + --color-accent: #4a6b40; /* primary accent */ + --color-accent-dim: #6a8b5e; /* softer variant */ + --color-accent-bg: rgba(74, 107, 64, 0.08); + --color-accent-border: rgba(74, 107, 64, 0.2); + + /* ── Overnight / stop: warm amber-brown, not orange ── */ + --color-stop: #8b6d3a; + --color-stop-bg: rgba(139, 109, 58, 0.1); + --color-stop-border: rgba(139, 109, 58, 0.25); + + /* ── Danger / no-go ── */ + --color-nogo: rgba(160, 60, 60, 0.12); + --color-nogo-border: rgba(160, 60, 60, 0.3); + + /* ── UI chrome ── */ + --color-border: #ddd9d0; + --color-border-md: #cac6bc; + + /* ── Elevation gradient (route line + chart) ── */ + --color-eg-lo: #5a8f46; + --color-eg-mid: #c4a840; + --color-eg-hi: #c46040; + + /* ── Shadows ── */ + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04); + --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 1px 4px rgba(0, 0, 0, 0.05); + + /* ── Typography ── */ + --font-sans: "Outfit Variable", "Outfit", ui-sans-serif, system-ui, sans-serif; + --font-mono: "Geist Mono Variable", ui-monospace, "SF Mono", monospace; +} + +@layer base { + body { + background-color: var(--color-bg); + color: var(--color-text-hi); + font-family: var(--font-sans); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6db6201..33e60ef 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,6 +6,12 @@ settings: catalogs: default: + '@fontsource-variable/geist-mono': + specifier: ^5.2.6 + version: 5.2.8 + '@fontsource-variable/outfit': + specifier: ^5.2.7 + version: 5.2.8 '@react-router/dev': specifier: ^7.17.0 version: 7.17.0 @@ -257,6 +263,9 @@ importers: '@trails-cool/types': specifier: workspace:* version: link:../../packages/types + '@trails-cool/ui': + specifier: workspace:* + version: link:../../packages/ui drizzle-orm: specifier: 'catalog:' version: 0.45.2(@opentelemetry/api@1.9.1)(@types/pg@8.15.6)(expo-sqlite@56.0.5(expo@56.0.12)(react-native@0.85.3(@babel/core@7.29.7)(@react-native/jest-preset@0.85.3(@babel/core@7.29.7)(react@19.2.7))(@react-native/metro-config@0.85.3(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.7))(react@19.2.7))(pg@8.22.0)(postgres@3.4.9) @@ -508,6 +517,9 @@ importers: '@trails-cool/types': specifier: workspace:* version: link:../../packages/types + '@trails-cool/ui': + specifier: workspace:* + version: link:../../packages/ui codemirror: specifier: ^6.0.2 version: 6.0.2 @@ -671,6 +683,15 @@ importers: packages/types: {} + packages/ui: + dependencies: + '@fontsource-variable/geist-mono': + specifier: 'catalog:' + version: 5.2.8 + '@fontsource-variable/outfit': + specifier: 'catalog:' + version: 5.2.8 + scripts: devDependencies: '@types/node': @@ -1989,6 +2010,12 @@ packages: engines: {node: '>=20.19.0'} hasBin: true + '@fontsource-variable/geist-mono@5.2.8': + resolution: {integrity: sha512-KI5bj+hkkRiHttYHmccotUZ80ZuZyai+RwI1d7UId0clkx/jXxlo8qYK8j54WzmpBjtMoEMPyllV7faDcj+6RA==} + + '@fontsource-variable/outfit@5.2.8': + resolution: {integrity: sha512-4oUDCZx/Tcz6HZP423w/niqEH31Gks5IsqHV2ZZz1qKHaVIZdj2f0/S1IK2n8jl6Xo0o3N+3RjNHlV9R73ozQA==} + '@garmin/fitsdk@21.208.0': resolution: {integrity: sha512-mEmPHyzqaS3bb8n+yaOmgmBo/0CUrsLsN5J4viAUN5Kh6+YiwdvPh5OAzb2Jgy2IaI23q7mGtNr/8WRJC1+HoA==} @@ -9545,6 +9572,10 @@ snapshots: - '@types/node' - rxjs + '@fontsource-variable/geist-mono@5.2.8': {} + + '@fontsource-variable/outfit@5.2.8': {} + '@garmin/fitsdk@21.208.0': {} '@geoman-io/leaflet-geoman-free@2.20.0(leaflet@1.9.4)': diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5dde4fc..4b973a7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,6 +4,8 @@ packages: - "scripts" catalog: + "@fontsource-variable/geist-mono": ^5.2.6 + "@fontsource-variable/outfit": ^5.2.7 "@react-router/dev": ^7.17.0 "@react-router/node": ^7.17.0 "@react-router/serve": ^7.17.0