|
|
@@ -63,6 +63,17 @@ export default function ReviewPage() {
|
|
|
// The comment we're annotating (null = annotating the main video, not a specific comment)
|
|
|
const [annotatingComment, setAnnotatingComment] = useState<Comment | null>(null);
|
|
|
|
|
|
+ // Portrait / landscape detection
|
|
|
+ const [isPortrait, setIsPortrait] = useState(false);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ const mq = window.matchMedia('(orientation: portrait)');
|
|
|
+ setIsPortrait(mq.matches);
|
|
|
+ const handler = (e: MediaQueryListEvent) => setIsPortrait(e.matches);
|
|
|
+ mq.addEventListener('change', handler);
|
|
|
+ return () => mq.removeEventListener('change', handler);
|
|
|
+ }, []);
|
|
|
+
|
|
|
const isDraggingRef = useRef(false);
|
|
|
const panelRef = useRef<HTMLDivElement>(null);
|
|
|
const resizeStartRef = useRef<{ x: number; w: number } | null>(null);
|
|
|
@@ -366,6 +377,22 @@ export default function ReviewPage() {
|
|
|
|
|
|
<div className="w-px h-5 shrink-0" style={{ background: 'rgba(255,255,255,0.08)' }} />
|
|
|
|
|
|
+ {/* Download */}
|
|
|
+ <a
|
|
|
+ href={`${API_BASE}/uploads/${asset.filePath}`}
|
|
|
+ download={asset.filename}
|
|
|
+ className="flex items-center gap-1.5 text-xs px-2.5 py-1 rounded-md transition-all shrink-0"
|
|
|
+ style={{ color: '#60A5FA', background: 'rgba(96,165,250,0.08)' }}
|
|
|
+ title="Download original video"
|
|
|
+ >
|
|
|
+ <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
|
|
|
+ <path strokeLinecap="round" strokeLinejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
|
|
+ </svg>
|
|
|
+ <span className="hidden sm:inline">Download</span>
|
|
|
+ </a>
|
|
|
+
|
|
|
+ <div className="w-px h-5 shrink-0" style={{ background: 'rgba(255,255,255,0.08)' }} />
|
|
|
+
|
|
|
{/* Status selector */}
|
|
|
<div className="relative shrink-0">
|
|
|
<button
|
|
|
@@ -409,10 +436,21 @@ export default function ReviewPage() {
|
|
|
</header>
|
|
|
|
|
|
{/* ── Body ───────────────────────────────────────────── */}
|
|
|
- <div className="flex flex-1 overflow-hidden">
|
|
|
+ {/* Landscape: side-by-side | Portrait: stacked (video top, comments bottom) */}
|
|
|
+ <div
|
|
|
+ className="flex flex-1 overflow-hidden"
|
|
|
+ style={isPortrait
|
|
|
+ ? { flexDirection: 'column', overflowY: 'auto' }
|
|
|
+ : { flexDirection: 'row' }}
|
|
|
+ >
|
|
|
|
|
|
{/* Video area */}
|
|
|
- <div className="flex-1 overflow-y-auto p-4 flex flex-col gap-3 min-w-0">
|
|
|
+ <div
|
|
|
+ className="overflow-y-auto p-3 sm:p-4 flex flex-col gap-3 min-w-0"
|
|
|
+ style={isPortrait
|
|
|
+ ? { flex: 'none', width: '100%', minHeight: '60vh' }
|
|
|
+ : { flex: 1, overflowY: 'auto' }}
|
|
|
+ >
|
|
|
|
|
|
<VideoPlayer
|
|
|
src={videoUrl}
|
|
|
@@ -509,14 +547,28 @@ export default function ReviewPage() {
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- {/* Resize handle */}
|
|
|
- <div className="resize-handle" onMouseDown={handleResizeStart} style={{ width: '4px' }} />
|
|
|
+ {/* Resize handle — only shown in landscape */}
|
|
|
+ {!isPortrait && (
|
|
|
+ <div className="resize-handle" onMouseDown={handleResizeStart} style={{ width: '4px' }} />
|
|
|
+ )}
|
|
|
|
|
|
{/* ── Comment panel ─────────────────────────────────── */}
|
|
|
<div
|
|
|
ref={panelRef}
|
|
|
className="flex flex-col overflow-hidden shrink-0"
|
|
|
- style={{ width: panelWidth, background: 'rgba(10,11,20,0.98)', borderLeft: '1px solid rgba(255,255,255,0.06)' }}
|
|
|
+ style={isPortrait
|
|
|
+ ? {
|
|
|
+ flex: 1,
|
|
|
+ width: '100%',
|
|
|
+ minHeight: '40vh',
|
|
|
+ background: 'rgba(10,11,20,0.98)',
|
|
|
+ borderTop: '1px solid rgba(255,255,255,0.06)',
|
|
|
+ }
|
|
|
+ : {
|
|
|
+ width: panelWidth,
|
|
|
+ background: 'rgba(10,11,20,0.98)',
|
|
|
+ borderLeft: '1px solid rgba(255,255,255,0.06)',
|
|
|
+ }}
|
|
|
>
|
|
|
{/* Panel header */}
|
|
|
<div className="px-4 py-3 flex items-center justify-between shrink-0"
|