phase-0: foundation, both apps boot end to end
Monorepo (pnpm workspaces) with two deployable apps and three pure packages. apps/api (Hono on Node): Zod validated env that fails fast and names the problem, Kysely factories for SQLite and Postgres chosen by DATABASE_URL scheme, portable migrations covering the whole SPEC section 5 schema, Better Auth with the four roles and seeded demo accounts, localized error envelope, /healthz and /readyz, graceful SIGTERM drain. Dialect specific SQL is confined to the two factories. apps/web (Next.js App Router): locale routed shell in es and en with a language switcher, sign in screen, and a runtime /api proxy so the browser only ever sees one origin and cookies stay first party. packages/i18n ships both catalogs complete; es is generated from COPY.md and a test re-derives it from the document on every run so it cannot drift. packages/contracts holds the Zod schemas and the typed client the web app uses. Verified: 43 vitest tests, 14 Playwright tests on mobile and desktop, typecheck and lint clean, migrate and seed from a clean database, sign in through the proxy with CSRF rejection of foreign origins. Not verified here: docker compose. This user has no access to the docker socket. RULES.md is absent from docs/, so packages/rules exports only RULES_VERSION and no tax rule, check digit or deadline was invented. See DECISIONS.md. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
/*
|
||||
* Design tokens, FLOWS.md section 1. Calm fintech: one accent, four semantic status
|
||||
* colors used consistently everywhere, generous whitespace, big confident numbers.
|
||||
*/
|
||||
@theme {
|
||||
--font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
|
||||
|
||||
/* Accent: deep teal. Primary actions and links only, nothing else. */
|
||||
--color-accent-50: oklch(0.97 0.02 190);
|
||||
--color-accent-100: oklch(0.93 0.04 190);
|
||||
--color-accent-200: oklch(0.87 0.07 190);
|
||||
--color-accent-400: oklch(0.68 0.11 190);
|
||||
--color-accent-500: oklch(0.58 0.11 190);
|
||||
--color-accent-600: oklch(0.48 0.1 191);
|
||||
--color-accent-700: oklch(0.4 0.085 192);
|
||||
--color-accent-900: oklch(0.27 0.055 194);
|
||||
|
||||
/* Status. positive = a favor, al dia. attention = action required.
|
||||
overdue = missed deadlines and invalid documents ONLY, never "tax to pay".
|
||||
neutral = everything else. */
|
||||
--color-positive: oklch(0.62 0.14 155);
|
||||
--color-positive-soft: oklch(0.95 0.04 155);
|
||||
--color-attention: oklch(0.75 0.15 78);
|
||||
--color-attention-soft: oklch(0.96 0.05 85);
|
||||
--color-overdue: oklch(0.58 0.19 25);
|
||||
--color-overdue-soft: oklch(0.95 0.04 25);
|
||||
--color-neutral-fg: oklch(0.45 0.02 250);
|
||||
|
||||
--radius-card: 1rem;
|
||||
}
|
||||
|
||||
/* Explicit toggle wins over the system preference in both directions (FLOWS.md
|
||||
section 1: dark mode from system preference plus a toggle). */
|
||||
@custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *));
|
||||
|
||||
:root {
|
||||
--surface: oklch(0.99 0.003 250);
|
||||
--surface-raised: oklch(1 0 0);
|
||||
--border-subtle: oklch(0.92 0.005 250);
|
||||
--text: oklch(0.22 0.015 255);
|
||||
--text-muted: oklch(0.52 0.015 255);
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme='light']) {
|
||||
--surface: oklch(0.18 0.012 255);
|
||||
--surface-raised: oklch(0.23 0.014 255);
|
||||
--border-subtle: oklch(0.31 0.012 255);
|
||||
--text: oklch(0.96 0.004 250);
|
||||
--text-muted: oklch(0.72 0.012 255);
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme='dark'] {
|
||||
--surface: oklch(0.18 0.012 255);
|
||||
--surface-raised: oklch(0.23 0.014 255);
|
||||
--border-subtle: oklch(0.31 0.012 255);
|
||||
--text: oklch(0.96 0.004 250);
|
||||
--text-muted: oklch(0.72 0.012 255);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
border-color: var(--border-subtle);
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--surface);
|
||||
color: var(--text);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
/* Every money figure is tabular. FLOWS.md section 1. */
|
||||
.tnum {
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-feature-settings: 'tnum';
|
||||
}
|
||||
}
|
||||
|
||||
/* Motion is purposeful and always interruptible. This disables the non essential
|
||||
kind globally, which the GSAP context mirrors. */
|
||||
@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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user