@tailwind base; @tailwind components; @tailwind utilities; /* ── Self-hosted fonts (no CDN — see Ethos threat model) ─────────────────── */ @font-face { font-family: 'Geist'; src: url('/fonts/Geist-Light.woff2') format('woff2'); font-weight: 300; font-style: normal; font-display: swap; } @font-face { font-family: 'Geist'; src: url('/fonts/Geist-Regular.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; } @font-face { font-family: 'Geist'; src: url('/fonts/Geist-Medium.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; } @font-face { font-family: 'Geist'; src: url('/fonts/Geist-SemiBold.woff2') format('woff2'); font-weight: 600; font-style: normal; font-display: swap; } @font-face { font-family: 'Geist'; src: url('/fonts/Geist-Bold.woff2') format('woff2'); font-weight: 700; font-style: normal; font-display: swap; } @font-face { font-family: 'Geist Mono'; src: url('/fonts/GeistMono-Regular.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; } @font-face { font-family: 'Geist Mono'; src: url('/fonts/GeistMono-Medium.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; } /* ── Ethos Design Tokens (default dark) ────────────────────────────────────── */ :root { /* warm brown-tinted neutral ramp — Ethos law, not cool grey */ --ethos-bg0: #14110D; --ethos-bg1: #1B1712; --ethos-bg2: #221D17; --ethos-surface0: #2C261D; --ethos-surface1: #372F24; --ethos-surface2: #423927; --ethos-border: rgba(244,234,220,0.09); --ethos-border-hi: rgba(244,234,220,0.16); --ethos-text: #F4EEE4; --ethos-secondary: #B4AA98; --ethos-muted: #756C5C; --ethos-disabled: #5a5347; /* Mono default (Ethos law 1) — machine values sit a step above `muted` so a count or a duration stays legible without competing with the human label. */ --ethos-machine: #9C917D; /* muzick fingerprint: honey amber */ --ethos-accent: #EDA24E; --ethos-accent-hover: #E08F32; --ethos-on-accent: #14110D; /* semantic colors */ --ethos-green: #22c55e; --ethos-amber: #eab308; --ethos-red: #ef4444; --ethos-purple: #a855f7; --ethos-cyan: #22d3ee; --ethos-orange: #f97316; } /* ── Base reset ───────────────────────────────────────────────────────────── */ body { margin: 0; padding: 0; background-color: var(--ethos-bg0); color: var(--ethos-text); font-family: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; font-size: 14px; line-height: 20px; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #root { position: relative; z-index: 1; min-height: 100dvh; } button, a, input[type='range'] { -webkit-tap-highlight-color: transparent; } @media (pointer: coarse) { input[type='range']::-webkit-slider-thumb { width: 18px; height: 18px; margin-top: -7px; } input[type='range']::-moz-range-thumb { width: 18px; height: 18px; } } /* ── Focus ring: 2px accent, for keyboard users only ──────────────────────── */ :focus-visible { outline: 2px solid var(--ethos-accent); outline-offset: 2px; border-radius: 4px; } /* ── Selection ────────────────────────────────────────────────────────────── */ ::selection { background: color-mix(in srgb, var(--ethos-accent) 35%, transparent); color: var(--ethos-text); } /* ── Scrollbars (thin, themed) ────────────────────────────────────────────── */ * { scrollbar-width: thin; scrollbar-color: var(--ethos-border) transparent; } *::-webkit-scrollbar { width: 8px; height: 8px; } *::-webkit-scrollbar-track { background: transparent; } *::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--ethos-border) 80%, transparent); border-radius: 9999px; border: 2px solid transparent; background-clip: padding-box; } *::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--ethos-muted) 60%, transparent); background-clip: padding-box; } /* ── Smooth scroll for main content ───────────────────────────────────────── */ html { scroll-behavior: smooth; } /* ── Utility classes ──────────────────────────────────────────────────────── */ /* Surfaced bar (top bar / playback bar) — depth from light, not blur */ .glass { background: var(--ethos-bg1); border-top: 1px solid var(--ethos-border); box-shadow: 0 2px 8px rgba(0,0,0,.35), 0 12px 32px rgba(0,0,0,.28); } /* Hide scrollbars but keep scroll behavior */ .scrollbar-hide { scrollbar-width: none; -ms-overflow-style: none; } .scrollbar-hide::-webkit-scrollbar { display: none; } /* Skeleton shimmer */ @keyframes shimmer { 0% { opacity: 0.4; } 50% { opacity: 0.8; } 100% { opacity: 0.4; } } .skeleton { animation: shimmer 1.5s ease-in-out infinite; background: var(--ethos-surface1); border-radius: 4px; } /* Vibe's profile aura: a small, old-player-inspired creature whose color and movement come from the active recommendation state. */ @keyframes vibe-aura-pulse { 0%, 100% { transform: scale(var(--vibe-scale)) rotate(-7deg); filter: brightness(.9); } 50% { transform: scale(calc(var(--vibe-scale) * 1.12)) rotate(8deg); filter: brightness(1.28); } } @keyframes vibe-aura-orbit { to { transform: rotate(360deg); } } @keyframes vibe-aura-spark { 0%, 100% { transform: translateY(0) scale(.75); opacity: .38; } 50% { transform: translateY(-5px) scale(1.15); opacity: 1; } } .vibe-aura-halo { position: absolute; inset: 7px; border-radius: 9999px; background: hsl(var(--vibe-hue) 88% 62% / .22); filter: blur(12px); animation: vibe-aura-pulse var(--vibe-pulse) ease-in-out infinite; } .vibe-aura-core { position: relative; width: 30px; height: 36px; border: 1px solid hsl(var(--vibe-hue) 95% 86% / .72); border-radius: 46% 54% 58% 42% / 43% 45% 55% 57%; background: radial-gradient(circle at 36% 28%, hsl(var(--vibe-hue) 100% 93%), hsl(var(--vibe-hue) 88% 61%) 38%, hsl(calc(var(--vibe-hue) + 35) 68% 31%) 100%); box-shadow: inset 3px 2px 8px hsl(0 0% 100% / .34), 0 0 17px hsl(var(--vibe-hue) 92% 59% / .78); animation: vibe-aura-pulse var(--vibe-pulse) ease-in-out infinite; } .vibe-aura-orbit { position: absolute; width: 47px; height: 47px; border: 1px solid hsl(var(--vibe-hue) 90% 76% / .36); border-radius: 47% 53% 43% 57%; animation: vibe-aura-orbit var(--vibe-orbit) linear infinite; } .vibe-aura-orbit-two { width: 56px; height: 37px; border-color: hsl(calc(var(--vibe-hue) + 55) 90% 76% / .24); animation-direction: reverse; animation-duration: calc(var(--vibe-orbit) * 1.45); } .vibe-aura-heart, .vibe-aura-spark { position: absolute; display: block; border-radius: 9999px; background: hsl(0 0% 100% / .9); box-shadow: 0 0 8px hsl(0 0% 100% / .92); } .vibe-aura-heart { width: 7px; height: 7px; left: 11px; top: 15px; } .vibe-aura-spark { width: 4px; height: 4px; animation: vibe-aura-spark 2.1s ease-in-out infinite; } .vibe-aura-spark-one { left: -4px; top: 5px; } .vibe-aura-spark-two { right: -3px; bottom: 7px; animation-delay: -1s; } /* Ambient variant: one soft organic blob of warm light behind the page. The scaled-up creature read as concentric rings at that size, which fought the content instead of sitting behind it. Gradient goes on the sized element — no inset-0 fill layer. */ @keyframes vibe-aura-drift { from { transform: scale(var(--vibe-scale)) translate3d(-2%, -1%, 0) rotate(-4deg); } to { transform: scale(calc(var(--vibe-scale) * 1.12)) translate3d(3%, 2%, 0) rotate(5deg); } } .vibe-aura-blob { border-radius: 46% 54% 62% 38% / 55% 43% 57% 45%; background: radial-gradient(closest-side at 40% 36%, hsl(var(--vibe-hue) 92% 60% / .55), transparent 72%), radial-gradient(closest-side at 66% 64%, hsl(calc(var(--vibe-hue) + 28) 86% 52% / .4), transparent 74%); filter: blur(44px); animation: vibe-aura-drift var(--vibe-orbit) ease-in-out infinite alternate; } @media (min-width: 640px) { .vibe-aura-blob { filter: blur(72px); } } @media (prefers-reduced-motion: reduce) { .vibe-aura-halo, .vibe-aura-core, .vibe-aura-orbit, .vibe-aura-spark, .vibe-aura-blob { animation: none; } } /* ── Component base classes ────────────────────────────────────────────────── */ /* Card surface — standard container */ .card-surface { display: flex; flex-direction: column; gap: 12px; border-radius: 8px; border: 1px solid var(--ethos-border); background: var(--ethos-surface0); padding: 12px; transition: background 150ms ease, border-color 150ms ease; } .card-surface:hover { background: var(--ethos-surface1); border-color: color-mix(in srgb, var(--ethos-accent) 40%, transparent); } /* Track list — one dense, hairline-separated column everywhere a list of tracks appears. Replaces the per-page `space-y-1` around bordered row cards, which cost 90px per track and fit only 7 rows on a 900px screen. */ .track-list > * + * { border-top: 1px solid color-mix(in srgb, var(--ethos-border) 60%, transparent); } /* Row hover: light falling off to the right, not a filled slab. A flat 770px-wide rectangle with a hard right edge is the ugly version, and it also squares off the Vibe timeline's stacked artwork. */ .track-row:hover { background: linear-gradient( 90deg, color-mix(in srgb, var(--ethos-surface1) 85%, transparent) 0%, color-mix(in srgb, var(--ethos-surface1) 40%, transparent) 38%, transparent 78% ); } /* Artwork frame */ .artwork-frame { aspect-ratio: 1 / 1; border-radius: 8px; overflow: hidden; } /* Play button overlay */ .play-overlay { position: absolute; inset: 0; display: flex; align-items: flex-end; justify-content: flex-end; padding: 8px; background: linear-gradient(to top, rgba(0,0,0,0.5), transparent 60%); opacity: 0; transition: opacity 150ms ease; } .group:hover .play-overlay { opacity: 1; } .play-overlay-btn { width: 44px; height: 44px; border-radius: 9999px; background: var(--ethos-accent); display: flex; align-items: center; justify-content: center; color: var(--ethos-on-accent); box-shadow: 0 8px 24px -4px color-mix(in srgb, var(--ethos-accent) 40%, transparent); transform: translateY(8px); transition: transform 150ms ease; } .group:hover .play-overlay-btn { transform: translateY(0); } /* Settings library actions use a compact, stateful button rather than an unstyled native control. */ .admin-btn { display: inline-flex; min-height: 36px; align-items: center; justify-content: center; gap: 8px; border: 1px solid var(--ethos-border-hi); border-radius: 8px; padding: 8px 12px; background: var(--ethos-surface1); color: var(--ethos-text); font-size: 13px; font-weight: 500; transition: background 150ms ease, border-color 150ms ease, color 150ms ease; } .admin-btn:hover:not(:disabled) { background: var(--ethos-surface2); border-color: color-mix(in srgb, var(--ethos-accent) 50%, transparent); } .admin-btn:disabled { cursor: wait; opacity: 0.65; } .admin-btn--done { border-color: color-mix(in srgb, var(--ethos-green) 60%, transparent); color: var(--ethos-green); } .admin-btn--error { border-color: color-mix(in srgb, var(--ethos-red) 60%, transparent); color: var(--ethos-red); } /* Keep row actions available on touch. On pointer-and-hover devices, reveal them when the row is hovered or any of its controls receives focus. */ .track-row-actions { opacity: 1; transition: opacity 150ms ease; } @media (hover: hover) and (pointer: fine) { .track-row-actions { opacity: 0; } .group:hover .track-row-actions, .group:focus-within .track-row-actions { opacity: 1; } } /* Transport button */ .transport-btn { width: 40px; height: 40px; border-radius: 9999px; background: var(--ethos-accent); display: flex; align-items: center; justify-content: center; color: var(--ethos-on-accent); transition: background 150ms ease, transform 150ms ease; } .transport-btn:hover { background: var(--ethos-accent-hover); transform: scale(1.06); } .transport-btn:active { transform: scale(0.94); } .transport-btn:disabled { opacity: 0.4; } /* Gradient text — used sparingly for page headings */ .text-gradient { background: linear-gradient(120deg, var(--ethos-text), color-mix(in srgb, var(--ethos-accent) 75%, var(--ethos-text))); -webkit-background-clip: text; background-clip: text; color: transparent; } /* Range slider styling */ input[type='range'] { -webkit-appearance: none; appearance: none; background: transparent; cursor: pointer; } input[type='range']::-webkit-slider-runnable-track { height: 4px; border-radius: 9999px; background: color-mix(in srgb, var(--ethos-border) 90%, transparent); } input[type='range']::-moz-range-track { height: 4px; border-radius: 9999px; background: color-mix(in srgb, var(--ethos-border) 90%, transparent); } input[type='range']::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; border-radius: 9999px; background: var(--ethos-accent); margin-top: -4px; transition: transform 150ms ease; } input[type='range']:hover::-webkit-slider-thumb { transform: scale(1.15); } input[type='range']::-moz-range-thumb { width: 12px; height: 12px; border: none; border-radius: 9999px; background: var(--ethos-accent); } input[type='range']:focus-visible { outline: none; } input[type='range']:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 6px color-mix(in srgb, var(--ethos-accent) 30%, transparent); } input[type='range']:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 6px color-mix(in srgb, var(--ethos-accent) 30%, transparent); }