diff options
| author | Pinapelz <yukais@pinapelz.com> | 2026-04-15 23:45:04 -0700 |
|---|---|---|
| committer | Pinapelz <yukais@pinapelz.com> | 2026-04-15 23:45:04 -0700 |
| commit | 30d2ca8480caea1ce76cc1ec29d454e3a669c638 (patch) | |
| tree | cf4e846151601d568d12f2ec7b1f4c003282325e | |
| parent | 6b168927b8995d428d243052e93713a2ab86cff9 (diff) | |
refactor: move styled components to their own style.ts file
| -rw-r--r-- | src/app/about/page.styles.ts | 61 | ||||
| -rw-r--r-- | src/app/about/page.tsx | 62 | ||||
| -rw-r--r-- | src/app/components/LRCPlayer.styles.ts | 44 | ||||
| -rw-r--r-- | src/app/components/LRCPlayer.tsx | 45 | ||||
| -rw-r--r-- | src/app/create/page.styles.ts | 155 | ||||
| -rw-r--r-- | src/app/create/page.tsx | 232 | ||||
| -rw-r--r-- | src/app/page.styles.ts | 281 | ||||
| -rw-r--r-- | src/app/page.tsx | 370 | ||||
| -rw-r--r-- | src/app/player/page.styles.ts | 315 | ||||
| -rw-r--r-- | src/app/player/page.tsx | 347 | ||||
| -rw-r--r-- | src/app/styles/shared.ts | 61 |
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; + } +`; |
