/* Minimal stylesheet to accompany Tailwind CDN styles */ :root { color-scheme: dark; /* --- Layout metrics ----------------------------------------------------- Single source of truth for the app chrome height so templates never have to hardcode `top-16` / `h-[calc(100vh-4rem)]` (which silently break when the header wraps on narrow viewports). */ --header-h: 3.5rem; /* 56px — mobile */ --safe-top: 0px; --safe-bottom: 0px; --safe-left: 0px; --safe-right: 0px; /* Height actually consumed by the fixed header, safe-area included. `--header-measured` is published at runtime by HeaderComponent via a ResizeObserver: the header grows when the mobile search sheet opens, when an iOS safe area applies, or at high zoom, and a hardcoded value would let the sticky themes bar slide under it (it was fully hidden in that case). */ --header-offset: var(--header-measured, calc(var(--header-h) + var(--safe-top))); /* Usable viewport height that follows the mobile browser chrome. */ --app-vh: 100vh; } @supports (height: 100dvh) { :root { --app-vh: 100dvh; } } @media (min-width: 768px) { :root { --header-h: 4rem; } /* 64px — desktop */ } html, body { height: 100%; min-height: 100%; } html { /* `clip` (not `hidden`) so the element never becomes a scroll container — `overflow: hidden` on silently breaks every `position: sticky` child, including the header rails. */ overflow-x: clip; -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scroll-behavior: smooth; } @supports not (overflow: clip) { html { overflow-x: hidden; } } @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; } } body { margin: 0; min-height: var(--app-vh); font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, "Helvetica Neue", Arial, "Apple Color Emoji", "Segoe UI Emoji"; background-color: #0f172a; /* slate-900 */ color: #e2e8f0; /* slate-200 */ /* No grey flash / no accidental text selection while tapping chrome. */ -webkit-tap-highlight-color: transparent; overscroll-behavior-y: none; } /* Inputs on iOS render at 16px by default which triggers focus zoom; make sure no field ever drops below that on small viewports. Buttons are excluded so compact `text-xs` toolbars keep their compact look. */ @media (max-width: 767px) { input:not([type="checkbox"]):not([type="radio"]), select, textarea { font-size: max(16px, 1em); } /* Never let a flex/grid child blow out the viewport. */ img, svg, video, iframe { max-width: 100%; } } /* --- Touch ergonomics ----------------------------------------------------- */ /* WCAG 2.5.5 / Apple HIG: 44px is the comfortable minimum on coarse pointers. Desktop keeps the compact visual sizing. */ @media (pointer: coarse) { button, [role="button"], a.chip { min-height: 44px; } /* Icon-only controls (no text child) get a square minimum too. */ button.icon-only, [role="button"].icon-only { min-width: 44px; } } .chip { transition: background-color .15s ease, color .15s ease, opacity .15s ease; } /* Expand tiny hit areas without changing visual size. */ .hit-expand { position: relative; } .hit-expand::after { content: ""; position: absolute; top: 50%; left: 50%; min-width: 44px; min-height: 44px; transform: translate(-50%, -50%); } /* --- Mobile primitives ---------------------------------------------------- */ /* Horizontal swipe rail: the standard mobile pattern for tab strips that would otherwise wrap or get clipped. */ .scroll-rail { overflow-x: auto; overflow-y: hidden; -webkit-overflow-scrolling: touch; scrollbar-width: thin; overscroll-behavior-x: contain; } /* Scroll affordance for swipe rails that hide their scrollbar. */ .scroll-rail-fade { position: relative; } .scroll-rail-fade::after { content: ""; position: absolute; top: 0; bottom: 0; right: 0; width: 2rem; pointer-events: none; background: linear-gradient(to right, transparent, var(--bg)); opacity: 0.9; } /* Vertical stacks inside dialogs must be able to scroll within dvh. */ .dialog-scroll { max-height: min(85dvh, 100%); overflow-y: auto; overscroll-behavior: contain; } /* Prevent long unbroken strings (UUIDs, URLs, tokens) from breaking layout. */ .break-anywhere { overflow-wrap: anywhere; word-break: break-word; } /* Focus ring for keyboard users only (touch devices get :focus instead). */ .focus-ring:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 0.5rem; } /* --- App chrome primitives ------------------------------------------------ */ /* Icon-only header/button. 40px on pointer devices, 44px on touch. */ .app-icon-btn { display: inline-flex; align-items: center; justify-content: center; min-width: 2.5rem; min-height: 2.5rem; padding: 0.375rem; border-radius: 9999px; color: var(--text); transition: background-color .15s ease, opacity .15s ease; } .app-icon-btn:hover { background-color: var(--panel-2); } .app-icon-btn:active { transform: scale(.94); } /* Avatar trigger (always >= 44px of tappable area). */ .app-avatar-btn { display: inline-flex; align-items: center; justify-content: center; padding: 0.25rem; border-radius: 9999px; transition: background-color .15s ease; } .app-avatar-btn:hover { background-color: var(--panel-2); } /* Circular user badge ("pastille"). Belt and braces: `rounded-full` from the markup plus these rules, so the badge can never degrade into a rectangle if a theme stylesheet overrides the radius. */ .avatar-circle { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 2rem; height: 2rem; min-width: 2rem; min-height: 2rem; aspect-ratio: 1 / 1; border-radius: 9999px !important; border-width: 2px; border-style: solid; font-size: 0.875rem; font-weight: 600; line-height: 1; overflow: hidden; text-transform: uppercase; } /* The fixed app header: honours the safe area and never overflows. */ .app-header { /* Nothing inside may widen the bar. */ max-width: 100vw; } /* --- Quick menu / command palette (Ctrl+K) -------------------------------- */ .quick-menu-backdrop { position: fixed; inset: 0; z-index: 9999; background-color: rgba(0, 0, 0, .6); display: flex; align-items: flex-start; justify-content: center; padding: max(1rem, env(safe-area-inset-top)) 1rem; overflow-y: auto; overscroll-behavior: contain; } .quick-menu-dialog { margin-top: 5vh; width: 100%; max-width: 56rem; max-height: min(85dvh, 100%); display: flex; flex-direction: column; overflow: hidden; border-radius: .75rem; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, .25); background-color: var(--bg, #0f172a); border: 1px solid var(--border, #334155); } @supports not (height: 100dvh) { .quick-menu-dialog { max-height: 85vh; } } /* Panneau de filtres : même coque que la palette Ctrl+K, plus étroit (les options sont des pastilles, pas des colonnes). */ .filter-panel-dialog { max-width: 44rem; } @media (max-width: 639px) { /* On phones the dialog owns the viewport: no floating "modal" look. */ .quick-menu-backdrop { padding: 0; } .quick-menu-dialog { margin-top: 0; max-height: 100dvh; height: 100dvh; border-radius: 0; border: 0; } } /* Container helper: responsive gutters + fluid cap (Tailwind's CDN `container` is pinned to a single width, which breaks small viewports). */ .container { width: 100%; margin-inline: auto; padding-inline: 1rem; max-width: 100%; } @media (min-width: 640px) { .container { padding-inline: 1.5rem; } } @media (min-width: 1024px) { .container { max-width: 1280px; padding-inline: 2rem; } } /* --- Theme System ------------------------------------------------------- */ /* Define CSS variables per theme and override a few common Tailwind utils */ /* Defaults (used as fallback) */ :root { --bg: #0f172a; /* dark slate */ --text: #e2e8f0; /* slate-200 */ --muted: #94a3b8; /* slate-400 */ --panel: #1f2937; /* slate-800 */ --panel-2: #334155; /* slate-700 */ --overlay: rgba(31,41,55,.5); --accent: #ef4444; /* red-500/600 */ --accent-hover: #dc2626; /* red-600/700 */ --border: #334155; --selection: rgba(255,255,255,.06); /* default subtle selection for dark */ } /* Dark */ [data-theme="dark"] { color-scheme: dark; /* Dark gray palette (softer than deep slate) */ --bg: #14171b; /* base background */ --text: #e5e7eb; /* slightly brighter text */ --muted: #a1a7b3; /* softer secondary text */ --panel: #1d2126; /* cards/headers */ --panel-2: #262b31; /* inputs/secondary panels */ --overlay: rgba(29,33,38,.6); --accent: #ef4444; /* unchanged accent */ --accent-hover: #dc2626; --border: #2f3540; /* subtle border */ --selection: #2b3139; /* hover/selection on dark */ } /* Pure Black */ [data-theme="black"] { color-scheme: dark; --bg: #000000; --text: #e5e7eb; --muted: #9ca3af; --panel: #0a0a0a; --panel-2: #111111; --overlay: rgba(0,0,0,.6); --accent: #ef4444; --accent-hover: #dc2626; --border: #1f2937; --selection: #121416; } /* Light */ [data-theme="light"] { color-scheme: light; --bg: #f8fafc; /* slate-50 */ --text: #0f172a; /* slate-900 */ --muted: #64748b;/* slate-500 for secondary text */ --panel: #ffffff; --panel-2: #f1f5f9; /* slate-100 */ --overlay: #ffffff; /* solid white for cards/headers using bg-slate-800/50 */ --accent: #ef4444; --accent-hover: #dc2626; --border: #e2e8f0; --selection: #eef2f7; /* light mode selection (ref. YouTube style) */ } /* Blue (navy) */ [data-theme="blue"] { color-scheme: dark; --bg: #0b1220; --text: #dbeafe; /* blue-100 */ --muted: #93c5fd; /* blue-300 */ --panel: #111a2b; --panel-2: #17223a; --overlay: rgba(17,26,43,.6); --accent: #3b82f6; /* blue-500 */ --accent-hover: #2563eb; /* blue-600 */ --border: #1e3a8a; /* blue-900 */ --selection: #1a2440; } /* System: default to light, override when OS is dark */ [data-theme="system"] { color-scheme: light; --bg: #f8fafc; --text: #0f172a; --muted: #334155; --panel: #ffffff; --panel-2: #f1f5f9; --overlay: rgba(255,255,255,.8); --accent: #ef4444; --accent-hover: #dc2626; --border: #e2e8f0; --selection: #eef2f7; } @media (prefers-color-scheme: dark) { [data-theme="system"] { color-scheme: dark; --bg: #0f172a; --text: #e2e8f0; --muted: #94a3b8; --panel: #1f2937; --panel-2: #334155; --overlay: rgba(31,41,55,.5); --accent: #ef4444; --accent-hover: #dc2626; --border: #334155; --selection: #2b3139; } } /* Apply variables */ body { background-color: var(--bg); color: var(--text); } /* Mild overrides so Tailwind utilities adapt to themes without rebuild */ [data-theme] .bg-slate-900 { background-color: var(--bg) !important; } [data-theme] .bg-gray-800, [data-theme] .bg-slate-800 { background-color: var(--panel) !important; } [data-theme] .bg-slate-700 { background-color: var(--panel-2) !important; } [data-theme] .bg-slate-800\/50 { background-color: var(--overlay) !important; } [data-theme] .text-slate-200 { color: var(--text) !important; } [data-theme] .text-slate-100 { color: var(--text) !important; } [data-theme] .text-slate-300 { color: color-mix(in srgb, var(--text) 80%, transparent) !important; } [data-theme] .text-slate-400 { color: var(--muted) !important; } [data-theme] .text-white { color: var(--text) !important; } [data-theme] .border-slate-700 { border-color: var(--border) !important; } /* Accent buttons */ [data-theme] .bg-red-600 { background-color: var(--accent) !important; } [data-theme] .hover\:bg-red-500:hover { background-color: var(--accent-hover) !important; } [data-theme] .bg-gray-600 { background-color: var(--panel-2) !important; } [data-theme] .hover\:bg-gray-500:hover { background-color: var(--panel) !important; } /* Light-specific refinements */ [data-theme="light"] header { box-shadow: 0 2px 12px rgba(0,0,0,.06); } [data-theme="light"] .shadow-2xl { box-shadow: 0 25px 50px -12px rgba(0,0,0,.15) !important; } [data-theme="light"] .bg-black\/75 { /* make duration pill less heavy on light */ background-color: rgba(0,0,0,.5) !important; } /* Cards and panels separation in Light */ [data-theme="light"] .bg-slate-800, [data-theme="light"] .bg-gray-800, [data-theme="light"] .bg-slate-800\/50 { border: 1px solid var(--border) !important; box-shadow: 0 1px 2px rgba(0,0,0,.05); } /* Hover states for list/grid cards */ [data-theme="light"] .hover\:bg-slate-700\/50:hover, [data-theme="light"] .hover\:bg-slate-800:hover, [data-theme="light"] .hover\:bg-slate-800\/50:hover, [data-theme="light"] .hover\:bg-slate-700:hover, [data-theme="light"] .hover\:bg-gray-800:hover { background-color: var(--selection) !important; /* subtle hover */ } /* In light mode, also treat any active dark-slate background as selection */ [data-theme="light"] .bg-slate-800, [data-theme="light"] .bg-slate-800\/50, [data-theme="light"] .bg-gray-800 { background-color: var(--panel) !important; border: 1px solid var(--border) !important; } /* Inputs in light mode */ [data-theme="light"] input.bg-slate-700, [data-theme="light"] input.bg-gray-800, [data-theme="light"] select.bg-gray-800, [data-theme="light"] select.bg-slate-800, [data-theme="light"] textarea.bg-gray-800, [data-theme="light"] textarea.bg-slate-800 { background-color: var(--panel-2) !important; color: var(--text) !important; border: 1px solid var(--border) !important; } [data-theme="light"] input::placeholder, [data-theme="light"] textarea::placeholder { color: #94a3b8; /* slate-400 */ } /* Hide scrollbars on horizontal lists where we still want scrolling */ .no-scrollbar::-webkit-scrollbar { display: none; } .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } /* --- Sidebar: scrollbar visible au survol / focus uniquement --------------- */ .sidebar-scroll { overflow-y: hidden; scrollbar-width: thin; scrollbar-color: #334155 transparent; scrollbar-gutter: stable; } .sidebar-scroll:hover, .sidebar-scroll:focus, .sidebar-scroll:focus-within { overflow-y: auto; } .sidebar-scroll::-webkit-scrollbar { width: 6px; } .sidebar-scroll::-webkit-scrollbar-track { background: transparent; } .sidebar-scroll::-webkit-scrollbar-thumb { background: #334155; border-radius: 9999px; } @media (hover: none) { /* tactile / mobile : toujours scrollable */ .sidebar-scroll { overflow-y: auto; } } @media (prefers-reduced-motion: reduce) { .sidebar-scroll { scroll-behavior: auto; } } /* --- Shorts player frame --------------------------------------------------- */ /* 9:16 frame constrained by BOTH the dynamic viewport height and the viewport width, so it never overflows a portrait phone nor a landscape one. `--shorts-chrome` reserves room for the provider rail + bottom navigation. */ .shorts-frame { --shorts-chrome: 11rem; aspect-ratio: 9 / 16; width: auto; max-width: 100%; height: min( calc(var(--app-vh) - var(--header-offset) - var(--shorts-chrome)), calc((100vw - 2rem) * 16 / 9) ); min-height: 0; } @media (min-width: 768px) { .shorts-frame { --shorts-chrome: 9rem; } } @media (max-width: 767px) { /* Mobile : quasi plein écran (seule la rangée de chips est réservée : la flèche retour et le chrome vivent EN OVERLAY dans le cadre). */ .shorts-frame { --shorts-chrome: 5.5rem; } } /* --- Shorts immersive stage (façon YouTube) -------------------------------- */ /* Plein écran vertical : le cadre 9:16 flotte sur un fond d'ambiance tiré de la vignette courante. `100dvh` (barres navigateur mobiles) avec repli. */ .shorts-stage { min-height: calc(var(--app-vh, 100dvh) - var(--header-offset, 0px)); touch-action: pan-y; overscroll-behavior: contain; } .shorts-ambient { position: absolute; inset: 0; z-index: 0; overflow: hidden; background: #000; pointer-events: none; } .shorts-ambient img { width: 100%; height: 100%; object-fit: cover; filter: blur(48px) brightness(0.45); transform: scale(1.2); } @media (prefers-reduced-motion: no-preference) { .shorts-ambient img { transition: opacity 0.4s ease; } } /* Zone de méta basse : la safe-area des encoches ne doit jamais manger le titre ou le bouton S'abonner. */ .shorts-meta { padding-bottom: max(0.75rem, env(safe-area-inset-bottom)); } /* --- Shorts : chrome auto-hide + capteur tactile ---------------------------- */ /* Desktop : le chrome (pastilles, boutons, rail, méta) s'efface après 2,5 s d'immobilité de la souris et revient au moindre mouvement (façon YouTube). Sur tactile, `mousemove` ne tire presque jamais : tout reste visible. */ .shorts-chrome { transition: opacity 0.25s ease; } .shorts-chrome-hidden .shorts-chrome { opacity: 0; pointer-events: none; } @media (prefers-reduced-motion: reduce) { .shorts-chrome { transition: none; } } /* Capteur tactile mobile : couche transparente AU-DESSUS de l'iframe (qui mangerait les touchers) mais SOUS le chrome. `touch-action: none` donne le geste à nos handlers (swipe = navigation, tap = play/pause ou accès lecteur) au lieu de scroller/zoomer la page. */ .shorts-gesture { touch-action: none; } /* --- Player FAB menu (shared /watch & /shorts) ---------------------------- */ /* Coque du menu flottant : pastille « verre dépoli », panneau en élévation avec en-tête, entrée/sortie animées. Harmonisé avec les commandes overlay des players YouTube/Dailymotion (fond sombre translucide, blur, ring fin). */ /* Pastille FAB (coin du lecteur) : disque compact en dégradé sombre, léger relief (ombre + highlight interne) et agrandissement au survol. */ .pm-fab { display: inline-flex; align-items: center; justify-content: center; width: 2.25rem; /* 36px */ height: 2.25rem; border-radius: 9999px; color: #fff; background: linear-gradient(180deg, rgba(30, 41, 59, .88), rgba(2, 6, 23, .9)); border: 1px solid rgba(255, 255, 255, .18); box-shadow: 0 4px 14px rgba(0, 0, 0, .45), inset 0 1px 0 rgba(255, 255, 255, .12); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); cursor: pointer; transition: background .15s ease, border-color .15s ease, transform .15s ease, box-shadow .15s ease; } .pm-fab svg { width: 1rem; height: 1rem; opacity: .92; } .pm-fab:hover { background: linear-gradient(180deg, rgba(51, 65, 85, .95), rgba(15, 23, 42, .95)); border-color: rgba(255, 255, 255, .32); transform: scale(1.06); } .pm-fab:active { transform: scale(.94); } .pm-fab-open { background: linear-gradient(180deg, rgba(51, 65, 85, .95), rgba(2, 6, 23, .95)); border-color: rgba(255, 255, 255, .32); } .pm-fab:focus-visible { outline: 2px solid rgba(255, 255, 255, .6); outline-offset: 2px; } /* Panneau déroulant (toujours monté, animé via open/close). */ .pm-panel { position: absolute; top: calc(100% + 0.5rem); left: 0; width: 15rem; /* 240px */ /* Borné à 65vh : le cadre /shorts a `overflow:hidden` — au-delà, le bas du menu serait rogné sur les petits écrans (le corps scrolle donc). */ max-height: min(65vh, 430px); display: flex; flex-direction: column; overflow: hidden; border-radius: 0.75rem; background: rgba(15, 23, 42, .94); /* slate-900/94 */ border: 1px solid rgba(255, 255, 255, .12); box-shadow: 0 20px 50px rgba(0, 0, 0, .6), 0 2px 10px rgba(0, 0, 0, .4), inset 0 1px 0 rgba(255, 255, 255, .07); backdrop-filter: blur(24px); -webkit-backdrop-filter: blur(24px); opacity: 0; transform: translateY(-6px) scale(.97); transform-origin: top left; visibility: hidden; pointer-events: none; transition: opacity .18s ease, transform .18s cubic-bezier(.2, .9, .3, 1), visibility 0s linear .18s; } .pm-panel-open { opacity: 1; transform: translateY(0) scale(1); visibility: visible; pointer-events: auto; transition: opacity .18s ease, transform .18s cubic-bezier(.2, .9, .3, 1); } /* En-tête : titre + bouton fermer. */ .pm-panel-header { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; padding: 0.4375rem 0.4375rem 0.4375rem 0.75rem; border-bottom: 1px solid rgba(255, 255, 255, .08); flex-shrink: 0; } .pm-panel-title { font-size: 0.75rem; /* 12px */ font-weight: 600; letter-spacing: .02em; color: rgba(255, 255, 255, .92); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .pm-panel-close { display: inline-flex; align-items: center; justify-content: center; width: 1.5rem; height: 1.5rem; border-radius: 9999px; color: rgba(255, 255, 255, .7); flex-shrink: 0; transition: background-color .15s ease, color .15s ease; } .pm-panel-close svg { width: 0.875rem; height: 0.875rem; } .pm-panel-close:hover { background: rgba(255, 255, 255, .1); color: #fff; } .pm-panel-close:focus-visible { outline: 2px solid rgba(255, 255, 255, .5); outline-offset: 1px; } /* Corps scrollable du panneau. */ .pm-panel-body { padding: 0.375rem; overflow-y: auto; overscroll-behavior: contain; flex: 1; min-height: 0; } .pm-panel-body::-webkit-scrollbar { width: 6px; } .pm-panel-body::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .18); border-radius: 9999px; } /* Libellé de section. */ .pm-section-label { padding: 0.5rem 0.625rem 0.25rem; font-size: 0.6875rem; /* 11px */ font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); } /* Élément de menu. */ .pm-item { display: flex; align-items: center; gap: 0.625rem; width: 100%; padding: 0.5rem 0.625rem; border-radius: 0.5rem; font-size: 0.8125rem; /* 13px */ color: var(--text); text-align: left; line-height: 1.3; cursor: pointer; transition: background-color .12s ease, color .12s ease; } .pm-item:hover { background-color: rgba(255, 255, 255, .08); } .pm-item:active { background-color: rgba(255, 255, 255, .12); } .pm-item:focus-visible { outline: 2px solid rgba(255, 255, 255, .35); outline-offset: -2px; } .pm-item:disabled { opacity: .4; cursor: not-allowed; } .pm-item svg { flex-shrink: 0; color: rgba(255, 255, 255, .75); } .pm-item .pm-item-label { flex: 1; min-width: 0; } .pm-item .pm-item-hint { font-size: 0.6875rem; color: var(--muted); } /* Séparateur. */ .pm-sep { height: 1px; margin: 0.25rem 0.5rem; background: rgba(255, 255, 255, .08); } /* /shorts : la flèche « retour accueil » occupe le coin haut-gauche (mobile) et passait PAR-DESSUS le FAB (elle est à z-20 au niveau de la scène, le FAB étant enfermé dans le contexte z-10). Le FAB passe donc en haut à droite du cadre, panneau ancré sur le même côté. */ .shorts-frame app-player-menu { left: auto; right: 0.5rem; } .shorts-frame app-player-menu .pm-panel { left: auto; right: 0; transform-origin: top right; } /* --- Drawer / overlay plumbing -------------------------------------------- */ /* Applied to while a full-screen drawer or modal owns the viewport. Note: deliberately NOT `touch-action: none` — touch-action is intersected up the ancestor chain, so it would also freeze scrolling *inside* the drawer. */ body.scroll-locked { overflow: hidden; } /* Skip link: visible only on keyboard focus. */ .skip-link { position: absolute; left: 0.5rem; top: -4rem; z-index: 100; padding: 0.5rem 0.875rem; border-radius: 0.5rem; background: var(--accent); color: #fff; font-size: 0.875rem; font-weight: 600; transition: top .15s ease; } .skip-link:focus { top: 0.5rem; }