diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 46c9742..e426abd 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -1,6 +1,5 @@ -/* Base styles and theme variables */ +/* Base styles - Navy theme */ :root { - /* Navy theme (default) */ --text: #c8d4e3; --text-h: #e8eef5; --text-muted: #8fa3bd; @@ -25,48 +24,6 @@ -moz-osx-font-smoothing: grayscale; } -/* Light theme */ -[data-theme="light"] { - --text: #333; - --text-h: #111; - --text-muted: #666; - --bg: #fafafa; - --bg-secondary: #fff; - --card-bg: #fff; - --border: #e5e5e5; - --accent: #4f46e5; - --accent-hover: #4338ca; - --accent-label: #dc2626; -} - -/* Dark theme */ -[data-theme="dark"] { - --text: #e5e5e5; - --text-h: #ffffff; - --text-muted: #888; - --bg: #0a0a0a; - --bg-secondary: #1a1a1a; - --card-bg: #1a1a1a; - --border: #2a2a2a; - --accent: #646cff; - --accent-hover: #535bf2; - --accent-label: #e63946; -} - -/* Navy-blue pastel theme */ -[data-theme="navy"] { - --text: #c8d4e3; - --text-h: #e8eef5; - --text-muted: #8fa3bd; - --bg: #1a2332; - --bg-secondary: #232f42; - --card-bg: #232f42; - --border: #2d3d54; - --accent: #6b8cce; - --accent-hover: #5a7bc4; - --accent-label: #f0a3a3; -} - * { box-sizing: border-box; } @@ -90,27 +47,3 @@ h3 { button { font-family: inherit; } - -/* Theme toggle button styles */ -.theme-toggle { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 0.75rem; - background: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: 8px; - color: var(--text); - font-size: 0.8125rem; - cursor: pointer; - transition: all 0.2s; -} - -.theme-toggle:hover { - border-color: var(--accent); -} - -.theme-toggle svg { - width: 16px; - height: 16px; -} diff --git a/apps/web/src/lib/theme.ts b/apps/web/src/lib/theme.ts deleted file mode 100644 index e4502b3..0000000 --- a/apps/web/src/lib/theme.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Theme management with localStorage persistence - * - * Themes: system (follows OS), light, dark, navy (navy-blue pastel) - */ - -export type Theme = 'system' | 'light' | 'dark' | 'navy' - -const STORAGE_KEY = 'streamhut-theme' - -export function getStoredTheme(): Theme { - if (typeof window === 'undefined') return 'navy' - const stored = localStorage.getItem(STORAGE_KEY) - if (stored === 'light' || stored === 'dark' || stored === 'navy' || stored === 'system') { - return stored - } - return 'navy' -} - -export function setStoredTheme(theme: Theme): void { - localStorage.setItem(STORAGE_KEY, theme) -} - -export function getEffectiveTheme(theme: Theme): 'light' | 'dark' | 'navy' { - if (theme === 'system') { - if (typeof window === 'undefined') return 'navy' - return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark' - } - return theme -} - -export function applyTheme(theme: Theme): void { - const effective = getEffectiveTheme(theme) - document.documentElement.setAttribute('data-theme', effective) -} - -// Initialize theme on load (call this early to prevent flash) -export function initTheme(): void { - const theme = getStoredTheme() - applyTheme(theme) - - // Listen for system theme changes when in system mode - if (theme === 'system') { - window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', () => { - if (getStoredTheme() === 'system') { - applyTheme('system') - } - }) - } -} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 4b5b2b1..32f1620 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -1,15 +1,11 @@ import { StrictMode, useState, useEffect } from 'react' import { createRoot } from 'react-dom/client' import './index.css' -import { initTheme } from './lib/theme.ts' import { UI4Editorial } from './ui/UI4Editorial.tsx' import { ProfilePage } from './ui/ProfilePage.tsx' import { DocsPage } from './ui/DocsPage.tsx' import { onRouteChange, matchPath } from './router.ts' -// Initialize theme before render to prevent flash -initTheme() - function Router() { const [route, setRoute] = useState(window.location.pathname) diff --git a/apps/web/src/ui/ProfilePage.tsx b/apps/web/src/ui/ProfilePage.tsx index a563c6d..b872b7e 100644 --- a/apps/web/src/ui/ProfilePage.tsx +++ b/apps/web/src/ui/ProfilePage.tsx @@ -11,32 +11,21 @@ const styles = ` .profile-page { min-height: 100vh; - background: #fff; - color: #111; + background: var(--bg); + color: var(--text); font-family: 'Inter', system-ui, sans-serif; } -@media (prefers-color-scheme: dark) { - .profile-page { - background: #0a0a0a; - color: #f5f5f5; - } -} - .profile-header { padding: 1.5rem 2rem; display: flex; justify-content: space-between; align-items: center; - border-bottom: 1px solid #eee; + border-bottom: 1px solid var(--border); max-width: 1600px; margin: 0 auto; } -@media (prefers-color-scheme: dark) { - .profile-header { border-color: #222; } -} - .profile-logo { font-family: 'Instrument Serif', Georgia, serif; font-size: 1.75rem; @@ -91,14 +80,10 @@ const styles = ` height: 180px; border-radius: 50%; object-fit: cover; - background: #e5e5e5; + background: var(--border); flex-shrink: 0; } -@media (prefers-color-scheme: dark) { - .profile-avatar { background: #222; } -} - .profile-avatar-fallback { width: 180px; height: 180px; @@ -133,25 +118,17 @@ const styles = ` .profile-handle { font-size: 1.125rem; - color: #666; + color: var(--text-muted); margin-bottom: 1.5rem; } -@media (prefers-color-scheme: dark) { - .profile-handle { color: #999; } -} - .profile-description { font-size: 1rem; line-height: 1.6; - color: #444; + color: var(--text); max-width: 600px; } -@media (prefers-color-scheme: dark) { - .profile-description { color: #bbb; } -} - .profile-stats { display: flex; gap: 2rem; @@ -175,22 +152,14 @@ const styles = ` .profile-stat-label { font-size: 0.875rem; - color: #666; -} - -@media (prefers-color-scheme: dark) { - .profile-stat-label { color: #999; } + color: var(--text-muted); } .profile-section { max-width: 1600px; margin: 0 auto; padding: 3rem 2rem; - border-top: 1px solid #eee; -} - -@media (prefers-color-scheme: dark) { - .profile-section { border-color: #222; } + border-top: 1px solid var(--border); } .profile-section-title { @@ -223,14 +192,10 @@ const styles = ` border-radius: 8px; overflow: hidden; margin-bottom: 1rem; - background: #f5f5f5; + background: var(--bg-secondary); position: relative; } -@media (prefers-color-scheme: dark) { - .profile-card-media { background: #1a1a1a; } -} - .profile-card-img { width: 100%; height: 100%; @@ -249,18 +214,11 @@ const styles = ` flex-direction: column; align-items: center; justify-content: center; - background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%); - color: #fff; + background: var(--card-bg); + color: var(--text); gap: 0.75rem; } -@media (prefers-color-scheme: light) { - .profile-card-fallback { - background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 100%); - color: #333; - } -} - .profile-card-fallback-icon { font-size: 2rem; opacity: 0.5; @@ -301,17 +259,13 @@ const styles = ` .profile-card-meta { font-size: 0.8125rem; - color: #777; + color: var(--text-muted); } .profile-empty { text-align: center; padding: 4rem 2rem; - color: #666; -} - -@media (prefers-color-scheme: dark) { - .profile-empty { color: #999; } + color: var(--text-muted); } .profile-empty-icon { @@ -327,19 +281,12 @@ const styles = ` } .profile-skeleton { - background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); + background: linear-gradient(90deg, var(--bg-secondary) 25%, var(--border) 50%, var(--bg-secondary) 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: 8px; } -@media (prefers-color-scheme: dark) { - .profile-skeleton { - background: linear-gradient(90deg, #1a1a1a 25%, #2a2a2a 50%, #1a1a1a 75%); - background-size: 200% 100%; - } -} - .profile-skeleton-avatar { width: 180px; height: 180px; diff --git a/apps/web/src/ui/UI4Editorial.tsx b/apps/web/src/ui/UI4Editorial.tsx index 290816b..399704f 100644 --- a/apps/web/src/ui/UI4Editorial.tsx +++ b/apps/web/src/ui/UI4Editorial.tsx @@ -12,7 +12,6 @@ import { useState, useEffect, useRef, useMemo, useCallback } from "react" import { listVideos, batchCheckMetadata, batchGetProfiles, formatDuration, getPlaylistUrl, type Video, type VideoMetadata, type Profile } from "../api" import { sortByRecommendation, recordWatch, getContinueWatching } from "../lib/watchHistory" -import { getStoredTheme, setStoredTheme, applyTheme, type Theme } from "../lib/theme" import { navigate } from "../router" import Hls from "hls.js" @@ -856,6 +855,98 @@ const styles = ` aspect-ratio: 4 / 3; border-radius: 8px; } + +/* Creators section */ +.editorial-creators-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 1.5rem; +} + +@media (max-width: 1200px) { + .editorial-creators-grid { grid-template-columns: repeat(3, 1fr); } +} + +@media (max-width: 800px) { + .editorial-creators-grid { grid-template-columns: repeat(2, 1fr); } +} + +@media (max-width: 500px) { + .editorial-creators-grid { grid-template-columns: 1fr; } +} + +.editorial-creator-card { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + background: var(--card-bg); + border: 1px solid var(--border); + border-radius: 12px; + cursor: pointer; + transition: all 0.2s; +} + +.editorial-creator-card:hover { + border-color: var(--accent); + transform: translateY(-2px); +} + +.editorial-creator-avatar { + width: 64px; + height: 64px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; +} + +.editorial-creator-avatar-fallback { + width: 64px; + height: 64px; + border-radius: 50%; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + display: flex; + align-items: center; + justify-content: center; + font-family: 'Instrument Serif', Georgia, serif; + font-size: 1.5rem; + color: #fff; + flex-shrink: 0; +} + +.editorial-creator-info { + flex: 1; + min-width: 0; +} + +.editorial-creator-name { + font-family: 'Instrument Serif', Georgia, serif; + font-size: 1.125rem; + font-weight: 400; + margin: 0 0 0.25rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.editorial-creator-handle { + font-size: 0.8125rem; + color: var(--text-muted); + margin-bottom: 0.5rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.editorial-creator-stats { + font-size: 0.75rem; + color: var(--text-muted); +} + +.editorial-creator-stats strong { + color: var(--text); + font-weight: 600; +} ` interface CardProps { @@ -1140,29 +1231,6 @@ function Player({ video, profile, relatedVideos, metadataMap, profileMap, onClos ) } -// Theme toggle component -function ThemeToggle({ theme, onChange }: { theme: Theme; onChange: (t: Theme) => void }) { - const themes: { value: Theme; label: string; icon: string }[] = [ - { value: 'system', label: 'System', icon: '๐Ÿ’ป' }, - { value: 'light', label: 'Light', icon: 'โ˜€๏ธ' }, - { value: 'dark', label: 'Dark', icon: '๐ŸŒ™' }, - { value: 'navy', label: 'Navy', icon: '๐ŸŒŠ' }, - ] - const current = themes.find(t => t.value === theme) || themes[0] - const nextIndex = (themes.findIndex(t => t.value === theme) + 1) % themes.length - - return ( - - ) -} - export function UI4Editorial() { const [videos, setVideos] = useState([]) const [metadataMap, setMetadataMap] = useState>(new Map()) @@ -1170,13 +1238,7 @@ export function UI4Editorial() { const [playing, setPlaying] = useState