CI / build-and-test (push) Successful in 15m1s
- Mobile : le panneau d'informations démarre à `var(--header-offset)` (bas du header fixe z-50) au lieu de `top: 0` — ses 56 premiers px passaient DERRIÈRE le header (bouton Fermer compris). Même motif que le drawer latéral de app.component.html ; `md:static` neutralise ce top au bureau. - Le cadre vidéo est arrondi partout (`rounded-2xl`, plus `rounded-none` sous 768 px). - La ligne de couleur du fournisseur était invisible sur /shorts : l'iframe positionnée peint par-dessus l'outline du cadre. Redessinée en overlay `.shorts-frame[data-video-provider]::after` (bord 1 px `var(--prov)`, `border-radius: inherit`, z-index 5 sous le chrome, pointer-events: none). Vérifié au navigateur (harnais Playwright local) en mobile 390x844 ET desktop 1280x900 : gap tiroir/header, coins, couleur calculée du ::after, et avant/après sur l'instance de test locale.
1226 lines
40 KiB
CSS
1226 lines
40 KiB
CSS
/* 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 <html> 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 */
|
|
}
|
|
|
|
/* --- Harmonisation : TOUS les utilitaires neutres suivent le thème -------- */
|
|
/* Le bloc ci-dessus ne couvrait que 15 classes ; le reste de l'app parlait
|
|
« slate/zinc/gray » quand le thème parlait --panel/--text (36 fichiers, 75
|
|
jetons). Ici : familles slate/zinc/gray -> variables du thème, alpha ->
|
|
color-mix, variants hover/focus/disabled/placeholders/ring/divide inclus.
|
|
Règle : !important gagne sur l'utilitaire Tailwind (injecté après ou avant
|
|
selon le CDN). VOLONTAIREMENT exclus — ils fonctionnent déjà dans tous les
|
|
thèmes et/ou sont conditionnels au thème clair : bg-white/text-black (boutons
|
|
inversés), bg-slate-50/100/200 + text-slate-700/800/900 (pastilles
|
|
« inversées » de la barre de filtres, bouton Google, avatar clair, barre de
|
|
thèmes en thème clair), bg-black/* + text-white au-dessus des vidéos. */
|
|
|
|
.bg-gray-900,
|
|
.bg-slate-950,
|
|
.bg-zinc-900,
|
|
.bg-zinc-950 {
|
|
background-color: var(--bg) !important;
|
|
}
|
|
|
|
.bg-zinc-800 {
|
|
background-color: var(--panel) !important;
|
|
}
|
|
|
|
.bg-slate-600 {
|
|
background-color: var(--panel-2) !important;
|
|
}
|
|
|
|
.disabled\:bg-slate-800:disabled {
|
|
background-color: var(--panel) !important;
|
|
}
|
|
|
|
.bg-slate-900\/40 {
|
|
background-color: color-mix(in srgb, var(--bg) 40%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-900\/50,
|
|
.bg-zinc-900\/50 {
|
|
background-color: color-mix(in srgb, var(--bg) 50%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-900\/60 {
|
|
background-color: color-mix(in srgb, var(--bg) 60%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-900\/95,
|
|
.bg-slate-950\/95,
|
|
.bg-zinc-950\/95 {
|
|
background-color: color-mix(in srgb, var(--bg) 95%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-800\/40 {
|
|
background-color: color-mix(in srgb, var(--panel) 40%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-800\/60 {
|
|
background-color: color-mix(in srgb, var(--panel) 60%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-800\/70 {
|
|
background-color: color-mix(in srgb, var(--panel) 70%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-800\/80,
|
|
.bg-zinc-800\/80 {
|
|
background-color: color-mix(in srgb, var(--panel) 80%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-800\/90 {
|
|
background-color: color-mix(in srgb, var(--panel) 90%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-100\/10 {
|
|
background-color: color-mix(in srgb, var(--text) 10%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-700\/50 {
|
|
background-color: color-mix(in srgb, var(--panel-2) 50%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-700\/60 {
|
|
background-color: color-mix(in srgb, var(--panel-2) 60%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-700\/70 {
|
|
background-color: color-mix(in srgb, var(--panel-2) 70%, transparent) !important;
|
|
}
|
|
|
|
.bg-zinc-700\/80 {
|
|
background-color: color-mix(in srgb, var(--panel-2) 80%, transparent) !important;
|
|
}
|
|
|
|
.bg-slate-700\/90 {
|
|
background-color: color-mix(in srgb, var(--panel-2) 90%, transparent) !important;
|
|
}
|
|
|
|
.focus-within\:bg-slate-800\/60:focus-within {
|
|
background-color: color-mix(in srgb, var(--panel) 60%, transparent) !important;
|
|
}
|
|
|
|
.hover\:bg-slate-800\/60:hover {
|
|
background-color: color-mix(in srgb, var(--panel) 60%, transparent) !important;
|
|
}
|
|
|
|
.hover\:bg-slate-700\/40:hover {
|
|
background-color: color-mix(in srgb, var(--panel-2) 40%, transparent) !important;
|
|
}
|
|
|
|
.hover\:bg-slate-700\/50:hover {
|
|
background-color: color-mix(in srgb, var(--panel-2) 50%, transparent) !important;
|
|
}
|
|
|
|
.hover\:bg-slate-700\/60:hover {
|
|
background-color: color-mix(in srgb, var(--panel-2) 60%, transparent) !important;
|
|
}
|
|
|
|
.hover\:bg-slate-700\/80:hover {
|
|
background-color: color-mix(in srgb, var(--panel-2) 80%, transparent) !important;
|
|
}
|
|
|
|
.hover\:bg-slate-500:hover,
|
|
.hover\:bg-slate-600:hover,
|
|
.hover\:bg-slate-700:hover,
|
|
.hover\:bg-slate-800:hover,
|
|
.hover\:bg-zinc-600:hover {
|
|
background-color: color-mix(in srgb, var(--panel-2) 90%, var(--text)) !important;
|
|
}
|
|
|
|
.text-gray-400,
|
|
.text-slate-500,
|
|
.text-slate-600,
|
|
.text-zinc-400,
|
|
.text-zinc-500,
|
|
.text-zinc-600 {
|
|
color: var(--muted) !important;
|
|
}
|
|
|
|
.placeholder-slate-400::placeholder,
|
|
.placeholder-slate-500::placeholder,
|
|
.placeholder\:text-slate-500::placeholder,
|
|
.placeholder\:text-zinc-500::placeholder {
|
|
color: var(--muted) !important;
|
|
}
|
|
|
|
.disabled\:text-slate-400:disabled {
|
|
color: var(--muted) !important;
|
|
}
|
|
|
|
.group:hover .group-hover\:text-slate-200,
|
|
.text-zinc-100,
|
|
.text-zinc-200,
|
|
.text-zinc-300 {
|
|
color: var(--text) !important;
|
|
}
|
|
|
|
.hover\:text-slate-100:hover,
|
|
.hover\:text-slate-200:hover,
|
|
.hover\:text-zinc-400:hover {
|
|
color: var(--text) !important;
|
|
}
|
|
|
|
.border-slate-700\/60 {
|
|
border-color: color-mix(in srgb, var(--border) 60%, transparent) !important;
|
|
}
|
|
|
|
.border-slate-500,
|
|
.border-slate-600,
|
|
.border-slate-800,
|
|
.border-zinc-700,
|
|
.border-zinc-800,
|
|
.border-zinc-950,
|
|
.divide-slate-700,
|
|
.divide-slate-800 {
|
|
border-color: var(--border) !important;
|
|
}
|
|
|
|
.hover\:border-slate-400:hover,
|
|
.hover\:border-slate-500:hover,
|
|
.hover\:border-slate-600:hover,
|
|
.hover\:border-zinc-500:hover,
|
|
.hover\:border-zinc-600:hover {
|
|
border-color: var(--muted) !important;
|
|
}
|
|
|
|
.ring-slate-500\/50,
|
|
.ring-slate-600\/50 {
|
|
--tw-ring-color: color-mix(in srgb, var(--border) 50%, transparent) !important;
|
|
}
|
|
|
|
.focus-visible\:ring-slate-400\/60:focus-visible {
|
|
--tw-ring-color: var(--accent) !important;
|
|
}
|
|
|
|
.from-slate-800 {
|
|
--tw-gradient-from: var(--panel) !important; --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important;
|
|
}
|
|
|
|
.from-slate-700 {
|
|
--tw-gradient-from: var(--panel-2) !important; --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to) !important;
|
|
}
|
|
|
|
.to-slate-800\/50 {
|
|
--tw-gradient-to: color-mix(in srgb, var(--panel) 50%, transparent) !important;
|
|
}
|
|
|
|
.to-slate-800 {
|
|
--tw-gradient-to: var(--panel) !important;
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.md\:bg-slate-900\/70 { background-color: color-mix(in srgb, var(--bg) 70%, transparent) !important; }
|
|
}
|
|
|
|
/* Marqueur des titres de page (barre `border-l-4`) : suit l'accent du thème
|
|
au lieu d'être figé en rouge (et rattrape les titres qui n'avaient pas de
|
|
couleur explicite, ex. Bibliothèque → Aimés). */
|
|
.border-l-4 { border-left-color: var(--accent) !important; }
|
|
|
|
/* Texte blanc posé SUR une image/vidéo : le `text-white` global est thémé
|
|
(sinon le mot-marque et les boutons disparaissent en thème clair), mais là
|
|
le fond est du noir/une vignette — il doit rester blanc. Les deux sélecteurs
|
|
couvrent les pastilles de durée, les badges LIVE/SHORT/CLIP et tout le
|
|
chrome Shorts ; le panneau ⓘ garde la priorité (scope plus spécifique). */
|
|
[data-theme] [data-video-provider] .text-white,
|
|
[data-theme] [data-video-provider].text-white,
|
|
[data-theme] .shorts-stage .text-white { color: #fff !important; }
|
|
|
|
/* --- Shorts : le panneau d'informations (ⓘ) suit le thème ----------------- */
|
|
/* Ses couleurs sont du « verre blanc » (bg-white/*, text-white/*) : les mêmes
|
|
jetons servent AU-DESSUS de la vidéo (rail, méta) et doivent y rester
|
|
blancs — donc pas de reprise globale, uniquement un scope sur l'aside. */
|
|
[data-theme] aside.shorts-info {
|
|
background-color: color-mix(in srgb, var(--panel) 94%, transparent) !important;
|
|
border-color: var(--border) !important;
|
|
color: var(--text) !important;
|
|
}
|
|
[data-theme] aside.shorts-info [class*="border-white/"] { border-color: var(--border) !important; }
|
|
[data-theme] aside.shorts-info [class*="bg-white/"] {
|
|
background-color: color-mix(in srgb, var(--panel-2) 75%, transparent) !important;
|
|
}
|
|
[data-theme] aside.shorts-info .hover\:bg-white\/20:hover,
|
|
[data-theme] aside.shorts-info .hover\:bg-white\/25:hover {
|
|
background-color: color-mix(in srgb, var(--panel-2) 90%, var(--text)) !important;
|
|
}
|
|
[data-theme] aside.shorts-info .bg-white { background-color: var(--text) !important; }
|
|
[data-theme] aside.shorts-info .text-black { color: var(--bg) !important; }
|
|
[data-theme] aside.shorts-info .hover\:bg-slate-200:hover {
|
|
background-color: color-mix(in srgb, var(--text) 85%, var(--bg)) !important;
|
|
}
|
|
[data-theme] aside.shorts-info [class*="ring-white/"] { --tw-ring-color: var(--border) !important; }
|
|
[data-theme] aside.shorts-info .text-white { color: var(--text) !important; }
|
|
[data-theme] aside.shorts-info .text-white\/40,
|
|
[data-theme] aside.shorts-info .text-white\/45,
|
|
[data-theme] aside.shorts-info .text-white\/50,
|
|
[data-theme] aside.shorts-info .text-white\/60 { color: var(--muted) !important; }
|
|
[data-theme] aside.shorts-info .text-white\/75,
|
|
[data-theme] aside.shorts-info .text-white\/80 {
|
|
color: color-mix(in srgb, var(--text) 85%, transparent) !important;
|
|
}
|
|
/* Pastilles d'action du panneau (définies en dur en rgba blanc plus haut). */
|
|
[data-theme] aside.shorts-info .shorts-info-action {
|
|
color: var(--muted) !important;
|
|
background-color: color-mix(in srgb, var(--panel-2) 70%, transparent) !important;
|
|
}
|
|
[data-theme] aside.shorts-info .shorts-info-action:hover:not(:disabled) {
|
|
color: var(--text) !important;
|
|
background-color: color-mix(in srgb, var(--panel-2) 95%, var(--text)) !important;
|
|
}
|
|
[data-theme] aside.shorts-info .shorts-info-action--on {
|
|
color: var(--text) !important;
|
|
background-color: color-mix(in srgb, var(--panel-2) 90%, var(--text)) !important;
|
|
}
|
|
|
|
/* --- Contour fournisseur autour de chaque cadre vidéo --------------------- */
|
|
/* Contour fin (1 px) à la couleur de la pastille du fournisseur, sur toutes les
|
|
présentations de vidéo. La couleur = le PREMIER stop du dégradé de
|
|
app-provider-badge : c'est la seule table à tenir si une marque change.
|
|
`data-video-provider` accepte l'id long ET l'id court (les deux circulent :
|
|
VideoItem.provider est long, ChannelMeta.provider est court).
|
|
`outline` plutôt que `border` : zéro effet sur la boîte (les cadres sont
|
|
dimensionnés en aspect-ratio) et le rayon suit automatiquement. */
|
|
[data-video-provider] { outline: 1px solid var(--prov, transparent); outline-offset: -1px; }
|
|
/* /shorts : le cadre contient une iframe positionnée, qui peint PAR-DESSUS
|
|
l'outline du parent → la ligne fournisseur y est invisible. On la
|
|
redessine en overlay au-dessus de l'iframe (z-index 5 : sous le chrome
|
|
du cadre, z-10/z-20, et `pointer-events: none` laisse passer les gestes). */
|
|
.shorts-frame[data-video-provider]::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: 0;
|
|
border: 1px solid var(--prov, transparent);
|
|
border-radius: inherit;
|
|
pointer-events: none;
|
|
z-index: 5;
|
|
}
|
|
[data-video-provider="youtube"],
|
|
[data-video-provider="yt"] { --prov: #dc2626; }
|
|
[data-video-provider="dailymotion"],
|
|
[data-video-provider="dm"] { --prov: #0284c7; }
|
|
[data-video-provider="twitch"],
|
|
[data-video-provider="tw"] { --prov: #7c3aed; }
|
|
[data-video-provider="peertube"],
|
|
[data-video-provider="pt"] { --prov: #f59e0b; }
|
|
[data-video-provider="rumble"],
|
|
[data-video-provider="ru"] { --prov: #059669; }
|
|
[data-video-provider="odysee"],
|
|
[data-video-provider="od"] { --prov: #ec4899; }
|
|
|
|
/* 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 - var(--shorts-panel-w, 0px)) * 16 / 9)
|
|
);
|
|
min-height: 0;
|
|
}
|
|
@media (min-width: 768px) {
|
|
/* Bureau : pas de barre du bas, les flèches sont sur les côtés — on ne
|
|
réserve que la scène (py-3) + la charge « Chargement… ». La vidéo gagne
|
|
~80 px de hauteur (et autant de largeur, 9:16 oblige). */
|
|
.shorts-frame {
|
|
--shorts-chrome: 4rem;
|
|
}
|
|
}
|
|
@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;
|
|
}
|
|
/* Le rail est ancré au-dessus de la méta (bas) : il ne doit jamais la
|
|
chevaucher — sur un écran court il défile en interne au lieu de rogner. */
|
|
.shorts-rail {
|
|
max-height: calc(100% - 11rem);
|
|
}
|
|
}
|
|
|
|
/* Embed PAYSAGE dans le cadre vertical : la boîte de l'iframe suit le16:9
|
|
(sinon le lecteur étire ou réduit la vidéo dans un cadre 9:16). */
|
|
.shorts-frame iframe.embed-wide {
|
|
top: 50%;
|
|
bottom: auto;
|
|
height: auto;
|
|
aspect-ratio: 16 / 9;
|
|
transform: translateY(-50%);
|
|
}
|
|
|
|
/* --- 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;
|
|
}
|
|
/* --- Shorts : rail d'actions (bord DROIT de la vidéo) --------------------- */
|
|
/* Colonne d'icônes façon Shorts : informations, flèches ↑/↓, j'aime,
|
|
s'abonner, commentaires, partager, lecture auto. TOUJOURS montée (c'est la
|
|
navigation utilisable dans tous les états, avec le FAB). Ancrage bas sur
|
|
mobile (pouce, au-dessus de la méta) puis centré verticalement au bureau
|
|
(classes du template). */
|
|
.shorts-rail {
|
|
max-height: calc(100% - 1.5rem);
|
|
overflow-y: auto;
|
|
scrollbar-width: none;
|
|
-ms-overflow-style: none;
|
|
}
|
|
.shorts-rail::-webkit-scrollbar { display: none; }
|
|
|
|
.shorts-rail-btn {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex: none;
|
|
width: 2.5rem; /* 40px : cible tactile mobile */
|
|
height: 2.5rem;
|
|
border-radius: 9999px;
|
|
color: #fff;
|
|
background: rgba(0, 0, 0, .55);
|
|
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .5);
|
|
backdrop-filter: blur(6px);
|
|
-webkit-backdrop-filter: blur(6px);
|
|
transition: opacity .25s ease, background .15s ease, transform .15s ease;
|
|
}
|
|
.shorts-rail-btn:hover:not(:disabled) { background: rgba(0, 0, 0, .75); transform: scale(1.06); }
|
|
.shorts-rail-btn:disabled { opacity: .4; }
|
|
.shorts-rail-btn svg { width: 1.15rem; height: 1.15rem; }
|
|
/* Flèches ↑/↓ : plus grosses (c'est LA cible de défilement). */
|
|
.shorts-rail-btn--nav { width: 2.75rem; height: 2.75rem; }
|
|
.shorts-rail-btn--nav svg { width: 1.5rem; height: 1.5rem; }
|
|
/* Bouton actif (j'aime, abonné, lecture auto) : plein, contraste maximal. */
|
|
.shorts-rail-btn--on {
|
|
background: rgba(255, 255, 255, .92);
|
|
color: #0f172a;
|
|
box-shadow: inset 0 0 0 2px rgba(255, 255, 255, .9);
|
|
}
|
|
.shorts-rail-btn--on:hover:not(:disabled) { background: #fff; }
|
|
/* Abonnement : la pastille EST l'avatar de la chaîne. */
|
|
.shorts-rail-btn--sub { overflow: visible; }
|
|
.shorts-rail-badge {
|
|
position: absolute;
|
|
right: -2px;
|
|
bottom: -2px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 1.05rem;
|
|
height: 1.05rem;
|
|
border-radius: 9999px;
|
|
background: #ef4444;
|
|
color: #fff;
|
|
font-size: .7rem;
|
|
font-weight: 800;
|
|
line-height: 1;
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, .5);
|
|
}
|
|
.shorts-rail-btn--on .shorts-rail-badge { background: #16a34a; }
|
|
@media (min-width: 768px) {
|
|
/* Bureau : rail plus compact, la vidéo reste le sujet. */
|
|
.shorts-rail-btn {
|
|
width: 2.25rem;
|
|
height: 2.25rem;
|
|
box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, .45);
|
|
}
|
|
.shorts-rail-btn svg { width: 1.05rem; height: 1.05rem; }
|
|
.shorts-rail-btn--nav { width: 2.5rem; height: 2.5rem; }
|
|
.shorts-rail-btn--nav svg { width: 1.35rem; height: 1.35rem; }
|
|
}
|
|
|
|
/* --- Shorts : panneau d'informations (bouton ⓘ) ---------------------------- */
|
|
.shorts-stage { --shorts-panel-w: 0px; }
|
|
@media (min-width: 768px) {
|
|
/* Le cadre est dimensionné par la hauteur du viewport : on retranche aussi
|
|
la largeur du panneau pour que la vidéo RÉTRÉCISSE au lieu d'être rognée
|
|
(exigence : « sans masquer la vidéo »). */
|
|
.shorts-stage.shorts-panel-open { --shorts-panel-w: 356px; }
|
|
}
|
|
.shorts-info-action {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: .4rem;
|
|
border-radius: 9999px;
|
|
padding: .3rem .6rem;
|
|
font-size: .72rem;
|
|
font-weight: 600;
|
|
color: rgba(255, 255, 255, .75);
|
|
background: rgba(255, 255, 255, .07);
|
|
transition: background .15s ease, color .15s ease;
|
|
}
|
|
.shorts-info-action:hover:not(:disabled) { background: rgba(255, 255, 255, .16); color: #fff; }
|
|
.shorts-info-action:disabled { opacity: .45; }
|
|
.shorts-info-action--on { color: #fff; background: rgba(255, 255, 255, .2); }
|
|
|
|
/* Contrôles flottants (rail + FAB menu) : presque fondu au repos pour laisser
|
|
la vidéo respirer (ton pro), pleins dès le moindre mouvement de souris
|
|
(chromeVisible) ou survol direct. Le bouton actif et le FAB ouvert ne sont
|
|
jamais atténués. Le tactile ne déclenche presque pas mousemove : sur mobile
|
|
tout reste donc plein (comportement voulu). */
|
|
.shorts-frame .shorts-rail-btn,
|
|
.shorts-frame app-player-menu .pm-fab {
|
|
transition: opacity 0.25s ease;
|
|
}
|
|
.shorts-chrome-hidden .shorts-frame .shorts-rail-btn {
|
|
opacity: 0.18;
|
|
}
|
|
.shorts-chrome-hidden .shorts-frame .shorts-rail-btn:not(:disabled):hover {
|
|
opacity: 1;
|
|
}
|
|
.shorts-chrome-hidden .shorts-frame app-player-menu .pm-fab {
|
|
opacity: 0.18;
|
|
}
|
|
.shorts-chrome-hidden .shorts-frame app-player-menu .pm-fab:hover,
|
|
.shorts-chrome-hidden .shorts-frame app-player-menu .pm-fab.pm-fab-open {
|
|
opacity: 1;
|
|
}
|
|
@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;
|
|
touch-action: none; /* glisser le FAB ne défile pas la page */
|
|
cursor: grab;
|
|
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); cursor: grabbing; }
|
|
.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; }
|
|
|
|
/* FAB glissé ailleurs que son coin d'origine : le panneau s'ouvre TOUJOURS
|
|
vers l'intérieur du conteneur (sinon `overflow: hidden` le rogne) et vers
|
|
le haut quand le FAB est en bas. Les classes sont posées par
|
|
PlayerMenuComponent.applyAnchor(). */
|
|
app-player-menu.pm-opens-left .pm-panel { left: auto; right: 0; transform-origin: top right; }
|
|
app-player-menu.pm-opens-right .pm-panel { left: 0; right: auto; transform-origin: top left; }
|
|
app-player-menu.pm-opens-up .pm-panel { top: auto; bottom: calc(100% + 0.5rem); transform-origin: bottom left; }
|
|
app-player-menu.pm-opens-left.pm-opens-up .pm-panel { transform-origin: bottom right; }
|
|
|
|
/* Bureau : menu plus compact pour laisser la place à la vidéo (shorts). */
|
|
@media (min-width: 768px) {
|
|
.shorts-frame app-player-menu .pm-fab { width: 1.875rem; height: 1.875rem; }
|
|
.shorts-frame app-player-menu .pm-fab svg { width: 0.875rem; height: 0.875rem; }
|
|
.shorts-frame app-player-menu .pm-panel { width: 13rem; max-height: min(60vh, 380px); }
|
|
.shorts-frame app-player-menu .pm-item { padding: 0.375rem 0.5rem; gap: 0.5rem; font-size: 0.75rem; }
|
|
.shorts-frame app-player-menu .pm-section-label { padding: 0.375rem 0.5rem 0.125rem; font-size: 0.625rem; }
|
|
.shorts-frame app-player-menu .pm-panel-header { padding: 0.3125rem 0.3125rem 0.3125rem 0.625rem; }
|
|
}
|
|
|
|
/* --- Drawer / overlay plumbing -------------------------------------------- */
|
|
/* Applied to <body> 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;
|
|
}
|
|
|