Everything else on the Displays page auto-applies — a preset click, the game-session choice, the experimental toggles — but the Custom block does not, and its Save button sat at the bottom of a block taller than most viewports. People edited, never scrolled far enough to find it, navigated away, and silently lost the lot. The draft is now compared against the last-seeded server value, and that one boolean drives the whole affordance: a badge in the card header (visible without scrolling), an amber ring plus a title on the block, and a sticky action bar pinned to the viewport for as long as any part of the block is on screen. The bar states which of the two states you are in rather than leaving it implied, Save disables when there is nothing to save, and Discard changes puts the stored policy back. Two ways edits could still vanish are closed: a preset click now confirms before overwriting pending edits, and a reload/close warns. Re-picking Custom while already on Custom is a no-op instead of re-seeding, which would otherwise fill in defaults for unset fields and report "unsaved changes" for a click that changed nothing. Adds a `warning` badge variant + --warning token for the pending state — attention, not failure, and distinct from the destructive red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> (cherry picked from commit c4318609c0da5ae1313081c5e488f685504f70d1)
223 lines
7.2 KiB
CSS
223 lines
7.2 KiB
CSS
@import "tailwindcss";
|
|
@import "tw-animate-css";
|
|
@import "./timing-functions.css";
|
|
|
|
@custom-variant dark (&:is(.dark *));
|
|
|
|
/* Pull @unom/ui's compiled component classes (bg-neutral,
|
|
rounded-card, p-padding-card, ring-accent, h-input-height, material…) into the
|
|
Tailwind 4 scan so their utilities aren't purged. */
|
|
@source "../node_modules/@unom/ui/dist/**/*.{js,mjs}";
|
|
|
|
/* ── punktfunk brand · violet product chrome ────────────────────────────────
|
|
Two themes on one violet identity: LIGHT (lavender docs surface — the same
|
|
palette the design/Scalar reference uses: white bg, faint-violet cards/borders,
|
|
#6c5bf3 brand) is the :root default; DARK (the violet-tinted app-icon chrome
|
|
#141019 / #1c1530, #a79ff8 brand) is the `.dark` override. The live console
|
|
pins `<html class="dark">` so it stays dark by default — removing or toggling
|
|
that class (e.g. Storybook's theme switch) yields light. The token set feeds
|
|
BOTH @unom/ui's semantic contract (--brand/--primary/--accent/--neutral/
|
|
--main…) and the shadcn-style tokens the console's primitives + routes use
|
|
(--background/--card/--muted…), mapped onto one palette so both render the
|
|
same identity.
|
|
|
|
Brand constants and the surface tokens that are pure indirections
|
|
(--main, --neutral*, --error → other tokens) live in :root only — CSS resolves
|
|
var() per-theme at use time, so .dark overrides just the raw values. */
|
|
:root {
|
|
--radius: 0.625rem;
|
|
|
|
/* Brand — the violet lens mark (from the punktfunk app icon). Theme-independent. */
|
|
--pf-brand: #6c5bf3; /* deep violet — primary on light */
|
|
--pf-brand-light: #a79ff8; /* light violet — primary on dark */
|
|
--pf-highlight: #d2c9fb; /* lens highlight */
|
|
|
|
/* Surfaces — light · lavender (white bg, faint-violet cards/borders). */
|
|
--background: #ffffff;
|
|
--foreground: #1b1430;
|
|
--card: #f6f2ff;
|
|
--card-foreground: #1b1430;
|
|
--popover: #ffffff;
|
|
--popover-foreground: #1b1430;
|
|
--muted: #f1ecfd;
|
|
--muted-foreground: #6f6a86;
|
|
--secondary: #ece6fb;
|
|
--secondary-foreground: #1b1430;
|
|
/* shadcn `accent` = subtle hover surface; also @unom/ui's card ring colour,
|
|
so we tint it toward the brand violet (the same in both themes). */
|
|
--accent: var(--pf-brand);
|
|
--accent-foreground: #ffffff;
|
|
--border: #e4dcf7;
|
|
--input: #e4dcf7;
|
|
--ring: var(--pf-brand);
|
|
|
|
/* Primary = the brand (buttons, active nav, default badges). */
|
|
--primary: var(--pf-brand);
|
|
--primary-foreground: #ffffff;
|
|
|
|
--success: oklch(0.6 0.14 160);
|
|
/* Amber — "needs your attention but nothing is broken" (unsaved edits), the badge
|
|
counterpart of the inline amber warning text. */
|
|
--warning: oklch(0.72 0.15 75);
|
|
--destructive: oklch(0.55 0.22 18);
|
|
--destructive-foreground: #ffffff;
|
|
|
|
/* ── @unom/ui semantic token contract (its components read these names). ──
|
|
These are indirections — they follow the raw tokens above per-theme. */
|
|
--main: var(--foreground);
|
|
--brand: var(--pf-brand);
|
|
--brand-light: var(--pf-brand-light);
|
|
--highlight: var(--pf-highlight);
|
|
--neutral: var(--card); /* @unom card default surface (bg-neutral) */
|
|
--neutral-accent: var(
|
|
--secondary
|
|
); /* accent / nested surface (bg-neutral-accent) */
|
|
--neutral-highlight: var(--border);
|
|
--error: var(--destructive);
|
|
|
|
--font-display: "Geist Variable", ui-sans-serif, system-ui, sans-serif;
|
|
--font-sans: "Geist Variable", ui-sans-serif, system-ui, sans-serif;
|
|
|
|
/* @unom/ui radius/spacing contract (pill buttons, rounded cards, tall inputs). */
|
|
--radius-card-min: var(--radius);
|
|
}
|
|
|
|
/* Dark · the violet-tinted app-icon chrome. Overrides only the raw values —
|
|
the indirection tokens in :root resolve to these automatically. */
|
|
.dark {
|
|
--background: #141019;
|
|
--foreground: oklch(0.985 0 0);
|
|
--card: #1c1530;
|
|
--card-foreground: oklch(0.985 0 0);
|
|
--popover: #1c1530;
|
|
--popover-foreground: oklch(0.985 0 0);
|
|
--muted: #1f1830;
|
|
--muted-foreground: oklch(0.728 0.03 286);
|
|
--secondary: #241c3d;
|
|
--secondary-foreground: oklch(0.985 0 0);
|
|
--border: #2a2148;
|
|
--input: #2a2148;
|
|
--ring: var(--pf-brand-light);
|
|
|
|
/* Lighter violet reads better against the dark surface. */
|
|
--primary: var(--pf-brand-light);
|
|
--primary-foreground: #141019;
|
|
|
|
--success: oklch(0.7 0.15 160);
|
|
--warning: oklch(0.78 0.16 75);
|
|
--destructive: oklch(0.62 0.21 18);
|
|
--destructive-foreground: oklch(0.985 0 0);
|
|
}
|
|
|
|
/* Map the palette to Tailwind colour/util tokens — both the shadcn vocabulary
|
|
and @unom/ui's, resolved to one set of values. */
|
|
@theme inline {
|
|
--radius-sm: calc(var(--radius) - 4px);
|
|
--radius-md: calc(var(--radius) - 2px);
|
|
--radius-lg: var(--radius);
|
|
--radius-xl: calc(var(--radius) + 4px);
|
|
--radius-button: 9999px;
|
|
--radius-card: calc(var(--radius) * 2);
|
|
--radius-main: calc(var(--radius) * 2);
|
|
|
|
--spacing-input-height: 3rem;
|
|
--spacing-padding-card: 1.25rem;
|
|
--spacing-card: 1.5rem;
|
|
--spacing-main: 15px;
|
|
|
|
--font-sans: var(--font-sans);
|
|
--font-display: var(--font-display);
|
|
|
|
/* shadcn-style colour tokens. */
|
|
--color-background: var(--background);
|
|
--color-foreground: var(--foreground);
|
|
--color-card: var(--card);
|
|
--color-card-foreground: var(--card-foreground);
|
|
--color-popover: var(--popover);
|
|
--color-popover-foreground: var(--popover-foreground);
|
|
--color-primary: var(--primary);
|
|
--color-primary-foreground: var(--primary-foreground);
|
|
--color-secondary: var(--secondary);
|
|
--color-secondary-foreground: var(--secondary-foreground);
|
|
--color-muted: var(--muted);
|
|
--color-muted-foreground: var(--muted-foreground);
|
|
--color-accent: var(--accent);
|
|
--color-accent-foreground: var(--accent-foreground);
|
|
--color-destructive: var(--destructive);
|
|
--color-destructive-foreground: var(--destructive-foreground);
|
|
--color-success: var(--success);
|
|
--color-warning: var(--warning);
|
|
--color-border: var(--border);
|
|
--color-input: var(--input);
|
|
--color-ring: var(--ring);
|
|
|
|
/* @unom/ui colour tokens. */
|
|
--color-main: var(--main);
|
|
--color-brand: var(--brand);
|
|
--color-brand-light: var(--brand-light);
|
|
--color-neutral: var(--neutral);
|
|
--color-neutral-accent: var(--neutral-accent);
|
|
--color-neutral-highlight: var(--neutral-highlight);
|
|
--color-highlight: var(--highlight);
|
|
--color-error: var(--error);
|
|
}
|
|
|
|
/* Accordion / collapsible keyframes @unom/ui's interactive surfaces animate to. */
|
|
@theme {
|
|
--animate-accordion-down: accordion-down 0.4s var(--ease-out-quart);
|
|
--animate-accordion-up: accordion-up 0.4s var(--ease-out-quart);
|
|
--animate-collapsible-down: collapsible-down 0.4s var(--ease-out-quart);
|
|
--animate-collapsible-up: collapsible-up 0.4s var(--ease-out-quart);
|
|
|
|
@keyframes accordion-down {
|
|
from {
|
|
height: 0;
|
|
}
|
|
to {
|
|
height: var(--radix-accordion-content-height);
|
|
}
|
|
}
|
|
@keyframes accordion-up {
|
|
from {
|
|
height: var(--radix-accordion-content-height);
|
|
}
|
|
to {
|
|
height: 0;
|
|
}
|
|
}
|
|
@keyframes collapsible-down {
|
|
from {
|
|
height: 0;
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
height: var(--radix-collapsible-content-height);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
@keyframes collapsible-up {
|
|
from {
|
|
height: var(--radix-collapsible-content-height);
|
|
opacity: 1;
|
|
}
|
|
to {
|
|
height: 0;
|
|
opacity: 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
@layer base {
|
|
* {
|
|
border-color: var(--border);
|
|
}
|
|
body {
|
|
background-color: var(--background);
|
|
color: var(--foreground);
|
|
font-family: var(--font-sans);
|
|
font-feature-settings:
|
|
"rlig" 1,
|
|
"calt" 1;
|
|
}
|
|
}
|