From cab451f0e5cb566e2b226150a152f91f3a5b4450 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Tue, 25 Aug 2026 23:05:05 +0700 Subject: [PATCH] feat(frontend): revamp media frequency deck with GSAP animations and tactical visualizer --- .../src/app/(dashboard)/media/view.tsx | 406 +++++++++--------- 1 file changed, 204 insertions(+), 202 deletions(-) diff --git a/services/frontend/src/app/(dashboard)/media/view.tsx b/services/frontend/src/app/(dashboard)/media/view.tsx index 0a13a2a0..96c0efeb 100644 --- a/services/frontend/src/app/(dashboard)/media/view.tsx +++ b/services/frontend/src/app/(dashboard)/media/view.tsx @@ -1,20 +1,21 @@ "use client"; import { + Disc, ListMusic, Play, - Radio, Repeat, SkipForward, + Sliders, Square, - Volume2, } from "lucide-react"; import { useEffect, useState } from "react"; import { useAmbient } from "@/components/ambient/ambient-context"; +import { Equalizer } from "@/components/charts"; import { Button, GlassPanel, Input, toast } from "@/components/primitives"; import { ErrorState, - SectionHeader, + PageTransition, SkeletonHero, SkeletonPanel, } from "@/components/shared"; @@ -26,9 +27,9 @@ import { useMediaStop, useMediaWsSync, } from "@/hooks"; +import { useStaggerReveal } from "@/hooks/use-gsap-animation"; import { formatDuration } from "@/lib/format"; import type { MediaState } from "@/lib/types"; -import { staggerDelay } from "@/lib/utils"; import { useWebSocket } from "@/lib/ws/context"; export function MediaView({ initialStatus }: { initialStatus?: MediaState }) { @@ -56,6 +57,12 @@ export function MediaView({ initialStatus }: { initialStatus?: MediaState }) { 0, ); + const mediaRef = useStaggerReveal(".media-tile", { + stagger: 0.06, + y: 16, + dependencies: [playing, queueList.length], + }); + const tone = playing ? "signal" : queueList.length ? "amber" : "signal"; useEffect(() => { ambient.set( @@ -74,7 +81,7 @@ export function MediaView({ initialStatus }: { initialStatus?: MediaState }) { try { await queue.mutateAsync({ url: u, mode: "music" }); setUrl(""); - toast({ title: "Queued", tone: "signal" }); + toast({ title: "Queued track", tone: "signal" }); } catch (e) { toast({ title: "Queue failed", @@ -94,206 +101,201 @@ export function MediaView({ initialStatus }: { initialStatus?: MediaState }) { ); + const mockLevels = playing + ? [0.4, 0.7, 0.9, 0.6, 0.8, 0.5, 0.9, 0.7, 0.4, 0.8, 0.6, 0.9] + : [0.1, 0.1, 0.1, 0.1, 0.1, 0.1]; + return ( -
- -
-
-
-
- {current?.thumbnailUrl ? ( - // biome-ignore lint/performance/noImgElement: external CDN thumbnails, next/image needs remote allowlist - +
+ {/* Top Media Matrix Stage */} +
+ {/* Spatial Grid Header */} +
+
+
+ - ) : ( - - )} -
-
- -
-
- {playing ? ( - <> - - {[0, 1, 2].map((i) => ( - - ))} - - Now playing - - ) : current ? ( - "Paused" - ) : ( - "Nothing queued" - )} -
-

- {current?.title ?? "Nothing queued"} -

-
- {current?.source && ( - {current.source} - )} - {current?.mode && ( - {current.mode} - )} - {formatDuration(current?.durationMs) && ( - - {formatDuration(current?.durationMs)} - - )} -
-
- - - - -
-
-
- -
-
- setUrl(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && onPlay()} - /> -
-
- -
-
-
- - {Math.round((media?.musicVolume ?? 0) * 100)}% - -
-
- - - - - {queueList.length} track{queueList.length === 1 ? "" : "s"} - {queueTotal && ` · ${formatDuration(queueTotal)}`} - - } - /> - {queueList.length === 0 ? ( -
- -
Queue is empty
-
- Paste a URL above to start playback. -
-
- ) : ( -
- {queueList.map((item, i) => { - const isNext = i === 0 && playing; - return ( -
- {i + 1} - {item.thumbnailUrl ? ( - // biome-ignore lint/performance/noImgElement: external CDN thumbnails, next/image needs remote allowlist - - ) : ( - - - - )} -
-
- {item.title} -
-
- {item.source} -
-
- {isNext && ( - - up next - - )} - - {item.mode ?? "music"} - - {formatDuration(item.durationMs) && ( - - {formatDuration(item.durationMs)} - - )} +
+
+

+ MEDIA FREQUENCY DECK +

+
+ {playing + ? "STREAM_ACTIVE · 48KHZ STEREO" + : "DECK_IDLE · STANDBY"}
- ); - })} +
+
+ +
+ + + {playing ? "TRANSMITTING" : "STANDBY"} + +
- )} - -
+ + {/* Player Core View */} +
+ {/* Vinyl & Visualizer Stage */} +
+
+
+ {current?.thumbnailUrl ? ( +
+ ) : ( +
+ +
+ )} + {playing && ( +
+ )} +
+
+ + {/* Current Track Telemetry & Controls */} +
+
+
+ {current ? "CURRENT TRANSMISSION" : "DECK UNLOADED"} +
+

+ {current?.title ?? "No active stream"} +

+
+ {current?.source + ? `Source: ${current.source}` + : "Queue a URL below to broadcast"} +
+
+ + {/* Realtime Equalizer preview */} +
+ +
+ + {/* Interactive Playback Toolbar */} +
+ + + + + +
+
+
+
+ + {/* Ingest & Queue Spatial Deck */} +
+ {/* URL Ingest Box */} +
+ +
+ INJECT AUDIO STREAM + +
+
+ setUrl(e.target.value)} + placeholder="Paste YouTube or Audio URL..." + className="font-mono text-xs" + /> + +
+
+
+ + {/* Queue List Stage */} +
+ +
+
+ QUEUE STACK ({queueList.length}) +
+
+ Total: {formatDuration(queueTotal)} +
+
+ +
+ {queueList.length > 0 ? ( + queueList.map((item, idx) => ( +
+
+ + {idx + 1}. + + + {item.title ?? "Unknown Track"} + +
+ + {formatDuration(item.durationMs ?? 0)} + +
+ )) + ) : ( +
+ NO QUEUED AUDIO IN BUFFER +
+ )} +
+
+
+
+
+ ); }