aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2026-04-15 23:45:04 -0700
committerPinapelz <yukais@pinapelz.com>2026-04-15 23:45:04 -0700
commit30d2ca8480caea1ce76cc1ec29d454e3a669c638 (patch)
treecf4e846151601d568d12f2ec7b1f4c003282325e
parent6b168927b8995d428d243052e93713a2ab86cff9 (diff)
refactor: move styled components to their own style.ts file
-rw-r--r--src/app/about/page.styles.ts61
-rw-r--r--src/app/about/page.tsx62
-rw-r--r--src/app/components/LRCPlayer.styles.ts44
-rw-r--r--src/app/components/LRCPlayer.tsx45
-rw-r--r--src/app/create/page.styles.ts155
-rw-r--r--src/app/create/page.tsx232
-rw-r--r--src/app/page.styles.ts281
-rw-r--r--src/app/page.tsx370
-rw-r--r--src/app/player/page.styles.ts315
-rw-r--r--src/app/player/page.tsx347
-rw-r--r--src/app/styles/shared.ts61
11 files changed, 997 insertions, 976 deletions
diff --git a/src/app/about/page.styles.ts b/src/app/about/page.styles.ts
new file mode 100644
index 0000000..06d0cd3
--- /dev/null
+++ b/src/app/about/page.styles.ts
@@ -0,0 +1,61 @@
+import styled, { createGlobalStyle } from "styled-components";
+
+export const GlobalStyle = createGlobalStyle`
+ body {
+ font-family: 'Roboto', sans-serif;
+ }
+`;
+
+export const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: left;
+ padding: 20px;
+ background-color: #f9f9f9;
+ color: #333;
+`;
+
+export const Title = styled.h1`
+ font-size: 2.5em;
+ margin-bottom: 0.5em;
+ font-weight: 700;
+`;
+
+export const Subtitle = styled.h2`
+ font-size: 1.5em;
+ margin-bottom: 1em;
+ font-weight: 600;
+`;
+
+export const Paragraph = styled.p`
+ font-size: 1.2em;
+ line-height: 1.6;
+ margin-bottom: 2em;
+ text-align: left;
+ font-weight: 450;
+`;
+
+export const Preformatted = styled.pre`
+ font-size: 1em;
+ background-color: #eaeaea;
+ padding: 10px;
+ border-radius: 5px;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+`;
+
+export const BackLink = styled.a`
+ font-size: 1em;
+ color: #007bff;
+ text-decoration: none;
+ &:hover {
+ text-decoration: underline;
+ }
+`;
+
+export const Video = styled.video`
+ width: 100%;
+ max-width: 600px;
+ margin: 20px 0;
+ border-radius: 10px;
+`;
diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx
index e75c6f8..ba24f53 100644
--- a/src/app/about/page.tsx
+++ b/src/app/about/page.tsx
@@ -1,66 +1,6 @@
"use client";
import React from "react";
-import styled, { createGlobalStyle } from "styled-components";
-
-const GlobalStyle = createGlobalStyle`
- body {
- font-family: 'Roboto', sans-serif;
- }
-`;
-
-const Container = styled.div`
- display: flex;
- flex-direction: column;
- align-items: left;
- padding: 20px;
- background-color: #f9f9f9;
- color: #333;
-`;
-
-const Title = styled.h1`
- font-size: 2.5em;
- margin-bottom: 0.5em;
- font-weight: 700;
-`;
-
-const Subtitle = styled.h2`
- font-size: 1.5em;
- margin-bottom: 1em;
- font-weight: 600;
-`;
-
-const Paragraph = styled.p`
- font-size: 1.2em;
- line-height: 1.6;
- margin-bottom: 2em;
- text-align: left;
- font-weight: 450;
-`;
-
-const Preformatted = styled.pre`
- font-size: 1em;
- background-color: #eaeaea;
- padding: 10px;
- border-radius: 5px;
- white-space: pre-wrap;
- word-wrap: break-word;
-`;
-
-const BackLink = styled.a`
- font-size: 1em;
- color: #007bff;
- text-decoration: none;
- &:hover {
- text-decoration: underline;
- }
-`;
-
-const Video = styled.video`
- width: 100%;
- max-width: 600px;
- margin: 20px 0;
- border-radius: 10px;
-`;
+import { GlobalStyle, Container, Title, Subtitle, Paragraph, Preformatted, BackLink, Video } from "./page.styles";
const lyrics = `[ti:CRUSH]
[al:CRUSH]
diff --git a/src/app/components/LRCPlayer.styles.ts b/src/app/components/LRCPlayer.styles.ts
new file mode 100644
index 0000000..560db2d
--- /dev/null
+++ b/src/app/components/LRCPlayer.styles.ts
@@ -0,0 +1,44 @@
+import styled, { css } from "styled-components";
+
+interface LineProps {
+ $active: boolean;
+ $next: boolean;
+ $animate: boolean;
+ $lrcColor: string;
+ $fontColor: string;
+}
+
+export const Line = styled.div<LineProps>`
+ min-height: 10px;
+ padding: 14px 30px;
+
+ font-size: 40px;
+ font-family: "Roboto", sans-serif;
+ font-weight: 500;
+ text-align: center;
+ color: ${({ $fontColor }) => $fontColor};
+
+ background: ${({ $lrcColor }) => `linear-gradient(
+ to right,
+ rgba(0, 0, 0, 0) 50%,
+ ${$lrcColor} 50%
+ )`};
+ background-size: 200% 100%;
+ background-position: right bottom;
+
+ ${({ $animate }) =>
+ $animate &&
+ css`
+ transition:
+ color 0.3s ease,
+ background-position 0.5s ease;
+ `}
+
+ ${({ $active }) =>
+ $active &&
+ css`
+ color: rgb(50, 50, 50);
+ font-weight: 700;
+ background-position: left bottom;
+ `}
+`;
diff --git a/src/app/components/LRCPlayer.tsx b/src/app/components/LRCPlayer.tsx
index 684a6b7..7c80320 100644
--- a/src/app/components/LRCPlayer.tsx
+++ b/src/app/components/LRCPlayer.tsx
@@ -1,49 +1,6 @@
import React, { CSSProperties, useCallback } from "react";
-import styled, { css } from "styled-components";
import { Lrc, LrcLine } from "react-lrc";
-
-interface LineProps {
- $active: boolean;
- $next: boolean;
- $animate: boolean;
- $lrcColor: string;
- $fontColor: string;
-}
-
-const Line = styled.div<LineProps>`
- min-height: 10px;
- padding: 14px 30px;
-
- font-size: 40px;
- font-family: "Roboto", sans-serif;
- font-weight: 500;
- text-align: center;
- color: ${({ $fontColor }) => $fontColor};
-
- background: ${({ $lrcColor }) => `linear-gradient(
- to right,
- rgba(0, 0, 0, 0) 50%,
- ${$lrcColor} 50%
- )`};
- background-size: 200% 100%;
- background-position: right bottom;
-
- ${({ $animate }) =>
- $animate &&
- css`
- transition:
- color 0.3s ease,
- background-position 0.5s ease;
- `}
-
- ${({ $active }) =>
- $active &&
- css`
- color: rgb(50, 50, 50);
- font-weight: 700;
- background-position: left bottom;
- `}
-`;
+import { Line } from "./LRCPlayer.styles";
const lrcStyle: CSSProperties = {
flex: 1,
diff --git a/src/app/create/page.styles.ts b/src/app/create/page.styles.ts
new file mode 100644
index 0000000..341ba08
--- /dev/null
+++ b/src/app/create/page.styles.ts
@@ -0,0 +1,155 @@
+import styled from "styled-components";
+
+export const Content = styled.div`
+ max-width: 600px;
+ margin: 40px auto;
+ padding: 0 24px 60px;
+`;
+
+export const Heading = styled.h1`
+ font-size: 22px;
+ font-weight: 800;
+ margin: 0 0 4px;
+`;
+
+export const Subheading = styled.p`
+ font-size: 13px;
+ color: #909090;
+ margin: 0 0 32px;
+`;
+
+export const Form = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+`;
+
+export const FieldGroup = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+`;
+
+export const Label = styled.label`
+ font-size: 12px;
+ font-weight: 600;
+ color: #606060;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+`;
+
+export const Input = styled.input`
+ height: 40px;
+ padding: 0 12px;
+ border: 1px solid #d4d4d4;
+ border-radius: 8px;
+ font-size: 14px;
+ color: #1a1a1a;
+ background-color: #fff;
+ transition: border-color 0.15s;
+ &:focus {
+ outline: none;
+ border-color: #1a1a1a;
+ }
+ &::placeholder {
+ color: #b0b0b0;
+ }
+`;
+
+export const Divider = styled.div`
+ height: 1px;
+ background-color: #e5e5e5;
+ margin: 6px 0;
+`;
+
+export const Row = styled.div`
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px;
+`;
+
+export const GenerateButton = styled.button`
+ height: 42px;
+ padding: 0 24px;
+ border-radius: 10px;
+ border: none;
+ background-color: #1a1a1a;
+ color: #fff;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background-color 0.15s;
+ margin-top: 6px;
+ &:hover {
+ background-color: #333;
+ }
+`;
+
+export const OutputSection = styled.div`
+ margin-top: 32px;
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+`;
+
+export const OutputLabel = styled.div`
+ font-size: 12px;
+ font-weight: 600;
+ color: #606060;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: 5px;
+`;
+
+export const CodeBox = styled.div`
+ position: relative;
+ background-color: #f0f0f0;
+ border: 1px solid #d4d4d4;
+ border-radius: 10px;
+ padding: 14px 48px 14px 14px;
+ font-family: "Courier New", monospace;
+ font-size: 13px;
+ color: #1a1a1a;
+ word-break: break-all;
+ line-height: 1.5;
+`;
+
+export const CopyButton = styled.button<{ $copied: boolean }>`
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ width: 30px;
+ height: 30px;
+ border-radius: 6px;
+ border: none;
+ background-color: ${(p) => (p.$copied ? "#22c55e" : "#d4d4d4")};
+ color: ${(p) => (p.$copied ? "#fff" : "#606060")};
+ font-size: 13px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background-color 0.15s, color 0.15s;
+ &:hover {
+ background-color: ${(p) => (p.$copied ? "#16a34a" : "#c0c0c0")};
+ color: #1a1a1a;
+ }
+`;
+
+export const OpenLink = styled.a`
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 13px;
+ font-weight: 500;
+ color: #1a1a1a;
+ text-decoration: none;
+ border: 1px solid #d4d4d4;
+ border-radius: 8px;
+ padding: 8px 14px;
+ background-color: #fff;
+ transition: background-color 0.15s;
+ &:hover {
+ background-color: #f0f0f0;
+ }
+`;
diff --git a/src/app/create/page.tsx b/src/app/create/page.tsx
index 76027a0..5bcca2a 100644
--- a/src/app/create/page.tsx
+++ b/src/app/create/page.tsx
@@ -1,221 +1,25 @@
"use client";
import { useState } from "react";
-import styled from "styled-components";
-import Link from "next/link";
import { MdLibraryMusic } from "react-icons/md";
import { FaCopy, FaCheck, FaExternalLinkAlt } from "react-icons/fa";
-
-const Root = styled.div`
- min-height: 100vh;
- background-color: #f9f9f9;
- color: #1a1a1a;
- font-family: "Roboto", "Segoe UI", Arial, sans-serif;
-`;
-
-const Navbar = styled.nav`
- position: sticky;
- top: 0;
- z-index: 100;
- display: flex;
- align-items: center;
- justify-content: space-between;
- height: 56px;
- padding: 0 20px;
- background-color: #ffffffee;
- backdrop-filter: blur(12px);
- border-bottom: 1px solid #e5e5e5;
-`;
-
-const Logo = styled(Link)`
- font-size: 17px;
- font-weight: 800;
- color: #1a1a1a;
- text-decoration: none;
- display: flex;
- align-items: center;
- gap: 7px;
- user-select: none;
-`;
-
-const LogoIcon = styled.span`
- display: inline-flex;
- align-items: center;
- justify-content: center;
- background-color: #1a1a1a;
- color: #fff;
- border-radius: 6px;
- width: 30px;
- height: 22px;
- font-size: 10px;
-`;
-
-const NavLink = styled(Link)`
- font-size: 13px;
- font-weight: 500;
- color: #606060;
- text-decoration: none;
- padding: 6px 10px;
- border-radius: 8px;
- transition: background-color 0.15s, color 0.15s;
- &:hover {
- background-color: #f0f0f0;
- color: #1a1a1a;
- }
-`;
-
-const Content = styled.div`
- max-width: 600px;
- margin: 40px auto;
- padding: 0 24px 60px;
-`;
-
-const Heading = styled.h1`
- font-size: 22px;
- font-weight: 800;
- margin: 0 0 4px;
-`;
-
-const Subheading = styled.p`
- font-size: 13px;
- color: #909090;
- margin: 0 0 32px;
-`;
-
-const Form = styled.div`
- display: flex;
- flex-direction: column;
- gap: 14px;
-`;
-
-const FieldGroup = styled.div`
- display: flex;
- flex-direction: column;
- gap: 5px;
-`;
-
-const Label = styled.label`
- font-size: 12px;
- font-weight: 600;
- color: #606060;
- text-transform: uppercase;
- letter-spacing: 0.5px;
-`;
-
-const Input = styled.input`
- height: 40px;
- padding: 0 12px;
- border: 1px solid #d4d4d4;
- border-radius: 8px;
- font-size: 14px;
- color: #1a1a1a;
- background-color: #fff;
- transition: border-color 0.15s;
- &:focus {
- outline: none;
- border-color: #1a1a1a;
- }
- &::placeholder {
- color: #b0b0b0;
- }
-`;
-
-const Divider = styled.div`
- height: 1px;
- background-color: #e5e5e5;
- margin: 6px 0;
-`;
-
-const Row = styled.div`
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 12px;
-`;
-
-const GenerateButton = styled.button`
- height: 42px;
- padding: 0 24px;
- border-radius: 10px;
- border: none;
- background-color: #1a1a1a;
- color: #fff;
- font-size: 14px;
- font-weight: 600;
- cursor: pointer;
- transition: background-color 0.15s;
- margin-top: 6px;
- &:hover {
- background-color: #333;
- }
-`;
-
-const OutputSection = styled.div`
- margin-top: 32px;
- display: flex;
- flex-direction: column;
- gap: 14px;
-`;
-
-const OutputLabel = styled.div`
- font-size: 12px;
- font-weight: 600;
- color: #606060;
- text-transform: uppercase;
- letter-spacing: 0.5px;
- margin-bottom: 5px;
-`;
-
-const CodeBox = styled.div`
- position: relative;
- background-color: #f0f0f0;
- border: 1px solid #d4d4d4;
- border-radius: 10px;
- padding: 14px 48px 14px 14px;
- font-family: "Courier New", monospace;
- font-size: 13px;
- color: #1a1a1a;
- word-break: break-all;
- line-height: 1.5;
-`;
-
-const CopyButton = styled.button<{ $copied: boolean }>`
- position: absolute;
- top: 10px;
- right: 10px;
- width: 30px;
- height: 30px;
- border-radius: 6px;
- border: none;
- background-color: ${(p) => (p.$copied ? "#22c55e" : "#d4d4d4")};
- color: ${(p) => (p.$copied ? "#fff" : "#606060")};
- font-size: 13px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- transition: background-color 0.15s, color 0.15s;
- &:hover {
- background-color: ${(p) => (p.$copied ? "#16a34a" : "#c0c0c0")};
- color: #1a1a1a;
- }
-`;
-
-const OpenLink = styled.a`
- display: inline-flex;
- align-items: center;
- gap: 6px;
- font-size: 13px;
- font-weight: 500;
- color: #1a1a1a;
- text-decoration: none;
- border: 1px solid #d4d4d4;
- border-radius: 8px;
- padding: 8px 14px;
- background-color: #fff;
- transition: background-color 0.15s;
- &:hover {
- background-color: #f0f0f0;
- }
-`;
+import { Root, Navbar, Logo, LogoIcon, NavLink } from "../styles/shared";
+import {
+ Content,
+ Heading,
+ Subheading,
+ Form,
+ FieldGroup,
+ Label,
+ Input,
+ Divider,
+ Row,
+ GenerateButton,
+ OutputSection,
+ OutputLabel,
+ CodeBox,
+ CopyButton,
+ OpenLink,
+} from "./page.styles";
interface Payload {
lrc?: string;
diff --git a/src/app/page.styles.ts b/src/app/page.styles.ts
new file mode 100644
index 0000000..dd43880
--- /dev/null
+++ b/src/app/page.styles.ts
@@ -0,0 +1,281 @@
+import styled from "styled-components";
+import Link from "next/link";
+
+export const NavLeft = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 14px;
+`;
+
+export const NavCenter = styled.div`
+ display: flex;
+ align-items: center;
+ flex: 0 1 560px;
+`;
+
+export const SearchBox = styled.div`
+ display: flex;
+ align-items: center;
+ flex: 1;
+ height: 38px;
+ border: 1px solid #d4d4d4;
+ border-radius: 10px;
+ overflow: hidden;
+ background-color: #f0f0f0;
+ transition: border-color 0.2s;
+ &:focus-within {
+ border-color: #1a1a1a;
+ }
+`;
+
+export const SearchInput = styled.input`
+ flex: 1;
+ height: 100%;
+ padding: 0 14px;
+ background: transparent;
+ border: none;
+ outline: none;
+ color: #1a1a1a;
+ font-size: 14px;
+ &::placeholder {
+ color: #909090;
+ }
+`;
+
+export const SearchButton = styled.button`
+ width: 52px;
+ height: 100%;
+ background-color: #e8e8e8;
+ border: none;
+ border-left: 1px solid #d4d4d4;
+ color: #606060;
+ font-size: 14px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ &:hover {
+ background-color: #d4d4d4;
+ color: #1a1a1a;
+ }
+`;
+
+export const NavRight = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 6px;
+`;
+
+export const Avatar = styled.div`
+ font-size: 28px;
+ color: #909090;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ padding: 4px;
+ border-radius: 50%;
+ &:hover {
+ color: #606060;
+ }
+`;
+
+export const ChipsBar = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 14px 24px;
+ overflow-x: auto;
+ background-color: #f9f9f9;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+export const Chip = styled.button<{ $active?: boolean }>`
+ white-space: nowrap;
+ padding: 7px 16px;
+ border-radius: 10px;
+ border: 1px solid ${(p) => (p.$active ? "transparent" : "#d4d4d4")};
+ font-size: 13px;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.15s;
+ background-color: ${(p) => (p.$active ? "#1a1a1a" : "transparent")};
+ color: ${(p) => (p.$active ? "#fff" : "#606060")};
+ &:hover {
+ background-color: ${(p) => (p.$active ? "#333" : "#f0f0f0")};
+ color: ${(p) => (p.$active ? "#fff" : "#1a1a1a")};
+ }
+`;
+
+export const GridContainer = styled.div`
+ padding: 8px 24px 24px;
+`;
+
+export const CardGrid = styled.div`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 20px;
+`;
+
+export const Card = styled(Link)`
+ cursor: pointer;
+ border-radius: 14px;
+ text-decoration: none;
+ color: inherit;
+ display: block;
+ transition: transform 0.15s, box-shadow 0.15s;
+ &:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
+ }
+`;
+
+export const ThumbnailWrapper = styled.div`
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ background-color: #e4e4e4;
+ border-radius: 12px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #c0c0c0;
+ font-size: 36px;
+ overflow: hidden;
+ position: relative;
+`;
+
+export const Thumbnail = styled.img`
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+`;
+
+export const PlayOverlay = styled.div`
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: rgba(0, 0, 0, 0);
+ border-radius: 12px;
+ transition: background 0.2s;
+ ${Card}:hover & {
+ background: rgba(0, 0, 0, 0.25);
+ }
+`;
+
+export const PlayCircle = styled.div`
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ background: rgba(0, 0, 0, 0.7);
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 16px;
+ opacity: 0;
+ transform: scale(0.8);
+ transition: opacity 0.2s, transform 0.2s;
+ ${Card}:hover & {
+ opacity: 1;
+ transform: scale(1);
+ }
+`;
+
+export const BadgeRow = styled.div`
+ position: absolute;
+ bottom: 8px;
+ left: 8px;
+ display: flex;
+ gap: 4px;
+`;
+
+export const Badge = styled.span<{ $color: string }>`
+ font-size: 10px;
+ font-weight: 700;
+ letter-spacing: 0.4px;
+ padding: 2px 6px;
+ border-radius: 4px;
+ background-color: ${(p) => p.$color};
+ color: #fff;
+ text-transform: uppercase;
+`;
+
+export const CardMeta = styled.div`
+ display: flex;
+ gap: 12px;
+ margin-top: 12px;
+ padding: 0 4px 12px;
+`;
+
+export const CardInfo = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ min-width: 0;
+`;
+
+export const CardTitle = styled.span`
+ font-size: 14px;
+ font-weight: 600;
+ color: #1a1a1a;
+ line-height: 1.35;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+`;
+
+export const CardSub = styled.span`
+ font-size: 12px;
+ color: #909090;
+ line-height: 1.3;
+`;
+
+export const EmptyState = styled.div`
+ grid-column: 1 / -1;
+ padding: 48px 0;
+ text-align: center;
+ font-size: 14px;
+ color: #909090;
+`;
+
+export const CtaSection = styled.div`
+ padding: 32px 24px;
+ border-top: 1px solid #e5e5e5;
+ margin-top: 8px;
+`;
+
+export const SectionHeading = styled.h2`
+ font-size: 17px;
+ font-weight: 700;
+ color: #1a1a1a;
+ margin: 0 0 14px;
+`;
+
+export const OpenPlayerLink = styled(Link)`
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 22px;
+ border-radius: 10px;
+ background-color: #1a1a1a;
+ color: #fff;
+ font-size: 14px;
+ font-weight: 600;
+ text-decoration: none;
+ transition: background-color 0.15s;
+ &:hover {
+ background-color: #333;
+ }
+`;
+
+export const PlayerDescription = styled.p`
+ font-size: 13px;
+ color: #909090;
+ margin: 14px 0 0;
+ line-height: 1.6;
+ max-width: 480px;
+`;
diff --git a/src/app/page.tsx b/src/app/page.tsx
index a278f78..fbe91be 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,9 +1,37 @@
"use client";
import { useState, useEffect } from "react";
-import styled from "styled-components";
-import Link from "next/link";
import { FaPlay, FaMusic, FaSearch, FaUserCircle } from "react-icons/fa";
import { MdLibraryMusic } from "react-icons/md";
+import { Root, Navbar, Logo, LogoIcon, NavLink } from "./styles/shared";
+import {
+ NavLeft,
+ NavCenter,
+ SearchBox,
+ SearchInput,
+ SearchButton,
+ NavRight,
+ Avatar,
+ ChipsBar,
+ Chip,
+ GridContainer,
+ CardGrid,
+ Card,
+ ThumbnailWrapper,
+ Thumbnail,
+ PlayOverlay,
+ PlayCircle,
+ BadgeRow,
+ Badge,
+ CardMeta,
+ CardInfo,
+ CardTitle,
+ CardSub,
+ EmptyState,
+ CtaSection,
+ SectionHeading,
+ OpenPlayerLink,
+ PlayerDescription,
+} from "./page.styles";
interface KaraokeEntry {
title: string;
@@ -16,344 +44,6 @@ interface KaraokeEntry {
type KaraokeData = Record<string, KaraokeEntry[]>;
-const Root = styled.div`
- min-height: 100vh;
- background-color: #f9f9f9;
- color: #1a1a1a;
- font-family: "Roboto", "Segoe UI", Arial, sans-serif;
-`;
-
-const Navbar = styled.nav`
- position: sticky;
- top: 0;
- z-index: 100;
- display: flex;
- align-items: center;
- justify-content: space-between;
- height: 56px;
- padding: 0 20px;
- background-color: #ffffffee;
- backdrop-filter: blur(12px);
- border-bottom: 1px solid #e5e5e5;
-`;
-
-const NavLeft = styled.div`
- display: flex;
- align-items: center;
- gap: 14px;
-`;
-
-const Logo = styled(Link)`
- font-size: 17px;
- font-weight: 800;
- letter-spacing: 0.3px;
- color: #1a1a1a;
- text-decoration: none;
- display: flex;
- align-items: center;
- gap: 7px;
- user-select: none;
-`;
-
-const LogoIcon = styled.span`
- display: inline-flex;
- align-items: center;
- justify-content: center;
- background-color: #1a1a1a;
- color: #fff;
- border-radius: 6px;
- width: 30px;
- height: 22px;
- font-size: 10px;
-`;
-
-const NavCenter = styled.div`
- display: flex;
- align-items: center;
- flex: 0 1 560px;
-`;
-
-const SearchBox = styled.div`
- display: flex;
- align-items: center;
- flex: 1;
- height: 38px;
- border: 1px solid #d4d4d4;
- border-radius: 10px;
- overflow: hidden;
- background-color: #f0f0f0;
- transition: border-color 0.2s;
- &:focus-within {
- border-color: #1a1a1a;
- }
-`;
-
-const SearchInput = styled.input`
- flex: 1;
- height: 100%;
- padding: 0 14px;
- background: transparent;
- border: none;
- outline: none;
- color: #1a1a1a;
- font-size: 14px;
- &::placeholder {
- color: #909090;
- }
-`;
-
-const SearchButton = styled.button`
- width: 52px;
- height: 100%;
- background-color: #e8e8e8;
- border: none;
- border-left: 1px solid #d4d4d4;
- color: #606060;
- font-size: 14px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- &:hover {
- background-color: #d4d4d4;
- color: #1a1a1a;
- }
-`;
-
-const NavRight = styled.div`
- display: flex;
- align-items: center;
- gap: 6px;
-`;
-
-const NavLink = styled(Link)`
- font-size: 13px;
- font-weight: 500;
- color: #606060;
- text-decoration: none;
- padding: 6px 10px;
- border-radius: 8px;
- transition: background-color 0.15s, color 0.15s;
- &:hover {
- background-color: #f0f0f0;
- color: #1a1a1a;
- }
-`;
-
-const Avatar = styled.div`
- font-size: 28px;
- color: #909090;
- display: flex;
- align-items: center;
- cursor: pointer;
- padding: 4px;
- border-radius: 50%;
- &:hover {
- color: #606060;
- }
-`;
-
-const ChipsBar = styled.div`
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 14px 24px;
- overflow-x: auto;
- background-color: #f9f9f9;
- &::-webkit-scrollbar {
- display: none;
- }
-`;
-
-const Chip = styled.button<{ $active?: boolean }>`
- white-space: nowrap;
- padding: 7px 16px;
- border-radius: 10px;
- border: 1px solid ${(p) => (p.$active ? "transparent" : "#d4d4d4")};
- font-size: 13px;
- font-weight: 500;
- cursor: pointer;
- transition: all 0.15s;
- background-color: ${(p) => (p.$active ? "#1a1a1a" : "transparent")};
- color: ${(p) => (p.$active ? "#fff" : "#606060")};
- &:hover {
- background-color: ${(p) => (p.$active ? "#333" : "#f0f0f0")};
- color: ${(p) => (p.$active ? "#fff" : "#1a1a1a")};
- }
-`;
-
-const GridContainer = styled.div`
- padding: 8px 24px 24px;
-`;
-
-const CardGrid = styled.div`
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
- gap: 20px;
-`;
-
-const Card = styled(Link)`
- cursor: pointer;
- border-radius: 14px;
- text-decoration: none;
- color: inherit;
- display: block;
- transition: transform 0.15s, box-shadow 0.15s;
- &:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
- }
-`;
-
-const ThumbnailWrapper = styled.div`
- width: 100%;
- aspect-ratio: 16 / 9;
- background-color: #e4e4e4;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #c0c0c0;
- font-size: 36px;
- overflow: hidden;
- position: relative;
-`;
-
-const Thumbnail = styled.img`
- width: 100%;
- height: 100%;
- object-fit: cover;
-`;
-
-const PlayOverlay = styled.div`
- position: absolute;
- inset: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- background: rgba(0, 0, 0, 0);
- border-radius: 12px;
- transition: background 0.2s;
- ${Card}:hover & {
- background: rgba(0, 0, 0, 0.25);
- }
-`;
-
-const PlayCircle = styled.div`
- width: 48px;
- height: 48px;
- border-radius: 50%;
- background: rgba(0, 0, 0, 0.7);
- color: #fff;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 16px;
- opacity: 0;
- transform: scale(0.8);
- transition: opacity 0.2s, transform 0.2s;
- ${Card}:hover & {
- opacity: 1;
- transform: scale(1);
- }
-`;
-
-const BadgeRow = styled.div`
- position: absolute;
- bottom: 8px;
- left: 8px;
- display: flex;
- gap: 4px;
-`;
-
-const Badge = styled.span<{ $color: string }>`
- font-size: 10px;
- font-weight: 700;
- letter-spacing: 0.4px;
- padding: 2px 6px;
- border-radius: 4px;
- background-color: ${(p) => p.$color};
- color: #fff;
- text-transform: uppercase;
-`;
-
-const CardMeta = styled.div`
- display: flex;
- gap: 12px;
- margin-top: 12px;
- padding: 0 4px 12px;
-`;
-
-const CardInfo = styled.div`
- display: flex;
- flex-direction: column;
- gap: 3px;
- min-width: 0;
-`;
-
-const CardTitle = styled.span`
- font-size: 14px;
- font-weight: 600;
- color: #1a1a1a;
- line-height: 1.35;
- display: -webkit-box;
- -webkit-line-clamp: 2;
- -webkit-box-orient: vertical;
- overflow: hidden;
-`;
-
-const CardSub = styled.span`
- font-size: 12px;
- color: #909090;
- line-height: 1.3;
-`;
-
-const EmptyState = styled.div`
- grid-column: 1 / -1;
- padding: 48px 0;
- text-align: center;
- font-size: 14px;
- color: #909090;
-`;
-
-const CtaSection = styled.div`
- padding: 32px 24px;
- border-top: 1px solid #e5e5e5;
- margin-top: 8px;
-`;
-
-const SectionHeading = styled.h2`
- font-size: 17px;
- font-weight: 700;
- color: #1a1a1a;
- margin: 0 0 14px;
-`;
-
-const OpenPlayerLink = styled(Link)`
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 10px 22px;
- border-radius: 10px;
- background-color: #1a1a1a;
- color: #fff;
- font-size: 14px;
- font-weight: 600;
- text-decoration: none;
- transition: background-color 0.15s;
- &:hover {
- background-color: #333;
- }
-`;
-
-const PlayerDescription = styled.p`
- font-size: 13px;
- color: #909090;
- margin: 14px 0 0;
- line-height: 1.6;
- max-width: 480px;
-`;
-
function capitalize(s: string) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
diff --git a/src/app/player/page.styles.ts b/src/app/player/page.styles.ts
new file mode 100644
index 0000000..df07317
--- /dev/null
+++ b/src/app/player/page.styles.ts
@@ -0,0 +1,315 @@
+import styled, { css } from "styled-components";
+
+export const Root = styled.div`
+ position: absolute;
+ inset: 0;
+ display: flex;
+ flex-direction: column;
+ background-color: #f5f5f5;
+ overflow: hidden;
+`;
+
+export const PanesContainer = styled.div`
+ display: flex;
+ flex: 1;
+ height: 100vh;
+ overflow: hidden;
+ user-select: none;
+`;
+
+export const LyricsPane = styled.div<{ $width: number }>`
+ width: ${({ $width }) => $width}%;
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ background-color: #ffffff;
+`;
+
+export 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;
+ }
+`;
+
+export const VideoPane = styled.div<{ $dragOver: boolean }>`
+ flex: 1;
+ position: relative;
+ background-color: ${({ $dragOver }) => ($dragOver ? "#dbeeff" : "#ffffff")};
+ transition: background-color 0.15s ease;
+ overflow: hidden;
+`;
+
+export const VideoElement = styled.video`
+ position: absolute;
+ inset: 0;
+ width: 100%;
+ height: 100%;
+`;
+
+export const CaptionsOverlay = styled.div`
+ position: absolute;
+ inset: 0;
+ width: 90%;
+ height: 90%;
+ margin: auto;
+ cursor: pointer;
+`;
+
+export const ControlBar = styled.div`
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 50px;
+ display: flex;
+ align-items: center;
+ background-color: rgba(0, 0, 0, 0.6);
+ z-index: 10;
+`;
+
+export 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;
+ font-size: 15px;
+ transition: background-color 0.15s ease;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.12);
+ }
+`;
+
+export const ScrubBar = styled.input`
+ flex: 1;
+ height: 4px;
+ margin: 0 12px 0 4px;
+ cursor: pointer;
+ accent-color: #fff;
+`;
+
+export 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;
+`;
+
+export const PanelRow = styled.div`
+ display: flex;
+ align-items: center;
+ gap: 5px;
+ flex-wrap: wrap;
+`;
+
+export const PanelDivider = styled.div`
+ width: 1px;
+ height: 20px;
+ background-color: rgba(255, 255, 255, 0.15);
+ flex-shrink: 0;
+ margin: 0 2px;
+`;
+
+export 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 {
+ outline: none;
+ border-color: rgba(255, 255, 255, 0.35);
+ }
+`;
+
+export const PanelLabel = styled.label`
+ ${panelItemStyles}
+`;
+
+export const PanelButton = styled.button`
+ ${panelItemStyles}
+`;
+
+export const HiddenFileInput = styled.input`
+ display: none;
+`;
+
+export const PanelFieldLabel = styled.span`
+ color: rgba(255, 255, 255, 0.45);
+ font-size: 11px;
+ font-family: Arial, sans-serif;
+ white-space: nowrap;
+`;
+
+export 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 {
+ outline: none;
+ border-color: rgba(255, 255, 255, 0.4);
+ }
+
+ -moz-appearance: textfield;
+
+ &::-webkit-inner-spin-button,
+ &::-webkit-outer-spin-button {
+ opacity: 0.4;
+ }
+`;
+
+export const PanelRangeInput = styled.input`
+ width: 90px;
+ accent-color: rgba(255, 255, 255, 0.8);
+ cursor: pointer;
+ vertical-align: middle;
+`;
+
+export 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;
+ user-select: none;
+ white-space: nowrap;
+`;
+
+export 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;
+`;
+
+export const PlaceholderWrapper = styled.div`
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 24px;
+ box-sizing: border-box;
+ text-align: center;
+ font-family: Arial, sans-serif;
+`;
+
+export const PlaceholderHeading = styled.h1`
+ font-size: 28px;
+ font-weight: bold;
+ margin: 0 0 12px;
+`;
+
+export const PlaceholderBody = styled.p`
+ font-size: 18px;
+ line-height: 1.6;
+ margin: 0 0 20px;
+`;
+
+export const CodeInputWrapper = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ width: 100%;
+ max-width: 420px;
+ font-family: Arial, sans-serif;
+ font-size: 14px;
+`;
+
+export const CodeInput = styled.input`
+ width: 100%;
+ font-size: 15px;
+ padding: 6px 8px;
+ border: 1px solid #ddd;
+ border-radius: 5px;
+ box-sizing: border-box;
+`;
+
+export 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;
+ outline: none;
+ }
+`;
+
+export const StyledLink = styled.a`
+ font-family: Arial, sans-serif;
+ text-decoration: none;
+ color: #0066cc;
+
+ &:hover {
+ text-decoration: underline;
+ }
+`;
diff --git a/src/app/player/page.tsx b/src/app/player/page.tsx
index 1f1d80c..2ac6d50 100644
--- a/src/app/player/page.tsx
+++ b/src/app/player/page.tsx
@@ -6,7 +6,6 @@ import React, {
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";
@@ -21,322 +20,36 @@ import {
} from "react-icons/fa";
import { CaptionsRenderer } from "react-srv3";
import { useSearchParams } from "next/navigation";
-
-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;
-`;
-
-const LyricsPane = styled.div<{ $width: number }>`
- width: ${({ $width }) => $width}%;
- display: flex;
- flex-direction: column;
- overflow: hidden;
- background-color: #ffffff;
-`;
-
-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%;
-`;
-
-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;
- align-items: center;
- background-color: rgba(0, 0, 0, 0.6);
- z-index: 10;
-`;
-
-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;
- font-size: 15px;
- transition: background-color 0.15s ease;
-
- &:hover {
- background-color: rgba(255, 255, 255, 0.12);
- }
-`;
-
-const ScrubBar = styled.input`
- flex: 1;
- height: 4px;
- margin: 0 12px 0 4px;
- cursor: pointer;
- 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 {
- outline: none;
- border-color: rgba(255, 255, 255, 0.35);
- }
-`;
-
-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 {
- 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 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;
- user-select: none;
- white-space: nowrap;
-`;
-
-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 PlaceholderWrapper = styled.div`
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- 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 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;
- outline: none;
- }
-`;
-
-const StyledLink = styled.a`
- font-family: Arial, sans-serif;
- text-decoration: none;
- color: #0066cc;
-
- &:hover {
- text-decoration: underline;
- }
-`;
+import {
+ Root,
+ PanesContainer,
+ LyricsPane,
+ ResizeHandle,
+ VideoPane,
+ VideoElement,
+ CaptionsOverlay,
+ ControlBar,
+ PlayButton,
+ ScrubBar,
+ ControlPanel,
+ PanelRow,
+ PanelDivider,
+ PanelLabel,
+ PanelButton,
+ HiddenFileInput,
+ PanelFieldLabel,
+ PanelNumberInput,
+ PanelRangeInput,
+ PanelCheckboxLabel,
+ ColorSwatch,
+ PlaceholderWrapper,
+ PlaceholderHeading,
+ PlaceholderBody,
+ CodeInputWrapper,
+ CodeInput,
+ LoadButton,
+ StyledLink,
+} from "./page.styles";
function KaraokePage() {
const [currentMillisecond, setCurrentMillisecond] = useState(0);
diff --git a/src/app/styles/shared.ts b/src/app/styles/shared.ts
new file mode 100644
index 0000000..ad815ea
--- /dev/null
+++ b/src/app/styles/shared.ts
@@ -0,0 +1,61 @@
+import styled from "styled-components";
+import Link from "next/link";
+
+export const Root = styled.div`
+ min-height: 100vh;
+ background-color: #f9f9f9;
+ color: #1a1a1a;
+ font-family: "Roboto", "Segoe UI", Arial, sans-serif;
+`;
+
+export const Navbar = styled.nav`
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 56px;
+ padding: 0 20px;
+ background-color: #ffffffee;
+ backdrop-filter: blur(12px);
+ border-bottom: 1px solid #e5e5e5;
+`;
+
+export const Logo = styled(Link)`
+ font-size: 17px;
+ font-weight: 800;
+ letter-spacing: 0.3px;
+ color: #1a1a1a;
+ text-decoration: none;
+ display: flex;
+ align-items: center;
+ gap: 7px;
+ user-select: none;
+`;
+
+export const LogoIcon = styled.span`
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #1a1a1a;
+ color: #fff;
+ border-radius: 6px;
+ width: 30px;
+ height: 22px;
+ font-size: 10px;
+`;
+
+export const NavLink = styled(Link)`
+ font-size: 13px;
+ font-weight: 500;
+ color: #606060;
+ text-decoration: none;
+ padding: 6px 10px;
+ border-radius: 8px;
+ transition: background-color 0.15s, color 0.15s;
+ &:hover {
+ background-color: #f0f0f0;
+ color: #1a1a1a;
+ }
+`;
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage