From 8cfa334717481bc2fcfa716b546304924157abff Mon Sep 17 00:00:00 2001
From: Pinapelz <yukais@pinapelz.com>
Date: Sun, 12 Apr 2026 22:14:05 -0700
Subject: make windows re-sizeable general lint/formatting

---
 src/app/page.tsx | 927 ++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 539 insertions(+), 388 deletions(-)

(limited to 'src/app/page.tsx')

diff --git a/src/app/page.tsx b/src/app/page.tsx
index 6dba085..5051541 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,102 +1,334 @@
 "use client";
-import React, { useEffect, useRef, useState } from "react";
-import styled from "styled-components";
+import React, {
+  useCallback,
+  useEffect,
+  useRef,
+  useState,
+  Suspense,
+} from "react";
+import styled, { css } from "styled-components";
 import LRCPlayer from "./components/LRCPlayer";
 import { toast, ToastContainer } from "react-toastify";
 import "react-toastify/dist/ReactToastify.css";
-import { FaPlay, FaPause } from "react-icons/fa";
+import {
+  FaPlay,
+  FaPause,
+  FaFileAlt,
+  FaVideo,
+  FaClosedCaptioning,
+  FaHeadphones,
+  FaSyncAlt,
+} from "react-icons/fa";
 import { CaptionsRenderer } from "react-srv3";
 import { useSearchParams } from "next/navigation";
 
-// Styled components
+// ─── Layout ──────────────────────────────────────────────────────────────────
+
 const Root = styled.div`
   position: absolute;
+  inset: 0;
+  display: flex;
+  flex-direction: column;
+  background-color: #f5f5f5;
+  overflow: hidden;
+`;
+
+const PanesContainer = styled.div`
+  display: flex;
+  flex: 1;
+  height: 100vh;
+  overflow: hidden;
+  user-select: none;
+`;
+
+// ─── Lyrics pane ─────────────────────────────────────────────────────────────
+
+const LyricsPane = styled.div<{ $width: number }>`
+  width: ${({ $width }) => $width}%;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  background-color: #ffffff;
+`;
+
+// ─── Resize handle ───────────────────────────────────────────────────────────
+
+const ResizeHandle = styled.div`
+  width: 5px;
+  flex-shrink: 0;
+  background-color: #ddd;
+  cursor: col-resize;
+  transition: background-color 0.15s ease;
+  position: relative;
+
+  &:hover,
+  &:active {
+    background-color: #aaa;
+  }
+
+  &::after {
+    content: "";
+    position: absolute;
+    inset: 0 -4px;
+  }
+`;
+
+const VideoPane = styled.div<{ $dragOver: boolean }>`
+  flex: 1;
+  position: relative;
+  background-color: ${({ $dragOver }) => ($dragOver ? "#dbeeff" : "#ffffff")};
+  transition: background-color 0.15s ease;
+  overflow: hidden;
+`;
+
+const VideoElement = styled.video`
+  position: absolute;
+  inset: 0;
   width: 100%;
   height: 100%;
-  top: 0;
+`;
+
+const CaptionsOverlay = styled.div`
+  position: absolute;
+  inset: 0;
+  width: 90%;
+  height: 90%;
+  margin: auto;
+  cursor: pointer;
+`;
+
+const ControlBar = styled.div`
+  position: absolute;
+  bottom: 0;
   left: 0;
+  width: 100%;
+  height: 50px;
   display: flex;
-  flex-direction: column;
   align-items: center;
-  background-color: #f5f5f5;
+  background-color: rgba(0, 0, 0, 0.6);
+  z-index: 10;
 `;
 
-const FileInputContainer = styled.div`
-  margin-bottom: 20px;
+const PlayButton = styled.button`
+  flex-shrink: 0;
+  width: 50px;
+  height: 50px;
+  padding: 0;
+  border: none;
+  background-color: transparent;
+  color: #fff;
+  cursor: pointer;
   display: flex;
+  align-items: center;
   justify-content: center;
-  gap: 20px;
-  padding: 10px;
-  border-radius: 5px;
-  background-color: #ffffff;
-  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  font-size: 15px;
+  transition: background-color 0.15s ease;
+
+  &:hover {
+    background-color: rgba(255, 255, 255, 0.12);
+  }
 `;
 
-const FileInput = styled.input`
-  padding: 10px 15px;
-  border-radius: 5px;
-  border: 1px solid #ddd;
-  justify-content: center;
+const ScrubBar = styled.input`
+  flex: 1;
+  height: 4px;
+  margin: 0 12px 0 4px;
   cursor: pointer;
-  display: none;
-  font-family: Arial;
-  &:hover,
+  accent-color: #fff;
+`;
+
+const ControlPanel = styled.div<{ $visible: boolean }>`
+  position: absolute;
+  bottom: 50px;
+  left: 0;
+  right: 0;
+  background: rgba(14, 14, 14, 0.88);
+  backdrop-filter: blur(8px);
+  border-top: 1px solid rgba(255, 255, 255, 0.08);
+  padding: 7px 12px;
+  display: flex;
+  flex-direction: column;
+  gap: 5px;
+  z-index: 9;
+  transform: translateY(${({ $visible }) => ($visible ? "0" : "6px")});
+  opacity: ${({ $visible }) => ($visible ? 1 : 0)};
+  pointer-events: ${({ $visible }) => ($visible ? "auto" : "none")};
+  transition:
+    transform 0.18s ease,
+    opacity 0.18s ease;
+`;
+
+const PanelRow = styled.div`
+  display: flex;
+  align-items: center;
+  gap: 5px;
+  flex-wrap: wrap;
+`;
+
+const PanelDivider = styled.div`
+  width: 1px;
+  height: 20px;
+  background-color: rgba(255, 255, 255, 0.15);
+  flex-shrink: 0;
+  margin: 0 2px;
+`;
+
+const panelItemStyles = css`
+  display: inline-flex;
+  align-items: center;
+  gap: 5px;
+  padding: 4px 10px;
+  border-radius: 4px;
+  border: 1px solid rgba(255, 255, 255, 0.15);
+  background-color: rgba(255, 255, 255, 0.07);
+  color: rgba(255, 255, 255, 0.85);
+  font-size: 12px;
+  font-family: Arial, sans-serif;
+  cursor: pointer;
+  white-space: nowrap;
+  transition: background-color 0.15s ease;
+  line-height: 1.4;
+
+  &:hover {
+    background-color: rgba(255, 255, 255, 0.16);
+  }
+
   &:focus {
-    background-color: #eaeaea;
     outline: none;
+    border-color: rgba(255, 255, 255, 0.35);
   }
 `;
 
-const FileInputLabel = styled.label`
-  padding: 10px 15px;
-  border-radius: 5px;
-  border: 1px solid #ddd;
-  cursor: pointer;
-  &:hover,
+const PanelLabel = styled.label`
+  ${panelItemStyles}
+`;
+
+const PanelButton = styled.button`
+  ${panelItemStyles}
+`;
+
+const HiddenFileInput = styled.input`
+  display: none;
+`;
+
+const PanelFieldLabel = styled.span`
+  color: rgba(255, 255, 255, 0.45);
+  font-size: 11px;
+  font-family: Arial, sans-serif;
+  white-space: nowrap;
+`;
+
+const PanelNumberInput = styled.input`
+  width: 68px;
+  padding: 3px 6px;
+  border-radius: 4px;
+  border: 1px solid rgba(255, 255, 255, 0.15);
+  background-color: rgba(255, 255, 255, 0.08);
+  color: #fff;
+  font-size: 12px;
+  font-family: Arial, sans-serif;
+
   &:focus {
-    background-color: #eaeaea;
     outline: none;
+    border-color: rgba(255, 255, 255, 0.4);
+  }
+
+  /* Remove number input arrows in Firefox */
+  -moz-appearance: textfield;
+
+  /* Remove number input arrows in Chrome/Safari */
+  &::-webkit-inner-spin-button,
+  &::-webkit-outer-spin-button {
+    opacity: 0.4;
   }
 `;
 
-const ControlBarButton = styled.button`
-  padding: 10px 15px;
-  border-radius: 5px;
-  border: 1px solid #ddd;
+const PanelRangeInput = styled.input`
+  width: 90px;
+  accent-color: rgba(255, 255, 255, 0.8);
+  cursor: pointer;
+  vertical-align: middle;
+`;
+
+const PanelCheckboxLabel = styled.label`
+  display: inline-flex;
   align-items: center;
+  gap: 5px;
+  color: rgba(255, 255, 255, 0.8);
+  font-size: 12px;
+  font-family: Arial, sans-serif;
   cursor: pointer;
-  &:hover,
-  &:focus {
-    background-color: #eaeaea;
-    outline: none;
-  }
+  user-select: none;
+  white-space: nowrap;
 `;
 
-const StyledLink = styled.a`
-  font-size: 20px;
-  font-family: Arial;
-  text-decoration: none;
-  color: black;
-  &:hover {
-    text-decoration: underline;
-  }
+const ColorSwatch = styled.input`
+  width: 24px;
+  height: 24px;
+  padding: 1px;
+  border: 1px solid rgba(255, 255, 255, 0.2);
+  border-radius: 3px;
+  cursor: pointer;
+  background: none;
+  vertical-align: middle;
 `;
 
-const LRCPlayerWrapper = styled.div`
-  flex: 1;
+// ─── Placeholder (no video loaded) ───────────────────────────────────────────
+
+const PlaceholderWrapper = styled.div`
+  width: 100%;
+  height: 100%;
   display: flex;
   flex-direction: column;
-  height: 100vh;
-  overflow-y: auto;
-  scroll-behavior: smooth;
-  background-color: #ffffff;
+  align-items: center;
+  justify-content: center;
+  padding: 24px;
+  box-sizing: border-box;
+  text-align: center;
+  font-family: Arial, sans-serif;
+`;
+
+const PlaceholderHeading = styled.h1`
+  font-size: 28px;
+  font-weight: bold;
+  margin: 0 0 12px;
+`;
+
+const PlaceholderBody = styled.p`
+  font-size: 18px;
+  line-height: 1.6;
+  margin: 0 0 20px;
 `;
 
-const StyledButton = styled.button`
-  padding: 10px 15px;
+const CodeInputWrapper = styled.div`
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+  width: 100%;
+  max-width: 420px;
+  font-family: Arial, sans-serif;
+  font-size: 14px;
+`;
+
+const CodeInput = styled.input`
+  width: 100%;
+  font-size: 15px;
+  padding: 6px 8px;
+  border: 1px solid #ddd;
+  border-radius: 5px;
+  box-sizing: border-box;
+`;
+
+const LoadButton = styled.button`
+  padding: 7px 14px;
   border-radius: 5px;
   border: 1px solid #ddd;
+  background-color: #fff;
+  font-family: Arial, sans-serif;
+  font-size: 13px;
   cursor: pointer;
+  transition: background-color 0.15s ease;
+
   &:hover,
   &:focus {
     background-color: #eaeaea;
@@ -104,15 +336,28 @@ const StyledButton = styled.button`
   }
 `;
 
+// ─── Misc ────────────────────────────────────────────────────────────────────
+
+const StyledLink = styled.a`
+  font-family: Arial, sans-serif;
+  text-decoration: none;
+  color: #0066cc;
+
+  &:hover {
+    text-decoration: underline;
+  }
+`;
+
+// ─── Component ───────────────────────────────────────────────────────────────
+
 function KaraokePage() {
   const [currentMillisecond, setCurrentMillisecond] = useState(0);
   const [lrcContent, setLrcContent] = useState<string>("");
   const [videoUrl, setVideoUrl] = useState<string>("");
   const [supplementAudioUrl, setSupplementAudioUrl] = useState<string>("");
   const [isPlaying, setIsPlaying] = useState<boolean>(false);
-  const [showVolume, setShowVolume] = useState<boolean>(false);
   const [scrubValue, setScrubValue] = useState<number>(0);
-  const [showFileInputs, setShowFileInputs] = useState<boolean>(true);
+  const [showPanel, setShowPanel] = useState<boolean>(true);
   const videoRef = useRef<HTMLVideoElement>(null);
   const supplementAudioRef = useRef<HTMLAudioElement>(null);
   const [captionsText, setCaptionsText] = useState<string>("");
@@ -126,8 +371,15 @@ function KaraokePage() {
   const [supplementAudioOffset, setSupplementAudioOffset] = useState<number>(0);
   const [base64Input, setBase64Input] = useState<string>("");
 
+  // Resizable panes
+  const [leftWidth, setLeftWidth] = useState<number>(50);
+  const isResizing = useRef(false);
+  const containerRef = useRef<HTMLDivElement>(null);
+
   const searchParams = useSearchParams();
 
+  // ── Persist color preferences ─────────────────────────────────────────────
+
   useEffect(() => {
     const savedLrcColor = localStorage.getItem("lrcColor");
     const savedFontColor = localStorage.getItem("fontColor");
@@ -143,14 +395,46 @@ function KaraokePage() {
     localStorage.setItem("fontColor", fontColor);
   }, [fontColor]);
 
-  // Functions for handling file input changes
+  // ── Resize logic ──────────────────────────────────────────────────────────
+
+  const handleResizeMouseDown = useCallback(
+    (e: React.MouseEvent<HTMLDivElement>) => {
+      e.preventDefault();
+      isResizing.current = true;
+      document.body.style.cursor = "col-resize";
+    },
+    [],
+  );
+
+  useEffect(() => {
+    const onMouseMove = (e: MouseEvent) => {
+      if (!isResizing.current || !containerRef.current) return;
+      const rect = containerRef.current.getBoundingClientRect();
+      const newWidth = ((e.clientX - rect.left) / rect.width) * 100;
+      setLeftWidth(Math.min(Math.max(newWidth, 15), 85));
+    };
+
+    const onMouseUp = () => {
+      if (!isResizing.current) return;
+      isResizing.current = false;
+      document.body.style.cursor = "";
+    };
+
+    window.addEventListener("mousemove", onMouseMove);
+    window.addEventListener("mouseup", onMouseUp);
+    return () => {
+      window.removeEventListener("mousemove", onMouseMove);
+      window.removeEventListener("mouseup", onMouseUp);
+    };
+  }, []);
+
   const handleLrcFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
     const file = event.target.files?.[0];
     if (file) {
       const reader = new FileReader();
       reader.onload = (e) => {
         setLrcContent(e.target?.result as string);
-        if (videoUrl) setShowFileInputs(false);
+        if (videoUrl) setShowPanel(false);
       };
       reader.readAsText(file);
       toast.success("LRC file loaded successfully", { autoClose: 2000 });
@@ -167,9 +451,7 @@ function KaraokePage() {
       setCurrentMillisecond(0);
       setScrubValue(0);
       setIsPlaying(false);
-      toast.success("Video file loaded successfully", {
-        autoClose: 2000,
-      });
+      toast.success("Video file loaded successfully", { autoClose: 2000 });
     }
   };
 
@@ -177,9 +459,7 @@ function KaraokePage() {
     const file = event.target.files?.[0];
     if (file) {
       const reader = new FileReader();
-      reader.onload = (e) => {
-        setCaptionsText(e.target?.result as string);
-      };
+      reader.onload = (e) => setCaptionsText(e.target?.result as string);
       reader.readAsText(file);
       toast.success("SRV file loaded successfully", { autoClose: 2000 });
     }
@@ -197,7 +477,7 @@ function KaraokePage() {
       setScrubValue(0);
       setIsPlaying(false);
       if (video) video.pause();
-      toast.success("Supplemental Audio file loaded successfully", {
+      toast.success("Supplemental audio file loaded successfully", {
         autoClose: 2000,
       });
     }
@@ -210,10 +490,8 @@ function KaraokePage() {
     setOffset(-1550);
     fetch(
       "https://utfs.io/f/e2e18ea7-9841-437b-9ca3-5723355bd41a-rlck46.lrc",
-    ).then(function (response) {
-      response.text().then(function (responseString) {
-        setLrcContent(responseString);
-      });
+    ).then((response) => {
+      response.text().then((text) => setLrcContent(text));
     });
     setVideoUrl(
       "https://utfs.io/f/84f5dfa6-821d-407f-a16d-a685b09c11d9-7xx2h4.webm",
@@ -222,79 +500,67 @@ function KaraokePage() {
     toast.success("Applied offset of -1550ms");
   };
 
-  // Side effects for keyboard shortcuts
+  // ── Keyboard shortcuts ────────────────────────────────────────────────────
+
   useEffect(() => {
     const handleKeyDown = (e: KeyboardEvent) => {
-      if (e.code === "Space") {
-        handlePlayPause();
-      }
+      if (e.code === "Space") handlePlayPause();
       if (e.code === "ArrowRight") {
         if (document.activeElement?.tagName === "INPUT") return;
         const video = videoRef.current;
-        if (!video) return;
-        video.currentTime += 5;
+        if (video) video.currentTime += 5;
       }
       if (e.code === "ArrowLeft") {
         if (document.activeElement?.tagName === "INPUT") return;
         const video = videoRef.current;
-        if (!video) return;
-        video.currentTime -= 5;
+        if (video) video.currentTime -= 5;
       }
     };
     document.addEventListener("keydown", handleKeyDown);
-    return () => {
-      document.removeEventListener("keydown", handleKeyDown);
-    };
+    return () => document.removeEventListener("keydown", handleKeyDown);
   });
 
-  // Side effects for the video itself
   useEffect(() => {
     const video = videoRef.current;
     if (!video) return;
-    const syncLrcWithVideo = () => {
-      setCurrentMillisecond(video.currentTime * 1000 + offset); // updates lrc position
-      setScrubValue((video.currentTime / video.duration) * 100); // update playhead position
-    };
-    video.addEventListener("timeupdate", syncLrcWithVideo);
-
-    return () => {
-      video.removeEventListener("timeupdate", syncLrcWithVideo);
+    const sync = () => {
+      setCurrentMillisecond(video.currentTime * 1000 + offset);
+      setScrubValue((video.currentTime / video.duration) * 100);
     };
+    video.addEventListener("timeupdate", sync);
+    return () => video.removeEventListener("timeupdate", sync);
   });
 
-  // Side effect for volume controls
   useEffect(() => {
     const video = videoRef.current;
     const audio = supplementAudioRef.current;
     if (!video || !audio) return;
-
     if (balance < 0) {
       video.volume = 1 + balance;
+      audio.volume = 1;
     } else {
       video.volume = 1;
       audio.volume = 1 - balance;
     }
   }, [balance]);
 
-  // Side effect for audio
   useEffect(() => {
     const video = videoRef.current;
     const audio = supplementAudioRef.current;
     if (!video || !audio) return;
-    if (supplementAudioOffset === null || supplementAudioOffset == null) return;
     audio.currentTime = video.currentTime + supplementAudioOffset / 1000;
   }, [supplementAudioOffset]);
 
-  // General video control functionality
-
-  const handleVolumeToggle = () => {
-    setShowVolume(!showVolume);
-  };
+  useEffect(() => {
+    if (videoUrl && lrcContent) setStatusText("Ready to play!");
+    else if (videoUrl) setStatusText("No lyrics file selected");
+    else if (lrcContent) setStatusText("No video file selected");
+    else setStatusText("No video or lyrics file selected");
+  }, [videoUrl, lrcContent]);
 
   const handlePlayPause = () => {
     const video = videoRef.current;
     if (!video) return;
-
     if (video.paused) {
       video.play();
       if (supplementAudioUrl) supplementAudioRef.current?.play();
@@ -306,28 +572,12 @@ function KaraokePage() {
     }
   };
 
-  // Status text styling depending on whats loaded. Not all visible
-  useEffect(() => {
-    if (videoUrl && lrcContent) {
-      setStatusText("Ready to play!");
-    } else if (videoUrl) {
-      setStatusText("No lyrics file selected");
-    } else if (lrcContent) {
-      setStatusText("No video file selected");
-    } else {
-      setStatusText("No video or lyrics file selected");
-    }
-  }, [videoUrl, lrcContent]);
-
-  //  Video Control Bar functionality
   const handleScrub = (event: React.ChangeEvent<HTMLInputElement>) => {
     const time =
       (parseFloat(event.target.value) / 100) * videoRef.current!.duration;
     videoRef.current!.currentTime = time;
-    if (supplementAudioOffset === null || supplementAudioOffset == null) {
-      supplementAudioRef.current!.currentTime = time;
-    } else {
-      supplementAudioRef.current!.currentTime =
+    if (supplementAudioRef.current) {
+      supplementAudioRef.current.currentTime =
         time + supplementAudioOffset / 1000;
     }
     setScrubValue(parseFloat(event.target.value));
@@ -342,11 +592,9 @@ function KaraokePage() {
     const video = videoRef.current;
     const audio = supplementAudioRef.current;
     if (!video || !audio) return;
-    if (supplementAudioOffset === null || supplementAudioOffset == null) return;
     audio.currentTime = video.currentTime + supplementAudioOffset / 1000;
   };
 
-  // Handling drag and drop files
   const handleDragOver = (event: React.DragEvent<HTMLDivElement>) => {
     setDragOver(true);
     event.preventDefault();
@@ -366,19 +614,19 @@ function KaraokePage() {
     event.preventDefault();
     setDragOver(false);
     const file = event.dataTransfer.files?.[0];
+    if (!file) return;
+
     if (file.name.endsWith(".lrc")) {
       const reader = new FileReader();
       reader.onload = (e) => {
         setLrcContent(e.target?.result as string);
-        if (videoUrl) setShowFileInputs(false);
+        if (videoUrl) setShowPanel(false);
       };
       reader.readAsText(file);
       toast.success("LRC file loaded successfully", { autoClose: 2000 });
     } else if (file.name.endsWith(".srv3")) {
       const reader = new FileReader();
-      reader.onload = (e) => {
-        setCaptionsText(e.target?.result as string);
-      };
+      reader.onload = (e) => setCaptionsText(e.target?.result as string);
       reader.readAsText(file);
       toast.success("SRV file loaded successfully", { autoClose: 2000 });
     } else if (file.type.startsWith("video") || file.type.startsWith("audio")) {
@@ -398,98 +646,79 @@ function KaraokePage() {
   function processData(data: any) {
     if (data.lrc) {
       fetch(data.lrc)
-        .then((response) => response.text())
+        .then((r) => r.text())
         .then((text) => {
           setLrcContent(text);
-          if (videoUrl) setShowFileInputs(false);
-          toast.success("LRC file loaded successfully", {
-            autoClose: 2000,
-          });
+          if (videoUrl) setShowPanel(false);
+          toast.success("LRC file loaded successfully", { autoClose: 2000 });
         })
-        .catch((error) => {
-          toast.error("Failed to load LRC file", { autoClose: 2000 });
-        });
+        .catch(() =>
+          toast.error("Failed to load LRC file", { autoClose: 2000 }),
+        );
     }
     if (data.srv3) {
       fetch(data.srv3)
-        .then((response) => response.text())
+        .then((r) => r.text())
         .then((text) => {
           setCaptionsText(text);
-          toast.success("SRV file loaded successfully", {
-            autoClose: 2000,
-          });
+          toast.success("SRV file loaded successfully", { autoClose: 2000 });
         })
-        .catch((error) => {
-          toast.error("Failed to load SRV3 file", {
-            autoClose: 2000,
-          });
-        });
+        .catch(() =>
+          toast.error("Failed to load SRV3 file", { autoClose: 2000 }),
+        );
     }
     if (data.file1) {
       setVideoUrl(data.file1);
       setCurrentMillisecond(0);
       setScrubValue(0);
       setIsPlaying(false);
-      toast.success("Video file loaded successfully", {
-        autoClose: 2000,
-      });
+      toast.success("Video file loaded successfully", { autoClose: 2000 });
     }
     if (data.file2) {
       setSupplementAudioUrl(data.file2);
       setCurrentMillisecond(0);
       setScrubValue(0);
       setIsPlaying(false);
-      toast.success("Supplemental Audio file loaded successfully", {
+      toast.success("Supplemental audio file loaded successfully", {
         autoClose: 2000,
       });
     }
-    if (data.offset1) {
-      setOffset(Number(data.offset));
-    }
-    if (data.offset2) {
-      setOffset(Number(data.offset2));
-    }
+    if (data.offset1) setOffset(Number(data.offset));
+    if (data.offset2) setSupplementAudioOffset(Number(data.offset2));
   }
 
-  // Handle base64 input from user
   const handleKaraokeb64Code = () => {
     try {
-      const decodedString = atob(base64Input);
-      console.log(decodedString);
-      const data = JSON.parse(decodedString);
+      const data = JSON.parse(atob(base64Input));
       processData(data);
       toast.success("Data loaded successfully", { autoClose: 2000 });
-    } catch (e) {
+    } catch {
       toast.error("Invalid base64 or JSON data", { autoClose: 2000 });
     }
   };
 
-  // Check for query parameter
   useEffect(() => {
     const dataParam = searchParams.get("code");
     if (dataParam) {
       try {
-        const decodedString = atob(dataParam);
-        const data = JSON.parse(decodedString);
+        const data = JSON.parse(atob(dataParam));
         processData(data);
-        toast.success("Data loaded from query parameter", {
-          autoClose: 2000,
-        });
-      } catch (e) {
-        toast.error("Invalid data in query parameter", {
-          autoClose: 2000,
-        });
+        toast.success("Data loaded from query parameter", { autoClose: 2000 });
+      } catch {
+        toast.error("Invalid data in query parameter", { autoClose: 2000 });
       }
     }
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [searchParams]);
 
+  // ── Render ────────────────────────────────────────────────────────────────
+
   return (
     <Root>
       <ToastContainer />
-      {/*LRC viewer*/}
-      <div style={{ display: "flex", width: "100%", height: "100vh" }}>
-        <LRCPlayerWrapper>
+
+      <PanesContainer ref={containerRef}>
+        <LyricsPane $width={leftWidth}>
           <LRCPlayer
             lrc={lrcContent}
             currentMillisecond={currentMillisecond}
@@ -497,17 +726,14 @@ function KaraokePage() {
             lrcColor={lrcColor}
             fontColor={fontColor}
           />
-        </LRCPlayerWrapper>
-
-        {/* Ternary operation for if videoUrl has been set */}
-        <div
-          style={{
-            flex: 1,
-            position: "relative",
-            backgroundColor: dragOver ? "lightblue" : "white",
-          }}
-          onMouseEnter={() => setShowFileInputs(true)}
-          onMouseLeave={() => setShowFileInputs(false)}
+        </LyricsPane>
+
+        <ResizeHandle onMouseDown={handleResizeMouseDown} />
+
+        <VideoPane
+          $dragOver={dragOver}
+          onMouseEnter={() => setShowPanel(true)}
+          onMouseLeave={() => setShowPanel(false)}
           onDragOver={handleDragOver}
           onDragEnter={handleDragEnter}
           onDragLeave={handleDragLeave}
@@ -515,277 +741,202 @@ function KaraokePage() {
         >
           {videoUrl ? (
             <>
-              <video
+              <VideoElement
                 ref={videoRef}
                 src={videoUrl}
-                style={{
-                  position: "absolute",
-                  width: "100%",
-                  height: "100%",
-                }}
                 onEnded={handleVideoEnded}
               />
               <audio
                 ref={supplementAudioRef}
-                src={supplementAudioUrl}
+                src={supplementAudioUrl || undefined}
                 style={{ display: "none" }}
               />
-              <div
-                style={{
-                  width: "90%",
-                  height: "90%",
-                  margin: "auto",
-                }}
-                onClick={() => handlePlayPause()}
-              >
+              <CaptionsOverlay onClick={handlePlayPause}>
                 <CaptionsRenderer
                   srv3={captionsText}
                   currentTime={currentMillisecond / 1000}
                 />
-              </div>
-              {/*Video control bar*/}
-              <div
-                style={{
-                  position: "absolute",
-                  bottom: 0,
-                  left: 0,
-                  width: "100%",
-                  display: "flex",
-                  alignItems: "center",
-                  backgroundColor: "rgba(0, 0, 0, 0.5)",
-                  height: "50px",
-                }}
-              >
-                <button
+              </CaptionsOverlay>
+
+              <ControlBar>
+                <PlayButton
                   onClick={handlePlayPause}
-                  style={{
-                    width: "50px",
-                    height: "50px",
-                    padding: "10px",
-                    border: "none",
-                    borderRadius: "5px",
-                    backgroundColor: "white",
-                    color: "black",
-                  }}
+                  aria-label={isPlaying ? "Pause" : "Play"}
                 >
                   {isPlaying ? <FaPause /> : <FaPlay />}
-                </button>
-                <input
+                </PlayButton>
+                <ScrubBar
                   type="range"
                   min="0"
                   max="100"
                   value={scrubValue}
-                  style={{
-                    flex: 1,
-                    height: "50px",
-                    width: "100%",
-                  }}
-                  onInput={handleScrub}
+                  onChange={handleScrub}
                 />
-              </div>
+              </ControlBar>
             </>
           ) : (
-            <div
-              style={{
-                width: "100%",
-                height: "100%",
-                display: "flex",
-                justifyContent: "center",
-                alignItems: "center",
-                flexDirection: "column",
-              }}
-            >
-              <h1
-                style={{
-                  fontFamily: "Arial",
-                  fontWeight: "bold",
-                  fontSize: "32px",
-                }}
-              >
-                {statusText}
-              </h1>
-              {/* Show a placeholder while no video selected */}
-              <p
-                style={{
-                  fontSize: "30px",
-                  textAlign: "center",
-                  fontFamily: "Arial",
-                }}
-              >
-                Please select the video and lrc (lyrics) file <br />
-                (Drag and Drop them here, or use the menus below!) <br />
+            <PlaceholderWrapper>
+              <PlaceholderHeading>{statusText}</PlaceholderHeading>
+              <PlaceholderBody>
+                Please select the video and lrc (lyrics) file
+                <br />
+                (Drag and Drop them here, or use the menus below!)
+                <br />
                 <br />
                 Chrome is recommended!
                 <br />
-                <StyledLink href="/about"> About </StyledLink>
+                <StyledLink href="/about">About</StyledLink>
+                {" · "}
                 <StyledLink href="" onClick={handleOnClickDemoButton}>
-                  {" "}
-                  Demo{" "}
+                  Demo
                 </StyledLink>
-              </p>
-              <div>
+              </PlaceholderBody>
+
+              <CodeInputWrapper>
                 <label htmlFor="base64Input">
                   or enter a MoekyunKaraoke code:
                 </label>
-                <input
+                <CodeInput
                   id="base64Input"
                   type="text"
                   value={base64Input}
                   onChange={(e) => setBase64Input(e.target.value)}
-                  style={{ width: "100%", fontSize: "16px" }}
                 />
-                <StyledButton onClick={handleKaraokeb64Code}>
+                <LoadButton onClick={handleKaraokeb64Code}>
                   Load Data
-                </StyledButton>
-              </div>
-            </div>
+                </LoadButton>
+              </CodeInputWrapper>
+            </PlaceholderWrapper>
           )}
 
-          {/* File inputs, shown on hover over video div region*/}
-          {showFileInputs && (
-            <FileInputContainer
-              style={{
-                position: "absolute",
-                bottom: "30px",
-                left: 0,
-              }}
-            >
-              <FileInputLabel htmlFor="lrcUpload" style={{ cursor: "pointer" }}>
-                LRC
-              </FileInputLabel>
-              <FileInput
+          <ControlPanel $visible={showPanel}>
+            <PanelRow>
+              <PanelLabel htmlFor="lrcUpload">
+                <FaFileAlt /> LRC
+              </PanelLabel>
+              <HiddenFileInput
                 id="lrcUpload"
                 type="file"
                 accept=".lrc"
                 onChange={handleLrcFileChange}
               />
-              <FileInputLabel
-                htmlFor="videoUpload"
-                style={{ cursor: "pointer" }}
-              >
-                Media
-              </FileInputLabel>
-              <FileInput
+
+              <PanelLabel htmlFor="videoUpload">
+                <FaVideo /> Media
+              </PanelLabel>
+              <HiddenFileInput
                 id="videoUpload"
                 type="file"
                 accept="video/*,audio/*"
                 onChange={handleVideoFileChange}
               />
-              <FileInputLabel htmlFor="srvUpload" style={{ cursor: "pointer" }}>
-                SRV
-              </FileInputLabel>
-              <FileInput
+
+              <PanelLabel htmlFor="srvUpload">
+                <FaClosedCaptioning /> SRV
+              </PanelLabel>
+              <HiddenFileInput
                 id="srvUpload"
                 type="file"
                 accept=".srv3"
                 onChange={handleSrvFileChange}
               />
-              <FileInputLabel
-                htmlFor="supplementAudioUpload"
-                style={{ cursor: "pointer" }}
-              >
-                Audio #2
-              </FileInputLabel>
-              <FileInput
+
+              <PanelLabel htmlFor="supplementAudioUpload">
+                <FaHeadphones /> Audio #2
+              </PanelLabel>
+              <HiddenFileInput
                 id="supplementAudioUpload"
                 type="file"
                 accept="audio/*"
                 onChange={handleSupplementAudioFileChange}
               />
-              <ControlBarButton onClick={syncSupplementAudioWithVideo}>
-                Sync Audio
-              </ControlBarButton>
-              <div
-                style={{
-                  display: "flex",
-                  flexDirection: "column",
-                  fontFamily: "Arial",
-                }}
-              >
-                <label>Audio/Video Balance</label>
-                <input
-                  type="range"
-                  min="-1"
-                  max="1"
-                  step="0.01"
-                  value={balance}
-                  onChange={(e) => setBalance(Number(e.target.value))}
-                />
-                <label>Offset (±ms) </label>
+
+              <PanelDivider />
+
+              <PanelButton onClick={syncSupplementAudioWithVideo}>
+                <FaSyncAlt /> Sync Audio
+              </PanelButton>
+            </PanelRow>
+
+            <PanelRow>
+              <PanelFieldLabel>Balance</PanelFieldLabel>
+              <PanelRangeInput
+                type="range"
+                min="-1"
+                max="1"
+                step="0.01"
+                value={balance}
+                onChange={(e) => setBalance(Number(e.target.value))}
+              />
+
+              <PanelDivider />
+
+              <PanelFieldLabel>Offset</PanelFieldLabel>
+              <PanelNumberInput
+                type="number"
+                value={offset}
+                onChange={(e) => setOffset(Number(e.target.value))}
+                step="25"
+              />
+
+              <PanelDivider />
+
+              <PanelFieldLabel>Audio 2 Offset</PanelFieldLabel>
+              <PanelNumberInput
+                type="number"
+                value={supplementAudioOffset}
+                onChange={(e) =>
+                  setSupplementAudioOffset(Number(e.target.value))
+                }
+                step="25"
+              />
+
+              <PanelDivider />
+
+              <PanelCheckboxLabel>
                 <input
-                  type="number"
-                  style={{ fontSize: "14px" }}
-                  id="numberInput"
-                  value={offset}
-                  onChange={(e) => setOffset(Number(e.target.value))}
-                  step="25"
+                  type="checkbox"
+                  checked={animate}
+                  onChange={(e) => setAnimate(e.target.checked)}
                 />
-              </div>
-              <div
-                style={{
-                  display: "flex",
-                  flexDirection: "column",
-                  fontFamily: "Arial",
+                Animate
+              </PanelCheckboxLabel>
+
+              <PanelDivider />
+
+              <PanelFieldLabel>Colors</PanelFieldLabel>
+              <ColorSwatch
+                type="color"
+                value={lrcColor}
+                onChange={(e) => setLrcColor(e.target.value)}
+                title="Highlight colour"
+              />
+              <ColorSwatch
+                type="color"
+                value={fontColor}
+                onChange={(e) => setFontColor(e.target.value)}
+                title="Font colour"
+              />
+              <PanelButton
+                onClick={() => {
+                  setLrcColor("#C8BEBE");
+                  setFontColor("#000000");
                 }}
               >
-                <label>Audio 2 Offset (±ms) </label>
-                <input
-                  type="number"
-                  style={{ fontSize: "14px" }}
-                  id="numberInput"
-                  value={supplementAudioOffset}
-                  onChange={(e) =>
-                    setSupplementAudioOffset(Number(e.target.value))
-                  }
-                  step="25"
-                />
-                <label
-                  style={{
-                    fontSize: "14px",
-                    fontFamily: "Arial",
-                    userSelect: "none",
-                  }}
-                >
-                  <input
-                    type="checkbox"
-                    checked={animate}
-                    onChange={(e) => setAnimate(e.target.checked)}
-                    onSelect={(e) => e.preventDefault()}
-                    style={{ marginRight: "8px" }}
-                  />
-                  Line Animation
-                </label>
-                <div
-                  style={{
-                    display: "flex",
-                  }}
-                >
-                  <input
-                    type="color"
-                    value={lrcColor}
-                    onChange={(e) => setLrcColor(e.target.value)}
-                  />
-                  <input
-                    type="color"
-                    value={fontColor}
-                    onChange={(e) => setFontColor(e.target.value)}
-                  />
-                  <button
-                    onClick={() => {
-                      setLrcColor("#C8BEBE");
-                      setFontColor("#000000");
-                    }}
-                  >
-                    Reset
-                  </button>
-                </div>
-              </div>
-            </FileInputContainer>
-          )}
-        </div>
-      </div>
+                Reset
+              </PanelButton>
+            </PanelRow>
+          </ControlPanel>
+        </VideoPane>
+      </PanesContainer>
     </Root>
   );
 }
 
-export default KaraokePage;
+export default function Page() {
+  return (
+    <Suspense>
+      <KaraokePage />
+    </Suspense>
+  );
+}
-- 
cgit v1.2.3

