Closes the migrate-button-input-mobilenav-to-glass-primitive convoy (seeded by PR #127). All 3 residual handrolled var(--glass-surface-*) inline-style usages migrated to either purpose-built utility classes or the <GlassSurface> primitive. CI allowlist reduced from 6 entries to 3 (chrome only). Architect decisions (D1-D3, ratified): D1 — Button.secondary → new .btn-glass-secondary utility class. NOT <GlassSurface>: the primitive sets `background` inline via composedStyle, which CSS :hover rules can't override without !important. The new class composes the same high-tint gradient-border that .glass-panel-strong uses, plus a pure-CSS :hover swap (high → mid fill on the padding-box layer). Identical visual contract; the hover behavior is now driven by CSS, not Tailwind's `hover:bg-[var(...)]` arbitrary class. D2 — Input → new .glass-input utility class. NOT <GlassSurface as="input"> and NOT <GlassSurface as="div"> wrap. Reason: <GlassSurface>'s gradient-border trick requires `border: 1px solid transparent` to expose the border-box layers, which conflicts with <Input>'s conditional error-state `1px solid #dc2626` red border. The new class adopts only the tint + blur layer; the visible 1px border + focus ring stay in JSX (class-controlled, not inline). Same visual contract as before for both normal AND error states. D3 — MobileNavigation → <GlassSurface as="div" tint="mid" blur="mid" rim="subtle" elevation="flat" cornerLights="chrome">. NOT .page-header-glass (the seed's first recommendation): .page-header-glass uses var(--glass-surface-high) (wrong tint — MobileNav uses mid) and sets a bottom-border separator (wrong for a fixed-bottom-nav where the bottom edge is the viewport edge). <GlassSurface> is the better fit AND brings the chrome-tier corner-light bleed that the parent convoy is unifying across all chrome surfaces. Implementation choice — single PR (not 3 parallel briefs): The seed recommended 3 small parallel-safe briefs (one per file). D1 and D2 both need styles/globals.css to gain new utility classes, so those 2 changes can't run truly in parallel without merge conflicts. Single PR is faster, simpler to review end-to-end, and the natural shape for a 2-3 hour convoy with tightly-coupled artifacts. Files changed (4): styles/globals.css (+50 / -1): - Adds .btn-glass-secondary (with :hover variant) — D1. - Adds .glass-input — D2. - Both classes documented inline with architect-decision references. components/ui/Button.js (+2 / -10): - Replaces inline variantStyle + Tailwind hover arbitrary class for `variant === 'secondary'` with `variantClass = 'btn-glass-secondary font-medium'`. variantStyle now `{}`. - Other variants (primary, danger, ghost) UNCHANGED. components/ui/Input.js (+1 / -7): - Adds `glass-input` to the className list. - Removes inline `background` + `backdropFilter` + `WebkitBackdropFilter` from the input's style block. - Conditional `border: inputBorder` stays in JSX (error swap). - All other props/behavior preserved. components/MobileNavigation.js (+11 / -8): - Adds `import { GlassSurface } from './ui'`. - Replaces the inline-styled backdrop <div> with <GlassSurface as="div" ...>. Same className ("absolute inset-0"), same visible behavior, plus the chrome-tier corner-light bleed. - Comment block updated to reference the convoy + decision. .github/workflows/ci.yml (+8 / -22): - forbidden-patterns Check 7/7 GLASS_ALLOWLIST reduced from 6 entries to 3 (chrome only). The TODO comments referencing this convoy are deleted (work is done). .convoys/migrate-button-input-mobilenav-to-glass-primitive.md (+74 / -3): - status: queued → closed, closed: 2026-06-05, prs: [131]. - Architect ratifications D1-D3 written into front-matter docs. - Closeout checklist with all acceptance criteria checked. - Note that parent convoy unify-glass-panel-surfaces is now fully closed — no residual handrolled glass-surface usage outside the 3 chrome blocks. Verification: - POSITIVE TEST: post-migration grep with the reduced 3-entry allowlist returns 0 violations. ✅ - grep on raw files: only Layout.js + TopSearchBar.js still match the literal regex (GlassSurface.js uses template literal which doesn't match — intentional, allowlist is forward-compat). - YAML parses (python3 yaml.safe_load). - npm run lint passes (1 pre-existing unrelated warning). - npm run test:run: 118/118 tests pass. Visual diff to be verified by reviewer in light + dark mode for: - <Button variant="secondary"> default + hover state. - <Input> default + error state. - Mobile bottom-nav backdrop. Co-authored-by: Cursor <cursoragent@cursor.com>
1428 lines
No EOL
48 KiB
CSS
1428 lines
No EOL
48 KiB
CSS
@tailwind base;
|
|
@tailwind components;
|
|
@tailwind utilities;
|
|
|
|
/* Custom styles */
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
sans-serif;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
transition: background-color 0.3s ease, color 0.3s ease;
|
|
}
|
|
|
|
/* Theme Variables - Deck Hearth Fire-Inspired */
|
|
:root {
|
|
/* Light Theme - Warm Daylight by the Hearth */
|
|
--bg-primary-light: #fefcf8; /* Warm cream, like sunlit stone */
|
|
--bg-secondary-light: #f7f3ed; /* Soft beige, like aged parchment */
|
|
--bg-tertiary-light: #f0e6d6; /* Light wood tone */
|
|
--text-primary-light: #2d1810; /* Dark chocolate brown */
|
|
--text-secondary-light: #5d4037; /* Medium brown, like burnt wood */
|
|
--text-accent-light: #d84315; /* Ember orange */
|
|
--border-light: #e8dcc6; /* Light wood border */
|
|
--shadow-light: 0 4px 6px -1px rgba(45, 24, 16, 0.1);
|
|
--gradient-primary-light: linear-gradient(135deg, #ff6f00 0%, #d84315 100%); /* Fire gradient */
|
|
--gradient-secondary-light: linear-gradient(135deg, #ffab40 0%, #ff6f00 100%); /* Golden flame */
|
|
--accent-flame-light: #ff6f00; /* Bright flame orange */
|
|
--accent-ember-light: #d84315; /* Deep ember red */
|
|
--accent-gold-light: #ffab40; /* Golden flame */
|
|
--accent-wood-light: #8d6e63; /* Rich wood tone */
|
|
--input-bg-light: #fefcf8;
|
|
--input-border-light: #e8dcc6;
|
|
--input-text-light: #2d1810;
|
|
--input-placeholder-light: #5d4037;
|
|
|
|
/* RGB color variables for backdrop-blur effects */
|
|
--bg-primary-rgb: 254, 252, 248;
|
|
--bg-secondary-rgb: 247, 243, 237;
|
|
--bg-tertiary-rgb: 240, 230, 214;
|
|
--accent-ember-rgb: 216, 67, 21; /* RGB version of #d84315 */
|
|
}
|
|
|
|
[data-theme="dark"] {
|
|
/* Dark Theme - Deep Navy Hearth Room (operator mockup, 2026-06-04)
|
|
The base is no longer warm-charcoal-brown — that read as "muddy"
|
|
and didn't let the ember corners glow. Switched to a deep cool-navy
|
|
base so the ember-orange and purple-magenta corner pools have
|
|
somewhere to be vivid against. The "fireplace warmth" survives as
|
|
gradient corner-glow, accent tokens, and the inner-rim highlights,
|
|
NOT as panel fill — same Liquid-Glass principle as the original
|
|
epic, just executed at mockup intensity rather than the imperceptible
|
|
intensity of PRs #100-#101. */
|
|
--bg-primary-dark: #0d0e1a; /* Deep cool-navy, like night sky over coals */
|
|
--bg-secondary-dark: #14162a; /* Slightly lighter navy, for inset surfaces */
|
|
--bg-tertiary-dark: #1d1f3a; /* Tertiary navy, for nested chrome */
|
|
--text-primary-dark: #fff8f0; /* Warm white, like firelight */
|
|
--text-secondary-dark: #d7c4b0; /* Soft cream, like candlelight */
|
|
--text-accent-dark: #ff8a50; /* Bright flame orange */
|
|
--border-dark: #4a2f1f; /* Dark wood border */
|
|
--shadow-dark: 0 4px 6px -1px rgba(26, 15, 10, 0.4);
|
|
--gradient-primary-dark: linear-gradient(135deg, #ff8a50 0%, #d84315 100%); /* Evening fire */
|
|
--gradient-secondary-dark: linear-gradient(135deg, #ffab40 0%, #ff6f00 100%); /* Golden embers */
|
|
--accent-flame-dark: #ff8a50; /* Bright flame */
|
|
--accent-ember-dark: #d84315; /* Glowing ember */
|
|
--accent-gold-dark: #ffab40; /* Golden glow */
|
|
--accent-wood-dark: #8d6e63; /* Warm wood */
|
|
--input-bg-dark: #2d1b12;
|
|
--input-border-dark: #4a2f1f;
|
|
--input-text-dark: #fff8f0;
|
|
--input-placeholder-dark: #d7c4b0;
|
|
--search-bg-dark: #2d1b12;
|
|
--search-border-dark: #4a2f1f;
|
|
--search-text-dark: #fff8f0;
|
|
--search-placeholder-dark: #d7c4b0;
|
|
|
|
/* RGB color variables for backdrop-blur effects */
|
|
--bg-primary-rgb: 26, 15, 10;
|
|
--bg-secondary-rgb: 45, 27, 18;
|
|
--bg-tertiary-rgb: 61, 35, 23;
|
|
--accent-ember-rgb: 216, 67, 21; /* RGB version of #d84315 */
|
|
}
|
|
|
|
/* ============================================================
|
|
Liquid Glass tokens — added by liquid-glass-design-tokens convoy
|
|
(2026-06-03). See docs/DESIGN_TOKENS.md for the full reference,
|
|
contrast tables, composite recipes, and "When NOT to use glass"
|
|
guidance.
|
|
============================================================ */
|
|
|
|
:root {
|
|
/* Glass surfaces (light) — 3-step legibility ramp.
|
|
low: modal panels inside a scrim, card detail, inline sub-panels.
|
|
mid: sidebar rail, header strip, mobile bottom-bar.
|
|
high: popovers, dropdowns, tooltips (can land over anything). */
|
|
--glass-surface-low: rgba(254, 252, 248, 0.55);
|
|
--glass-surface-mid: rgba(254, 252, 248, 0.68);
|
|
--glass-surface-high: rgba(254, 252, 248, 0.82);
|
|
|
|
/* Glass blur + saturate (theme-independent; inherited by dark). */
|
|
--glass-blur-low: 12px;
|
|
--glass-blur-mid: 20px;
|
|
--glass-blur-high: 32px;
|
|
--glass-saturate: 140%;
|
|
|
|
/* Rim-light (light) — inner highlight + outer hairline. */
|
|
--rim-light-inner: inset 0 1px 0 0 rgba(255, 255, 255, 0.65);
|
|
--rim-light-outer: 0 0 0 1px rgba(45, 24, 16, 0.08);
|
|
|
|
/* Ember rim — composable RGB triple + two preset variants.
|
|
Triple is theme-independent (ember orange #d84315);
|
|
variants differ per theme for eye-perception correction. */
|
|
--ember-rim-color: 216, 67, 21;
|
|
--ember-rim-subtle: inset 0 0 0 1px rgba(216, 67, 21, 0.35);
|
|
--ember-rim-pronounced:
|
|
inset 0 0 0 1px rgba(216, 67, 21, 0.55),
|
|
0 0 16px 0 rgba(216, 67, 21, 0.30);
|
|
|
|
/* Elevation (light) — warm-brown-tinted shadows. */
|
|
--elevation-flat: none;
|
|
--elevation-ambient:
|
|
0 4px 12px -2px rgba(45, 24, 16, 0.08),
|
|
0 2px 4px -1px rgba(45, 24, 16, 0.04);
|
|
--elevation-pronounced:
|
|
0 24px 48px -12px rgba(45, 24, 16, 0.20),
|
|
0 12px 24px -6px rgba(45, 24, 16, 0.10),
|
|
0 4px 8px -2px rgba(45, 24, 16, 0.06);
|
|
|
|
/* Modal scrim (light) — warm coffee-brown, NOT pure black. */
|
|
--modal-scrim: rgba(45, 24, 16, 0.35);
|
|
|
|
/* Motion (theme-independent). 4-tier duration taxonomy + 3 easings.
|
|
See docs/MOTION_SYSTEM.md for usage, examples, and the
|
|
prefers-reduced-motion contract. */
|
|
--motion-duration-instant: 0ms;
|
|
--motion-duration-quick: 150ms; /* hover, focus, micro-state */
|
|
--motion-duration-default: 250ms; /* default for most transitions */
|
|
--motion-duration-slow: 400ms; /* modal scale, sidebar slide */
|
|
--motion-duration-deliberate: 600ms; /* hero, onboarding, celebration */
|
|
|
|
--motion-ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* default */
|
|
--motion-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); /* delight */
|
|
--motion-ease-linear: linear; /* progress */
|
|
}
|
|
|
|
[data-theme="dark"] {
|
|
/* Glass surfaces (dark) — alpha ramp over the new deep-navy base
|
|
(#0d0e1a from the operator mockup, 2026-06-04). RGB is updated
|
|
from the prior warm-charcoal (26,15,10) so glass-on-dark blends
|
|
with the bg without muddy color shift. */
|
|
--glass-surface-low: rgba(20, 22, 42, 0.62);
|
|
--glass-surface-mid: rgba(20, 22, 42, 0.74);
|
|
--glass-surface-high: rgba(20, 22, 42, 0.86);
|
|
|
|
/* Rim-light (dark) — softer warm-white inner + faint outer. */
|
|
--rim-light-inner: inset 0 1px 0 0 rgba(255, 248, 240, 0.12);
|
|
--rim-light-outer: 0 0 0 1px rgba(255, 248, 240, 0.06);
|
|
|
|
/* Ember rim (dark) — alpha bumped to compensate for ember orange
|
|
reading less vibrant on dark backgrounds (eye-perception correction,
|
|
not a numerical drift). RGB triple inherits from :root. */
|
|
--ember-rim-subtle: inset 0 0 0 1px rgba(216, 67, 21, 0.40);
|
|
--ember-rim-pronounced:
|
|
inset 0 0 0 1px rgba(216, 67, 21, 0.65),
|
|
0 0 16px 0 rgba(216, 67, 21, 0.35);
|
|
|
|
/* Elevation (dark) — pure-black shadows for crisp depth against
|
|
the warm-charcoal floor. */
|
|
--elevation-ambient:
|
|
0 4px 12px -2px rgba(0, 0, 0, 0.40),
|
|
0 2px 4px -1px rgba(0, 0, 0, 0.30);
|
|
--elevation-pronounced:
|
|
0 24px 48px -12px rgba(0, 0, 0, 0.55),
|
|
0 12px 24px -6px rgba(0, 0, 0, 0.40),
|
|
0 4px 8px -2px rgba(0, 0, 0, 0.25);
|
|
|
|
/* Modal scrim (dark) — heavier black; dark theme starts dark so
|
|
needs more contrast to feel "behind" the modal. */
|
|
--modal-scrim: rgba(0, 0, 0, 0.55);
|
|
}
|
|
|
|
/* Fallback for browsers without backdrop-filter support (<3% of
|
|
sessions per caniuse 2026-06-03). Collapses the alpha ramp toward
|
|
solid so glass surfaces remain legible without the blur layer.
|
|
Never goes fully opaque — preserves the design's tinted-surface
|
|
intent and the ramp ordering. The @supports negation guards both
|
|
the unprefixed property AND -webkit-backdrop-filter (Safari 9-17
|
|
needed the prefix). */
|
|
@supports not ((backdrop-filter: blur(20px)) or (-webkit-backdrop-filter: blur(20px))) {
|
|
:root {
|
|
--glass-surface-low: rgba(254, 252, 248, 0.92);
|
|
--glass-surface-mid: rgba(254, 252, 248, 0.95);
|
|
--glass-surface-high: rgba(254, 252, 248, 0.98);
|
|
}
|
|
[data-theme="dark"] {
|
|
--glass-surface-low: rgba(20, 22, 42, 0.92);
|
|
--glass-surface-mid: rgba(20, 22, 42, 0.95);
|
|
--glass-surface-high: rgba(20, 22, 42, 0.98);
|
|
}
|
|
}
|
|
|
|
/* Apply theme colors.
|
|
The hearth gradient: two diagonal light sources that the floating
|
|
chrome chips visually "respond" to via the --corner-light-warm /
|
|
--corner-light-cool design tokens (defined further down in :root /
|
|
[data-theme="dark"]; consumed as radial-gradient layers on each
|
|
chip's BORDER, not as outer drop shadows — see `.glass-panel`).
|
|
- Warm ember light source: bottom-left of the viewport.
|
|
- Cool purpley-blue light source: top-right of the viewport.
|
|
The base linear-gradient runs along the same 45deg diagonal between
|
|
the two sources so the page itself reads as a single continuous
|
|
wash, not just two corner pools on a flat substrate. Pushed harder
|
|
2026-06-04 per operator feedback ("increase the gradient in the
|
|
background that goes from our nice warm red to a nice purpley
|
|
blue") — alphas roughly doubled vs the prior pass, ellipses
|
|
enlarged, and the top-right corner switched from gold to
|
|
purpley-blue on light theme so both themes carry the same warm↔
|
|
cool diagonal story.
|
|
Fixed attachment so scrolling content slides over the gradient
|
|
rather than pulling it along — that's what gives glass surfaces
|
|
their parallax-blur behavior. */
|
|
body {
|
|
background-color: var(--bg-primary-light);
|
|
background-image:
|
|
/* Warm ember pool, bottom-left — primary light source. */
|
|
radial-gradient(
|
|
ellipse 90% 65% at 5% 110%,
|
|
rgba(255, 95, 0, 0.55) 0%,
|
|
rgba(255, 130, 25, 0.28) 28%,
|
|
rgba(255, 171, 64, 0.10) 55%,
|
|
transparent 80%
|
|
),
|
|
/* Purpley-blue pool, top-right — secondary light source (replaces
|
|
the prior gold accent). Indigo→violet so the chip rims that
|
|
respond to this source via --corner-light-cool read as cool. */
|
|
radial-gradient(
|
|
ellipse 85% 65% at 100% -8%,
|
|
rgba(99, 102, 241, 0.42) 0%,
|
|
rgba(124, 58, 237, 0.20) 30%,
|
|
rgba(167, 139, 250, 0.08) 58%,
|
|
transparent 80%
|
|
),
|
|
/* Base diagonal wash: warm-cream bottom-left → cool-violet top-right.
|
|
45deg = bottom-left → top-right (matches the two light sources). */
|
|
linear-gradient(
|
|
45deg,
|
|
rgba(255, 240, 220, 1) 0%,
|
|
rgba(252, 244, 230, 1) 50%,
|
|
rgba(230, 232, 252, 1) 100%
|
|
);
|
|
background-attachment: fixed;
|
|
color: var(--text-primary-light);
|
|
}
|
|
|
|
[data-theme="dark"] body {
|
|
background-color: var(--bg-primary-dark);
|
|
background-image:
|
|
/* Warm ember-red pool, bottom-left — primary light source.
|
|
Pushed to 0.72 (was 0.55) so the operator's "nice warm red"
|
|
reads as a real glow against deep navy. */
|
|
radial-gradient(
|
|
ellipse 95% 70% at 5% 110%,
|
|
rgba(255, 65, 25, 0.72) 0%,
|
|
rgba(255, 95, 0, 0.42) 24%,
|
|
rgba(255, 130, 30, 0.16) 52%,
|
|
transparent 80%
|
|
),
|
|
/* Purpley-blue pool, top-right — secondary light source. Pushed
|
|
to 0.58 (was 0.26) + a more saturated indigo→violet ramp so
|
|
the corner reads unambiguously as the second light source the
|
|
chip rims respond to. */
|
|
radial-gradient(
|
|
ellipse 90% 70% at 100% -10%,
|
|
rgba(139, 92, 246, 0.58) 0%,
|
|
rgba(99, 102, 241, 0.32) 28%,
|
|
rgba(168, 85, 247, 0.12) 55%,
|
|
transparent 82%
|
|
),
|
|
/* Base diagonal wash: warm-tinted deep aubergine bottom-left →
|
|
cool-tinted deep indigo top-right. 45deg matches the light
|
|
sources. The midpoint stays near the previous deep-navy so
|
|
chrome surfaces still have a calm substrate to blur against. */
|
|
linear-gradient(
|
|
45deg,
|
|
rgba(40, 16, 22, 1) 0%,
|
|
rgba(17, 18, 38, 1) 50%,
|
|
rgba(28, 22, 58, 1) 100%
|
|
);
|
|
background-attachment: fixed;
|
|
color: var(--text-primary-dark);
|
|
}
|
|
|
|
/* .page-header-glass — the recurring "page header strip" pattern
|
|
(full-bleed band at the top of authenticated pages: dashboard, my-
|
|
cards, cards, collections, community/collections, collection/[id],
|
|
scanner). Uses a higher-tint surface than .glass-panel because the
|
|
strip spans the whole content width and needs more visual weight,
|
|
but inherits the same rim + blur recipe. Pairs with `border-bottom:
|
|
1px solid var(--border)` for the separator line below. */
|
|
.page-header-glass {
|
|
background: var(--glass-surface-high);
|
|
backdrop-filter: blur(20px) saturate(180%);
|
|
box-shadow:
|
|
var(--rim-light-inner),
|
|
0 1px 0 var(--border);
|
|
}
|
|
|
|
/* Card Scanner Animations */
|
|
@keyframes pulse {
|
|
0%, 100% {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
opacity: 0.8;
|
|
transform: scale(1.02);
|
|
}
|
|
}
|
|
|
|
/* Default theme variables for light theme */
|
|
:root {
|
|
--bg-primary: var(--bg-primary-light);
|
|
--bg-secondary: var(--bg-secondary-light);
|
|
--bg-tertiary: var(--bg-tertiary-light);
|
|
--text-primary: var(--text-primary-light);
|
|
--text-secondary: var(--text-secondary-light);
|
|
--text-accent: var(--text-accent-light);
|
|
--border: var(--border-light);
|
|
--shadow: var(--shadow-light);
|
|
--gradient-primary: var(--gradient-primary-light);
|
|
--gradient-secondary: var(--gradient-secondary-light);
|
|
--accent-flame: var(--accent-flame-light);
|
|
--accent-ember: var(--accent-ember-light);
|
|
--accent-gold: var(--accent-gold-light);
|
|
--accent-wood: var(--accent-wood-light);
|
|
/* Legacy color mappings for compatibility */
|
|
--accent-blue: var(--accent-flame-light);
|
|
--accent-purple: var(--accent-ember-light);
|
|
--accent-pink: var(--accent-gold-light);
|
|
--input-bg: var(--input-bg-light);
|
|
--input-border: var(--input-border-light);
|
|
--input-text: var(--input-text-light);
|
|
--input-placeholder: var(--input-placeholder-light);
|
|
|
|
/* RGB color variables for backdrop-blur effects */
|
|
--bg-primary-rgb: 254, 252, 248;
|
|
--bg-secondary-rgb: 247, 243, 237;
|
|
--bg-tertiary-rgb: 240, 230, 214;
|
|
|
|
/* Corner-border light tokens (2026-06-04 refinement of the prior
|
|
two-light-source pass — operator feedback: "borders are
|
|
reflecting the color, not necessarily the drop shadows behind
|
|
the cards. In fact, you could probably reduce those box shadows
|
|
quite a bit. At least they shouldn't span the entire edge of
|
|
the card. Just a subtle bit in the corner.").
|
|
The prior `--edge-light-warm` / `--edge-light-cool` tokens were
|
|
complete box-shadow declarations with offsets that spanned the
|
|
entire bottom-left + top-right edges of every chip. They've
|
|
been retired. The replacement is a pair of plain RGBA colors
|
|
consumed by radial-gradient layers in the chip's BACKGROUND
|
|
(border-box-clipped) — see `.glass-panel` below — so the
|
|
warm/cool light response now reads as a corner-concentrated
|
|
highlight ON the chip's 1px border, not as an outer drop
|
|
shadow extending into the body gradient.
|
|
The `--chip-border-base` is a faint diagonal white tint that
|
|
paints under the corner gradients so the rest of the rim isn't
|
|
completely invisible (gives the chip a sensible edge even far
|
|
from either light source).
|
|
|
|
Two intensity tiers (2026-06-04 refinement, operator feedback
|
|
after the original tokens went out — "reduce the vibrancy of
|
|
the border gradients on the cards"):
|
|
|
|
1. `--corner-light-{warm,cool}` (full intensity) — used by
|
|
the floating chrome chips (sidebar nav, top header) where
|
|
the catch-light SHOULD be the primary visual cue that the
|
|
element is a distinct floating surface responding to the
|
|
background light sources. Consumed via inline `boxShadow`
|
|
in `components/Layout.js` and `components/ui/TopSearchBar.js`.
|
|
|
|
2. `--corner-light-{warm,cool}-subtle` — used by `.glass-panel`
|
|
and `.glass-panel-strong` (data cards: stat cards, deck
|
|
cards, list cards, modals, dropdowns). These surfaces are
|
|
numerous and tile densely; at full intensity the catch
|
|
lights compete with the chrome and overwhelm the page.
|
|
Roughly 55% of the full-intensity alpha. */
|
|
--corner-light-warm: rgba(255, 110, 0, 0.50);
|
|
--corner-light-cool: rgba(99, 102, 241, 0.38);
|
|
--corner-light-warm-subtle: rgba(255, 110, 0, 0.28);
|
|
--corner-light-cool-subtle: rgba(99, 102, 241, 0.20);
|
|
--chip-border-base: linear-gradient(
|
|
135deg,
|
|
rgba(255, 255, 255, 0.14) 0%,
|
|
rgba(255, 255, 255, 0.04) 100%
|
|
);
|
|
}
|
|
|
|
/* Dark theme variables */
|
|
[data-theme="dark"] {
|
|
--bg-primary: var(--bg-primary-dark);
|
|
--bg-secondary: var(--bg-secondary-dark);
|
|
--bg-tertiary: var(--bg-tertiary-dark);
|
|
--text-primary: var(--text-primary-dark);
|
|
--text-secondary: var(--text-secondary-dark);
|
|
--text-accent: var(--text-accent-dark);
|
|
--border: var(--border-dark);
|
|
--shadow: var(--shadow-dark);
|
|
--gradient-primary: var(--gradient-primary-dark);
|
|
--gradient-secondary: var(--gradient-secondary-dark);
|
|
--accent-flame: var(--accent-flame-dark);
|
|
--accent-ember: var(--accent-ember-dark);
|
|
--accent-gold: var(--accent-gold-dark);
|
|
--accent-wood: var(--accent-wood-dark);
|
|
/* Legacy color mappings for compatibility */
|
|
--accent-blue: var(--accent-flame-dark);
|
|
--accent-purple: var(--accent-ember-dark);
|
|
--accent-pink: var(--accent-gold-dark);
|
|
--input-bg: var(--input-bg-dark);
|
|
--input-border: var(--input-border-dark);
|
|
--input-text: var(--input-text-dark);
|
|
--input-placeholder: var(--input-placeholder-dark);
|
|
|
|
/* Dark-theme corner-border colors — alphas bumped so the warm
|
|
and cool catch lights read clearly against the deep substrate.
|
|
See :root for the full rationale on the corner-vs-drop-shadow
|
|
refinement. The base white border tint drops in alpha here
|
|
because the deep canvas absorbs whites more readily.
|
|
|
|
The `-subtle` variants (used by `.glass-panel*` data cards
|
|
per the operator's "reduce the vibrancy" feedback) sit at
|
|
roughly 55% of the full-intensity alpha — slightly higher
|
|
proportionally than the light theme because dark substrates
|
|
swallow chromatic content. */
|
|
--corner-light-warm: rgba(255, 130, 30, 0.68);
|
|
--corner-light-cool: rgba(139, 92, 246, 0.58);
|
|
--corner-light-warm-subtle: rgba(255, 130, 30, 0.38);
|
|
--corner-light-cool-subtle: rgba(139, 92, 246, 0.32);
|
|
--chip-border-base: linear-gradient(
|
|
135deg,
|
|
rgba(255, 255, 255, 0.10) 0%,
|
|
rgba(255, 255, 255, 0.02) 100%
|
|
);
|
|
}
|
|
|
|
/* Custom component styles - Fire-themed */
|
|
.btn-primary {
|
|
@apply font-bold py-3 px-6 rounded-2xl transition-all duration-300 shadow-lg hover:shadow-xl;
|
|
background: var(--gradient-primary);
|
|
color: white;
|
|
}
|
|
|
|
.btn-flame {
|
|
@apply font-bold py-2 px-4 rounded-lg transition-all duration-300 shadow-md hover:shadow-lg;
|
|
background-color: var(--accent-flame);
|
|
color: white;
|
|
}
|
|
|
|
.btn-flame:hover {
|
|
background-color: var(--accent-ember);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn-ember {
|
|
@apply font-bold py-2 px-4 rounded-lg transition-all duration-300 shadow-md hover:shadow-lg;
|
|
background-color: var(--accent-ember);
|
|
color: white;
|
|
}
|
|
|
|
.btn-ember:hover {
|
|
background: var(--gradient-primary);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn-gold {
|
|
@apply font-bold py-2 px-4 rounded-lg transition-all duration-300 shadow-md hover:shadow-lg;
|
|
background-color: var(--accent-gold);
|
|
color: white;
|
|
}
|
|
|
|
.btn-gold:hover {
|
|
background: var(--gradient-secondary);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.gradient-bg-fire {
|
|
background: var(--gradient-primary);
|
|
}
|
|
|
|
.gradient-bg-golden {
|
|
background: var(--gradient-secondary);
|
|
}
|
|
|
|
.gradient-bg-ember {
|
|
background: linear-gradient(135deg, #d84315 0%, #bf360c 100%);
|
|
}
|
|
|
|
/* Fire glow effects */
|
|
.fire-glow {
|
|
box-shadow: 0 0 20px rgba(255, 111, 0, 0.3);
|
|
}
|
|
|
|
.ember-glow {
|
|
box-shadow: 0 0 15px rgba(216, 67, 21, 0.4);
|
|
}
|
|
|
|
/* Navigation Accessibility Enhancements */
|
|
/* Nav-item hover/focus — third pass 2026-06-04. Operator feedback
|
|
after the spotlight landed (PR #110): "remove the shading around
|
|
the hover that makes it feel like it's floating" + "the gradient
|
|
is a little too bold." Two changes from the prior pass:
|
|
1) Dropped the base background-color tint on the host. With the
|
|
bg-tint gone, the item no longer reads as a "floating chip"
|
|
against the sidebar — just the radial spotlight remains,
|
|
which feels like light pooling under the cursor rather than
|
|
a state-color rectangle. Text color shift to --text-primary
|
|
stays as the secondary affordance (and for AA contrast on the
|
|
label).
|
|
2) Toned down the radial-gradient alphas. Light: 35%/18% → 22%/
|
|
10%. Dark: 40%/22% → 26%/12%. Same shape, gentler glow. */
|
|
/* Hover/focus also surfaces a subtle 1px ember border so the
|
|
hit-target of the chip is unambiguous even when the cursor is
|
|
near an edge (operator feedback after PR #111: the spotlight-only
|
|
hover hid the exact bounds of the interactive surface). The
|
|
border is 28% alpha — distinctly visible but lighter than the
|
|
active state's full-saturation ember border so the two states
|
|
remain readable side-by-side (hover < active). Implemented via
|
|
box-shadow inset rather than border: 1px to avoid the 1px layout
|
|
shift a real border would cause when toggling on hover. */
|
|
.nav-item:hover,
|
|
.nav-item-bottom:hover,
|
|
.nav-item-hover:hover,
|
|
.nav-item:focus-within,
|
|
.nav-item-bottom:focus-within,
|
|
.nav-item-hover:focus-within {
|
|
color: var(--text-primary) !important;
|
|
box-shadow: inset 0 0 0 1px rgba(216, 67, 21, 0.28) !important;
|
|
}
|
|
|
|
[data-theme="dark"] .nav-item:hover,
|
|
[data-theme="dark"] .nav-item-bottom:hover,
|
|
[data-theme="dark"] .nav-item-hover:hover,
|
|
[data-theme="dark"] .nav-item:focus-within,
|
|
[data-theme="dark"] .nav-item-bottom:focus-within,
|
|
[data-theme="dark"] .nav-item-hover:focus-within {
|
|
box-shadow: inset 0 0 0 1px rgba(255, 138, 80, 0.34) !important;
|
|
}
|
|
|
|
/* Mouse-tracking radial spotlight on every hoverable nav surface.
|
|
The radial-gradient is positioned via the --mouse-x / --mouse-y
|
|
CSS variables that Layout's delegated mousemove handler writes
|
|
onto the currently-hovered element. Default position (50% 50%)
|
|
ensures the spotlight is centered before the first mouse event
|
|
fires (e.g. on focus-within from keyboard nav). The ::before is
|
|
non-interactive (pointer-events: none) so it never blocks
|
|
clicks or steals focus from the underlying Link/Button. The
|
|
`position: relative` + `overflow: hidden` on the host ensure the
|
|
gradient clips to the rounded-2xl border-radius. */
|
|
.nav-item,
|
|
.nav-item-bottom,
|
|
.nav-item-hover {
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.nav-item::before,
|
|
.nav-item-bottom::before,
|
|
.nav-item-hover::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: inherit;
|
|
background: radial-gradient(
|
|
circle 120px at var(--mouse-x, 50%) var(--mouse-y, 50%),
|
|
rgba(255, 140, 30, 0.22) 0%,
|
|
rgba(216, 67, 21, 0.10) 35%,
|
|
transparent 70%
|
|
);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity var(--motion-duration-quick) var(--motion-ease-out);
|
|
}
|
|
|
|
.nav-item:hover::before,
|
|
.nav-item-bottom:hover::before,
|
|
.nav-item-hover:hover::before,
|
|
.nav-item:focus-within::before,
|
|
.nav-item-bottom:focus-within::before,
|
|
.nav-item-hover:focus-within::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
[data-theme="dark"] .nav-item::before,
|
|
[data-theme="dark"] .nav-item-bottom::before,
|
|
[data-theme="dark"] .nav-item-hover::before {
|
|
background: radial-gradient(
|
|
circle 120px at var(--mouse-x, 50%) var(--mouse-y, 50%),
|
|
rgba(255, 160, 70, 0.26) 0%,
|
|
rgba(255, 110, 0, 0.12) 35%,
|
|
transparent 70%
|
|
);
|
|
}
|
|
|
|
/* Ensure the existing nav-item content sits above the ::before
|
|
spotlight overlay. Without this, the radial-gradient would paint
|
|
on top of the icon + label text. */
|
|
.nav-item > *,
|
|
.nav-item-bottom > *,
|
|
.nav-item-hover > * {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Card grid outer-glow (redesign-v2 sub-convoy #6, 2026-06-04).
|
|
The operator's mockup shows TCG card thumbnails in the Featured
|
|
Collection grid with a soft warm glow emanating around each card,
|
|
suggesting "light cast onto the cards" — the hearth metaphor at
|
|
the card level. Applied to the outermost CardItem container so it
|
|
sits OUTSIDE the rarity-color glow (which lives on the card's
|
|
inner chrome). Single cheap box-shadow per card; safe on long
|
|
scroll lists where stacked filters would jank.
|
|
AGENTS.md performance budget: "card grids may NOT use glass on
|
|
every card item" — this is a single-axis shadow, not a backdrop-
|
|
filter, so it stays well under that budget. */
|
|
.card-grid-outer-glow {
|
|
box-shadow:
|
|
0 0 24px -4px rgba(255, 128, 0, 0.18),
|
|
0 4px 12px -2px rgba(0, 0, 0, 0.10);
|
|
transition: box-shadow var(--motion-duration-default)
|
|
var(--motion-ease-out);
|
|
}
|
|
|
|
.card-grid-outer-glow:hover {
|
|
box-shadow:
|
|
0 0 32px -2px rgba(255, 128, 0, 0.32),
|
|
0 8px 16px -4px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
[data-theme="dark"] .card-grid-outer-glow {
|
|
box-shadow:
|
|
0 0 28px -4px rgba(255, 110, 30, 0.28),
|
|
0 4px 12px -2px rgba(0, 0, 0, 0.40);
|
|
}
|
|
|
|
[data-theme="dark"] .card-grid-outer-glow:hover {
|
|
box-shadow:
|
|
0 0 40px -2px rgba(255, 110, 30, 0.50),
|
|
0 8px 16px -4px rgba(0, 0, 0, 0.50);
|
|
}
|
|
|
|
/* Sidebar active-pill — refined again 2026-06-04 (spotlight-hover
|
|
refinement, second pass). Operator: "give the background of an
|
|
active state a subtle gradient." Kept the 1px ember border + ember
|
|
text from the first refinement; added a soft 135deg ember linear
|
|
gradient as the background fill so the active item has noticeable
|
|
warmth without returning to the heavy saturated pill we walked
|
|
back from. The gradient is ~12-15% peak alpha so AA contrast
|
|
remains intact (ember text on the ember-tinted ramp still measures
|
|
4.5:1+ against the visible substrate).
|
|
Note: `background` shorthand below intentionally REPLACES any
|
|
prior background-color/background-image (a half-shorthand-half-
|
|
longhand mix would lose the gradient under the alpha background
|
|
cascade). */
|
|
.nav-item-active {
|
|
background: linear-gradient(
|
|
135deg,
|
|
rgba(216, 67, 21, 0.14) 0%,
|
|
rgba(255, 110, 0, 0.06) 100%
|
|
) !important;
|
|
color: var(--accent-ember) !important;
|
|
box-shadow: inset 0 0 0 1px var(--accent-ember) !important;
|
|
}
|
|
|
|
[data-theme="dark"] .nav-item-active {
|
|
background: linear-gradient(
|
|
135deg,
|
|
rgba(255, 138, 80, 0.18) 0%,
|
|
rgba(255, 110, 0, 0.08) 100%
|
|
) !important;
|
|
color: rgb(255, 138, 80) !important;
|
|
box-shadow: inset 0 0 0 1px rgb(255, 138, 80) !important;
|
|
}
|
|
|
|
/* Hover state — refined 2026-06-04. Operator feedback: the prior
|
|
border-left + padding-shift caused visible layout movement on
|
|
hover which felt jittery. Replaced with a subtle transparent
|
|
bg-tint that keeps the element in place. Same treatment for
|
|
focus-within so keyboard nav matches mouse. */
|
|
.nav-item-hover:hover,
|
|
.nav-item-hover:focus-within {
|
|
background-color: rgba(216, 67, 21, 0.08);
|
|
}
|
|
|
|
[data-theme="dark"] .nav-item-hover:hover,
|
|
[data-theme="dark"] .nav-item-hover:focus-within {
|
|
background-color: rgba(255, 138, 80, 0.10);
|
|
}
|
|
|
|
/* High contrast mode support */
|
|
@media (prefers-contrast: high) {
|
|
.nav-item:hover,
|
|
.nav-item-bottom:hover,
|
|
.nav-item:focus-within,
|
|
.nav-item-bottom:focus-within {
|
|
outline: 2px solid var(--accent-ember);
|
|
outline-offset: 2px;
|
|
}
|
|
}
|
|
|
|
/* Reduced motion support — site-wide sweep (added by motion-system-pass
|
|
convoy, 2026-06-03). Per WCAG SC 2.3.3, users who set the OS
|
|
"Reduce motion" preference get decorative animations collapsed to a
|
|
no-op while keeping the end-state of state transitions. The 0.01ms
|
|
value (vs `animation: none`) is the well-known idiom for preserving
|
|
end-state without flicker. Essential motion (e.g. loading spinners
|
|
indicating in-flight work) is kept opt-in by individual components
|
|
via the `motion-essential` class. */
|
|
@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;
|
|
}
|
|
|
|
.motion-essential,
|
|
.motion-essential * {
|
|
animation-duration: revert !important;
|
|
animation-iteration-count: revert !important;
|
|
transition-duration: revert !important;
|
|
}
|
|
|
|
.nav-item,
|
|
.nav-item-bottom {
|
|
transition: none;
|
|
}
|
|
}
|
|
|
|
.btn-secondary {
|
|
@apply font-bold py-3 px-6 rounded-2xl transition-all duration-300 shadow-lg hover:shadow-xl;
|
|
background: var(--gradient-secondary);
|
|
color: white;
|
|
}
|
|
|
|
.input-field {
|
|
@apply w-full px-4 py-3 border rounded-2xl shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-0 transition-all duration-200;
|
|
background-color: var(--input-bg);
|
|
border-color: var(--input-border);
|
|
color: var(--input-text);
|
|
}
|
|
|
|
.input-field::placeholder {
|
|
color: var(--input-placeholder);
|
|
}
|
|
|
|
/* .card retired by Brief 5 of unify-glass-panel-surfaces convoy
|
|
(PR sequence following PR #119). All 7 prior consumers migrated to
|
|
.glass-panel rounded-3xl. Use .glass-panel (subtle corner-light
|
|
tier, per PR #118) for content panels going forward; use
|
|
.glass-panel-strong for elevated chrome and floating popovers; use
|
|
.page-header-glass for full-bleed page-top strips. */
|
|
|
|
/* ============================================================
|
|
.glass-panel — canonical "content card" utility for body
|
|
surfaces on authenticated pages. The Liquid Glass recipe in
|
|
class form so pages can apply it without importing the
|
|
<GlassSurface> primitive for every flat container. Added by
|
|
glass-body-panels PR after PR #96 left admin/settings/dashboard
|
|
body cards on solid --bg-secondary.
|
|
|
|
Composition matches <GlassSurface tint="mid" rim="subtle"
|
|
elevation="ambient" blur="mid">. Always pair with rounded-*
|
|
from Tailwind (the class itself doesn't set border-radius
|
|
because consumers vary between rounded-xl and rounded-2xl).
|
|
============================================================ */
|
|
/* IMPORTANT — composing rim + elevation tokens correctly.
|
|
The --rim-light-outer / --rim-light-inner / --elevation-* tokens are
|
|
COMPLETE box-shadow declarations (each contains offset/spread/color or
|
|
the `inset` keyword), not raw color values. They are designed to be
|
|
*chained into a single box-shadow*, exactly the way <GlassSurface>'s
|
|
`boxShadow: shadowParts.join(', ')` composes them. Do NOT use
|
|
`border: 1px solid var(--rim-light-outer)` — that parses
|
|
`border-width` from the first token of the shadow string ("0"), which
|
|
yields a 0px border. And do NOT wrap --rim-light-inner in another
|
|
`inset 0 1px 0` prefix — the token already contains its own `inset`
|
|
keyword, so wrapping produces invalid syntax that the browser drops
|
|
the entire box-shadow declaration for. PR #98 had both mistakes,
|
|
which is why the panels in PR #97/#98 had neither visible border nor
|
|
drop shadow despite the backdrop-filter being correct.
|
|
|
|
Backdrop-filter quirks with Next.js 16's Lightning CSS (also relevant):
|
|
1) `backdrop-filter: blur(var(--X)) saturate(var(--Y))` is silently
|
|
stripped (var() inside function args breaks the optimizer). Use
|
|
LITERAL values here. Tokens stay in :root for the <GlassSurface>
|
|
JSX primitive which sets backdropFilter via inline style.
|
|
2) Writing both `backdrop-filter` AND `-webkit-backdrop-filter` with
|
|
identical values triggers Lightning CSS's buggy de-dup; it keeps
|
|
only the `-webkit-` form which modern browsers ignore. Write only
|
|
the unprefixed form; Lightning CSS's autoprefixer adds the prefix
|
|
per browserslist (ios_saf <18 still needs it). */
|
|
/* Glass panels — the canonical floating-chrome surface. 2026-06-04
|
|
corner-border pass (refines the prior two-light-source pass):
|
|
the warm + cool light response now paints on the chip's 1px
|
|
border using the multi-layer background + transparent-border
|
|
technique:
|
|
1. linear-gradient(surface, surface) padding-box — the chip's
|
|
translucent fill, clipped to the padding box so it doesn't
|
|
cover the 1px border ring.
|
|
2. radial-gradient(at 0% 100%, warm) border-box — warm corner
|
|
catch-light, anchored at the chip's bottom-left.
|
|
3. radial-gradient(at 100% 0%, cool) border-box — cool corner
|
|
catch-light, anchored at the chip's top-right.
|
|
4. --chip-border-base border-box — faint diagonal white tint
|
|
under the corner gradients so the rest of the rim isn't
|
|
invisible (gives the chip a sensible edge far from either
|
|
light source).
|
|
The `border: 1px solid transparent` is what exposes those
|
|
border-box-clipped gradient layers as the visible rim.
|
|
Drop-shadow stack is now just rim-light-inner + elevation —
|
|
the directional warm/cool outer glows are gone per operator
|
|
feedback ("reduce those box shadows quite a bit").
|
|
|
|
`.glass-panel*` uses the SUBTLE corner-light variants (about 55%
|
|
of the full-intensity alpha used by the floating chrome chips in
|
|
Layout / TopSearchBar). Data cards tile densely, so at full
|
|
intensity the catch lights compete with the chrome and overwhelm
|
|
the page (2026-06-04 operator feedback: "reduce the vibrancy of
|
|
the border gradients on the cards"). */
|
|
.glass-panel {
|
|
background:
|
|
linear-gradient(var(--glass-surface-mid), var(--glass-surface-mid))
|
|
padding-box,
|
|
radial-gradient(at 0% 100%, var(--corner-light-warm-subtle) 0%, transparent 42%)
|
|
border-box,
|
|
radial-gradient(at 100% 0%, var(--corner-light-cool-subtle) 0%, transparent 42%)
|
|
border-box,
|
|
var(--chip-border-base) border-box;
|
|
border: 1px solid transparent;
|
|
backdrop-filter: blur(12px) saturate(180%);
|
|
box-shadow:
|
|
var(--rim-light-inner),
|
|
var(--elevation-ambient);
|
|
}
|
|
|
|
.glass-panel-strong {
|
|
background:
|
|
linear-gradient(var(--glass-surface-high), var(--glass-surface-high))
|
|
padding-box,
|
|
radial-gradient(at 0% 100%, var(--corner-light-warm-subtle) 0%, transparent 42%)
|
|
border-box,
|
|
radial-gradient(at 100% 0%, var(--corner-light-cool-subtle) 0%, transparent 42%)
|
|
border-box,
|
|
var(--chip-border-base) border-box;
|
|
border: 1px solid transparent;
|
|
backdrop-filter: blur(20px) saturate(180%);
|
|
box-shadow:
|
|
var(--rim-light-inner),
|
|
var(--elevation-pronounced);
|
|
}
|
|
|
|
/* Button — secondary (glass) variant.
|
|
Adopted by `<Button variant="secondary">` after the
|
|
migrate-button-input-mobilenav-to-glass-primitive convoy
|
|
(2026-06-05). Composes the same high-tint gradient-border that
|
|
.glass-panel-strong uses, but with a `:hover` swap that flips the
|
|
padding-box fill from high → mid so the button still "responds"
|
|
to mousing without changing its corner-light contract.
|
|
|
|
Why a class instead of `<GlassSurface>` (D1 of architect plan):
|
|
`<GlassSurface>` sets `background` inline, which CSS `:hover`
|
|
rules cannot override without `!important`. A purpose-built
|
|
class keeps the hover semantics pure CSS. */
|
|
.btn-glass-secondary {
|
|
background:
|
|
linear-gradient(var(--glass-surface-high), var(--glass-surface-high))
|
|
padding-box,
|
|
radial-gradient(at 0% 100%, var(--corner-light-warm-subtle) 0%, transparent 42%)
|
|
border-box,
|
|
radial-gradient(at 100% 0%, var(--corner-light-cool-subtle) 0%, transparent 42%)
|
|
border-box,
|
|
var(--chip-border-base) border-box;
|
|
border: 1px solid transparent;
|
|
backdrop-filter: blur(var(--glass-blur-low)) saturate(var(--glass-saturate));
|
|
-webkit-backdrop-filter: blur(var(--glass-blur-low)) saturate(var(--glass-saturate));
|
|
box-shadow:
|
|
var(--rim-light-inner),
|
|
var(--rim-light-outer);
|
|
color: var(--text-primary);
|
|
transition: background 200ms ease;
|
|
}
|
|
|
|
.btn-glass-secondary:hover {
|
|
background:
|
|
linear-gradient(var(--glass-surface-mid), var(--glass-surface-mid))
|
|
padding-box,
|
|
radial-gradient(at 0% 100%, var(--corner-light-warm-subtle) 0%, transparent 42%)
|
|
border-box,
|
|
radial-gradient(at 100% 0%, var(--corner-light-cool-subtle) 0%, transparent 42%)
|
|
border-box,
|
|
var(--chip-border-base) border-box;
|
|
}
|
|
|
|
/* Input — glass-surface variant.
|
|
Adopted by `<Input>` after the
|
|
migrate-button-input-mobilenav-to-glass-primitive convoy
|
|
(2026-06-05). Mirrors .glass-panel-strong's tint + blur but does
|
|
NOT use the gradient-border trick (D2 of architect plan): the
|
|
Input control needs a conditional 1px border that flips to red
|
|
on error (`aria-invalid`), and the gradient-border requires
|
|
`border: 1px solid transparent` to expose the border-box layers,
|
|
which is incompatible with an error border swap.
|
|
|
|
The visible 1px border stays in JSX, conditional on error state.
|
|
Focus ring (`:focus-visible`) stays in JSX too. */
|
|
.glass-input {
|
|
background: var(--glass-surface-high);
|
|
backdrop-filter: blur(var(--glass-blur-low)) saturate(var(--glass-saturate));
|
|
-webkit-backdrop-filter: blur(var(--glass-blur-low)) saturate(var(--glass-saturate));
|
|
}
|
|
|
|
/* Gradient text effects — Deck Hearth warm palette only.
|
|
The pre-Deck-Hearth aliases (.glow-blue / .glow-purple / .glow-pink,
|
|
.gradient-text-blue / .gradient-text-purple, .gradient-bg-purple)
|
|
were removed by the finish-liquid-glass-design PR after sweeping
|
|
their last consumers to --accent-ember equivalents. */
|
|
.gradient-text-gold {
|
|
background: linear-gradient(135deg, #ffab40 0%, #ff6f00 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
|
|
.gradient-text-flame {
|
|
background: linear-gradient(135deg, #ff6f00 0%, #d84315 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
|
|
.gradient-text-ember {
|
|
background: linear-gradient(135deg, #d84315 0%, #bf360c 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
|
|
/* Theme toggle button */
|
|
.theme-toggle {
|
|
@apply p-2 rounded-2xl transition-all duration-300;
|
|
background: var(--bg-secondary);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
/* Header icons */
|
|
.header-icon {
|
|
@apply p-2 rounded-xl transition-all duration-200;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* Logo styling */
|
|
.logo-container {
|
|
@apply rounded-2xl flex items-center justify-center transition-all duration-300;
|
|
background: var(--gradient-primary);
|
|
}
|
|
|
|
/* Custom scrollbar */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: var(--text-secondary);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: var(--text-accent);
|
|
}
|
|
|
|
/* Search bar specific styling */
|
|
.search-bar {
|
|
@apply w-full px-4 py-3 border rounded-2xl shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-0 transition-all duration-200;
|
|
background-color: var(--input-bg);
|
|
border-color: var(--input-border);
|
|
color: var(--input-text);
|
|
}
|
|
|
|
.search-bar::placeholder {
|
|
color: var(--input-placeholder);
|
|
}
|
|
|
|
/* Action buttons with proper gradients */
|
|
.action-btn-primary {
|
|
@apply font-bold py-3 px-6 rounded-2xl transition-all duration-300 shadow-lg hover:shadow-xl;
|
|
background: var(--gradient-primary);
|
|
color: white;
|
|
}
|
|
|
|
.action-btn-secondary {
|
|
@apply font-bold py-3 px-6 rounded-2xl transition-all duration-300 shadow-lg hover:shadow-xl;
|
|
background: var(--gradient-secondary);
|
|
color: white;
|
|
}
|
|
|
|
/* Particle Effects for High Rarity Cards */
|
|
@keyframes float {
|
|
0%, 100% {
|
|
transform: translateY(0px) translateX(0px);
|
|
opacity: 0.7;
|
|
}
|
|
25% {
|
|
transform: translateY(-8px) translateX(4px);
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
transform: translateY(-4px) translateX(-2px);
|
|
opacity: 0.8;
|
|
}
|
|
75% {
|
|
transform: translateY(-12px) translateX(6px);
|
|
opacity: 0.9;
|
|
}
|
|
}
|
|
|
|
@keyframes sparkle {
|
|
0%, 100% {
|
|
transform: scale(1) rotate(0deg);
|
|
opacity: 0.8;
|
|
}
|
|
25% {
|
|
transform: scale(1.5) rotate(90deg);
|
|
opacity: 1;
|
|
}
|
|
50% {
|
|
transform: scale(0.8) rotate(180deg);
|
|
opacity: 0.6;
|
|
}
|
|
75% {
|
|
transform: scale(1.2) rotate(270deg);
|
|
opacity: 0.9;
|
|
}
|
|
}
|
|
|
|
@keyframes aura {
|
|
0%, 100% {
|
|
opacity: 0.1;
|
|
transform: scale(1);
|
|
}
|
|
50% {
|
|
opacity: 0.2;
|
|
transform: scale(1.05);
|
|
}
|
|
}
|
|
|
|
@keyframes edgeFloat {
|
|
0%, 100% {
|
|
opacity: 0.7;
|
|
transform: translateY(0px) translateX(0px);
|
|
}
|
|
25% {
|
|
opacity: 1;
|
|
transform: translateY(-3px) translateX(2px);
|
|
}
|
|
50% {
|
|
opacity: 0.8;
|
|
transform: translateY(-1px) translateX(-1px);
|
|
}
|
|
75% {
|
|
opacity: 0.9;
|
|
transform: translateY(-4px) translateX(3px);
|
|
}
|
|
}
|
|
|
|
@keyframes edgeGlow {
|
|
0%, 100% {
|
|
opacity: 0.4;
|
|
transform: scale(1);
|
|
}
|
|
25% {
|
|
opacity: 0.8;
|
|
transform: scale(1.2);
|
|
}
|
|
50% {
|
|
opacity: 0.6;
|
|
transform: scale(0.8);
|
|
}
|
|
75% {
|
|
opacity: 0.9;
|
|
transform: scale(1.1);
|
|
}
|
|
}
|
|
|
|
/* Enhanced glow effects for mythic and enchanted cards */
|
|
.card-mythic-glow {
|
|
box-shadow:
|
|
0 0 20px rgba(255, 215, 0, 0.4),
|
|
0 0 40px rgba(255, 215, 0, 0.2),
|
|
0 0 60px rgba(255, 215, 0, 0.1);
|
|
}
|
|
|
|
.card-enchanted-glow {
|
|
box-shadow:
|
|
0 0 20px rgba(168, 85, 247, 0.4),
|
|
0 0 40px rgba(168, 85, 247, 0.2),
|
|
0 0 60px rgba(168, 85, 247, 0.1);
|
|
}
|
|
|
|
/* Card Hover Panel Styles */
|
|
.card-item-container {
|
|
position: relative;
|
|
}
|
|
|
|
.card-item-container:hover {
|
|
z-index: 50;
|
|
}
|
|
|
|
.card-item-container.selected {
|
|
z-index: 60;
|
|
}
|
|
|
|
/* Ensure proper layering during animations */
|
|
.card-grid-container {
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Prevent text selection during card interactions */
|
|
.card-item-container * {
|
|
user-select: none;
|
|
}
|
|
|
|
/* Side panel specific styles */
|
|
.card-side-panel {
|
|
transform-origin: left center;
|
|
backdrop-filter: blur(8px);
|
|
}
|
|
|
|
/* Responsive adjustments for side panels */
|
|
@media (max-width: 1280px) {
|
|
.card-side-panel {
|
|
width: 16rem; /* w-64 */
|
|
}
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.card-side-panel {
|
|
display: none; /* Hide panels on smaller screens */
|
|
}
|
|
}
|
|
|
|
/* Dark mode improvements */
|
|
@media (prefers-color-scheme: dark) {
|
|
.card-side-panel {
|
|
backdrop-filter: blur(8px) brightness(0.8);
|
|
}
|
|
}
|
|
|
|
/* Smooth panel transitions */
|
|
.card-panel-enter {
|
|
opacity: 0;
|
|
transform: translateX(8px);
|
|
}
|
|
|
|
.card-panel-enter-active {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
transition: all 300ms ease-out;
|
|
}
|
|
|
|
/* Rarity Effects */
|
|
/* Mythic/Legendary - Golden glow */
|
|
.rarity-glow-mythic {
|
|
box-shadow:
|
|
0 0 25px rgba(251, 191, 36, 0.5),
|
|
0 0 50px rgba(251, 191, 36, 0.35),
|
|
0 0 75px rgba(251, 191, 36, 0.25);
|
|
}
|
|
|
|
.rarity-particles-mythic::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: radial-gradient(circle at 20% 30%, rgba(251, 191, 36, 0.3) 0%, transparent 50%),
|
|
radial-gradient(circle at 80% 70%, rgba(251, 191, 36, 0.2) 0%, transparent 50%),
|
|
radial-gradient(circle at 60% 20%, rgba(251, 191, 36, 0.25) 0%, transparent 50%);
|
|
animation: mythic-sparkle 4s ease-in-out infinite;
|
|
border-radius: inherit;
|
|
}
|
|
|
|
@keyframes mythic-sparkle {
|
|
0%, 100% { opacity: 0.3; }
|
|
50% { opacity: 0.7; }
|
|
}
|
|
|
|
/* Rare - Purple glow */
|
|
.rarity-glow-rare {
|
|
box-shadow:
|
|
0 0 20px rgba(168, 85, 247, 0.45),
|
|
0 0 40px rgba(168, 85, 247, 0.35),
|
|
0 0 60px rgba(168, 85, 247, 0.25);
|
|
}
|
|
|
|
.rarity-particles-rare::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: radial-gradient(circle at 30% 40%, rgba(168, 85, 247, 0.2) 0%, transparent 40%),
|
|
radial-gradient(circle at 70% 60%, rgba(168, 85, 247, 0.15) 0%, transparent 40%);
|
|
animation: rare-shimmer 3s ease-in-out infinite;
|
|
border-radius: inherit;
|
|
}
|
|
|
|
@keyframes rare-shimmer {
|
|
0%, 100% { opacity: 0.2; }
|
|
50% { opacity: 0.5; }
|
|
}
|
|
|
|
/* Uncommon - Blue glow */
|
|
.rarity-glow-uncommon {
|
|
box-shadow:
|
|
0 0 12px rgba(96, 165, 250, 0.35),
|
|
0 0 25px rgba(96, 165, 250, 0.25);
|
|
}
|
|
|
|
.rarity-particles-uncommon::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: radial-gradient(circle at 40% 30%, rgba(96, 165, 250, 0.15) 0%, transparent 30%);
|
|
animation: uncommon-twinkle 2.5s ease-in-out infinite;
|
|
border-radius: inherit;
|
|
}
|
|
|
|
@keyframes uncommon-twinkle {
|
|
0%, 100% { opacity: 0.1; }
|
|
50% { opacity: 0.3; }
|
|
}
|
|
|
|
/* Enchanted/Secret - Pink/Rainbow glow */
|
|
.rarity-glow-enchanted {
|
|
box-shadow:
|
|
0 0 28px rgba(244, 114, 182, 0.5),
|
|
0 0 55px rgba(168, 85, 247, 0.35),
|
|
0 0 80px rgba(59, 130, 246, 0.25);
|
|
}
|
|
|
|
.rarity-particles-enchanted::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background:
|
|
radial-gradient(circle at 25% 25%, rgba(244, 114, 182, 0.3) 0%, transparent 40%),
|
|
radial-gradient(circle at 75% 75%, rgba(168, 85, 247, 0.2) 0%, transparent 40%),
|
|
radial-gradient(circle at 50% 50%, rgba(59, 130, 246, 0.25) 0%, transparent 40%);
|
|
animation: enchanted-rainbow 3s ease-in-out infinite;
|
|
border-radius: inherit;
|
|
}
|
|
|
|
@keyframes enchanted-rainbow {
|
|
0%, 100% {
|
|
opacity: 0.4;
|
|
transform: rotate(0deg);
|
|
}
|
|
33% {
|
|
opacity: 0.6;
|
|
transform: rotate(120deg);
|
|
}
|
|
66% {
|
|
opacity: 0.5;
|
|
transform: rotate(240deg);
|
|
}
|
|
}
|
|
|
|
/* Hover enhancement for rarity effects */
|
|
.card-item-container:hover .rarity-glow-mythic {
|
|
box-shadow:
|
|
0 0 30px rgba(251, 191, 36, 0.6),
|
|
0 0 60px rgba(251, 191, 36, 0.5),
|
|
0 0 90px rgba(251, 191, 36, 0.4);
|
|
}
|
|
|
|
.card-item-container:hover .rarity-glow-rare {
|
|
box-shadow:
|
|
0 0 25px rgba(168, 85, 247, 0.6),
|
|
0 0 50px rgba(168, 85, 247, 0.5),
|
|
0 0 75px rgba(168, 85, 247, 0.4);
|
|
}
|
|
|
|
.card-item-container:hover .rarity-glow-uncommon {
|
|
box-shadow:
|
|
0 0 20px rgba(96, 165, 250, 0.5),
|
|
0 0 40px rgba(96, 165, 250, 0.4);
|
|
}
|
|
|
|
.card-item-container:hover .rarity-glow-enchanted {
|
|
box-shadow:
|
|
0 0 35px rgba(244, 114, 182, 0.7),
|
|
0 0 70px rgba(244, 114, 182, 0.6),
|
|
0 0 105px rgba(244, 114, 182, 0.5);
|
|
}
|
|
|
|
/* Utility classes */
|
|
.flex-2 {
|
|
flex: 2;
|
|
}
|
|
|
|
.line-clamp-2 {
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Floating particle animation */
|
|
@keyframes float {
|
|
0%, 100% {
|
|
transform: translateY(0px) translateX(0px) scale(1);
|
|
opacity: 0.6;
|
|
}
|
|
25% {
|
|
transform: translateY(-20px) translateX(10px) scale(1.1);
|
|
opacity: 0.8;
|
|
}
|
|
50% {
|
|
transform: translateY(-10px) translateX(-15px) scale(0.9);
|
|
opacity: 1;
|
|
}
|
|
75% {
|
|
transform: translateY(-25px) translateX(5px) scale(1.05);
|
|
opacity: 0.7;
|
|
}
|
|
}
|
|
|
|
.animate-float {
|
|
animation: float 4s ease-in-out infinite;
|
|
}
|
|
|
|
/* Fire Glow Background Animation */
|
|
@keyframes fire-glow {
|
|
0%, 100% {
|
|
filter: hue-rotate(0deg) brightness(1) saturate(1);
|
|
transform: scale(1);
|
|
}
|
|
25% {
|
|
filter: hue-rotate(5deg) brightness(1.05) saturate(1.1);
|
|
transform: scale(1.02);
|
|
}
|
|
50% {
|
|
filter: hue-rotate(-3deg) brightness(0.98) saturate(1.05);
|
|
transform: scale(0.99);
|
|
}
|
|
75% {
|
|
filter: hue-rotate(8deg) brightness(1.03) saturate(1.08);
|
|
transform: scale(1.01);
|
|
}
|
|
}
|
|
|
|
.fire-glow-bg {
|
|
animation: fire-glow 12s ease-in-out infinite;
|
|
}
|
|
|
|
/* Ember Floating Animation */
|
|
@keyframes ember-float {
|
|
0% {
|
|
transform: translateY(0px) translateX(0px) scale(0.8);
|
|
opacity: 0;
|
|
}
|
|
10% {
|
|
opacity: 0.6;
|
|
}
|
|
20% {
|
|
transform: translateY(-30px) translateX(15px) scale(1);
|
|
opacity: 0.8;
|
|
}
|
|
40% {
|
|
transform: translateY(-60px) translateX(-10px) scale(0.9);
|
|
opacity: 1;
|
|
}
|
|
60% {
|
|
transform: translateY(-90px) translateX(20px) scale(1.1);
|
|
opacity: 0.7;
|
|
}
|
|
80% {
|
|
transform: translateY(-120px) translateX(-5px) scale(0.8);
|
|
opacity: 0.4;
|
|
}
|
|
100% {
|
|
transform: translateY(-150px) translateX(10px) scale(0.6);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
.animate-ember-float {
|
|
animation: ember-float 10s linear infinite;
|
|
}
|
|
|
|
/* Mobile Navigation Styles */
|
|
.h-safe-area-inset-bottom {
|
|
height: env(safe-area-inset-bottom, 0px);
|
|
}
|
|
|
|
/* Mobile responsive adjustments */
|
|
@media (max-width: 768px) {
|
|
/* Ensure content doesn't get hidden behind mobile nav */
|
|
.mobile-content-padding {
|
|
padding-bottom: 80px; /* Space for mobile navigation */
|
|
}
|
|
|
|
/* Mobile navigation backdrop blur support */
|
|
@supports (backdrop-filter: blur(16px)) {
|
|
.mobile-nav-backdrop {
|
|
backdrop-filter: blur(16px);
|
|
-webkit-backdrop-filter: blur(16px);
|
|
}
|
|
}
|
|
|
|
/* Fallback for browsers without backdrop-filter */
|
|
@supports not (backdrop-filter: blur(16px)) {
|
|
.mobile-nav-backdrop {
|
|
background-color: rgba(var(--bg-secondary-rgb), 0.95) !important;
|
|
}
|
|
}
|
|
} |