@tailwind base; @tailwind components; @tailwind utilities; :root { --font-scale: 1.06; /* ===== Glass tunables — every value that materially drives the look lives here so it can be tuned in ONE place (and live-driven by the dev GlassTuner during UAT). ===== */ /* GLOBAL baseline = "Adaptive": solid-enough dark glass that reads well on content-less chrome (nav/header/dialogs/filters/settings — where there's nothing behind the glass to refract). Baked from UAT 2026-07-12. Rich-backdrop surfaces override these via `.glass-media` below. */ --glass-blur: 20px; /* backdrop blur radius */ --glass-saturate: 1.8; /* backdrop saturation */ --glass-dark-bright: 1.25; /* extra brightness lift applied to .glass/.glass-menu in dark mode only */ --glass-surface-alpha: 94%; /* .glass panel translucency (higher = more solid = "adaptive") */ --glass-card-alpha: 94%; /* .glass-card translucency */ --glass-menu-alpha: 98%; /* .glass-menu translucency */ --glass-border-alpha: 78%; /* .glass border opacity */ --glass-inset: 16%; /* top specular highlight strength (#fff N%) */ --glass-edge: 10%; /* extra edge-light mixed into the TOP border (#fff N%) — 0 = off */ --glass-scrim: 14%; /* under-content scrim: darkens surfaces toward --bg for text contrast — 0 = off */ --ambient: 1.1; /* multiplier on the ambient accent backdrop pools */ } /* ===== Rich-backdrop scope: surfaces that float over real imagery (Plex art hero/detail, and later channel-page banners) want MORE translucency so the picture shows through — the "glass over content" look. Put `.glass-media` on such a page root; every .glass* inside inherits these (UAT-tuned 2026-07-12). Global chrome stays solid via the :root defaults above. ===== */ .glass-media { --glass-blur: 10px; --glass-saturate: 1.6; --glass-dark-bright: 1.2; --glass-surface-alpha: 50%; --glass-card-alpha: 60%; --glass-menu-alpha: 66%; --glass-border-alpha: 80%; --glass-edge: 25%; --glass-inset: 15%; --glass-scrim: 30%; } /* Make native controls (date picker, scrollbars) follow the theme. */ html[data-theme="dark"] { color-scheme: dark; } html[data-theme="light"] { color-scheme: light; } html { font-size: calc(16px * var(--font-scale)); background: var(--bg); } body { margin: 0; color: var(--fg); /* Ambient backdrop so translucent "glass" surfaces have soft color to refract. Kept subtle but a touch richer (three soft pools, incl. a bottom glow) now that the nav, header and dialogs are all frosted glass and refract it. */ background: radial-gradient(1100px 620px at 10% -10%, color-mix(in srgb, var(--accent) calc(20% * var(--ambient)), transparent), transparent 60%), radial-gradient(1000px 720px at 115% 10%, color-mix(in srgb, var(--accent) calc(14% * var(--ambient)), transparent), transparent 55%), radial-gradient(900px 640px at 50% 118%, color-mix(in srgb, var(--accent) calc(11% * var(--ambient)), transparent), transparent 60%), var(--bg); background-attachment: fixed; } /* ===== Liquid-glass surface system (theme-aware, GPU-light) ===== */ .glass { /* Frosted overlay surface: translucent enough that the blurred backdrop shows through (the glassy look), opaque enough that the background isn't distracting. The strong blur turns whatever is behind into soft colour, not a sharp image. Layer 1 (top) = the optional scrim; layer 2 = the translucent surface tint. */ background: linear-gradient(color-mix(in srgb, var(--bg) var(--glass-scrim), transparent), color-mix(in srgb, var(--bg) var(--glass-scrim), transparent)), color-mix(in srgb, var(--surface) var(--glass-surface-alpha), transparent); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); border: 1px solid color-mix(in srgb, var(--border) var(--glass-border-alpha), transparent); border-top-color: color-mix(in srgb, #fff var(--glass-edge), color-mix(in srgb, var(--border) var(--glass-border-alpha), transparent)); box-shadow: inset 0 1px 0 color-mix(in srgb, #fff var(--glass-inset), transparent), 0 18px 44px -16px rgba(0, 0, 0, 0.6); } .glass-card { /* No backdrop-filter here: cards render in bulk (feed grid) over a solid background where blur adds almost nothing visually but is GPU-expensive and triggers reflow. Translucency + border + depth keep the look; blur is reserved for .glass overlays. */ background: linear-gradient(color-mix(in srgb, var(--bg) var(--glass-scrim), transparent), color-mix(in srgb, var(--bg) var(--glass-scrim), transparent)), color-mix(in srgb, var(--card) var(--glass-card-alpha), transparent); border: 1px solid color-mix(in srgb, var(--border) 65%, transparent); border-top-color: color-mix(in srgb, #fff var(--glass-edge), color-mix(in srgb, var(--border) 65%, transparent)); box-shadow: inset 0 1px 0 color-mix(in srgb, #fff calc(var(--glass-inset) / 2), transparent), 0 8px 22px -14px rgba(0, 0, 0, 0.45); } .glass-menu { /* Floating menus/popovers hover over undimmed content (no backdrop scrim like dialogs), so they must be near-opaque to stay readable — only a hint of translucency + the blur. */ background: linear-gradient(color-mix(in srgb, var(--bg) var(--glass-scrim), transparent), color-mix(in srgb, var(--bg) var(--glass-scrim), transparent)), color-mix(in srgb, var(--surface) var(--glass-menu-alpha), transparent); backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); border: 1px solid color-mix(in srgb, var(--border) 80%, transparent); border-top-color: color-mix(in srgb, #fff var(--glass-edge), color-mix(in srgb, var(--border) 80%, transparent)); box-shadow: inset 0 1px 0 color-mix(in srgb, #fff calc(var(--glass-inset) - 2%), transparent), 0 18px 44px -16px rgba(0, 0, 0, 0.6); } .glass-hover:hover { border-color: color-mix(in srgb, var(--accent) 55%, transparent); box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 12%, transparent), 0 14px 30px -14px rgba(0, 0, 0, 0.5); } /* Dark mode: the ambient accent pools are faint over a near-black bg, so the docked frosted chrome (nav/header) barely reads — there's no content behind it to refract, unlike dialogs which sit over the feed. Strengthen the pools and let a touch more bleed through the glass. (The full "videos behind glass everywhere" effect is Phase 2 / end-of-project polish.) */ html[data-theme="dark"] body { background: radial-gradient(1100px 620px at 10% -10%, color-mix(in srgb, var(--accent) calc(34% * var(--ambient)), transparent), transparent 60%), radial-gradient(1000px 720px at 115% 10%, color-mix(in srgb, var(--accent) calc(24% * var(--ambient)), transparent), transparent 55%), radial-gradient(900px 640px at 50% 118%, color-mix(in srgb, var(--accent) calc(18% * var(--ambient)), transparent), transparent 60%), var(--bg); background-attachment: fixed; } /* Dark glass reads faint because the blurred backdrop is itself dark; lift its brightness so the frosted sheen shows even over dark UI (over colourful content it just looks richer). */ html[data-theme="dark"] .glass, html[data-theme="dark"] .glass-menu { backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)) brightness(var(--glass-dark-bright)); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)) brightness(var(--glass-dark-bright)); } /* Toasts surface bottom-left (by the nav's notification bell), off the conventional top-right; a brighter rim draws the eye there. Most needed in dark mode, where the default glass border (dark navy) all but vanishes against the backdrop. */ html[data-theme="dark"] .toast-card { border-color: color-mix(in srgb, #fff 50%, transparent); } /* Performance mode (Settings → Appearance) drops the expensive blur + soft shadows. */ html[data-perf="1"] .glass, html[data-perf="1"] .glass-menu, html[data-perf="1"] .glass-card { backdrop-filter: none; -webkit-backdrop-filter: none; background: var(--surface); box-shadow: 0 2px 8px -4px rgba(0, 0, 0, 0.4); } html[data-perf="1"] body { background: var(--bg); } /* Skip layout/paint for off-screen feed cards so long lists stay smooth while scrolling. contain-intrinsic-size reserves an approximate box so the scrollbar stays stable; `auto` lets the browser remember each card's real size after it has rendered once. */ .cv-card { content-visibility: auto; contain-intrinsic-size: auto 320px; } .cv-row { content-visibility: auto; contain-intrinsic-size: auto 96px; } /* ===== Motion ===== */ @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes overlayIn { from { opacity: 0; } to { opacity: 1; } } @keyframes overlayOut { from { opacity: 1; } to { opacity: 0; } } @keyframes panelIn { from { transform: translateX(100%); } to { transform: translateX(0); } } @keyframes panelOut { from { transform: translateX(0); } to { transform: translateX(100%); } } @keyframes popIn { from { opacity: 0; transform: translateY(-6px) scale(0.97); } to { opacity: 1; transform: none; } } /* One-shot attention flash for a chat dock window on a new message. Inset glow so the window's own overflow-hidden doesn't clip it; pulses twice then fades. */ @keyframes chatFlash { 0% { box-shadow: inset 0 0 0 0 transparent; } 18% { box-shadow: inset 0 0 0 3px var(--accent), inset 0 0 22px 0 color-mix(in srgb, var(--accent) 55%, transparent); } 40% { box-shadow: inset 0 0 0 1px transparent; } 62% { box-shadow: inset 0 0 0 3px var(--accent), inset 0 0 22px 0 color-mix(in srgb, var(--accent) 55%, transparent); } 100% { box-shadow: inset 0 0 0 0 transparent; } } .chat-flash { animation: chatFlash 1.1s ease-out; } /* ===== Color schemes (accent + neutrals), each with dark + light ===== */ /* Midnight — deep navy/slate with blue accent (default) */ html[data-scheme="midnight"][data-theme="dark"] { --bg: #0b1020; --surface: #121a2e; --card: #161f38; --border: #243049; --fg: #e6e9f0; --muted: #97a3c0; --accent: #6d8cff; --accent-fg: #0b1020; } html[data-scheme="midnight"][data-theme="light"] { --bg: #f4f6fc; --surface: #ffffff; --card: #ffffff; --border: #dde3f0; --fg: #1a2238; --muted: #5b6685; --accent: #3b5bdb; --accent-fg: #ffffff; } /* Forest — dark slate with teal/green accent */ html[data-scheme="forest"][data-theme="dark"] { --bg: #0a1512; --surface: #0f1f1a; --card: #12241e; --border: #1f3a30; --fg: #e6f0ec; --muted: #90b1a4; --accent: #2dd4bf; --accent-fg: #04110d; } html[data-scheme="forest"][data-theme="light"] { --bg: #f1f7f4; --surface: #ffffff; --card: #ffffff; --border: #d6e7e0; --fg: #102a22; --muted: #4d6b60; --accent: #0d9488; --accent-fg: #ffffff; } /* Slate — neutral grey with warm orange accent (muted in dark) */ html[data-scheme="slate"][data-theme="dark"] { --bg: #15171c; --surface: #1b1e25; --card: #1f232b; --border: #2c313b; --fg: #e7e9ee; --muted: #9aa1ad; --accent: #e8913c; --accent-fg: #1a1206; } html[data-scheme="slate"][data-theme="light"] { --bg: #f6f7f9; --surface: #ffffff; --card: #ffffff; --border: #e3e6eb; --fg: #1c1f26; --muted: #5d636e; --accent: #d97706; --accent-fg: #ffffff; } /* YouTube — near-black with red accent */ html[data-scheme="youtube"][data-theme="dark"] { --bg: #0f0f0f; --surface: #181818; --card: #1f1f1f; --border: #303030; --fg: #f1f1f1; --muted: #aaaaaa; --accent: #ff3b46; --accent-fg: #ffffff; } html[data-scheme="youtube"][data-theme="light"] { --bg: #ffffff; --surface: #f9f9f9; --card: #ffffff; --border: #e5e5e5; --fg: #0f0f0f; --muted: #606060; --accent: #ff0033; --accent-fg: #ffffff; } /* Starship — deep space blue-black with a bright azure "LCARS/computer" accent */ html[data-scheme="starship"][data-theme="dark"] { --bg: #050b16; --surface: #0b1728; --card: #101f36; --border: #1f3557; --fg: #dfeaff; --muted: #8aa6cc; --accent: #38b6ff; --accent-fg: #04101f; } html[data-scheme="starship"][data-theme="light"] { --bg: #eef4fb; --surface: #ffffff; --card: #ffffff; --border: #d3e0f0; --fg: #0b1b30; --muted: #4a5f7d; --accent: #0b6bd6; --accent-fg: #ffffff; } /* Matrix — near-black with a muted terminal-green accent (softened from neon phosphor) */ html[data-scheme="matrix"][data-theme="dark"] { --bg: #030a06; --surface: #08130d; --card: #0b1c12; --border: #163d27; --fg: #cfe8d6; --muted: #6fae86; --accent: #46b87c; --accent-fg: #04170c; } html[data-scheme="matrix"][data-theme="light"] { --bg: #f0f7f1; --surface: #ffffff; --card: #ffffff; --border: #cfe6d6; --fg: #08130b; --muted: #4c6b56; --accent: #12a150; --accent-fg: #ffffff; } /* Toast auto-dismiss countdown bar (shrinks left-to-right to zero) */ @keyframes toastbar { from { transform: scaleX(1); } to { transform: scaleX(0); } } /* The in-app player stage, when fullscreened via the F key / native button, fills the screen instead of keeping its 16:9 letterbox (and drops the rounded top corners). */ .player-stage:fullscreen { width: 100vw; height: 100vh; aspect-ratio: auto; border-radius: 0; } /* Thin, theme-aware scrollbars */ * { scrollbar-color: var(--border) transparent; } *::-webkit-scrollbar { width: 10px; height: 10px; } *::-webkit-scrollbar-thumb { background: var(--border); border-radius: 8px; }