diff --git a/.gitignore b/.gitignore index 8cd93c6..70aa409 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ */__pycache__/* .tmp frontend/dist/* +.playwright-mcp/* diff --git a/frontend/css/style.css b/frontend/css/style.css index cb1c9e3..1b67e5c 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -1,66 +1,89 @@ * { margin: 0; padding: 0; box-sizing: border-box; } :root { - --bg-primary: #000000; - --bg-secondary: rgba(0, 0, 0, 0.85); - --bg-tertiary: rgba(255, 255, 255, 0.05); - --text-primary: #ffffff; - --text-secondary: rgba(255, 255, 255, 0.8); - --accent: #ffffff; - --accent-hover: #e6e6e6; - --border: rgba(255, 255, 255, 0.1); - --border-hover: rgba(255, 255, 255, 0.22); - --shadow: rgba(0, 0, 0, 0.4); + /* 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: 'Space Grotesk', 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; - --font-logo: 'Lobster', 'Space Grotesk', 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif; - --radius-lg: 16px; - --radius-md: 12px; - --radius-sm: 8px; + --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(255, 255, 255, 0.18); + --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(1200px 600px at 12% -10%, rgba(255, 255, 255, 0.08), transparent 60%), - radial-gradient(900px 500px at 110% 0%, rgba(255, 255, 255, 0.06), transparent 55%), + 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; } 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 { - --bg-primary: #ffffff; - --bg-secondary: rgba(0, 0, 0, 0.08); - --bg-tertiary: rgba(0, 0, 0, 0.07); - --text-primary: #000000; - --text-secondary: rgba(0, 0, 0, 0.7); - --accent: #000000; - --accent-hover: #1a1a1a; - --border: rgba(0, 0, 0, 0.08); - --border-hover: rgba(0, 0, 0, 0.18); - --shadow: rgba(0, 0, 0, 0.1); - --focus-ring: rgba(0, 0, 0, 0.12); + /* 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(1200px 600px at 12% -10%, rgba(0, 0, 0, 0.05), transparent 60%), - radial-gradient(900px 500px at 110% 0%, rgba(0, 0, 0, 0.03), transparent 55%), + 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); } @@ -71,7 +94,7 @@ body.theme-light { justify-content: space-between; align-items: center; padding: 0 clamp(16px, 3vw, 36px); - background: linear-gradient(180deg, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.55)); + background: linear-gradient(180deg, rgba(20, 17, 13, 0.86), rgba(20, 17, 13, 0.62)); position: sticky; top: 0; z-index: 100; @@ -82,17 +105,17 @@ body.theme-light { } body.theme-light .top-bar { - background: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(255, 255, 255, 0.82)); + background: linear-gradient(180deg, rgba(246, 241, 231, 0.94), rgba(246, 241, 231, 0.78)); } .logo { - font-size: 26px; - font-weight: 400; - letter-spacing: 0.3px; - color: var(--accent); + 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; - text-shadow: 0 2px 8px var(--shadow); } .search-container { @@ -181,29 +204,35 @@ body.theme-light .top-bar { 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) */ +/* 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(100%) saturate(0%); + 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: none; + filter: invert(68%) sepia(42%) saturate(560%) hue-rotate(1deg) brightness(94%) contrast(86%); } body.theme-light .icon-svg { - filter: invert(0%) saturate(0%); + filter: invert(12%) sepia(20%) saturate(620%) hue-rotate(2deg) brightness(95%); } body.theme-light .icon-btn:hover .icon-svg { - filter: none; + filter: invert(46%) sepia(36%) saturate(720%) hue-rotate(1deg) brightness(82%) contrast(88%); } /* Hamburger Menu */ @@ -485,6 +514,11 @@ body.theme-light .input-row input:focus { .btn-secondary:hover { background: var(--bg-tertiary); + border-color: var(--border-hover); +} + +.btn-secondary:active { + transform: translateY(1px); } .sources-list { @@ -909,15 +943,16 @@ body.theme-light .setting-item select option { } .video-card h4 { - font-size: 14px; + font-size: 15px; font-weight: 500; - padding: 12px; - line-height: 1.4; + padding: 12px 12px 10px; + line-height: 1.3; color: var(--text-primary); margin: 0; flex: 1; font-family: var(--font-display); - letter-spacing: -0.2px; + font-optical-sizing: auto; + letter-spacing: -0.01em; white-space: nowrap; overflow: hidden; position: relative; @@ -1721,3 +1756,282 @@ body.feed-hud-idle .feed-fav-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; +} + +/* --- Player ambient backdrop: blurred poster behind the video --------- */ +.modal-content::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; +} + +.modal-content > * { + position: relative; + z-index: 1; +} + +/* --- 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; +} + +/* --- View Transition timing (player open/close crossfade) -------------- */ +@media (prefers-reduced-motion: no-preference) { + ::view-transition-old(root), + ::view-transition-new(root) { + animation-duration: 0.32s; + } +} diff --git a/frontend/index.html b/frontend/index.html index 2ec6223..ca81573 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -7,10 +7,11 @@ - +
+