Files
impuestospy/apps/web/app/globals.css
T
MichilisandClaude Opus 5 ae2ea20b7e 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>
2026-09-03 21:46:35 +00:00

97 lines
2.9 KiB
CSS

@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;
}
}