snipe/web/src/assets/theme.css
pyr0ball 7a704441a6 feat(snipe): Vue 3 frontend scaffold + Docker web service
- web/: Vue 3 + Vite + UnoCSS + Pinia, dark tactical theme (amber/#0d1117)
- AppNav, ListingCard, SearchView with filters/sort, composables
  (useSnipeMode, useKonamiCode, useMotion), Pinia search store
- Steal shimmer, auction countdown, Snipe Mode easter egg all native in Vue
- docker/web/: nginx + multi-stage Dockerfile (node build → nginx serve)
- compose.yml: api (8510) + web (8509) services
- Dockerfile CMD updated to uvicorn for upcoming FastAPI layer
- Clean build: 0 TS errors, 380 modules
2026-03-25 15:11:35 -07:00

218 lines
5.9 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* assets/theme.css — CENTRAL THEME FILE for Snipe
Dark tactical theme: near-black surfaces, amber accent, trust-signal colours.
ALL color/font/spacing tokens live here — nowhere else.
Snipe Mode easter egg: activated by Konami code (cf-snipe-mode in localStorage).
*/
/* ── Snipe — dark tactical (default — always dark) ─ */
:root {
/* Brand — amber target reticle */
--app-primary: #f59e0b;
--app-primary-hover: #d97706;
--app-primary-light: rgba(245, 158, 11, 0.12);
/* Surfaces — near-black GitHub-dark inspired */
--color-surface: #0d1117;
--color-surface-2: #161b22;
--color-surface-raised: #1c2129;
/* Borders */
--color-border: #30363d;
--color-border-light: #21262d;
/* Text */
--color-text: #e6edf3;
--color-text-muted: #8b949e;
--color-text-inverse: #0d1117;
/* Trust signal colours */
--trust-high: #3fb950; /* composite_score >= 80 — green */
--trust-mid: #d29922; /* composite_score 5079 — amber */
--trust-low: #f85149; /* composite_score < 50 — red */
/* Semantic */
--color-success: #3fb950;
--color-error: #f85149;
--color-warning: #d29922;
--color-info: #58a6ff;
/* Typography */
--font-display: 'Fraunces', Georgia, serif;
--font-body: 'Atkinson Hyperlegible', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', monospace;
/* Spacing scale */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
--space-16: 4rem;
--space-24: 6rem;
/* Radii */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 1rem;
--radius-full: 9999px;
/* Shadows — dark base */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5), 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.6), 0 4px 8px rgba(0, 0, 0, 0.3);
/* Transitions */
--transition: 200ms ease;
--transition-slow: 400ms ease;
/* Layout */
--sidebar-width: 220px;
}
/* ── Snipe Mode easter egg theme ─────────────────── */
/* Activated by Konami code; stored as 'cf-snipe-mode' in localStorage */
/* Applied: document.documentElement.dataset.snipeMode = 'active' */
[data-snipe-mode="active"] {
--app-primary: #ff6b35;
--app-primary-hover: #ff4500;
--app-primary-light: rgba(255, 107, 53, 0.15);
--color-surface: #050505;
--color-surface-2: #0a0a0a;
--color-surface-raised: #0f0f0f;
--color-border: #ff6b3530;
--color-border-light: #ff6b3518;
--color-text: #ff9970;
--color-text-muted: #ff6b3580;
/* Glow variants for snipe mode UI */
--snipe-glow-xs: rgba(255, 107, 53, 0.08);
--snipe-glow-sm: rgba(255, 107, 53, 0.15);
--snipe-glow-md: rgba(255, 107, 53, 0.4);
--shadow-sm: 0 1px 3px rgba(255, 107, 53, 0.08);
--shadow-md: 0 4px 12px rgba(255, 107, 53, 0.12);
--shadow-lg: 0 10px 30px rgba(255, 107, 53, 0.18);
}
/* ── Base resets ─────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
html {
font-family: var(--font-body);
color: var(--color-text);
background: var(--color-surface);
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body { margin: 0; min-height: 100vh; }
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-display);
color: var(--app-primary);
line-height: 1.2;
margin: 0;
}
/* Focus visible — keyboard nav — accessibility requirement */
:focus-visible {
outline: 2px solid var(--app-primary);
outline-offset: 3px;
border-radius: var(--radius-sm);
}
/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* ── Utility: screen reader only ────────────────── */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
/* ── Steal shimmer animation ─────────────────────
Applied to ListingCard when listing qualifies as a steal:
composite_score >= 80 AND price < marketPrice * 0.8
The shimmer sweeps left-to-right across the card border.
*/
@keyframes steal-shimmer {
0% { background-position: -200% center; }
100% { background-position: 200% center; }
}
.steal-card {
border: 1.5px solid transparent;
background-clip: padding-box;
position: relative;
}
.steal-card::before {
content: '';
position: absolute;
inset: -1.5px;
border-radius: inherit;
background: linear-gradient(
90deg,
var(--trust-high) 0%,
#7ee787 40%,
var(--app-primary) 60%,
var(--trust-high) 100%
);
background-size: 200% auto;
animation: steal-shimmer 2.4s linear infinite;
z-index: -1;
}
/* ── Auction de-emphasis ─────────────────────────
Auctions with >1h remaining have fluid prices — de-emphasise
the current price to avoid anchoring on a misleading figure.
*/
.auction-price--live {
opacity: 0.55;
font-style: italic;
}
.auction-badge {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: 2px var(--space-2);
border-radius: var(--radius-full);
background: var(--color-warning);
color: var(--color-text-inverse);
font-family: var(--font-mono);
font-size: 0.7rem;
font-weight: 700;
letter-spacing: 0.04em;
}
.fixed-price-badge {
display: inline-flex;
align-items: center;
gap: var(--space-1);
padding: 2px var(--space-2);
border-radius: var(--radius-full);
background: var(--color-surface-raised);
color: var(--color-text-muted);
border: 1px solid var(--color-border);
font-size: 0.7rem;
font-weight: 600;
}