Favorites now carry `favoriteDate`. Ones saved before this had no way of knowing when they were saved, so they are all stamped with the moment the client first reads them -- they sort together as one batch, at the point favorites learned to keep dates. An import brings the date the other client recorded instead, so a restored library keeps its history. The bar used to build a card per favorite, which an import of several hundred made an expensive way to open the app. It now renders a screenful and appends more as the strip is scrolled. "Browse all" turns the whole grid into favorites: the same cards, the same virtualized masonry, the same infinite scroll and reels mode as a channel listing -- App.videos.loadVideos simply pages out of localStorage instead of the server while that view is open. Sort applies to the bar and the grid together: recently added (default), oldest, title, longest, shortest, and a shuffle for rediscovering a long list. Tests (scratchpad): dates backfilled onto undated favorites, the bar paging as it scrolls rather than building every card, the grid paging to the full list with zero server calls, each sort order reordering it, and the way back to the channel listing. Also measured: 515 imported favorites load with the page responsive and 24 bar cards built. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QBDkEXP4htyXTCZUwMLphd
2589 lines
54 KiB
CSS
2589 lines
54 KiB
CSS
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
|
|
:root {
|
|
/* Warm "classic" dark: deep charcoal with a hint of brown, never pure black. */
|
|
--bg-primary: #14110d;
|
|
--bg-secondary: rgba(28, 24, 18, 0.86);
|
|
--bg-tertiary: rgba(244, 232, 212, 0.06);
|
|
--text-primary: #f4ece0;
|
|
--text-secondary: rgba(244, 236, 224, 0.62);
|
|
/* Modern brass accent — the single chromatic note against the neutrals. */
|
|
--accent: #c9a567;
|
|
--accent-hover: #d9b87f;
|
|
--border: rgba(244, 232, 212, 0.12);
|
|
--border-hover: rgba(201, 165, 103, 0.45);
|
|
--shadow: rgba(8, 6, 3, 0.5);
|
|
--font-body: 'Sora', 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
|
|
--font-display: 'Fraunces', 'Iowan Old Style', 'Palatino Linotype', Georgia, serif;
|
|
--font-logo: 'Fraunces', 'Iowan Old Style', 'Palatino Linotype', Georgia, serif;
|
|
--radius-lg: 14px;
|
|
--radius-md: 10px;
|
|
--radius-sm: 7px;
|
|
--grid-max: 1480px;
|
|
--focus-ring: rgba(201, 165, 103, 0.32);
|
|
color-scheme: dark;
|
|
}
|
|
|
|
html, body { height: 100%; }
|
|
html { scroll-behavior: smooth; }
|
|
|
|
body {
|
|
margin: 0;
|
|
background:
|
|
radial-gradient(1100px 560px at 14% -12%, rgba(201, 165, 103, 0.10), transparent 60%),
|
|
radial-gradient(900px 520px at 108% 4%, rgba(201, 165, 103, 0.06), transparent 58%),
|
|
var(--bg-primary);
|
|
background-attachment: fixed;
|
|
color: var(--text-primary);
|
|
font-family: var(--font-body);
|
|
overflow-x: hidden;
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
-webkit-font-smoothing: antialiased;
|
|
text-rendering: optimizeLegibility;
|
|
/* No accidental zoom: `manipulation` drops double-tap-to-zoom (the app's own
|
|
taps are handled in JS anyway) while leaving panning and deliberate
|
|
pinch-zoom alone, and text-size-adjust stops Safari inflating text of its
|
|
own accord after a rotation. */
|
|
touch-action: manipulation;
|
|
-webkit-text-size-adjust: 100%;
|
|
text-size-adjust: 100%;
|
|
}
|
|
|
|
body.drawer-open {
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Subtle film grain so large flat surfaces don't read as sterile vector flats. */
|
|
body::before {
|
|
content: '';
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 50;
|
|
pointer-events: none;
|
|
opacity: 0.035;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
}
|
|
|
|
body.theme-light::before {
|
|
opacity: 0.05;
|
|
mix-blend-mode: multiply;
|
|
}
|
|
|
|
body:not(.theme-light) {
|
|
color-scheme: dark;
|
|
}
|
|
|
|
body.theme-light {
|
|
/* Warm ivory "paper" with a warm-ink text — editorial, not stark white/black. */
|
|
--bg-primary: #f6f1e7;
|
|
--bg-secondary: rgba(255, 252, 245, 0.86);
|
|
--bg-tertiary: rgba(43, 33, 18, 0.05);
|
|
--text-primary: #241d12;
|
|
--text-secondary: rgba(36, 29, 18, 0.62);
|
|
--accent: #9a7338;
|
|
--accent-hover: #835f2c;
|
|
--border: rgba(43, 33, 18, 0.12);
|
|
--border-hover: rgba(154, 115, 56, 0.5);
|
|
--shadow: rgba(74, 56, 28, 0.14);
|
|
--focus-ring: rgba(154, 115, 56, 0.28);
|
|
color-scheme: light;
|
|
background:
|
|
radial-gradient(1100px 560px at 14% -12%, rgba(154, 115, 56, 0.08), transparent 60%),
|
|
radial-gradient(900px 520px at 108% 4%, rgba(154, 115, 56, 0.05), transparent 58%),
|
|
var(--bg-primary);
|
|
}
|
|
|
|
/* Top Bar */
|
|
.top-bar {
|
|
height: 64px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 0 clamp(16px, 3vw, 36px);
|
|
background: linear-gradient(180deg, rgba(20, 17, 13, 0.86), rgba(20, 17, 13, 0.62));
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
border-bottom: 1px solid var(--border);
|
|
gap: 24px;
|
|
backdrop-filter: blur(12px);
|
|
box-shadow: 0 8px 24px var(--shadow);
|
|
}
|
|
|
|
body.theme-light .top-bar {
|
|
background: linear-gradient(180deg, rgba(246, 241, 231, 0.94), rgba(246, 241, 231, 0.78));
|
|
}
|
|
|
|
.logo {
|
|
font-size: 28px;
|
|
font-weight: 500;
|
|
letter-spacing: -0.01em;
|
|
color: var(--text-primary);
|
|
font-family: var(--font-logo);
|
|
font-optical-sizing: auto;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.search-container {
|
|
flex: 1;
|
|
max-width: 500px;
|
|
position: relative;
|
|
}
|
|
|
|
.search-container input {
|
|
width: 100%;
|
|
padding: 10px 44px 10px 16px;
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.search-container input:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
background: var(--bg-tertiary);
|
|
box-shadow: 0 0 0 4px var(--focus-ring);
|
|
}
|
|
|
|
.search-container input::placeholder {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.search-clear-btn {
|
|
position: absolute;
|
|
top: 50%;
|
|
right: 12px;
|
|
transform: translateY(-50%);
|
|
width: 26px;
|
|
height: 26px;
|
|
border: none;
|
|
border-radius: 50%;
|
|
background: var(--bg-secondary);
|
|
color: var(--text-secondary);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 16px;
|
|
line-height: 1;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.2s ease, background 0.2s ease, color 0.2s ease;
|
|
}
|
|
|
|
.search-clear-btn.is-visible {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.search-clear-btn:hover {
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.icon-btn {
|
|
width: 48px;
|
|
height: 48px;
|
|
border: none;
|
|
background: transparent;
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--text-primary);
|
|
transition: all 0.2s ease;
|
|
font-size: 20px;
|
|
}
|
|
|
|
.icon-btn:hover {
|
|
background: var(--bg-tertiary);
|
|
color: var(--accent);
|
|
}
|
|
|
|
.icon-btn:active {
|
|
transform: scale(0.92);
|
|
}
|
|
|
|
:focus-visible {
|
|
outline: 2px solid var(--accent);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* CDN-served icon images (Heroicons). Black SVG sources, tinted via filter:
|
|
neutral to match text by default, brass on hover for the accent moment. */
|
|
.icon-svg {
|
|
width: 24px;
|
|
height: 24px;
|
|
display: block;
|
|
filter: invert(92%) sepia(6%) saturate(220%) hue-rotate(2deg) brightness(101%);
|
|
}
|
|
|
|
/* Brass tint approximated from a pure-black source. */
|
|
.icon-btn:hover .icon-svg {
|
|
filter: invert(68%) sepia(42%) saturate(560%) hue-rotate(1deg) brightness(94%) contrast(86%);
|
|
}
|
|
|
|
body.theme-light .icon-svg {
|
|
filter: invert(12%) sepia(20%) saturate(620%) hue-rotate(2deg) brightness(95%);
|
|
}
|
|
|
|
body.theme-light .icon-btn:hover .icon-svg {
|
|
filter: invert(46%) sepia(36%) saturate(720%) hue-rotate(1deg) brightness(82%) contrast(88%);
|
|
}
|
|
|
|
/* Hamburger Menu */
|
|
.hamburger {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
|
|
.hamburger span {
|
|
width: 24px;
|
|
height: 2px;
|
|
background: currentColor;
|
|
border-radius: 1px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.menu-toggle.active .hamburger span:nth-child(1) {
|
|
transform: translateY(6px) rotate(45deg);
|
|
}
|
|
|
|
.menu-toggle.active .hamburger span:nth-child(2) {
|
|
opacity: 0;
|
|
}
|
|
|
|
.menu-toggle.active .hamburger span:nth-child(3) {
|
|
transform: translateY(-6px) rotate(-45deg);
|
|
}
|
|
|
|
/* Sidebar */
|
|
.sidebar {
|
|
position: fixed;
|
|
top: 0;
|
|
right: -100%;
|
|
width: 320px;
|
|
height: 100vh;
|
|
background: var(--bg-secondary);
|
|
z-index: 1001;
|
|
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
border-left: 1px solid var(--border);
|
|
display: flex;
|
|
flex-direction: column;
|
|
backdrop-filter: blur(15px);
|
|
box-shadow: -20px 0 40px var(--shadow);
|
|
}
|
|
|
|
body.theme-light .sidebar {
|
|
background: rgba(255, 255, 255, 0.9);
|
|
backdrop-filter: blur(10px);
|
|
}
|
|
|
|
.sidebar.open {
|
|
right: 0;
|
|
}
|
|
|
|
.sidebar-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 20px 24px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.sidebar-header h3 {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
font-family: var(--font-display);
|
|
}
|
|
|
|
.close-btn {
|
|
background: none;
|
|
border: none;
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
font-size: 24px;
|
|
padding: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: color 0.2s ease;
|
|
}
|
|
|
|
.close-btn:hover {
|
|
color: var(--accent);
|
|
}
|
|
|
|
.sidebar-content {
|
|
flex: 1;
|
|
padding: 12px 0;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.sidebar-section {
|
|
padding: 8px 0 4px 0;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.sidebar-section:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.sidebar-subtitle {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.6px;
|
|
padding: 8px 24px 4px 24px;
|
|
}
|
|
|
|
.sidebar-item {
|
|
display: block;
|
|
padding: 12px 24px;
|
|
color: var(--text-primary);
|
|
text-decoration: none;
|
|
transition: all 0.2s ease;
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
.sidebar-item:hover {
|
|
background: var(--bg-tertiary);
|
|
border-left-color: var(--accent);
|
|
color: var(--accent);
|
|
}
|
|
|
|
.setting-item {
|
|
padding: 16px 24px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.filters-container .setting-item {
|
|
padding-top: 12px;
|
|
padding-bottom: 12px;
|
|
}
|
|
|
|
.filters-empty {
|
|
padding: 12px 24px 20px 24px;
|
|
color: var(--text-secondary);
|
|
font-size: 13px;
|
|
}
|
|
|
|
.setting-item label {
|
|
display: block;
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 8px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.setting-label-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
/* Explanatory line under a control -- and where the import reports back. Not a
|
|
`label`, so it keeps sentence case and normal letter spacing. */
|
|
.setting-note {
|
|
margin: 8px 0 0;
|
|
font-size: 12px;
|
|
line-height: 1.45;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.setting-label-row label {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.setting-toggle .setting-label-row {
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.toggle {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
width: 46px;
|
|
height: 26px;
|
|
}
|
|
|
|
.toggle input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.toggle-track {
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 999px;
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border);
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.toggle-track::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 2px;
|
|
left: 2px;
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
background: var(--text-primary);
|
|
transition: transform 0.2s ease, background 0.2s ease;
|
|
box-shadow: 0 4px 10px var(--shadow);
|
|
}
|
|
|
|
.toggle input:checked + .toggle-track {
|
|
background: var(--accent);
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
.toggle input:checked + .toggle-track::after {
|
|
transform: translateX(20px);
|
|
background: var(--bg-primary);
|
|
}
|
|
|
|
.toggle input:focus-visible + .toggle-track {
|
|
box-shadow: 0 0 0 4px var(--focus-ring);
|
|
}
|
|
|
|
.btn-link {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--text-secondary);
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
text-decoration: underline;
|
|
padding: 0;
|
|
}
|
|
|
|
.btn-link:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.btn-link:disabled {
|
|
color: var(--border);
|
|
cursor: not-allowed;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.input-row {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
}
|
|
|
|
.input-row input {
|
|
flex: 1;
|
|
padding: 9px 12px;
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
}
|
|
|
|
.input-row input::placeholder {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.input-row input:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 4px var(--focus-ring);
|
|
}
|
|
|
|
body.theme-light .input-row input:focus {
|
|
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.btn-secondary {
|
|
padding: 9px 14px;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--border);
|
|
background: transparent;
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
transition: all 0.2s ease;
|
|
width: 100%;
|
|
text-align: center;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: var(--bg-tertiary);
|
|
border-color: var(--border-hover);
|
|
}
|
|
|
|
.btn-secondary:active {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
.sources-list {
|
|
padding: 8px 24px 16px 24px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.source-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
padding: 9px 12px;
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.source-item span {
|
|
font-size: 12px;
|
|
color: var(--text-primary);
|
|
word-break: break-all;
|
|
}
|
|
|
|
.source-item button {
|
|
padding: 6px 10px;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--border);
|
|
background: transparent;
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.source-item button:hover {
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.setting-item input[type="range"] {
|
|
width: 100%;
|
|
appearance: none;
|
|
-webkit-appearance: none;
|
|
height: 6px;
|
|
border-radius: 999px;
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border);
|
|
cursor: pointer;
|
|
margin: 6px 0;
|
|
}
|
|
|
|
.setting-item input[type="range"]::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
background: var(--text-primary);
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.setting-item input[type="range"]::-moz-range-thumb {
|
|
width: 18px;
|
|
height: 18px;
|
|
border-radius: 50%;
|
|
background: var(--text-primary);
|
|
border: none;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.setting-item select {
|
|
width: 100%;
|
|
padding: 9px 12px;
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 10px;
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
color-scheme: dark;
|
|
appearance: none;
|
|
background-image:
|
|
linear-gradient(45deg, transparent 50%, var(--text-secondary) 50%),
|
|
linear-gradient(135deg, var(--text-secondary) 50%, transparent 50%),
|
|
linear-gradient(to right, transparent, transparent);
|
|
background-position:
|
|
calc(100% - 16px) calc(1em + 1px),
|
|
calc(100% - 11px) calc(1em + 1px),
|
|
100% 0;
|
|
background-size:
|
|
5px 5px,
|
|
5px 5px,
|
|
2.5em 2.5em;
|
|
background-repeat: no-repeat;
|
|
}
|
|
|
|
.multi-select {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.multi-select-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
font-size: 13px;
|
|
color: var(--text-primary);
|
|
padding: 6px 10px;
|
|
border-radius: 8px;
|
|
border: 1px solid transparent;
|
|
background: transparent;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.multi-select-item input[type="checkbox"] {
|
|
width: 18px;
|
|
height: 18px;
|
|
accent-color: var(--accent);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.multi-select-item span {
|
|
padding-left: 6px;
|
|
}
|
|
|
|
.multi-select-item:hover {
|
|
border-color: var(--border);
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.multi-select-item input[type="checkbox"]:checked + span {
|
|
color: var(--accent);
|
|
}
|
|
|
|
.setting-item select:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
box-shadow: 0 0 0 4px var(--focus-ring);
|
|
}
|
|
|
|
body.theme-light .setting-item select:focus {
|
|
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
.setting-item select,
|
|
.setting-item select option,
|
|
.setting-item select optgroup {
|
|
background-color: #0a0a0a;
|
|
color: #ffffff;
|
|
}
|
|
|
|
body.theme-light .setting-item select {
|
|
color-scheme: light;
|
|
}
|
|
|
|
body.theme-light .setting-item select,
|
|
body.theme-light .setting-item select option,
|
|
body.theme-light .setting-item select optgroup {
|
|
background-color: #ffffff;
|
|
color: #000000;
|
|
}
|
|
.setting-item select option {
|
|
background: #0a0a0a;
|
|
color: #ffffff;
|
|
}
|
|
|
|
body.theme-light .setting-item select option {
|
|
background: #ffffff;
|
|
color: #000000;
|
|
}
|
|
|
|
/* Overlay */
|
|
.sidebar-overlay {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
display: none;
|
|
z-index: 1000;
|
|
transition: opacity 0.3s ease;
|
|
backdrop-filter: blur(2px);
|
|
}
|
|
|
|
.sidebar-overlay.open {
|
|
display: block;
|
|
}
|
|
|
|
.load-more-btn {
|
|
position: sticky;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
margin: 16px auto 32px auto;
|
|
width: 52px;
|
|
height: 52px;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-secondary);
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
z-index: 10;
|
|
box-shadow: 0 10px 20px var(--shadow);
|
|
}
|
|
|
|
.load-more-btn:hover {
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.load-more-btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Favorites Bar */
|
|
.favorites-bar {
|
|
padding: 16px clamp(16px, 3vw, 36px) 4px;
|
|
max-width: var(--grid-max);
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.favorites-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.favorites-header h3 {
|
|
font-size: 12px;
|
|
letter-spacing: 1px;
|
|
text-transform: uppercase;
|
|
color: var(--text-secondary);
|
|
font-family: var(--font-display);
|
|
}
|
|
|
|
.favorites-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.favorites-sort {
|
|
height: 28px;
|
|
padding: 0 8px;
|
|
border-radius: var(--radius-sm, 6px);
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-primary);
|
|
font-size: 12px;
|
|
}
|
|
|
|
.favorites-actions .btn-secondary {
|
|
height: 28px;
|
|
padding: 0 12px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* Browsing favorites as a grid: the bar above it would be the same list twice,
|
|
so it collapses to its header (which carries the way back out). */
|
|
body.favorites-view-open .favorites-list,
|
|
body.favorites-view-open .favorites-empty {
|
|
display: none;
|
|
}
|
|
|
|
.favorites-list {
|
|
display: flex;
|
|
gap: 12px;
|
|
overflow-x: auto;
|
|
padding-bottom: 12px;
|
|
scroll-snap-type: x mandatory;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.favorites-list::-webkit-scrollbar {
|
|
height: 8px;
|
|
}
|
|
|
|
.favorites-list::-webkit-scrollbar-thumb {
|
|
background: var(--bg-tertiary);
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.favorites-list::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
.favorite-card {
|
|
position: relative;
|
|
flex: 0 0 auto;
|
|
width: 220px;
|
|
border-radius: var(--radius-md);
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-secondary);
|
|
overflow: hidden;
|
|
cursor: pointer;
|
|
box-shadow: 0 8px 18px var(--shadow);
|
|
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
|
|
scroll-snap-align: start;
|
|
}
|
|
|
|
.favorite-card:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 14px 24px var(--shadow);
|
|
border-color: var(--border-hover);
|
|
}
|
|
|
|
.favorite-card img {
|
|
width: 100%;
|
|
aspect-ratio: 16 / 9;
|
|
object-fit: cover;
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.favorite-info {
|
|
padding: 10px 12px 12px 12px;
|
|
}
|
|
|
|
.favorite-info h4 {
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
margin: 0;
|
|
color: var(--text-primary);
|
|
font-family: var(--font-display);
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.favorites-empty {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
padding: 4px 0 12px 0;
|
|
}
|
|
|
|
/* Grid Container */
|
|
.grid-container {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
grid-auto-rows: 10px;
|
|
gap: 16px;
|
|
align-items: start;
|
|
padding: 24px;
|
|
max-width: var(--grid-max);
|
|
margin: 0 auto;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.grid-container {
|
|
/* Exactly two larger cards per row on phones, rather than packing in
|
|
several small ones. */
|
|
grid-template-columns: repeat(2, 1fr);
|
|
grid-auto-rows: 10px;
|
|
gap: 12px;
|
|
padding: 16px;
|
|
}
|
|
|
|
.top-bar {
|
|
flex-wrap: wrap;
|
|
height: auto;
|
|
padding: 12px 16px;
|
|
row-gap: 10px;
|
|
}
|
|
|
|
.search-container {
|
|
order: 3;
|
|
width: 100%;
|
|
max-width: 100%;
|
|
flex-basis: 100%;
|
|
}
|
|
|
|
.actions {
|
|
order: 2;
|
|
width: auto;
|
|
margin-left: auto;
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.grid-container {
|
|
/* One full-width card per row on phones. */
|
|
grid-template-columns: 1fr;
|
|
gap: 16px;
|
|
}
|
|
|
|
.logo {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.icon-btn {
|
|
width: 44px;
|
|
height: 44px;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1600px) {
|
|
body {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.top-bar {
|
|
height: 72px;
|
|
padding: 0 36px;
|
|
}
|
|
|
|
.grid-container {
|
|
gap: 24px;
|
|
padding: 32px 48px;
|
|
}
|
|
|
|
.video-card h4 {
|
|
font-size: calc(16px * var(--card-font-scale, 1));
|
|
}
|
|
|
|
.video-card p {
|
|
font-size: calc(13px * var(--card-font-scale, 1));
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1920px) {
|
|
.grid-container {
|
|
column-width: 280px;
|
|
}
|
|
}
|
|
|
|
@media (pointer: coarse) {
|
|
.icon-btn {
|
|
width: 52px;
|
|
height: 52px;
|
|
}
|
|
|
|
.btn-secondary {
|
|
padding: 10px 14px;
|
|
}
|
|
|
|
.setting-item select,
|
|
.input-row input {
|
|
padding: 10px 14px;
|
|
}
|
|
|
|
/* iOS zooms the whole page when you focus a control whose text is under
|
|
16px, and it ignores user-scalable=no -- so the font size is the only
|
|
lever that actually stops it. Every text control gets 16px on touch
|
|
devices; the padding above keeps them looking the same size. */
|
|
.search-container input,
|
|
.input-row input,
|
|
.setting-item select,
|
|
.cmdk-input,
|
|
input[type="text"],
|
|
input[type="search"],
|
|
input[type="number"],
|
|
input[type="url"],
|
|
input[type="email"],
|
|
input[type="password"],
|
|
input:not([type]),
|
|
textarea,
|
|
select {
|
|
font-size: 16px;
|
|
}
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
* {
|
|
transition: none !important;
|
|
animation: none !important;
|
|
}
|
|
}
|
|
|
|
/* Video Card */
|
|
.video-card {
|
|
cursor: pointer;
|
|
border-radius: var(--radius-lg);
|
|
overflow: hidden;
|
|
transition: transform 0.25s ease, box-shadow 0.25s ease, border-color 0.25s ease;
|
|
background: var(--bg-secondary);
|
|
display: inline-flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
border: 1px solid var(--border);
|
|
box-shadow: 0 6px 16px var(--shadow);
|
|
position: relative;
|
|
margin-bottom: 0;
|
|
/* The grid is virtualized in JS (see App.virtualGrid): only cards near the
|
|
viewport are in the DOM at all, and each is absolutely positioned at a
|
|
precomputed (top,left). */
|
|
}
|
|
|
|
.video-card:hover {
|
|
transform: translateY(-6px);
|
|
box-shadow: 0 16px 28px var(--shadow);
|
|
border-color: var(--border-hover);
|
|
}
|
|
|
|
.video-thumb {
|
|
position: relative;
|
|
display: block;
|
|
}
|
|
|
|
.video-card img {
|
|
width: 100%;
|
|
aspect-ratio: 16 / 9;
|
|
object-fit: cover;
|
|
display: block;
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
/* Dark-transparent pills overlaid on the bottom of the thumbnail.
|
|
Scoped under .video-thumb so they outrank the later .uploader-link rule. */
|
|
.video-thumb .video-uploader,
|
|
.video-thumb .video-duration {
|
|
position: absolute;
|
|
bottom: 8px;
|
|
z-index: 3;
|
|
padding: 3px 8px;
|
|
border-radius: 6px;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
color: #fff;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
line-height: 1.2;
|
|
backdrop-filter: blur(2px);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.video-thumb .video-duration {
|
|
right: 8px;
|
|
}
|
|
|
|
.video-thumb .video-uploader {
|
|
left: 8px;
|
|
max-width: 60%;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
border: none;
|
|
text-align: left;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.video-thumb .video-uploader:hover {
|
|
background: rgba(0, 0, 0, 0.78);
|
|
color: #fff;
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.video-card h4 {
|
|
font-size: calc(15px * var(--card-font-scale, 1));
|
|
font-weight: 500;
|
|
padding: 12px 12px 10px;
|
|
line-height: 1.3;
|
|
color: var(--text-primary);
|
|
margin: 0;
|
|
flex: 1;
|
|
font-family: var(--font-display);
|
|
font-optical-sizing: auto;
|
|
letter-spacing: -0.01em;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
position: relative;
|
|
}
|
|
|
|
.video-title-text {
|
|
display: inline-block;
|
|
padding-right: 24px;
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.video-card.is-title-active .video-title-text {
|
|
animation: video-title-marquee var(--marquee-duration, 10s) linear infinite;
|
|
will-change: transform;
|
|
}
|
|
|
|
@keyframes video-title-marquee {
|
|
to {
|
|
transform: translateX(calc(-1 * var(--marquee-distance, 0px)));
|
|
}
|
|
}
|
|
|
|
.video-card p {
|
|
font-size: calc(12px * var(--card-font-scale, 1));
|
|
color: var(--text-secondary);
|
|
padding: 0 12px 12px 12px;
|
|
margin: 0;
|
|
}
|
|
|
|
.video-tags {
|
|
display: flex;
|
|
flex-wrap: nowrap;
|
|
gap: 6px;
|
|
padding: 0 12px 8px 12px;
|
|
overflow-x: auto;
|
|
overflow-y: hidden;
|
|
scrollbar-width: thin;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.video-tag {
|
|
font-size: calc(11px * var(--card-font-scale, 1));
|
|
color: var(--text-secondary);
|
|
background: var(--bg-tertiary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 999px;
|
|
padding: 3px 8px;
|
|
line-height: 1.2;
|
|
letter-spacing: 0.2px;
|
|
white-space: nowrap;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.video-tag:focus-visible {
|
|
outline: 2px solid var(--text-primary);
|
|
outline-offset: 1px;
|
|
}
|
|
|
|
.uploader-link {
|
|
background: transparent;
|
|
border: none;
|
|
color: inherit;
|
|
font: inherit;
|
|
cursor: pointer;
|
|
padding: 0;
|
|
text-align: left;
|
|
}
|
|
|
|
.video-loading {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.45);
|
|
opacity: 0;
|
|
transition: opacity 0.2s ease;
|
|
pointer-events: none;
|
|
z-index: 4;
|
|
}
|
|
|
|
.video-card.is-loading .video-loading,
|
|
.favorite-card.is-loading .video-loading {
|
|
opacity: 1;
|
|
}
|
|
|
|
.video-loading-spinner {
|
|
width: 34px;
|
|
height: 34px;
|
|
border-radius: 50%;
|
|
border: 3px solid rgba(255, 255, 255, 0.35);
|
|
border-top-color: var(--text-primary);
|
|
animation: video-card-spinner 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes video-card-spinner {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
.uploader-link:hover {
|
|
color: var(--text-primary);
|
|
text-decoration: underline;
|
|
}
|
|
|
|
.video-menu-btn {
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 10px;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--border);
|
|
background: rgba(0, 0, 0, 0.55);
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 20px;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
z-index: 2;
|
|
transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease;
|
|
}
|
|
|
|
body.theme-light .video-menu-btn {
|
|
background: rgba(255, 255, 255, 0.8);
|
|
}
|
|
|
|
.video-menu-btn:hover {
|
|
transform: scale(1.05);
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
.video-menu {
|
|
position: absolute;
|
|
top: 48px;
|
|
right: 10px;
|
|
min-width: 140px;
|
|
border-radius: 10px;
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-secondary);
|
|
box-shadow: 0 12px 24px var(--shadow);
|
|
opacity: 0;
|
|
transform: translateY(-6px);
|
|
pointer-events: none;
|
|
transition: opacity 0.18s ease, transform 0.18s ease;
|
|
z-index: 3;
|
|
}
|
|
|
|
.video-menu.open {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.video-menu-item {
|
|
width: 100%;
|
|
padding: 10px 12px;
|
|
text-align: left;
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--text-primary);
|
|
cursor: pointer;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.video-menu-item:hover {
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.live-badge {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 50px;
|
|
z-index: 2;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 3px 8px;
|
|
border-radius: 999px;
|
|
background: #e0245e;
|
|
color: #fff;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
letter-spacing: 0.04em;
|
|
line-height: 1;
|
|
text-transform: uppercase;
|
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.feed-live-badge {
|
|
top: 16px;
|
|
left: 16px;
|
|
}
|
|
|
|
/* Live streams can't be seeked, so hide the feed scrubber. */
|
|
.feed-slide.is-live .feed-timeline {
|
|
display: none;
|
|
}
|
|
|
|
.favorite-btn {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 10px;
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--border);
|
|
background: rgba(0, 0, 0, 0.55);
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 18px;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
z-index: 1;
|
|
transition: transform 0.2s ease, background 0.2s ease, border-color 0.2s ease;
|
|
}
|
|
|
|
body.theme-light .favorite-btn {
|
|
background: rgba(255, 255, 255, 0.8);
|
|
}
|
|
|
|
.favorite-btn:hover {
|
|
transform: scale(1.05);
|
|
background: var(--bg-secondary);
|
|
}
|
|
|
|
.favorite-btn.is-favorite {
|
|
color: #ff3b30;
|
|
border-color: rgba(255, 59, 48, 0.6);
|
|
background: rgba(255, 59, 48, 0.12);
|
|
}
|
|
|
|
/* Info Modal */
|
|
.info-modal {
|
|
position: fixed;
|
|
inset: 0;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
z-index: 2500;
|
|
padding: 20px;
|
|
}
|
|
|
|
.info-modal.open {
|
|
display: flex;
|
|
}
|
|
|
|
.info-card {
|
|
position: relative;
|
|
width: min(520px, 92vw);
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
padding: 20px 22px;
|
|
box-shadow: 0 18px 36px var(--shadow);
|
|
}
|
|
|
|
.info-card h3 {
|
|
font-family: var(--font-display);
|
|
font-size: 18px;
|
|
margin: 0 0 16px 0;
|
|
}
|
|
|
|
.info-close {
|
|
position: absolute;
|
|
right: 16px;
|
|
top: 16px;
|
|
border: none;
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-primary);
|
|
border-radius: 50%;
|
|
width: 32px;
|
|
height: 32px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.info-rows,
|
|
.info-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.info-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
border-bottom: 1px solid var(--border);
|
|
padding-bottom: 8px;
|
|
}
|
|
|
|
.info-row:last-child {
|
|
border-bottom: none;
|
|
padding-bottom: 0;
|
|
}
|
|
|
|
.info-label {
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.6px;
|
|
font-size: 11px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.info-value {
|
|
text-align: right;
|
|
color: var(--text-primary);
|
|
word-break: break-all;
|
|
}
|
|
|
|
.info-json {
|
|
margin: 0;
|
|
white-space: pre-wrap;
|
|
text-align: left;
|
|
color: var(--text-primary);
|
|
background: var(--bg-tertiary);
|
|
border-radius: 8px;
|
|
padding: 8px 10px;
|
|
font-size: 12px;
|
|
line-height: 1.4;
|
|
max-height: 180px;
|
|
overflow: auto;
|
|
flex: 1;
|
|
}
|
|
|
|
.info-empty {
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
padding-top: 6px;
|
|
}
|
|
|
|
/* --- Custom player: single fake-fullscreen state everywhere ------------ */
|
|
.custom-player {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
background: #000;
|
|
z-index: 3000;
|
|
touch-action: none;
|
|
transition: transform 0.25s ease, opacity 0.25s ease;
|
|
}
|
|
|
|
.custom-player.open {
|
|
display: block;
|
|
animation: cp-open 0.22s ease;
|
|
}
|
|
|
|
@keyframes cp-open {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
.cp-surface {
|
|
position: absolute;
|
|
inset: 0;
|
|
}
|
|
|
|
/* Ambient backdrop: a blurred copy of the poster fills any letterbox bars
|
|
behind the contained video (set via the --poster custom property). */
|
|
.cp-surface::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background-image: var(--poster);
|
|
background-size: cover;
|
|
background-position: center;
|
|
filter: blur(60px) saturate(1.3) brightness(0.5);
|
|
transform: scale(1.25);
|
|
opacity: 0.55;
|
|
z-index: 0;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.cp-video {
|
|
position: relative;
|
|
z-index: 1;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
background: #000;
|
|
}
|
|
|
|
.cp-flash {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
padding: 8px 18px;
|
|
border-radius: 999px;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
color: #fff;
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
letter-spacing: 0.02em;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
z-index: 5;
|
|
}
|
|
|
|
.cp-flash.is-visible {
|
|
animation: cp-flash-fade 0.7s ease forwards;
|
|
}
|
|
|
|
@keyframes cp-flash-fade {
|
|
0% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
100% { opacity: 0; transform: translate(-50%, -50%) scale(1.08); }
|
|
}
|
|
|
|
.cp-spinner {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.15s ease;
|
|
z-index: 4;
|
|
}
|
|
|
|
.cp-spinner.is-visible {
|
|
opacity: 1;
|
|
}
|
|
|
|
.cp-spinner-ring {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
border: 3px solid rgba(255, 255, 255, 0.25);
|
|
border-top-color: var(--accent);
|
|
animation: cp-spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes cp-spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.cp-error {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 20px 24px;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
border-radius: var(--radius-lg);
|
|
color: #fff;
|
|
text-align: center;
|
|
max-width: min(360px, 80vw);
|
|
z-index: 5;
|
|
}
|
|
|
|
.cp-error-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
|
|
.cp-retry-btn,
|
|
.cp-open-btn {
|
|
padding: 8px 20px;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--accent);
|
|
background: transparent;
|
|
color: var(--accent);
|
|
cursor: pointer;
|
|
font: inherit;
|
|
text-decoration: none;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.cp-retry-btn:hover,
|
|
.cp-open-btn:hover {
|
|
background: rgba(201, 165, 103, 0.15);
|
|
}
|
|
|
|
.cp-open-btn[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
.cp-replay-btn {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 64px;
|
|
height: 64px;
|
|
border-radius: 50%;
|
|
border: none;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
z-index: 5;
|
|
}
|
|
|
|
.cp-replay-btn .icon-svg {
|
|
width: 30px;
|
|
height: 30px;
|
|
filter: invert(1);
|
|
}
|
|
|
|
.cp-hud {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
pointer-events: none;
|
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.55), transparent 18%, transparent 78%, rgba(0, 0, 0, 0.6));
|
|
opacity: 1;
|
|
transition: opacity 0.3s ease;
|
|
z-index: 3;
|
|
}
|
|
|
|
.custom-player.cp-hud-idle .cp-hud {
|
|
opacity: 0;
|
|
}
|
|
|
|
.cp-hud * {
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.cp-top-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: max(14px, env(safe-area-inset-top)) 16px 0;
|
|
}
|
|
|
|
.cp-close-btn {
|
|
width: 40px;
|
|
height: 40px;
|
|
flex-shrink: 0;
|
|
border-radius: 50%;
|
|
border: none;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
color: #fff;
|
|
font-size: 18px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cp-title {
|
|
flex: 1;
|
|
min-width: 0;
|
|
margin: 0;
|
|
font-family: var(--font-display);
|
|
font-size: 16px;
|
|
color: #fff;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.cp-title-text {
|
|
display: inline-block;
|
|
padding-right: 24px;
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.cp-title.has-marquee .cp-title-text {
|
|
animation: video-title-marquee var(--marquee-duration, 10s) linear infinite;
|
|
}
|
|
|
|
.cp-fav-btn {
|
|
position: static;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.cp-bottom-bar {
|
|
padding: 0 16px max(14px, env(safe-area-inset-bottom));
|
|
}
|
|
|
|
.custom-player.is-live .cp-timeline,
|
|
.custom-player.is-live .cp-skip-back-btn,
|
|
.custom-player.is-live .cp-skip-fwd-btn {
|
|
display: none;
|
|
}
|
|
|
|
.cp-timeline {
|
|
padding: 10px 0;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cp-timeline-track {
|
|
position: relative;
|
|
height: 4px;
|
|
border-radius: 2px;
|
|
background: rgba(255, 255, 255, 0.25);
|
|
}
|
|
|
|
.cp-timeline-buffered {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
height: 100%;
|
|
width: 0%;
|
|
border-radius: 2px;
|
|
background: rgba(255, 255, 255, 0.35);
|
|
}
|
|
|
|
.cp-timeline-fill {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
height: 100%;
|
|
width: 0%;
|
|
border-radius: 2px;
|
|
background: var(--accent);
|
|
}
|
|
|
|
.cp-timeline-handle {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0%;
|
|
width: 13px;
|
|
height: 13px;
|
|
border-radius: 50%;
|
|
background: var(--accent);
|
|
transform: translate(-50%, -50%) scale(0.7);
|
|
transition: transform 0.15s ease;
|
|
}
|
|
|
|
.cp-timeline:hover .cp-timeline-handle,
|
|
.cp-timeline.is-scrubbing .cp-timeline-handle {
|
|
transform: translate(-50%, -50%) scale(1);
|
|
}
|
|
|
|
.cp-time-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.cp-time {
|
|
color: #fff;
|
|
font-size: 12px;
|
|
font-variant-numeric: tabular-nums;
|
|
flex-shrink: 0;
|
|
min-width: 34px;
|
|
}
|
|
|
|
.cp-time-duration {
|
|
text-align: right;
|
|
}
|
|
|
|
.cp-transport {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex: 1;
|
|
justify-content: center;
|
|
}
|
|
|
|
.cp-transport button {
|
|
position: relative;
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 50%;
|
|
border: none;
|
|
background: transparent;
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cp-play-btn {
|
|
width: 48px !important;
|
|
height: 48px !important;
|
|
background: rgba(255, 255, 255, 0.12) !important;
|
|
}
|
|
|
|
.cp-play-btn .icon-svg {
|
|
width: 22px;
|
|
height: 22px;
|
|
}
|
|
|
|
.cp-skip-back-btn .icon-svg,
|
|
.cp-skip-fwd-btn .icon-svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
}
|
|
|
|
.cp-skip-amount {
|
|
position: absolute;
|
|
bottom: 2px;
|
|
font-size: 9px;
|
|
font-weight: 600;
|
|
background: rgba(0, 0, 0, 0.6);
|
|
border-radius: 6px;
|
|
padding: 0 3px;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.cp-secondary-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.cp-mute-btn,
|
|
.cp-pip-btn {
|
|
width: 34px;
|
|
height: 34px;
|
|
border-radius: 50%;
|
|
border: none;
|
|
background: transparent;
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cp-mute-btn .icon-svg,
|
|
.cp-pip-btn .icon-svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
}
|
|
|
|
.cp-volume-range {
|
|
width: 64px;
|
|
accent-color: var(--accent);
|
|
}
|
|
|
|
.cp-format-btn {
|
|
height: 26px;
|
|
padding: 0 8px;
|
|
border-radius: 6px;
|
|
border: 1px solid rgba(255, 255, 255, 0.35);
|
|
background: rgba(0, 0, 0, 0.4);
|
|
color: #fff;
|
|
font-size: 11px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.cp-format-menu {
|
|
position: absolute;
|
|
right: 16px;
|
|
bottom: 64px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: rgba(20, 17, 13, 0.92);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-md);
|
|
overflow: hidden;
|
|
z-index: 6;
|
|
max-height: 40vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.cp-format-option {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 9px 18px 9px 14px;
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--text-primary);
|
|
font-size: 13px;
|
|
text-align: left;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* A fixed-width tick column so every label starts on the same x, with only
|
|
the playing format's tick actually inked. */
|
|
.cp-format-option::before {
|
|
content: '\2713';
|
|
width: 1em;
|
|
flex: none;
|
|
opacity: 0;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.cp-format-option:hover {
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.cp-format-option.is-active {
|
|
color: var(--accent);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.cp-format-option.is-active::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* `.cp-error`/`.cp-replay-btn`/`.cp-format-menu` above set `display` on the
|
|
bare class so JS can toggle visibility via the `hidden` attribute alone;
|
|
these higher-specificity overrides keep that attribute effective (an
|
|
unconditional `display` on the class would otherwise beat the UA
|
|
`[hidden] { display: none }` rule). */
|
|
.cp-error[hidden],
|
|
.cp-replay-btn[hidden],
|
|
.cp-format-menu[hidden],
|
|
.cp-pip-btn[hidden],
|
|
.favorite-btn[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.cp-title { font-size: 14px; }
|
|
.cp-volume-range { display: none; }
|
|
}
|
|
|
|
.error-toast {
|
|
position: fixed;
|
|
right: 20px;
|
|
bottom: 20px;
|
|
max-width: min(360px, 90vw);
|
|
background: rgba(0, 0, 0, 0.85);
|
|
color: #ffffff;
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
border-radius: 10px;
|
|
padding: 12px 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
|
|
z-index: 3000;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transform: translateY(8px);
|
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
}
|
|
|
|
.error-toast.show {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.error-toast button {
|
|
background: transparent;
|
|
border: none;
|
|
color: inherit;
|
|
cursor: pointer;
|
|
font-size: 16px;
|
|
}
|
|
|
|
body.theme-light .error-toast {
|
|
background: rgba(255, 255, 255, 0.95);
|
|
color: #000000;
|
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
|
}
|
|
|
|
.update-banner {
|
|
position: fixed;
|
|
left: 50%;
|
|
bottom: 20px;
|
|
transform: translateX(-50%) translateY(8px);
|
|
max-width: min(420px, 92vw);
|
|
background: rgba(0, 0, 0, 0.88);
|
|
color: #ffffff;
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
border-radius: 10px;
|
|
padding: 12px 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
|
|
z-index: 3100;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
|
}
|
|
|
|
.update-banner.show {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
transform: translateX(-50%) translateY(0);
|
|
}
|
|
|
|
.update-banner button {
|
|
background: #ffffff;
|
|
color: #000000;
|
|
border: none;
|
|
border-radius: 6px;
|
|
padding: 6px 14px;
|
|
font: inherit;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
body.theme-light .update-banner {
|
|
background: rgba(255, 255, 255, 0.96);
|
|
color: #000000;
|
|
border: 1px solid rgba(0, 0, 0, 0.12);
|
|
}
|
|
|
|
body.theme-light .update-banner button {
|
|
background: #111111;
|
|
color: #ffffff;
|
|
}
|
|
|
|
/* Scrollbar styling */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: var(--bg-primary);
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: var(--border);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: var(--text-secondary);
|
|
}
|
|
|
|
/* Mode toggle FAB (grid <-> reels) */
|
|
.mode-toggle-btn {
|
|
position: fixed;
|
|
right: 24px;
|
|
bottom: 90px;
|
|
width: 52px;
|
|
height: 52px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-secondary);
|
|
backdrop-filter: blur(12px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
box-shadow: 0 10px 24px var(--shadow);
|
|
z-index: 2600;
|
|
transition: transform 0.2s ease, background 0.2s ease, opacity 0.4s ease;
|
|
}
|
|
|
|
.mode-toggle-btn:hover {
|
|
background: var(--bg-tertiary);
|
|
transform: scale(1.06);
|
|
}
|
|
|
|
.mode-toggle-btn:active {
|
|
transform: scale(0.94);
|
|
}
|
|
|
|
body.feed-mode-open .mode-toggle-btn {
|
|
background: rgba(0, 0, 0, 0.55);
|
|
border-color: rgba(255, 255, 255, 0.18);
|
|
}
|
|
|
|
body.feed-mode-open .mode-toggle-btn:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
body.feed-mode-open .mode-toggle-btn .icon-svg {
|
|
filter: invert(100%) saturate(0%) !important;
|
|
}
|
|
|
|
/* Reels-style scrollable feed */
|
|
.feed-view {
|
|
display: none;
|
|
position: fixed;
|
|
inset: 0;
|
|
background: #000;
|
|
z-index: 2500;
|
|
}
|
|
|
|
.feed-view.open {
|
|
display: block;
|
|
}
|
|
|
|
.feed-scroll {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow-y: scroll;
|
|
scroll-snap-type: y mandatory;
|
|
scrollbar-width: none;
|
|
}
|
|
|
|
.feed-scroll::-webkit-scrollbar {
|
|
display: none;
|
|
}
|
|
|
|
.feed-sentinel {
|
|
height: 1px;
|
|
}
|
|
|
|
/* Stands in for the slides above the rendered window so scroll position and
|
|
snap points stay stable while slides are virtualized in and out. */
|
|
.feed-top-spacer {
|
|
width: 100%;
|
|
height: 0;
|
|
flex: none;
|
|
}
|
|
|
|
.feed-slide {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100vh;
|
|
height: 100dvh;
|
|
scroll-snap-align: start;
|
|
scroll-snap-stop: always;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow: hidden;
|
|
background: #000;
|
|
}
|
|
|
|
.feed-poster,
|
|
.feed-video {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.feed-poster {
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.feed-slide.is-loaded .feed-poster {
|
|
opacity: 0;
|
|
}
|
|
|
|
.feed-info {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
padding: 24px 20px 44px;
|
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.75), transparent);
|
|
color: #fff;
|
|
pointer-events: none;
|
|
transition: opacity 0.4s ease;
|
|
}
|
|
|
|
.feed-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
margin: 0 0 4px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.feed-title-text {
|
|
display: inline-block;
|
|
padding-right: 28px;
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.feed-title.is-marquee .feed-title-text {
|
|
animation: video-title-marquee var(--marquee-duration, 10s) linear infinite;
|
|
will-change: transform;
|
|
}
|
|
|
|
.feed-uploader {
|
|
font-size: 13px;
|
|
opacity: 0.8;
|
|
margin: 0;
|
|
}
|
|
|
|
.feed-timeline {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 4px;
|
|
height: 28px;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 16px;
|
|
cursor: pointer;
|
|
touch-action: none;
|
|
z-index: 5;
|
|
}
|
|
|
|
.feed-timeline-track {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 3px;
|
|
background: rgba(255, 255, 255, 0.3);
|
|
border-radius: 999px;
|
|
overflow: visible;
|
|
transition: height 0.15s ease;
|
|
}
|
|
|
|
.feed-timeline:hover .feed-timeline-track,
|
|
.feed-timeline.is-scrubbing .feed-timeline-track {
|
|
height: 5px;
|
|
}
|
|
|
|
.feed-timeline-fill {
|
|
position: absolute;
|
|
left: 0;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 0%;
|
|
background: #fff;
|
|
border-radius: 999px;
|
|
}
|
|
|
|
.feed-timeline-handle {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0%;
|
|
width: 11px;
|
|
height: 11px;
|
|
margin-left: -5.5px;
|
|
background: #fff;
|
|
border-radius: 50%;
|
|
transform: translateY(-50%) scale(0);
|
|
transition: transform 0.15s ease;
|
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
.feed-timeline:hover .feed-timeline-handle,
|
|
.feed-timeline.is-scrubbing .feed-timeline-handle {
|
|
transform: translateY(-50%) scale(1);
|
|
}
|
|
|
|
.feed-mute-btn {
|
|
position: fixed;
|
|
right: 24px;
|
|
bottom: 154px;
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
z-index: 2600;
|
|
transition: background 0.2s ease, opacity 0.4s ease;
|
|
}
|
|
|
|
.feed-mute-btn:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
}
|
|
|
|
.feed-mute-btn .icon-svg {
|
|
filter: invert(100%) saturate(0%) !important;
|
|
}
|
|
|
|
/* Per-slide favorite (heart) button on the feed HUD right rail. */
|
|
.feed-fav-btn {
|
|
position: absolute;
|
|
top: auto;
|
|
left: auto;
|
|
right: 24px;
|
|
bottom: 210px;
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
background: rgba(0, 0, 0, 0.5);
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 22px;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
z-index: 6;
|
|
transition: background 0.2s ease, transform 0.2s ease, opacity 0.4s ease;
|
|
}
|
|
|
|
.feed-fav-btn:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.feed-fav-btn.is-favorite {
|
|
color: #ff3b30;
|
|
border-color: rgba(255, 59, 48, 0.6);
|
|
background: rgba(255, 59, 48, 0.18);
|
|
}
|
|
|
|
/* Reels HUD right rail additions: PiP + quality, stacked above favorite. */
|
|
.feed-pip-btn {
|
|
position: absolute;
|
|
right: 24px;
|
|
bottom: 266px;
|
|
z-index: 6;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
|
transition: background 0.2s ease, opacity 0.4s ease;
|
|
}
|
|
|
|
.feed-pip-btn .icon-svg {
|
|
filter: invert(100%) saturate(0%);
|
|
}
|
|
|
|
.feed-format-btn {
|
|
position: absolute;
|
|
right: 24px;
|
|
bottom: 322px;
|
|
z-index: 6;
|
|
transition: opacity 0.4s ease;
|
|
}
|
|
|
|
.feed-format-menu {
|
|
right: 76px;
|
|
bottom: 322px;
|
|
}
|
|
|
|
.feed-flash {
|
|
z-index: 5;
|
|
}
|
|
|
|
/* Reels/TikTok mode: HUD auto-hides after a short idle. Elements stay
|
|
interactive (pointer-events untouched) so the buttons keep working while
|
|
invisible; any pointer/scroll activity reveals them again (see App.feed). */
|
|
body.feed-hud-idle .feed-info,
|
|
body.feed-hud-idle .feed-timeline,
|
|
body.feed-hud-idle .feed-mute-btn,
|
|
body.feed-hud-idle .feed-fav-btn,
|
|
body.feed-hud-idle .feed-pip-btn,
|
|
body.feed-hud-idle .feed-format-btn,
|
|
body.feed-hud-idle .mode-toggle-btn {
|
|
opacity: 0;
|
|
}
|
|
|
|
/* =====================================================================
|
|
Modern enhancements (see enhance.js)
|
|
===================================================================== */
|
|
|
|
/* --- Card entrance: cards rise/fade in the first time they mount -------- */
|
|
@keyframes card-rise {
|
|
from { opacity: 0; transform: translateY(18px) scale(0.985); }
|
|
to { opacity: 1; transform: none; }
|
|
}
|
|
|
|
.video-card.is-revealing {
|
|
animation: card-rise 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) both;
|
|
}
|
|
|
|
/* --- Cursor-tracking brass spotlight border on hover ------------------- */
|
|
.video-card::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: inherit;
|
|
padding: 1px;
|
|
background: radial-gradient(180px circle at var(--mx, 50%) var(--my, 50%),
|
|
rgba(201, 165, 103, 0.65), rgba(201, 165, 103, 0) 60%);
|
|
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
-webkit-mask-composite: xor;
|
|
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
|
|
mask-composite: exclude;
|
|
opacity: 0;
|
|
transition: opacity 0.25s ease;
|
|
pointer-events: none;
|
|
z-index: 3;
|
|
}
|
|
|
|
.video-card:hover::after {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* --- Thumbnail skeleton shimmer until the image paints ----------------- */
|
|
@keyframes thumb-shimmer {
|
|
from { background-position: 200% 0; }
|
|
to { background-position: -200% 0; }
|
|
}
|
|
|
|
.video-card img:not(.is-loaded) {
|
|
background-image: linear-gradient(100deg,
|
|
transparent 30%, rgba(255, 255, 255, 0.08) 50%, transparent 70%);
|
|
background-size: 200% 100%;
|
|
background-repeat: no-repeat;
|
|
animation: thumb-shimmer 1.3s ease-in-out infinite;
|
|
}
|
|
|
|
body.theme-light .video-card img:not(.is-loaded) {
|
|
background-image: linear-gradient(100deg,
|
|
transparent 30%, rgba(43, 33, 18, 0.06) 50%, transparent 70%);
|
|
}
|
|
|
|
/* --- Hover video preview (sits over the poster, under the buttons) ----- */
|
|
.card-preview {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
object-fit: cover;
|
|
background: #000;
|
|
z-index: 0;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
}
|
|
|
|
.video-card.is-previewing .card-preview {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* --- Favorite heart: pop + expanding brass ring on add ----------------- */
|
|
@keyframes heart-pop {
|
|
0% { transform: scale(1); }
|
|
35% { transform: scale(1.35); }
|
|
100% { transform: scale(1); }
|
|
}
|
|
|
|
@keyframes heart-ring {
|
|
from { opacity: 0.8; transform: scale(0.6); }
|
|
to { opacity: 0; transform: scale(1.7); }
|
|
}
|
|
|
|
.favorite-btn.just-favorited {
|
|
animation: heart-pop 0.45s ease;
|
|
}
|
|
|
|
.favorite-btn.just-favorited::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -4px;
|
|
border-radius: 50%;
|
|
border: 2px solid var(--accent);
|
|
animation: heart-ring 0.5s ease forwards;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* --- Scroll progress bar ---------------------------------------------- */
|
|
.scroll-progress {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
height: 2px;
|
|
width: 0;
|
|
background: linear-gradient(90deg, var(--accent), var(--accent-hover));
|
|
z-index: 200;
|
|
transition: width 0.1s linear;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* --- Back-to-top FAB --------------------------------------------------- */
|
|
.back-to-top {
|
|
position: fixed;
|
|
right: 24px;
|
|
bottom: 154px;
|
|
width: 46px;
|
|
height: 46px;
|
|
border-radius: 50%;
|
|
border: 1px solid var(--border);
|
|
background: var(--bg-secondary);
|
|
color: var(--text-primary);
|
|
font-size: 20px;
|
|
line-height: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
box-shadow: 0 10px 24px var(--shadow);
|
|
backdrop-filter: blur(12px);
|
|
opacity: 0;
|
|
transform: translateY(12px) scale(0.9);
|
|
pointer-events: none;
|
|
transition: opacity 0.3s ease, transform 0.3s ease, background 0.2s ease;
|
|
z-index: 2400;
|
|
}
|
|
|
|
.back-to-top.is-visible {
|
|
opacity: 1;
|
|
transform: none;
|
|
pointer-events: auto;
|
|
}
|
|
|
|
.back-to-top:hover {
|
|
background: var(--bg-tertiary);
|
|
border-color: var(--border-hover);
|
|
}
|
|
|
|
.back-to-top:active {
|
|
transform: scale(0.92);
|
|
}
|
|
|
|
/* --- Command palette (⌘K) --------------------------------------------- */
|
|
.command-palette {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 4000;
|
|
display: none;
|
|
align-items: flex-start;
|
|
justify-content: center;
|
|
padding-top: 14vh;
|
|
background: rgba(10, 8, 4, 0.55);
|
|
backdrop-filter: blur(6px);
|
|
}
|
|
|
|
.command-palette.open {
|
|
display: flex;
|
|
}
|
|
|
|
.cmdk-box {
|
|
width: min(560px, 92vw);
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: 0 30px 70px var(--shadow);
|
|
overflow: hidden;
|
|
animation: card-rise 0.2s ease both;
|
|
}
|
|
|
|
.cmdk-input {
|
|
width: 100%;
|
|
padding: 18px 20px;
|
|
border: none;
|
|
border-bottom: 1px solid var(--border);
|
|
background: transparent;
|
|
color: var(--text-primary);
|
|
font-size: 16px;
|
|
font-family: var(--font-body);
|
|
}
|
|
|
|
.cmdk-input::placeholder { color: var(--text-secondary); }
|
|
.cmdk-input:focus { outline: none; }
|
|
|
|
.cmdk-list {
|
|
max-height: 50vh;
|
|
overflow-y: auto;
|
|
padding: 8px;
|
|
}
|
|
|
|
.cmdk-item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
width: 100%;
|
|
text-align: left;
|
|
padding: 11px 14px;
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--text-primary);
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
font: inherit;
|
|
}
|
|
|
|
.cmdk-item.is-active {
|
|
background: var(--bg-tertiary);
|
|
}
|
|
|
|
.cmdk-label { font-size: 14px; }
|
|
|
|
.cmdk-hint {
|
|
font-size: 11px;
|
|
letter-spacing: 0.04em;
|
|
text-transform: uppercase;
|
|
color: var(--text-secondary);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* --- Reels HUD polish: serif title + brass scrubber + mute pulse ------- */
|
|
.feed-title { font-family: var(--font-display); }
|
|
|
|
.feed-timeline-fill,
|
|
.feed-timeline-handle { background: #c9a567; }
|
|
|
|
@keyframes mute-pulse {
|
|
0% { transform: scale(0.8); opacity: 0.75; }
|
|
100% { transform: scale(1.5); opacity: 0; }
|
|
}
|
|
|
|
.feed-mute-btn.is-muted::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -4px;
|
|
border-radius: 50%;
|
|
border: 2px solid rgba(201, 165, 103, 0.7);
|
|
animation: mute-pulse 2s ease-out infinite;
|
|
pointer-events: none;
|
|
}
|
|
|