Files
HealthLog/src/app/globals.css
T
Marc-André Bombeck 0255dbad7d fix(medications): repoint dose accent onto an AA-safe theme token
The medication cards stamped the upcoming schedule dose with Tailwind
stock text-purple-400, which reads ~3.1:1 on the white Alucard light
card and fails WCAG AA for body text. Add a theme-aware --dose-accent
token: the exact text-purple-400 oklch in dark mode (so the dark card
stays pixel-identical) and a darker AA-safe purple on the light card,
mirroring how the semantic feedback tokens land each tone above AA in
both themes. The cards switch to text-dose-accent in the follow-up
commit.
2026-06-03 07:07:48 +02:00

411 lines
14 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@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 `<repo-root>/.planning` and
* `<repo-root>/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 `<html>` for exactly those branches.
*
* The authenticated app shell is height-locked (`flex h-dvh`) and scrolls
* inside its own `<main>`, 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 `<AdminShell>` and
* `<SettingsShell>` 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 <HeroStrip> 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;
}
}