/* @hb/ui design tokens + semantic utilities.
 *
 * The single source of truth for the shared appearance layer: brand + data
 * colours (theme-aware via `.dark`), the semantic money utilities, the brand
 * accent for checkboxes/radios, and the global keyframes/scrollbar chrome.
 *
 * Consumers import this ONCE (after their own `@import "tailwindcss"`):
 *   @import "@hb/ui/tokens.css";
 * and add the package to Tailwind's source scan so its class names are kept:
 *   @source "../node_modules/@hb/ui/src";
 *
 * NOTE: these :root/.dark values MIRROR DEFAULT_PRESET in the palette theme module,
 * which is what actually paints them — the palette layer writes the active preset
 * tokens inline on <html> at runtime (Tailwind v4 strips a root block that holds only
 * custom properties, so this block is a documentation mirror, not the live source).
 *
 * Because it is inert, it drifts silently, and it had: the surfaces and text colour
 * below still held the pre-#328 palette while DEFAULT_PRESET had moved to Solar Dusk.
 * They were resynced when --text-secondary/--text-muted were added (the new tones are
 * derived from these surfaces, so a stale mirror would have documented a contrast ratio
 * that was not the shipped one). The MONEY TRIO and the nine chart hues below are still
 * the older values and have not been touched — nothing reads them, but do not cite them
 * as the default without checking DEFAULT_PRESET first. */

@custom-variant dark (&:where(.dark, .dark *));

:root {
  --bg-page: #ede3cf;          /* warm dim sand — low-glare light page */
  --bg-surface: #f6ecd9;       /* warm off-white card/panel (not pure white) */
  --bg-surface-2: #f3ead6;     /* inset/well surface */
  --border: #e2d6bd;           /* warm hairline border */
  --text-primary: #463f33;     /* warm dark grey — not pure black */
  /* The two quieter text roles. Contrast against the WORST of the three surfaces
     above: 7.0:1 and 4.8:1, so both clear WCAG AA for normal text on all of them —
     which is the reason they are tokens and not `text-slate-500`, a cool grey that
     ignores the palette and sat at 4.4:1 on this ground. */
  --text-secondary: #50493c;
  --text-muted: #696153;
  --brand: #4f46e5;            /* indigo-600 */
  --brand-hover: #4338ca;      /* indigo-700 */
  --brand-contrast: #ffffff;
  --money-income: #0f766e;     /* teal-700 */
  --money-expense: #b45309;    /* amber-700 */
  --money-net: #6d28d9;        /* violet-700 */
  --money-neutral: #6b6253;    /* warm slate */
  --chart-1: #332288;
  --chart-2: #88ccee;
  --chart-3: #44aa99;
  --chart-4: #117733;
  --chart-5: #999933;
  --chart-6: #ddcc77;
  --chart-7: #cc6677;
  --chart-8: #882255;
  --chart-9: #aa4499;
}

.dark {
  --bg-page: #0e0f1c;          /* indigo near-black */
  --bg-surface: #181a2c;
  --bg-surface-2: #21243a;     /* inset/well surface */
  --border: #2e3252;
  --text-primary: #e6e8f4;
  /* As in `:root` — 7.0:1 and 4.8:1 against the worst of the three dark surfaces. */
  --text-secondary: #adafbe;
  --text-muted: #8d90a0;
  --brand: #818cf8;            /* indigo-400 */
  --brand-hover: #a5b4fc;      /* indigo-300 */
  --brand-contrast: #1e1b4b;   /* indigo-950 */
  --money-income: #2dd4bf;     /* teal-400 */
  /* Deliberately NOT amber-400 (#fbbf24). Every other colour on a dark surface
     sits around OKLab L 0.68–0.74 (brand 0.68, net 0.70, neutral 0.71, income
     0.74); amber-400 lands at 0.84, so outflows — most rows in a register — shouted
     a tenth of a lightness step louder than inflows carrying the same weight of
     meaning. Same hue (84°) and chroma, pulled to L 0.785: still 8.9:1 on the card
     surface, still far from income in CVD space (Keksdose dev#434). */
  --money-expense: #e4b035;
  --money-net: #a98fd6;
  --money-neutral: #a1a1aa;    /* zinc-400 */
  --chart-1: #7e72d6;
  --chart-2: #9fd8f2;
  --chart-3: #5fc4b0;
  --chart-4: #3fa45f;
  --chart-5: #bfbf5e;
  --chart-6: #e8dda0;
  --chart-7: #e08c9a;
  --chart-8: #c25a78;
  --chart-9: #cc78be;
}

/* Semantic colour utilities backed by the tokens above. Unlayered so they win
   over Tailwind's layered colour utilities, and theme-aware for free (the var
   flips under .dark) — no dark: variant needed at the call site. */
.text-money-pos { color: var(--money-income); }
.text-money-neg { color: var(--money-expense); }
.text-money-net { color: var(--money-net); }
.text-money-pos-muted { color: var(--money-income); opacity: 0.72; }
.text-money-neg-muted { color: var(--money-expense); opacity: 0.72; }
.text-brand { color: var(--brand); }
.bg-brand { background-color: var(--brand); }
.bg-money-pos { background-color: var(--money-income); }
.bg-money-neg { background-color: var(--money-expense); }
/* "No money has moved" — the fill for markers on rows that are not real yet
   (scheduled / future-dated). Deliberately NOT brand and NOT money-pos: those
   two mean settled, and a future row wearing one of them reads as done. */
.bg-money-neutral { background-color: var(--money-neutral); }
.border-brand { border-color: var(--brand); }

html, body, #root {
  height: 100%;
}

/* NOT a `scrollbar-gutter` on the DOCUMENT — measured, and it is the wrong instrument.
 *
 * Below `AppShell`'s md breakpoint the viewport is the scroll container (main is only
 * `md:overflow-y-auto`), so nothing reserves a gutter down there and a classic
 * scrollbar takes ~15px out of the content box the moment a page outgrows the window:
 * Keksdose live #307's follow-up, reported as "resizes slightly horizontally".
 *
 * Reserving it on `html` fixes that and breaks something worse. `scrollbar-gutter` on
 * the root shrinks the INITIAL CONTAINING BLOCK, which is what `position: fixed` is
 * laid out against — so every full-bleed overlay in the app stops 15px short on each
 * edge. Measured at a 406px viewport: the row editor's dialog went from x=0/w=406 to
 * x=15/w=376, with live page showing through both strips and tappable. That is
 * Keksdose feedback #32's failure, worse: #32's strip at least belonged to the
 * backdrop, and a tap on this one reaches the register behind the form.
 *
 * The only fix without that side effect is to give the viewport's job to an ELEMENT —
 * dropping the `md:` gate on main's own `overflow-y-auto` — which changes how a phone
 * scrolls (the URL bar stops collapsing) for a bug phones do not have, since overlay
 * scrollbars take no layout space. Left as a decision rather than a silent trade. */

/* Checkboxes/radios default to a visible brand accent. The native dark fill
   blended into the dark UI, so a checked box was almost indistinguishable from
   an unchecked one (feedback #219). Must live in the base layer: unlayered
   styles beat Tailwind's layered utilities regardless of specificity, which
   silently overrode deliberate `accent-*` utilities like the destructive rose
   fresh-start checkbox (feedback #264). */
@layer base {
  :where(input[type="checkbox"], input[type="radio"]) {
    accent-color: var(--brand); /* unified indigo brand accent (theme-aware) */
  }
}

body {
  background-color: var(--bg-page);
  color: var(--text-primary);
}

/* Indeterminate progress bar (e.g. long imports, feedback #270): a third-width
   segment sweeping the track. */
@keyframes indeterminate-sweep {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(300%); }
}
.animate-indeterminate {
  animation: indeterminate-sweep 1.4s ease-in-out infinite;
}

/* Overlay motion (Keksdose live #320: *"Evaluate how much work it would be to have
   nicer transitions for the mobile users when clicking on a row … for all hoc derived
   tables"*).
 *
 * Two keyframes rather than a library. Every overlay in this package is bottom-anchored
 * on a phone and centred from md up, so one pair covers the row sheet, the modal, the
 * picker and the numpad — and every table in every consuming app gets it by getting
 * them, which is what the row asked for.
 *
 * Enter AND exit since the #320 rework — *"Recognized the opening transition. Choose
 * transition should be the same, only backwards"*. The first round shipped the enter
 * alone on the argument that an exit needs the element to stay mounted past the state
 * change; that is still true, and `useCloseTransition` is where that state machine now
 * lives, once, for every overlay in this package.
 *
 * The exit is the same two moves read backwards, at the same durations, on the mirrored
 * curve (`ease-in` against `ease-out`, and the sheet's cubic reversed): a close that
 * eases OUT of the screen at the speed it eased in reads as the same object leaving.
 *
 * 180ms and an ease-out curve: long enough to read as movement, short enough that it
 * never delays a screen whose data is already in memory — which most of these are. The
 * exit is what that budget is really spent on: every millisecond here is a millisecond
 * between a tap on "Close" and the list being usable again, which is why it does not
 * get the slower, more cinematic curve a first attempt reaches for.
 */
@keyframes overlay-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes sheet-rise-in {
  from { transform: translateY(100%); }
  to { transform: none; }
}
@keyframes overlay-fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}
@keyframes sheet-lower-out {
  from { transform: none; }
  to { transform: translateY(100%); }
}
.animate-overlay { animation: overlay-fade-in 0.18s ease-out; }
.animate-sheet { animation: sheet-rise-in 0.22s cubic-bezier(0.32, 0.72, 0, 1); }
/* `forwards` on both: the element is unmounted by a timer, and without a retained end
   state the last painted frame before that timer fires is the panel back at full
   opacity in its opening position — a flash of the thing you just dismissed. */
.animate-overlay-out { animation: overlay-fade-out 0.18s ease-in forwards; }
.animate-sheet-out { animation: sheet-lower-out 0.22s cubic-bezier(1, 0, 0.68, 0.28) forwards; }

/* Motion is the first thing to drop here, not the last. The overlay still fades — a
   backdrop appearing instantly is a flash, not a transition — but nothing travels.
   The EXIT pair is not listed: `useCloseTransition` reads the same media query and
   closes immediately under it, so these classes are never applied at all. */
@media (prefers-reduced-motion: reduce) {
  .animate-sheet { animation: none; }
  .animate-overlay { animation-duration: 0.01ms; }
}

/* Chart drilldown fan-out/-in (feedback #260 rework): the remounted chart eases
   in instead of snapping. */
@keyframes drill-in {
  from { opacity: 0; transform: scale(0.96); }
  to { opacity: 1; transform: none; }
}
.animate-drill {
  animation: drill-in 0.25s ease-out;
}

/* Scrollbars: keep light defaults; theme to slate when dark mode is active. */
.dark {
  color-scheme: dark;
  scrollbar-color: rgb(51 65 85) rgb(15 23 42); /* slate-700 thumb on slate-900 track */
}

.dark ::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}

.dark ::-webkit-scrollbar-track {
  background-color: rgb(15 23 42); /* slate-900 */
}

.dark ::-webkit-scrollbar-thumb {
  background-color: rgb(51 65 85); /* slate-700 */
  border-radius: 6px;
  border: 2px solid rgb(15 23 42); /* slate-900 — creates inset padding */
}

.dark ::-webkit-scrollbar-thumb:hover {
  background-color: rgb(71 85 105); /* slate-600 */
}

.dark ::-webkit-scrollbar-corner {
  background-color: rgb(15 23 42); /* slate-900 */
}
