From ddb283cc28e09c5142fc0e6e0b00eee9b4a4ce73 Mon Sep 17 00:00:00 2001 From: jlightner Date: Sat, 4 Apr 2026 10:59:14 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Wired=20/embed/:videoId=20route=20outsi?= =?UTF-8?q?de=20AppShell=20for=20chrome-free=20rend=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "frontend/src/App.tsx" - "frontend/src/pages/WatchPage.tsx" - "frontend/src/App.css" GSD-Task: S03/T02 --- frontend/src/App.css | 30 ++++++++++++++++++++++++++++++ frontend/src/App.tsx | 6 +++++- frontend/src/pages/WatchPage.tsx | 24 ++++++++++++++++++++++-- frontend/tsconfig.app.tsbuildinfo | 2 +- 4 files changed, 58 insertions(+), 4 deletions(-) diff --git a/frontend/src/App.css b/frontend/src/App.css index 4f2ffb7..27ad9f6 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -6449,6 +6449,13 @@ a.app-footer__about:hover, margin-bottom: 1.25rem; } +.watch-page__header-top { + display: flex; + align-items: baseline; + gap: 1rem; + flex-wrap: wrap; +} + .watch-page__title { font-size: 1.25rem; font-weight: 600; @@ -6456,6 +6463,29 @@ a.app-footer__about:hover, margin: 0 0 0.25rem; } +.watch-page__embed-btn { + font-size: 0.8rem; + padding: 0.3rem 0.75rem; + border: 1px solid var(--border); + border-radius: 6px; + background: var(--surface-alt, var(--surface)); + color: var(--text-secondary); + cursor: pointer; + white-space: nowrap; + transition: background 0.15s, color 0.15s, border-color 0.15s; +} + +.watch-page__embed-btn:hover { + border-color: var(--accent); + color: var(--text-primary); +} + +.watch-page__embed-btn--copied { + background: var(--accent); + color: var(--surface); + border-color: var(--accent); +} + .watch-page__creator { font-size: 0.9rem; color: var(--accent); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2dd8a6d..1fa454d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -28,6 +28,7 @@ const CreatorTiers = React.lazy(() => import("./pages/CreatorTiers")); const PostEditor = React.lazy(() => import("./pages/PostEditor")); const PostsList = React.lazy(() => import("./pages/PostsList")); const ShortPlayer = React.lazy(() => import("./pages/ShortPlayer")); +const EmbedPlayer = React.lazy(() => import("./pages/EmbedPlayer")); import AdminDropdown from "./components/AdminDropdown"; import ImpersonationBanner from "./components/ImpersonationBanner"; import AppFooter from "./components/AppFooter"; @@ -228,7 +229,10 @@ function AppShell() { export default function App() { return ( - + + }>} /> + } /> + ); } diff --git a/frontend/src/pages/WatchPage.tsx b/frontend/src/pages/WatchPage.tsx index f61fce9..567ab79 100644 --- a/frontend/src/pages/WatchPage.tsx +++ b/frontend/src/pages/WatchPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useState, useCallback } from "react"; import { useParams, useSearchParams, Link } from "react-router-dom"; import { useDocumentTitle } from "../hooks/useDocumentTitle"; import { useMediaSync } from "../hooks/useMediaSync"; @@ -10,6 +10,7 @@ import VideoPlayer from "../components/VideoPlayer"; import AudioWaveform from "../components/AudioWaveform"; import PlayerControls from "../components/PlayerControls"; import TranscriptSidebar from "../components/TranscriptSidebar"; +import { copyToClipboard } from "../utils/clipboard"; export default function WatchPage() { const { videoId } = useParams<{ videoId: string }>(); @@ -25,9 +26,20 @@ export default function WatchPage() { const [error, setError] = useState(null); const [transcriptError, setTranscriptError] = useState(false); const [chapters, setChapters] = useState([]); + const [embedCopied, setEmbedCopied] = useState(false); const mediaSync = useMediaSync(); + const handleCopyEmbed = useCallback(async () => { + const height = video?.video_url ? 405 : 120; + const snippet = ``; + const ok = await copyToClipboard(snippet); + if (ok) { + setEmbedCopied(true); + setTimeout(() => setEmbedCopied(false), 2000); + } + }, [videoId, video?.video_url]); + useDocumentTitle(video ? `${video.filename} — Chrysopedia` : "Loading…"); // Fetch video detail @@ -106,7 +118,15 @@ export default function WatchPage() { return (
-

{video.filename}

+
+

{video.filename}

+ +
{video.creator_name && video.creator_slug && (