@import "tailwindcss" source("../.."); @import "tw-animate-css"; @import "shadcn/tailwind.css"; /* * Exclude planning + docs directories from Tailwind v4's content scan. * * v4's scanner walks the entire project root by default and picks any * class-shaped token out of every text file. Planning reports and * design docs often quote literal class-name fragments such as * `bg-[var(...)]/12` as examples, and the bracketed-CSS-variable form * combined with an opacity modifier generates an escaped selector that * Turbopack's CSS parser rejects with `Unexpected token Delim('.')`, * 500-ing every page. Constraining the scan to source code avoids * fixture-style strings poisoning the generated CSS. * * The `source("../..")` argument on the `@import "tailwindcss"` line * above pins auto-detection to the project root (resolved from this * stylesheet) instead of letting Tailwind walk up from whatever cwd * the build tool happens to be sitting at. That mirrors the * cwd-anchored fix the safety-contracts loader landed in * commit `dd44a0d` and stops the planning / docs directories from * leaking class-shaped fragments into the generated CSS regardless of * whether the build runs from the repo root or a tooling subdir. * * The `@source not` directives below are resolved relative to this * stylesheet, so they point at `/.planning` and * `/docs`. */ @source not "../../.planning"; @source not "../../docs"; @custom-variant dark (&:is(.dark *)); @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); --font-sans: var(--font-sans); --font-mono: var(--font-mono); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar: var(--sidebar); --color-chart-5: var(--chart-5); --color-chart-4: var(--chart-4); --color-chart-3: var(--chart-3); --color-chart-2: var(--chart-2); --color-chart-1: var(--chart-1); --color-ring: var(--ring); --color-input: var(--input); --color-border: var(--border); --color-destructive: var(--destructive); --color-accent-foreground: var(--accent-foreground); --color-accent: var(--accent); --color-muted-foreground: var(--muted-foreground); --color-muted: var(--muted); --color-secondary-foreground: var(--secondary-foreground); --color-secondary: var(--secondary); --color-primary-foreground: var(--primary-foreground); --color-primary: var(--primary); --color-popover-foreground: var(--popover-foreground); --color-popover: var(--popover); --color-card-foreground: var(--card-foreground); --color-card: var(--card); --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) + 4px); --radius-2xl: calc(var(--radius) + 8px); --radius-3xl: calc(var(--radius) + 12px); --radius-4xl: calc(var(--radius) + 16px); /* Dracula semantic colors for charts */ --color-dracula-purple: var(--dracula-purple); --color-dracula-pink: var(--dracula-pink); --color-dracula-green: var(--dracula-green); --color-dracula-cyan: var(--dracula-cyan); --color-dracula-orange: var(--dracula-orange); --color-dracula-red: var(--dracula-red); --color-dracula-yellow: var(--dracula-yellow); --color-dracula-lavender: var(--dracula-lavender); /* * Semantic feedback tokens — aliases over the Dracula palette so badges, * banners, and status pills can stop hard-coding `text-dracula-green` / * `bg-dracula-orange/15` and ask for the *meaning* instead. Each is a * straight alias of the existing colour at the time of introduction so * the visual stays pixel-identical to v1.4.6 — what changes is intent. * - success → dracula-green (positive outcome, e.g. published) * - warning → dracula-orange (caution / non-blocking attention) * - info → dracula-cyan (neutral informational note) */ --color-success: var(--success); --color-warning: var(--warning); --color-info: var(--info); /* * Status-pill text tokens. The target status pill paints coloured text * on a 10% same-hue tint; the raw Dracula green/orange/red would fail * WCAG AA (4.5:1) for the "out" tone on the muted reference panel in * dark mode and for every tone on a white card in light mode. These * theme-aware tokens land each tone at or above AA on both `bg-card` * and `bg-muted/30` in both themes while keeping the Dracula feel. */ --color-status-pill-in: var(--status-pill-in); --color-status-pill-near: var(--status-pill-near); --color-status-pill-out: var(--status-pill-out); /* * Dose-accent token. The medication cards stamp the upcoming schedule * dose in purple. They previously hard-coded Tailwind stock * `text-purple-400` (`oklch(0.714 0.203 305.504)`), which reads ~3.1:1 on * the white Alucard card and fails WCAG AA (4.5:1) for body text. This * theme-aware token keeps the exact dark-mode purple and swaps to a darker * AA-safe purple on the light card — mirroring how the semantic feedback * tokens (`--success` / `--warning` / `--info`) land each tone above AA in * both themes. */ --color-dose-accent: var(--dose-accent); } /* * Dracula Theme * Based on https://draculatheme.com/contribute * Mapped to shadcn/ui CSS variable system */ /* ── Dracula palette (shared) ────────────────────── */ :root { --dracula-bg: #282a36; --dracula-current: #44475a; --dracula-fg: #f8f8f2; --dracula-comment: #9aa3b3; --dracula-cyan: #8be9fd; --dracula-green: #50fa7b; --dracula-orange: #ffb86c; --dracula-pink: #ff79c6; --dracula-purple: #bd93f9; --dracula-red: #ff5555; --dracula-yellow: #f1fa8c; --dracula-lavender: #d6acff; /* * Semantic feedback tokens (see @theme `--color-success` / `--color-warning` * / `--color-info` re-exports). Aliased over the Dracula palette so existing * dark-mode visuals stay identical; light mode (Alucard) below provides the * higher-contrast counterpart so AA contrast lands on the white card. */ --success: var(--dracula-green); --warning: var(--dracula-orange); --info: var(--dracula-cyan); --radius: 0.625rem; } /* ── Dark mode (default) ─────────────────────────── */ :root.dark { --background: #282a36; --foreground: #f8f8f2; --card: #1e1f29; --card-foreground: #f8f8f2; --popover: #1e1f29; --popover-foreground: #f8f8f2; --primary: #bd93f9; --primary-foreground: #282a36; --secondary: #44475a; --secondary-foreground: #f8f8f2; --muted: #44475a; --muted-foreground: #9aa3b3; --accent: #44475a; --accent-foreground: #f8f8f2; --destructive: #ff5555; --border: #44475a; --input: #44475a; --ring: #bd93f9; --chart-1: #bd93f9; --chart-2: #50fa7b; --chart-3: #ff79c6; --chart-4: #8be9fd; --chart-5: #ffb86c; --sidebar: #1e1f29; --sidebar-foreground: #f8f8f2; --sidebar-primary: #bd93f9; --sidebar-primary-foreground: #f8f8f2; --sidebar-accent: #44475a; --sidebar-accent-foreground: #f8f8f2; --sidebar-border: #44475a; --sidebar-ring: #bd93f9; /* * Status-pill text in dark mode. "in" / "near" keep the Dracula * green / orange (both clear AA on the muted panel); "out" lifts off * the raw `#ff5555`, which lands at 3.99:1 on `bg-muted/30`, to a * lighter Dracula red that clears 4.5:1 on both card and panel. */ --status-pill-in: #50fa7b; --status-pill-near: #ffb86c; --status-pill-out: #ff8a8a; /* * Dose accent in dark mode — the exact Tailwind `text-purple-400` * (`oklch(0.714 0.203 305.504)`) the cards used before, so the dark card * stays pixel-identical. AA on the dark card at ~6.8:1. */ --dose-accent: oklch(0.714 0.203 305.504); color-scheme: dark; } /* ── Light mode (Alucard — official Dracula light) ── */ :root.light { --background: #f5f5f5; --foreground: #1f1f1f; --card: #ffffff; --card-foreground: #1f1f1f; --popover: #ffffff; --popover-foreground: #1f1f1f; --primary: #644ac9; --primary-foreground: #ffffff; --secondary: #dcdeef; --secondary-foreground: #1f1f1f; --muted: #dcdeef; --muted-foreground: #5b6273; --accent: #dcdeef; --accent-foreground: #1f1f1f; --destructive: #cb3a2a; /* * Alucard light-mode overrides for the semantic tokens — straight Dracula * green/orange/cyan would fail AA contrast on a white card. These match the * darker palette already used for `--destructive`/`--chart-*` in light mode. */ --success: #14720a; --warning: #a34d14; --info: #036a96; --border: #cfcfde; --input: #cfcfde; --ring: #644ac9; --chart-1: #644ac9; --chart-2: #14720a; --chart-3: #a3134d; --chart-4: #036a96; --chart-5: #a34d14; --sidebar: #ffffff; --sidebar-foreground: #1f1f1f; --sidebar-primary: #644ac9; --sidebar-primary-foreground: #ffffff; --sidebar-accent: #dcdeef; --sidebar-accent-foreground: #1f1f1f; --sidebar-border: #cfcfde; --sidebar-ring: #644ac9; /* * Status-pill text in light mode (Alucard). The bright Dracula * green / orange / red read 1.2–2.7:1 on a near-white tint; these * darker tones — the same family already used for `--success` / * `--warning` / the light-mode reds — clear AA on both the white card * and the muted reference panel. */ --status-pill-in: #14720a; --status-pill-near: #a34d14; --status-pill-out: #b32d1e; /* * Dose accent in light mode (Alucard). The bright `text-purple-400` reads * ~3.1:1 on the white card and fails AA; this darker purple — the same * family as the light-mode `--primary` (#644ac9) — clears 4.5:1 on both * the white card and the muted panel while keeping the purple identity. */ --dose-accent: #6f42c1; color-scheme: light; } @layer base { * { @apply border-border outline-ring/50; } /* * Reserve the scrollbar gutter on the document root ONLY for the * body-scrolled shells (login / register / onboarding / standalone legal * pages), where the document itself overflows. Those keep a fixed content * width whether or not the page paints the bar, so any `mx-auto` content * does not recentre a few px sideways on navigation. `AuthShell` sets * `data-scroll="body"` on `` for exactly those branches. * * The authenticated app shell is height-locked (`flex h-dvh`) and scrolls * inside its own `
`, which reserves its own gutter. Applying this * rule globally there reserved a SECOND, never-painted gutter on classic * (non-overlay) scrollbar platforms (Windows / Linux) — a ~15px dead band. * Scoping it to the body-scrolled branch leaves exactly one gutter per * branch. */ html[data-scroll="body"] { scrollbar-gutter: stable; } body { @apply bg-background text-foreground; } p { color: var(--foreground); } } /* * `.no-scrollbar` — keeps a container scrollable but hides the visible * bar. Used by the mobile section strips in `` and * `` so 13 admin / 10 settings entries can swipe * horizontally without painting an always-on scrollbar at the top of * every admin/settings page (a recurring maintainer complaint — most * recently mistaken for a `/admin/api-tokens` table scrollbar in * v1.4.16/17 because the strip sits right above the api-tokens card). * * Standard `scrollbar-width: none` (Firefox) + `::-webkit-scrollbar` * (Chromium/WebKit) covers every browser HealthLog targets. Touch * scrolling, keyboard arrow scrolling, and `Tab` focus traversal all * keep working — this is a purely cosmetic suppression, not an * `overflow: hidden`. */ .no-scrollbar { scrollbar-width: none; -ms-overflow-style: none; } .no-scrollbar::-webkit-scrollbar { display: none; } /* ── Insight card animations ─────────────────────── */ @keyframes insight-fade-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } .animate-insight-in { animation: insight-fade-in 0.4s ease-out forwards; } @media (prefers-reduced-motion: reduce) { .animate-insight-in { animation: none; } } /* ── v1.4.20 phase B1 — Insights redesign hero strip ─────────────── */ /* * `.hero-gradient` — purple → pink linear gradient at low opacity, for * the new band on /insights. Sits above `var(--card)` so * the band reads as a richer surface than the card it overlays. * * Matches the design handoff (`prototype/artboard-fullpage.jsx → * BriefingHero` background) — purple/pink at ~12% opacity over the * card colour, with a subtle border-tint to anchor it on the dark bg. */ .hero-gradient { background: linear-gradient( 135deg, color-mix(in srgb, var(--dracula-purple) 18%, var(--card)) 0%, color-mix(in srgb, var(--dracula-pink) 12%, var(--card)) 100% ); border: 1px solid color-mix(in srgb, var(--dracula-purple) 24%, transparent); } /* * `.glow-purple` — soft purple aura behind the hero card, painted via * `box-shadow` so it stays GPU-cheap and respects the existing card * border radius without an extra wrapper element. */ .glow-purple { box-shadow: 0 0 0 1px color-mix(in srgb, var(--dracula-purple) 16%, transparent), 0 18px 48px -28px color-mix(in srgb, var(--dracula-purple) 50%, transparent); } /* * `pulseDot` keyframes — the "live, refreshed N min ago" green dot in * the hero meta row. Used as `.pulse-dot { animation: pulseDot ... }`. * Reduced-motion users get a static dot via the prefers-reduced-motion * guard below. */ @keyframes pulseDot { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(0.85); } } .pulse-dot { animation: pulseDot 1.8s ease-in-out infinite; } @media (prefers-reduced-motion: reduce) { .pulse-dot { animation: none; } }