@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', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans Devanagari', 'Impact', 'Arial Black', sans-serif; letter-spacing: 0.04em; } /* Barlow Condensed — used for subheadings, labels, nav */ .font-condensed { font-family: 'Barlow Condensed', 'Kanit', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans Devanagari', 'Arial Narrow', sans-serif; } .neon-header { animation: neon-glow 2s ease-in-out infinite; font-family: 'Bebas Neue', 'Kanit', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans Devanagari', '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 splashLogoHandoff { 0% { opacity: 1; transform: translateY(56px) scale(0.96); } 100% { opacity: 1; transform: translateY(0) scale(1); } } @keyframes splashLogoDrop { 0% { opacity: 0; transform: translateY(-14px) scale(0.92); } 62% { opacity: 1; transform: translateY(2px) scale(1.02); } 100% { opacity: 1; transform: translateY(0) scale(1); } } @keyframes splashDust { 0%, 100% { opacity: 0.28; transform: translateX(-50%) scaleX(1); } 50% { opacity: 0.5; transform: translateX(-50%) scaleX(1.25); } } @keyframes splashSpark { 0% { opacity: 0; transform: translate(-50%, -50%) translate(var(--sx), var(--sy)) scale(0.45); } 12% { opacity: 1; } 100% { opacity: 0; transform: translate(-50%, -50%) translate(var(--ex), var(--ey)) scale(0); } } .splash-spark { position: absolute; left: 50%; top: 50%; width: 5px; height: 5px; border-radius: 9999px; background: #ffb347; box-shadow: 0 0 10px 3px rgba(255, 106, 0, 0.85); animation: splashSpark 560ms ease-out 1 both; } .splash-spark:nth-child(3n) { width: 4px; height: 4px; background: #ccff00; box-shadow: 0 0 9px 3px rgba(204, 255, 0, 0.55); } .splash-spark:nth-child(3n + 1) { background: #ffd166; } @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', 'Noto Sans JP', 'Noto Sans KR', 'Noto Sans Devanagari', 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; } } /* ── Caseless scripts ───────────────────────────────────────────────────── The nav/menu labels use `uppercase tracking-wide` for the Latin UI. Those classes are no-ops or actively wrong for Devanagari, Thai and CJK: letter spacing separates conjuncts and uppercase does nothing. Keep the labels natural when the document language is one of those. */ :lang(hi) .uppercase, :lang(th) .uppercase, :lang(ja) .uppercase, :lang(ko) .uppercase, :lang(zh) .uppercase { text-transform: none; } :lang(hi) .tracking-wide, :lang(th) .tracking-wide, :lang(ja) .tracking-wide, :lang(ko) .tracking-wide, :lang(zh) .tracking-wide { letter-spacing: 0; } /* Delayed fade for the in-layout route skeleton. Route chunks that resolve quickly never show a flash; slow ones fade in under the persistent chrome. */ @keyframes pageFallbackIn { from { opacity: 0; } to { opacity: 1; } }