* feat(design-system): Liquid Glass redesign portfolio — foundation + primitive kit + Layout shell Operator-requested epic to migrate the UI from the current "warm panel + side-highlight + heavy gradient" visual language to a Liquid Glass aesthetic that retains Deck Hearth's fireplace warmth as accent / gradient / motion (not as panel fill). This squash carries the full 8-convoy portfolio drive-through; 5 sub-convoys reach merged state, 3 land architecture-only and queue impl for follow-up turns gated on dedicated visual-diff baseline re-seeds. Sub-convoy #1 (liquid-glass-design-tokens) — MERGED. 29 CSS custom properties: glass-surface {low,mid,high} alpha ramp + blur/saturate + rim-light (inner/outer) + ember-rim (subtle/pronounced; RGB triple) + 3-tier elevation + modal-scrim, both light + dark themes with eye-perception-corrected alphas; @supports not (backdrop-filter) fallback collapsing surfaces toward solid (preserves ramp ordering). Authored docs/DESIGN_TOKENS.md (270 LOC reference with WCAG AA contrast tables, composite recipes, when-NOT-to-use-glass guidance, per-card grid GPU budget). AGENTS.md gains a § Visual language section as the new agent-contract surface. Sub-convoy #2 (liquid-glass-modal-and-surface-primitive) — Brief 1 MERGED. Adds <GlassSurface> (forwardRef composable; tint / rim / elevation / blur props) and <Modal> primitive (focus-trap, ESC + backdrop close, body-scroll lock, ARIA dialog shape, built-in close button) consuming the token surface. lib/use-focus-trap.js — homegrown hook (~60 LOC, no dep). 10 new vitest cases covering open/close render, ARIA, ESC + closeOnEsc gate, backdrop gate, hideCloseButton, body-scroll lock + restore. 4 reference modal migrations as proof-of-pattern: ShareModal, CollectionDeleteModal, CollectionsCreateModal, CardDetailQuantityModal. Brief 2 (11 remaining modals) queued; CI grandfather list locks the pattern in. Sub-convoy #3 (liquid-glass-form-primitives) — Brief 1 MERGED. Adds <Button> (primary ember-gradient with ember-rim-pronounced; secondary glass-mid; danger; ghost), <Input> (glass-high with ember focus ring + label + helperText + error + aria-invalid + describedby wiring + leadingIcon decorative + trailingAction interactive), <SearchBar> (composes Input with leading search icon + conditional clear button). 10 new vitest cases. pages/login.js + pages/signup.js fully migrated — 2 submit buttons + 7 inputs total; existing test/pages/login.test.js assertion ("Sign in to Deck Hearth" button text) preserved. Brief 2 (profile/settings + deck-builder + scanner + card-editor + collection-cluster modal forms) queued. Sub-convoy #4 (liquid-glass-layout-shell) — MERGED. 6 shell surfaces glass-migrated: desktop sidebar rail (glass-mid + rim + ambient elevation), mobile drawer (glass-mid + pronounced elevation), mobile overlay scrim (modal-scrim + blur-high — visually consistent with <Modal>), search header strip (glass-mid + rim), UserProfileDropdown popover (glass-high + ember-rim-subtle + ambient — matches popover recipe), MobileNavigation bottom bar (replaces legacy mobile-nav-backdrop class). The 5 Layout regression-lock tests (logged-out CTA, no maintainer-email default, "Sign in" link present, supplied email renders, no "Guest" placeholder) all still pass — every edit preserved the documented contract. Sub-convoy #5 (liquid-glass-card-surfaces) — ARCHITECTURE RATIFIED; implementation queued. Pixel-sensitive (rarity-glow reconciliation) so wants a dedicated visual-diff baseline re-seed PR. Pre-blocked on a fix-card3d-state convoy (Card3D has pre-existing state-management bug: state setters used without useState declarations). Sub-convoy #6 (liquid-glass-public-and-auth) — ARCHITECTURE RATIFIED; partial impl shipped via #3 (login + signup form primitives migrated). Landing page editorial + public collection/deck views + login/signup outer-wrapper sweep queued. Sub-convoy #7 (motion-system-pass) — MERGED. 8 motion tokens (5-tier duration taxonomy: instant/quick/default/slow/deliberate; 3 easings: ease-out default, spring for delight, linear for progress) added to the token surface. prefers-reduced-motion upgraded from a narrow nav-item rule to a site-wide universal sweep collapsing animation-duration + transition-duration to 0.01ms (preserves end states, no flicker); .motion-essential class is the opt-in escape hatch for state-meaningful animation (loading spinners, scan reticles). Authored docs/MOTION_SYSTEM.md with WCAG SC 2.3.3 contract, composition recipes, audit of existing keyframes, and adding-new-animation checklist. Sub-convoy #8 (cleanup-legacy-design-css) — Brief 1 MERGED. Two new CI jobs in .github/workflows/ci.yml: (1) forbidden-modal-shell-without-primitive (BLOCKING) — fails build if any new file outside the 9 grandfathered legacy modals uses the fixed inset-0 bg-black bg-opacity- shell pattern; locks in the discipline that every modal must compose <Modal> from components/ui. (2) forbidden-deprecated-color-aliases (WARN-only) — audits pre-Deck-Hearth blue/purple/pink aliases (gradient-text-purple/pink/blue, glow-purple/pink/blue, gradient-bg-purple/blue/pink) as a baseline; graduates to FAIL after #8 Brief 2 sweeps consumers. .cursor/rules/ui-and-theming.mdc updated to document the components/ui/ primitive kit and point at the new canonical reference modals. Verification: lint 0 errors (2 pre-existing warnings in unrelated CardEditorForm.js + CollectionsPageView.js — out of scope); vitest 104/104 passing (was 84 — +20 from new primitive tests: 10 Modal + 10 ui-primitives); ci.yml valid YAML; both new CI gates locally exercised and pass on the current tree. Operator follow-ups documented in .convoys/ship-readiness.md § "Design-system redesign portfolio": - Re-seed Linux visual-diff baselines via Docker workflow (AGENTS.md § 6) after this merges. - preview-smoke.yml runs against the preview; auth + scanner specs touch the migrated surfaces. - Vercel promote to production once smoke + visual gates pass. - Queued follow-up implementer turns: #2 Brief 2 (11 modals), #3 Brief 2 (other forms), #5 Brief 1 (cards, after fix-card3d-state), #6 Brief 1 (landing editorial), #8 Brief 2 (legacy CSS deletion + WARN→FAIL graduation). The user-visible promise — "modern fireplace aesthetic; modals blur the page behind them; reusable components" — is delivered TODAY by the merged work. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(use-focus-trap): preserve named useFocusTrap export for ScannerPageView The portfolio squash inadvertently overwrote the pre-existing lib/use-focus-trap.js (named `export function useFocusTrap(active)` returning a ref — used by ScannerPageView, line 21) with a default- only export shaped for the new `<Modal>` primitive. Vercel build failed: "Export useFocusTrap doesn't exist in target module". Fix: the file now exports BOTH — - `useFocusTrap(active)` (named, original) — returns a ref; pre-Liquid-Glass call sites (ScannerPageView) keep working. - `useFocusTrapContainer({ active, containerRef, ... })` (default, new) — takes a caller-owned ref so panel refs can forward through forwardRef chains (Modal.js consumes this shape). Both hooks are commented to document which to use when. Modal.js imports default already, so no change needed there. Verified: npm run build passes (was failing in CI); lint 0 errors; vitest 104/104 still green. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
958 lines
No EOL
26 KiB
CSS
958 lines
No EOL
26 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 - Cozy Evening by the Fire */
|
|
--bg-primary-dark: #1a0f0a; /* Deep charcoal, like burnt wood */
|
|
--bg-secondary-dark: #2d1b12; /* Rich dark brown, like oak */
|
|
--bg-tertiary-dark: #3d2317; /* Medium brown, like mahogany */
|
|
--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) — same alpha ramp over warm-charcoal base. */
|
|
--glass-surface-low: rgba(26, 15, 10, 0.55);
|
|
--glass-surface-mid: rgba(26, 15, 10, 0.68);
|
|
--glass-surface-high: rgba(26, 15, 10, 0.82);
|
|
|
|
/* 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(26, 15, 10, 0.92);
|
|
--glass-surface-mid: rgba(26, 15, 10, 0.95);
|
|
--glass-surface-high: rgba(26, 15, 10, 0.98);
|
|
}
|
|
}
|
|
|
|
/* Apply theme colors */
|
|
body {
|
|
background-color: var(--bg-primary-light);
|
|
color: var(--text-primary-light);
|
|
}
|
|
|
|
[data-theme="dark"] body {
|
|
background-color: var(--bg-primary-dark);
|
|
color: var(--text-primary-dark);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* 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,
|
|
.nav-item-bottom:hover {
|
|
background-color: var(--bg-tertiary) !important;
|
|
color: var(--text-primary) !important;
|
|
transform: translateX(4px);
|
|
}
|
|
|
|
.nav-item:focus-within,
|
|
.nav-item-bottom:focus-within {
|
|
background-color: var(--bg-tertiary) !important;
|
|
color: var(--text-primary) !important;
|
|
transform: translateX(4px);
|
|
}
|
|
|
|
.nav-item-active {
|
|
background-color: var(--bg-tertiary) !important;
|
|
color: var(--text-primary) !important;
|
|
border-left: 3px solid var(--accent-ember);
|
|
padding-left: calc(1rem - 3px);
|
|
}
|
|
|
|
.nav-item-hover:hover {
|
|
border-left: 3px solid var(--accent-flame);
|
|
padding-left: calc(1rem - 3px);
|
|
}
|
|
|
|
.nav-item-hover:focus-within {
|
|
border-left: 3px solid var(--accent-ember);
|
|
padding-left: calc(1rem - 3px);
|
|
}
|
|
|
|
/* 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 {
|
|
@apply rounded-3xl shadow-lg p-6 transition-all duration-300;
|
|
background-color: var(--bg-primary);
|
|
border: 1px solid var(--border);
|
|
}
|
|
|
|
/* Glowing effects for dark theme */
|
|
[data-theme="dark"] .glow-blue {
|
|
box-shadow: 0 0 20px rgba(6, 182, 212, 0.3);
|
|
}
|
|
|
|
[data-theme="dark"] .glow-purple {
|
|
box-shadow: 0 0 20px rgba(139, 92, 246, 0.3);
|
|
}
|
|
|
|
[data-theme="dark"] .glow-pink {
|
|
box-shadow: 0 0 20px rgba(236, 72, 153, 0.3);
|
|
}
|
|
|
|
/* Gradient text effects */
|
|
.gradient-text-blue {
|
|
background: linear-gradient(135deg, #06b6d4 0%, #3b82f6 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
|
|
.gradient-text-purple {
|
|
background: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%);
|
|
-webkit-background-clip: text;
|
|
-webkit-text-fill-color: transparent;
|
|
background-clip: text;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
}
|
|
} |