SkateSpots/src/index.css

916 lines
30 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.

@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* Light theme - Clean with subtle neon accents */
--background: 0 0% 100%;
--foreground: 0 0% 10%;
--card: 0 0% 98%;
--card-foreground: 0 0% 10%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 10%;
/* Darker neon red for light mode */
--primary: 349 85% 45%;
--primary-foreground: 0 0% 100%;
--secondary: 0 0% 95%;
--secondary-foreground: 0 0% 10%;
--muted: 0 0% 96%;
--muted-foreground: 0 0% 40%;
/* Darker neon lime for light mode.
Foreground is near-black (~9.5:1 on lime) — white (2.35:1) fails WCAG AA,
and lime is the "go"/CTA color, so its filled state must stay readable. */
--accent: 75 80% 40%;
--accent-foreground: 0 0% 10%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 0 0% 90%;
--input: 0 0% 95%;
--ring: 349 85% 45%;
--radius: 1rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 0 0% 10%;
--sidebar-primary: 349 85% 45%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 0 0% 95%;
--sidebar-accent-foreground: 0 0% 10%;
--sidebar-border: 0 0% 90%;
--sidebar-ring: 349 85% 45%;
/* Custom gradient colors */
--gradient-start: 349 85% 45%;
--gradient-end: 75 80% 40%;
}
.dark {
/* Pure black THUG2 aesthetic */
--background: 0 0% 0%;
--foreground: 0 0% 100%;
--card: 0 0% 5%;
--card-foreground: 0 0% 100%;
--popover: 0 0% 5%;
--popover-foreground: 0 0% 100%;
/* Neon red primary */
--primary: 349 100% 50%;
--primary-foreground: 0 0% 100%;
--secondary: 0 0% 10%;
--secondary-foreground: 0 0% 100%;
--muted: 0 0% 10%;
--muted-foreground: 0 0% 60%;
/* Neon lime accent */
--accent: 75 100% 60%;
--accent-foreground: 0 0% 0%;
--destructive: 0 84% 60%;
--destructive-foreground: 0 0% 100%;
--border: 349 100% 50% / 0.3;
--input: 0 0% 10%;
--ring: 349 100% 50%;
--sidebar-background: 0 0% 0%;
--sidebar-foreground: 0 0% 100%;
--sidebar-primary: 349 100% 50%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 0 0% 10%;
--sidebar-accent-foreground: 0 0% 100%;
--sidebar-border: 349 100% 50% / 0.3;
--sidebar-ring: 349 100% 50%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
/* Leaflet map overrides */
.leaflet-container {
height: 100%;
width: 100%;
border-radius: var(--radius);
z-index: 0;
}
/* Push Leaflet bottom controls (attribution, zoom) above the mobile nav bar.
The nav bar is ~60px tall + safe area inset. This prevents "Map data © OpenStreetMap"
from overlapping the navigation buttons on mobile. */
.leaflet-bottom {
bottom: env(safe-area-inset-bottom, 0px);
padding-bottom: 64px;
}
/* The map page's toolbar floats over the top of the map on mobile, which would
otherwise sit on top of Leaflet's default top-left zoom control. Push that
control clear of it (desktop keeps the toolbar in flow, so no offset). */
@media (max-width: 767px) {
.ss-map-floating-toolbar .leaflet-top.leaflet-left {
margin-top: 4.25rem;
}
}
.leaflet-popup-content-wrapper {
border-radius: calc(var(--radius) - 4px);
box-shadow: 0 10px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
}
.leaflet-popup-content {
margin: 0;
}
/* THUG2-inspired neon effects */
@keyframes neon-glow {
0%, 100% { text-shadow: 0 0 10px currentColor, 0 0 20px currentColor, 0 0 30px currentColor, 0 0 40px currentColor; }
50% { text-shadow: 0 0 5px currentColor, 0 0 10px currentColor, 0 0 15px currentColor, 0 0 20px currentColor; }
}
/* ── Display font utilities ───────────────────────────────────────────────── */
/* Bebas Neue — used for all major headlines */
.font-display {
font-family: 'Bebas Neue', 'Kanit', 'Impact', 'Arial Black', sans-serif;
letter-spacing: 0.04em;
}
/* Barlow Condensed — used for subheadings, labels, nav */
.font-condensed {
font-family: 'Barlow Condensed', 'Kanit', 'Arial Narrow', sans-serif;
}
.neon-header {
animation: neon-glow 2s ease-in-out infinite;
font-family: 'Bebas Neue', 'Kanit', 'Impact', 'Arial Black', sans-serif;
letter-spacing: 0.06em;
}
.neon-red {
color: #ff0040;
text-shadow:
0 0 10px #ff0040,
0 0 20px #ff0040,
0 0 30px #ff0040,
0 0 40px #ff0040,
2px 2px 4px rgba(0,0,0,0.9);
}
.neon-lime {
color: #ccff00;
text-shadow:
0 0 10px #ccff00,
0 0 20px #ccff00,
0 0 30px #ccff00,
0 0 40px #ccff00,
2px 2px 4px rgba(0,0,0,0.9);
}
.neon-orange {
color: #ff6600;
text-shadow:
0 0 10px #ff6600,
0 0 20px #ff6600,
0 0 30px #ff6600;
}
.gradient-text {
background: linear-gradient(135deg, #ff0040 0%, #ff6600 50%, #ccff00 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-slideUp {
animation: slideUp 0.6s ease-out;
}
.animate-pulse-slow {
animation: pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes stagger {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.stagger-children > * {
animation: stagger 0.5s ease-out backwards;
}
.stagger-children > *:nth-child(1) { animation-delay: 0.1s; }
.stagger-children > *:nth-child(2) { animation-delay: 0.2s; }
.stagger-children > *:nth-child(3) { animation-delay: 0.3s; }
.stagger-children > *:nth-child(4) { animation-delay: 0.4s; }
.stagger-children > *:nth-child(5) { animation-delay: 0.5s; }
.stagger-children > *:nth-child(6) { animation-delay: 0.6s; }
.dark .leaflet-tile-pane {
filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9);
}
.dark .leaflet-control-zoom a {
background-color: hsl(var(--card));
color: hsl(var(--foreground));
border-color: hsl(var(--border));
}
.dark .leaflet-control-zoom a:hover {
background-color: hsl(var(--muted));
}
.dark .leaflet-popup-content-wrapper,
.dark .leaflet-popup-tip {
background-color: hsl(var(--card));
color: hsl(var(--foreground));
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: hsl(var(--muted));
border-radius: 4px;
}
::-webkit-scrollbar-thumb {
background: hsl(var(--muted-foreground) / 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: hsl(var(--muted-foreground) / 0.5);
}
/* On touch/phone screens, hide the visible scrollbar entirely for a clean,
app-like feel. Swipe-to-scroll still works — only the bar is hidden. */
@media (max-width: 767px) {
html,
body {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/old Edge */
}
::-webkit-scrollbar {
width: 0;
height: 0;
display: none; /* Chrome/Safari/WebKit */
}
}
/* Animations */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulse-glow {
0%, 100% { box-shadow: 0 0 0 0 hsl(var(--primary) / 0.4); }
50% { box-shadow: 0 0 0 8px hsl(var(--primary) / 0); }
}
/* "You are here" user-location marker pulse halo */
@keyframes ssPulse {
0% { transform: scale(0.5); opacity: 0.8; }
70% { transform: scale(1.6); opacity: 0; }
100% { transform: scale(1.6); opacity: 0; }
}
/* Pure-opacity reveal for the app shell under the departing splash — no
translate, so it never nudges a full-bleed map on reveal. */
@keyframes appReveal {
from { opacity: 0; }
to { opacity: 1; }
}
.animate-app-reveal {
animation: appReveal 0.5s ease-out both;
}
/* ── Cold-start splash screen ── */
@keyframes splashPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.04); }
}
@keyframes splashGlow {
0%, 100% { opacity: 0.7; transform: translate(-50%, -50%) scale(1); }
50% { opacity: 1; transform: translate(-50%, -50%) scale(1.08); }
}
@keyframes splashSpin {
to { transform: rotate(360deg); }
}
@keyframes splashRise {
from { opacity: 0; transform: translateY(14px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes splashShine {
0% { transform: translateX(-160%); }
100% { transform: translateX(420%); }
}
@keyframes splashFade {
from { opacity: 0; transform: translateY(3px); }
to { opacity: 1; transform: translateY(0); }
}
/* ── Map skeleton "ghost pins" (shown on a cold, empty, loading map) ──
The pin gently rises + breathes; the shadow under it pulses like a radar
ping. Kept subtle so it reads as "loading", never as real content. */
@keyframes ghostPin {
0%, 100% { transform: translateY(0) scale(1); opacity: 0.55; }
50% { transform: translateY(-6px) scale(1.04); opacity: 0.9; }
}
@keyframes ghostPing {
0%, 100% { transform: translateX(-50%) translateY(-0.25rem) scaleX(1); opacity: 0.35; }
50% { transform: translateX(-50%) translateY(-0.25rem) scaleX(1.6); opacity: 0.12; }
}
.animate-ghost-pin {
animation: ghostPin 2.4s ease-in-out infinite;
}
.animate-ghost-ping {
animation: ghostPing 2.4s ease-in-out infinite;
}
/* ── Live map markers: crisp pins that feel alive ─────────────────────────────
A new pin drops in with a quick spring; hovering/tapping lifts it and pulses
its neon halo once. Purely presentational, GPU-friendly (transform/opacity),
and fully disabled under reduced-motion. */
@keyframes ssPinDrop {
0% { transform: translateY(-14px) scale(0.6); opacity: 0; }
60% { transform: translateY(2px) scale(1.06); opacity: 1; }
100% { transform: translateY(0) scale(1); opacity: 1; }
}
@keyframes ssPinPulse {
0% { filter: drop-shadow(0 0 2px rgba(255,0,64,0.0)); }
50% { filter: drop-shadow(0 0 10px rgba(255,0,64,0.9)); }
100% { filter: drop-shadow(0 0 2px rgba(255,0,64,0.0)); }
}
/* The teardrop wrapper inside our divIcon. */
.skate-spot-marker .ss-pin {
transition: transform 0.18s cubic-bezier(0.34, 1.56, 0.64, 1);
transform-origin: 50% 100%;
will-change: transform;
}
@media (prefers-reduced-motion: no-preference) {
.skate-spot-marker .ss-pin {
animation: ssPinDrop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
/* Lift + one halo pulse on hover/focus/press. Leaflet keeps the marker
focusable, so this also covers keyboard + tap. */
.skate-spot-marker:hover .ss-pin,
.skate-spot-marker:focus .ss-pin,
.skate-spot-marker:active .ss-pin {
transform: translateY(-4px) scale(1.12);
animation: ssPinPulse 0.6s ease-out;
}
}
/* Indeterminate top loading bar for the map ("Loading spots"). A short segment
sweeps left→right on a faint track, so it reads as active work without
pretending to know real progress. Disabled under reduced-motion. */
@keyframes ssLoadBar {
0% { transform: translateX(-120%); }
100% { transform: translateX(420%); }
}
.ss-loadbar {
animation: ssLoadBar 1.1s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@media (prefers-reduced-motion: reduce) {
.ss-loadbar { animation: none; width: 100%; opacity: 0.7; }
}
.animate-fadeIn {
animation: fadeIn 0.4s ease-out forwards;
}
.animate-slideUp {
animation: slideUp 0.5s ease-out forwards;
}
.animate-pulse-glow {
animation: pulse-glow 2s infinite;
}
/* Stagger animation for lists */
.stagger-children > * {
opacity: 0;
animation: fadeIn 0.4s ease-out forwards;
}
.stagger-children > *:nth-child(1) { animation-delay: 0ms; }
.stagger-children > *:nth-child(2) { animation-delay: 50ms; }
.stagger-children > *:nth-child(3) { animation-delay: 100ms; }
.stagger-children > *:nth-child(4) { animation-delay: 150ms; }
.stagger-children > *:nth-child(5) { animation-delay: 200ms; }
.stagger-children > *:nth-child(6) { animation-delay: 250ms; }
.stagger-children > *:nth-child(7) { animation-delay: 300ms; }
.stagger-children > *:nth-child(8) { animation-delay: 350ms; }
/* Gradient text */
.gradient-text {
background: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(var(--accent)) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
/* Glass effect */
.glass {
background: hsl(var(--card) / 0.8);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
/* Safe area insets for iOS notched devices */
.h-safe-area-bottom {
height: env(safe-area-inset-bottom, 0px);
}
.pb-safe {
padding-bottom: env(safe-area-inset-bottom, 0px);
}
.pt-safe {
padding-top: env(safe-area-inset-top, 0px);
}
.safe-area-top {
padding-top: env(safe-area-inset-top, 0px);
}
.safe-area-bottom {
padding-bottom: env(safe-area-inset-bottom, 0px);
}
/* Bottom navigation height token — used for full-bleed pages (map, etc.) */
:root {
/* Mobile header: 3.5rem (56px) */
--header-mobile: 3.5rem;
/* Mobile bottom nav: 58px base + safe area */
--bottom-nav: calc(3.625rem + env(safe-area-inset-bottom, 0px));
/* Desktop footer: py-6 (3rem) + 1px border + ~1 line of content ≈ 5rem.
Full-bleed pages (map) subtract this so the footer stays in view without
scrolling. */
--footer-desktop: 5rem;
}
/* Full-bleed page — removes Layout bottom padding so the page can manage its own height */
.page-fullbleed > main {
padding-bottom: 0 !important;
}
.safe-area-left {
padding-left: env(safe-area-inset-left, 0px);
}
.safe-area-right {
padding-right: env(safe-area-inset-right, 0px);
}
/* Mobile bottom nav spacing - add padding to content to avoid overlap */
@media (max-width: 768px) {
.mobile-nav-padding {
padding-bottom: calc(72px + env(safe-area-inset-bottom, 0px));
}
}
/* Prevent horizontal overflow on mobile */
html, body {
overflow-x: hidden;
max-width: 100vw;
/* Allow normal vertical scrolling — do NOT set overscroll-behavior: none here
as it blocks touch scroll on Android browsers */
}
/* Better tap highlight on mobile — subtle red glow */
a, button {
-webkit-tap-highlight-color: rgba(255, 0, 64, 0.08);
}
/* Improve touch targets for mobile — WCAG minimum 44×44px */
@media (max-width: 768px) {
button, [role="button"] {
min-height: 44px;
min-width: 44px;
}
}
/* Fix input zoom on iOS — font-size must be ≥16px to prevent auto-zoom */
input, select, textarea {
font-size: max(16px, 1em);
}
/* Prevent text size adjustments after orientation changes on iOS */
html {
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
/* Better scrolling containers — prevent pull-to-refresh on inner scrollable areas */
.scroll-container {
overscroll-behavior-y: contain;
}
/* Touch-friendly horizontal scroll rows (e.g. filter chips) */
.scroll-x {
overflow-x: auto;
overflow-y: hidden;
overscroll-behavior-x: contain;
scroll-snap-type: x proximity;
-webkit-overflow-scrolling: touch;
}
.scroll-x > * {
scroll-snap-align: start;
}
/* Hide scrollbar utility - useful for horizontal scrolling filters */
.scrollbar-hide {
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
.scrollbar-hide::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
/* Extra small breakpoint for very small phones (375px+) */
@media (min-width: 375px) {
.xs\:inline { display: inline; }
.xs\:flex { display: flex; }
.xs\:hidden { display: none; }
.xs\:flex-row { flex-direction: row; }
.xs\:px-4 { padding-left: 1rem; padding-right: 1rem; }
.xs\:-mx-4 { margin-left: -1rem; margin-right: -1rem; }
.xs\:text-xs { font-size: 0.75rem; line-height: 1rem; }
.xs\:text-sm { font-size: 0.875rem; line-height: 1.25rem; }
.xs\:gap-2 { gap: 0.5rem; }
}
/* ─── Global UI/UX Polish ────────────────────────────────────────────────── */
/* Smooth page transitions */
@media (prefers-reduced-motion: no-preference) {
.page-enter {
animation: fadeIn 0.25s ease-out;
}
}
/* ── Focus rings — accessible, on-brand ────────────────────────────────── */
:focus-visible {
outline: 2px solid #ff0040;
outline-offset: 2px;
border-radius: 4px;
}
/* ── Card hover micro-interactions ─────────────────────────────────────── */
.card-hover {
transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.card-hover:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(255,0,64,0.12), 0 2px 8px rgba(0,0,0,0.4);
}
/* ── Button ripple feel ─────────────────────────────────────────────────── */
button, [role="button"], a {
transition-property: color, background-color, border-color, box-shadow, transform, opacity;
transition-duration: 150ms;
transition-timing-function: ease;
}
/* ── Modal / dialog backdrop improvements ──────────────────────────────── */
[data-radix-portal] [data-state="open"][data-overlay] {
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
/* ── Sheet / drawer slide animations ───────────────────────────────────── */
[data-radix-dialog-content],
[data-radix-sheet-content] {
transition: transform 0.3s cubic-bezier(0.32, 0.72, 0, 1);
}
/* ── Toast notification improvements ──────────────────────────────────── */
[data-radix-toast-root] {
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
}
/* ── Skeleton loading shimmer ──────────────────────────────────────────── */
@keyframes skeleton-shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
.skeleton-shimmer {
background: linear-gradient(
90deg,
rgba(255,255,255,0.03) 0%,
rgba(255,255,255,0.08) 50%,
rgba(255,255,255,0.03) 100%
);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s infinite;
}
/* ── Improved empty state cards ────────────────────────────────────────── */
.empty-state {
border: 2px dashed rgba(255,255,255,0.08);
border-radius: 16px;
padding: 3rem 2rem;
text-align: center;
background: rgba(255,255,255,0.01);
}
/* ── Better dropdown / select styling ─────────────────────────────────── */
select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
padding-right: 2.5rem;
appearance: none;
-webkit-appearance: none;
}
/* ── Map container full height ─────────────────────────────────────────── */
.map-full-height {
height: calc(100dvh - 4rem);
}
@media (max-width: 768px) {
.map-full-height {
height: calc(100dvh - 3.5rem - env(safe-area-inset-bottom, 0px) - 56px);
}
}
/* ── Globe container ───────────────────────────────────────────────────── */
.globe-container canvas {
touch-action: none; /* prevents scroll interference on mobile */
}
/* ── Typography hierarchy helpers ─────────────────────────────────────── */
.text-balance {
text-wrap: balance;
}
.text-pretty {
text-wrap: pretty;
}
/* ── Accessibility — reduced motion ────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.001ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.001ms !important;
}
}
/* ─── SkateSpots Rider Card ──────────────────────────────────────────────── */
/* Credit-card aspect ratio: 85.6mm × 54mm = 1.586:1 */
.rider-card {
width: 360px;
height: 227px;
border-radius: 14px;
position: relative;
overflow: hidden;
flex-shrink: 0;
font-family: 'Inter Variable', 'Inter', 'Kanit', system-ui, sans-serif;
box-shadow: 0 8px 40px rgba(0,0,0,0.6), 0 2px 8px rgba(0,0,0,0.4);
}
/* Holographic shimmer overlay — appears on hover/focus for screen,
hidden on print so the card prints cleanly */
@keyframes holo-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
.rider-card-holo::after {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: linear-gradient(
125deg,
transparent 20%,
rgba(255,255,255,0.08) 30%,
rgba(255,255,255,0.18) 40%,
rgba(255,0,64,0.08) 50%,
rgba(204,255,0,0.10) 60%,
rgba(255,255,255,0.08) 70%,
transparent 80%
);
background-size: 300% 300%;
animation: holo-shift 4s ease infinite;
pointer-events: none;
z-index: 10;
mix-blend-mode: screen;
}
/* ── Force background graphics to print on all card themes ──
Browsers suppress backgrounds by default. -webkit-print-color-adjust: exact
overrides this for Chrome/Safari. color-adjust: exact covers Firefox/Edge. */
.rider-card,
.rider-card *,
[class^="card-theme-"],
[class^="card-theme-"] * {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
color-adjust: exact !important;
}
/* ── Theme: THUG (default — black + neon red) ── */
.card-theme-thug {
background: linear-gradient(135deg, #0a0a0a 0%, #1a0008 60%, #0d0d0d 100%) !important;
border: 1.5px solid rgba(255,0,64,0.5);
box-shadow: 0 0 30px rgba(255,0,64,0.2), inset 0 1px 0 rgba(255,255,255,0.05);
color: #fff !important;
}
.card-theme-thug .card-accent { color: #ff0040 !important; }
.card-theme-thug .card-accent2 { color: #ccff00 !important; }
.card-theme-thug .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6600) !important; }
.card-theme-thug .card-muted { color: rgba(255,255,255,0.6) !important; }
.card-theme-thug .card-bg-chip { background: rgba(255,0,64,0.25) !important; border: 1px solid rgba(255,0,64,0.5) !important; }
/* ── Theme: NEON LIME ── */
.card-theme-lime {
background: linear-gradient(135deg, #050f00 0%, #0a1a00 60%, #050f00 100%) !important;
border: 1.5px solid rgba(204,255,0,0.5);
box-shadow: 0 0 30px rgba(204,255,0,0.15), inset 0 1px 0 rgba(255,255,255,0.05);
color: #fff !important;
}
.card-theme-lime .card-accent { color: #ccff00 !important; }
.card-theme-lime .card-accent2 { color: #ff6600 !important; }
.card-theme-lime .card-stripe { background: linear-gradient(90deg, #ccff00, #88ff00) !important; }
.card-theme-lime .card-muted { color: rgba(255,255,255,0.6) !important; }
.card-theme-lime .card-bg-chip { background: rgba(204,255,0,0.2) !important; border: 1px solid rgba(204,255,0,0.5) !important; }
/* ── Theme: CHROME (silver/steel) ── */
.card-theme-chrome {
background: linear-gradient(135deg, #1a1a1a 0%, #2e2e2e 40%, #111 100%) !important;
border: 1.5px solid rgba(200,200,200,0.4);
box-shadow: 0 0 20px rgba(180,180,180,0.1), inset 0 1px 0 rgba(255,255,255,0.15);
color: #e8e8e8 !important;
}
.card-theme-chrome .card-accent { color: #c8c8c8 !important; }
.card-theme-chrome .card-accent2 { color: #ff6600 !important; }
.card-theme-chrome .card-stripe { background: linear-gradient(90deg, #888, #ccc, #888) !important; }
.card-theme-chrome .card-muted { color: rgba(232,232,232,0.6) !important; }
.card-theme-chrome .card-bg-chip { background: rgba(200,200,200,0.15) !important; border: 1px solid rgba(200,200,200,0.3) !important; }
/* ── Theme: WOODGRAIN ── */
.card-theme-wood {
background: linear-gradient(160deg, #3d2008 0%, #5c3210 30%, #2e1804 60%, #4a2a0e 100%) !important;
border: 1.5px solid rgba(160,100,40,0.6);
box-shadow: 0 0 20px rgba(120,70,20,0.2), inset 0 1px 0 rgba(255,200,100,0.1);
color: #f5e6c8 !important;
}
.card-theme-wood .card-accent { color: #f5c842 !important; }
.card-theme-wood .card-accent2 { color: #ff6600 !important; }
.card-theme-wood .card-stripe { background: linear-gradient(90deg, #8b5e3c, #d4a05a, #8b5e3c) !important; }
.card-theme-wood .card-muted { color: rgba(245,230,200,0.65) !important; }
.card-theme-wood .card-bg-chip { background: rgba(160,100,40,0.3) !important; border: 1px solid rgba(160,100,40,0.6) !important; }
/* ── Theme: MIDNIGHT BLUE ── */
.card-theme-midnight {
background: linear-gradient(135deg, #020818 0%, #050e2a 60%, #030b1a 100%) !important;
border: 1.5px solid rgba(100,160,255,0.4);
box-shadow: 0 0 30px rgba(60,120,255,0.15), inset 0 1px 0 rgba(100,160,255,0.08);
color: #d0e4ff !important;
}
.card-theme-midnight .card-accent { color: #64a0ff !important; }
.card-theme-midnight .card-accent2 { color: #ccff00 !important; }
.card-theme-midnight .card-stripe { background: linear-gradient(90deg, #3060cc, #64a0ff) !important; }
.card-theme-midnight .card-muted { color: rgba(208,228,255,0.6) !important; }
.card-theme-midnight .card-bg-chip { background: rgba(100,160,255,0.15) !important; border: 1px solid rgba(100,160,255,0.4) !important; }
/* ── Theme: SUNSET (orange/pink) ── */
.card-theme-sunset {
background: linear-gradient(135deg, #120006 0%, #1a0808 40%, #0e0810 100%) !important;
border: 1.5px solid rgba(255,80,0,0.5);
box-shadow: 0 0 30px rgba(255,60,0,0.2), inset 0 1px 0 rgba(255,150,50,0.08);
color: #ffd0a0 !important;
}
.card-theme-sunset .card-accent { color: #ff6020 !important; }
.card-theme-sunset .card-accent2 { color: #ff0080 !important; }
.card-theme-sunset .card-stripe { background: linear-gradient(90deg, #ff0040, #ff6020, #ffaa00) !important; }
.card-theme-sunset .card-muted { color: rgba(255,208,160,0.65) !important; }
.card-theme-sunset .card-bg-chip { background: rgba(255,80,0,0.2) !important; border: 1px solid rgba(255,80,0,0.5) !important; }
/* ── Custom accent: overrides the theme's primary accent (applied on top of
any theme via --ss-accent / --ss-accent2 set inline on the card). Placed
last so it wins at equal specificity. ── */
.card-custom-accent .card-accent { color: var(--ss-accent) !important; }
.card-custom-accent .card-stripe { background: linear-gradient(90deg, var(--ss-accent), var(--ss-accent2, var(--ss-accent))) !important; }
.card-custom-accent .card-bg-chip { border-color: var(--ss-accent) !important; }
/* ── Card back side ── */
.rider-card-back {
background: #0b0b0b !important;
color: #e8e8e8 !important;
}
/* ── Print: the card is rendered to PNG via html2canvas before printing.
The @media print block below is only a safety fallback for direct Ctrl+P.
It will never look as good as the canvas approach. ── */
@media print {
@page { size: 85.6mm 54mm; margin: 0; }
body > * { display: none !important; }
.rider-card-print-fallback {
display: block !important;
position: fixed !important;
top: 0; left: 0;
width: 85.6mm !important;
height: 54mm !important;
}
}
/* ── Dark basemap without a keyed tile provider ──
We use free OpenStreetMap standard tiles and darken them client-side with a
CSS filter so the map matches the app's dark UI. Applied via the tileLayer
`className: 'skatespots-dark-tiles'` in SpotMap.tsx. */
.skatespots-dark-tiles {
filter: invert(1) hue-rotate(180deg) brightness(0.9) contrast(0.9) saturate(0.7);
}
/* ── Calculator sheet tables ──────────────────────────────────────────────
The deck-cost tables carry 5–8 columns (name + numbers + per-board + row
actions). That cannot fit a 390px phone: values get clipped ("900" → "90")
and columns fall off the edge. On phones each row becomes a stacked card
(label left, control right); on sm+ it stays a real table. `td::before`
pulls the column title from `data-label`. */
@media (max-width: 639px) {
.ss-sheet-table, .ss-sheet-table tbody, .ss-sheet-table tr, .ss-sheet-table td { display: block; width: 100%; }
.ss-sheet-table thead { display: none; }
.ss-sheet-table tr {
margin-bottom: 0.5rem;
padding: 0.5rem 0.625rem;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0.75rem;
background: rgba(255, 255, 255, 0.02);
}
.ss-sheet-table td {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 0.75rem;
padding: 0.2rem 0;
border: 0;
}
.ss-sheet-table td[data-label]::before {
content: attr(data-label);
flex: 0 0 auto;
color: #6b7280;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
}
.ss-sheet-table td > input,
.ss-sheet-table td > div { max-width: 62%; }
.ss-sheet-table td.ss-cell-name { margin-bottom: 0.35rem; }
.ss-sheet-table td.ss-cell-name > input { max-width: 100%; }
.ss-sheet-table td.ss-cell-actions { justify-content: flex-end; padding-top: 0.35rem; }
}