feat(ui): light/dark SaaS design system + redesigned sidebar & dashboard

Phase A+dashboard of the UI redesign:
- variables.css: light-default tokens + [data-theme=dark] overrides; old var
  names aliased to theme tokens so existing markup adapts to both themes
- style.css: refined components (card/btn/nav/badge/bar/table/forms), tokenized
  the dark-assuming rgba colors
- theme toggle: pre-paint init in base.html <head> + toggleTheme() in common.js,
  persisted to localStorage; toggle button in sidebar
- sidebar.html: labeled nav with sections (분석/파이프라인/제작), active state,
  account; Korean labels
- dashboard.html: tokenized inline colors; verified light & dark with real data

Spec: docs/superpowers/specs/2026-06-12-ui-redesign-design.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hehihoho3@gmail.com 2026-06-12 22:26:01 +09:00
parent 9c276789f3
commit adb51943a3
6 changed files with 355 additions and 716 deletions

View File

@ -1,93 +1,28 @@
/* @import 'variables.css'; - Loaded via HTML */
/* variables.css loaded separately via HTML */
/* Reset & Basics */
.container {
max-width: 1600px;
margin: 0 auto;
padding: 0 2.5rem;
}
/* ===== Reset & Basics ===== */
.container { max-width: 1600px; margin: 0 auto; padding: 0 2.5rem; }
a { text-decoration: none; color: inherit; }
ul { list-style: none; padding: 0; margin: 0; }
a {
text-decoration: none;
color: inherit;
}
/* ===== Utility Classes ===== */
.flex { display: flex; }
.flex-col { flex-direction: column; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-2 { gap: var(--space-2); }
.gap-4 { gap: 1rem; }
.w-full { width: 100%; }
.text-sm { font-size: 0.875rem; }
.text-lg { font-size: 1.125rem; font-weight: 600; }
.text-xl { font-size: 1.5rem; font-weight: 700; }
.text-muted { color: var(--text-muted); }
.font-bold { font-weight: 700; }
.mt-4 { margin-top: 1rem; }
.mb-4 { margin-bottom: 1rem; }
.p-4 { padding: 1rem; }
.p-6 { padding: 1.5rem; }
ul {
list-style: none;
padding: 0;
margin: 0;
}
/* Utility Classes */
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.gap-2 {
gap: var(--space-2);
}
.gap-4 {
gap: 1rem;
}
.w-full {
width: 100%;
}
.text-sm {
font-size: 0.875rem;
}
.text-lg {
font-size: 1.125rem;
font-weight: 600;
}
.text-xl {
font-size: 1.5rem;
font-weight: 700;
}
.text-muted {
color: var(--text-muted);
}
.font-bold {
font-weight: 700;
}
.mt-4 {
margin-top: 1rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.p-4 {
padding: 1rem;
}
.p-6 {
padding: 1.5rem;
}
/* ===== Utility backfill (classes already used in templates, previously undefined) ===== */
/* Spacing — padding */
.p-0 { padding: 0; }
.p-2 { padding: var(--space-2); }
.p-3 { padding: var(--space-3); }
@ -98,7 +33,6 @@ ul {
.py-2 { padding-top: var(--space-2); padding-bottom: var(--space-2); }
.pr-4 { padding-right: var(--space-4); }
/* Spacing — margin */
.mt-1 { margin-top: var(--space-1); }
.mt-2 { margin-top: var(--space-2); }
.mb-1 { margin-bottom: var(--space-1); }
@ -107,11 +41,9 @@ ul {
.mb-6 { margin-bottom: var(--space-6); }
.ml-2 { margin-left: var(--space-2); }
/* Spacing — gap */
.gap-1 { gap: var(--space-1); }
.gap-3 { gap: var(--space-3); }
/* Fl/grid helpers */
.flex-wrap { flex-wrap: wrap; }
.flex-shrink-0 { flex-shrink: 0; }
.justify-center { justify-content: center; }
@ -135,141 +67,96 @@ ul {
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.text-center { text-align: center; }
/* Typography */
.text-xs { font-size: 0.75rem; }
.text-2xl { font-size: 1.875rem; font-weight: 700; }
.font-medium { font-weight: 500; }
.font-semibold { font-weight: 600; }
.font-normal { font-weight: 400; }
/* Colors */
.text-white { color: #fff; }
.text-secondary { color: var(--text-secondary); }
.text-danger { color: var(--danger); }
.text-success { color: var(--success); }
.text-blue-400 { color: #60a5fa; }
.text-cyan-400 { color: #22d3ee; }
.text-pink-400 { color: #f472b6; }
.text-purple-400 { color: #c084fc; }
.text-orange-400 { color: #fb923c; }
.text-yellow-400 { color: #facc15; }
.text-emerald-400 { color: #34d399; }
.text-blue-400 { color: var(--accent); }
.text-cyan-400 { color: var(--sky); }
.text-pink-400 { color: var(--red); }
.text-purple-400 { color: var(--purple); }
.text-orange-400 { color: var(--amber); }
.text-yellow-400 { color: var(--amber); }
.text-emerald-400 { color: var(--green); }
/* Transitions */
.transition-colors { transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease; }
.transition-opacity { transition: opacity 0.2s ease; }
.transition-all { transition: all 0.2s ease; }
/* Hover utilities (escaped selectors) */
.hover\:text-white:hover { color: #fff; }
.hover\:text-white:hover { color: var(--text); }
.hover\:underline:hover { text-decoration: underline; }
.hover\:text-\[var\(--primary\)\]:hover { color: var(--primary); }
.hover\:border-\[var\(--primary\)\]:hover { border-color: var(--primary); }
.border-\[var\(--glass-border\)\] { border: 1px solid var(--glass-border); }
.hover\:bg-blue-400\/10:hover { background: rgba(96, 165, 250, 0.1); }
.hover\:bg-cyan-400\/10:hover { background: rgba(34, 211, 238, 0.1); }
.hover\:bg-pink-400\/10:hover { background: rgba(244, 114, 182, 0.1); }
.hover\:bg-purple-400\/10:hover { background: rgba(192, 132, 252, 0.1); }
.hover\:bg-orange-400\/10:hover { background: rgba(251, 146, 60, 0.1); }
.hover\:bg-emerald-400\/10:hover { background: rgba(52, 211, 153, 0.1); }
.hover\:bg-yellow-400\/10:hover { background: rgba(250, 204, 21, 0.1); }
.border-\[var\(--glass-border\)\] { border: 1px solid var(--border); }
.hover\:bg-blue-400\/10:hover { background: rgba(79, 124, 255, 0.10); }
.hover\:bg-cyan-400\/10:hover { background: rgba(14, 165, 233, 0.10); }
.hover\:bg-pink-400\/10:hover { background: rgba(244, 63, 94, 0.10); }
.hover\:bg-purple-400\/10:hover { background: rgba(139, 92, 246, 0.10); }
.hover\:bg-orange-400\/10:hover { background: rgba(245, 158, 11, 0.10); }
.hover\:bg-emerald-400\/10:hover { background: rgba(16, 185, 129, 0.10); }
.hover\:bg-yellow-400\/10:hover { background: rgba(245, 158, 11, 0.10); }
/* Group hover reveal */
.group:hover .group-hover\:opacity-100 { opacity: 1; }
/* Spin animation */
@keyframes spin { to { transform: rotate(360deg); } }
.animate-spin { animation: spin 1s linear infinite; }
@media (prefers-reduced-motion: reduce) {
.animate-spin { animation: none; }
}
@media (prefers-reduced-motion: reduce) { .animate-spin { animation: none; } }
/* Components */
/* ===== Components ===== */
/* GLASS CARD */
/* CARD — clean surface, subtle border + shadow */
.card {
background: var(--bg-glass);
backdrop-filter: blur(var(--backdrop-blur));
-webkit-backdrop-filter: blur(var(--backdrop-blur));
border: 1px solid var(--glass-border);
border-radius: var(--radius-lg);
padding: 1.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--r);
padding: 1.25rem 1.4rem;
box-shadow: var(--shadow);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
position: relative;
overflow: hidden;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
border-color: var(--glass-highlight);
}
.card:hover { border-color: var(--border-strong); }
/* BUTTON */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.6rem 1.25rem;
border-radius: var(--radius-md);
gap: 0.45rem;
padding: 0.55rem 1rem;
border-radius: var(--r-sm);
font-weight: 600;
transition: all 0.2s;
font-size: 0.875rem;
transition: all 0.18s ease;
cursor: pointer;
border: 1px solid transparent;
line-height: 1.2;
}
.btn-primary {
background: var(--primary-gradient);
color: white;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
color: #fff;
box-shadow: 0 2px 8px rgba(79, 99, 235, 0.25);
}
.btn-primary:hover {
box-shadow: 0 6px 20px rgba(37, 99, 235, 0.5);
transform: translateY(-1px);
}
.btn-ghost {
background: transparent;
color: var(--text-secondary);
}
.btn-ghost:hover {
background: rgba(255, 255, 255, 0.05);
color: var(--text-primary);
border-color: var(--glass-border);
}
.btn-primary:hover { filter: brightness(1.05); box-shadow: 0 4px 14px rgba(79, 99, 235, 0.35); }
.btn-ghost { background: transparent; color: var(--text-2); }
.btn-ghost:hover { background: var(--hover); color: var(--text); }
.btn-secondary {
background: rgba(255, 255, 255, 0.06);
color: var(--text-primary);
border: 1px solid var(--glass-border);
background: var(--surface);
color: var(--text);
border: 1px solid var(--border-strong);
box-shadow: var(--shadow);
}
.btn-secondary:hover { background: var(--surface-2); }
.btn-outline { background: transparent; color: var(--text-2); border: 1px solid var(--border-strong); }
.btn-outline:hover { color: var(--text); border-color: var(--primary); }
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
border-color: var(--glass-highlight);
transform: translateY(-1px);
}
.btn-outline {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--glass-border);
}
.btn-outline:hover {
color: var(--text-primary);
border-color: var(--primary);
}
/* SIDEBAR SPECIFIC */
:root {
--sidebar-width: 260px;
--sidebar-collapsed-width: 80px;
--header-height: 64px;
}
/* ===== Sidebar ===== */
:root { --sidebar-width: 244px; --sidebar-collapsed-width: 76px; --header-height: 64px; }
.sidebar {
width: var(--sidebar-width);
@ -279,468 +166,211 @@ ul {
top: 0;
display: flex;
flex-direction: column;
background-color: var(--bg-glass);
backdrop-filter: blur(20px);
border-right: 1px solid var(--glass-border);
background: var(--side-bg);
border-right: 1px solid var(--border);
z-index: 50;
transition: width 0.3s ease;
overflow-x: hidden;
/* Prevent horizontal scroll during transition */
}
/* User Profile Section */
.nav-section {
font-size: 10.5px;
text-transform: uppercase;
letter-spacing: .08em;
color: var(--text-3);
font-weight: 600;
padding: 14px 1.25rem 6px;
}
.user-profile-container {
padding: 1rem;
margin: 0 1rem 1rem 1rem;
border-radius: 12px;
border: 1px solid var(--glass-border);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0) 100%);
transition: all 0.3s ease;
padding: 0.85rem 1rem;
margin-top: auto;
border-top: 1px solid var(--border);
}
.user-profile-content { display: flex; align-items: center; gap: 0.7rem; overflow: hidden; }
.user-profile-content {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
overflow: hidden;
}
/* Toggle Button */
.sidebar-toggle-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
display: flex; align-items: center; justify-content: center;
width: 28px; height: 28px;
border-radius: 6px;
color: var(--text-muted);
color: var(--text-3);
cursor: pointer;
transition: all 0.2s;
border: 1px solid transparent;
background: transparent;
}
.sidebar-toggle-btn:hover { color: var(--text); background: var(--hover); }
.sidebar-toggle-btn:hover {
color: var(--text-primary);
background: rgba(255, 255, 255, 0.05);
border-color: var(--glass-border);
}
/* Collapsed State */
.sidebar.collapsed {
width: var(--sidebar-collapsed-width);
/* Theme toggle */
.theme-toggle {
display: flex; align-items: center; gap: 0.5rem;
width: 100%;
padding: 0.55rem 0.75rem;
margin-bottom: 0.5rem;
border-radius: var(--r-sm);
background: transparent;
border: 1px solid var(--border);
color: var(--text-2);
font-size: 0.8rem; font-weight: 500;
cursor: pointer; transition: all 0.18s ease;
}
.theme-toggle:hover { background: var(--hover); color: var(--text); }
.theme-toggle .sun { display: none; }
.theme-toggle .moon { display: inline-flex; }
:root[data-theme="dark"] .theme-toggle .sun { display: inline-flex; }
:root[data-theme="dark"] .theme-toggle .moon { display: none; }
/* Collapsed state */
.sidebar.collapsed { width: var(--sidebar-collapsed-width); }
.sidebar.collapsed .brand-text,
.sidebar.collapsed .nav-text,
.sidebar.collapsed .user-info {
display: none;
opacity: 0;
}
/* Header in collapsed menu */
.sidebar.collapsed .p-6 {
/* Target the header div which has p-6 */
padding: 1rem;
flex-direction: column;
gap: 1rem;
justify-content: center;
}
.sidebar.collapsed .sidebar-toggle-btn {
transform: rotate(180deg);
/* Rotate chevron */
}
.sidebar.collapsed .nav-item {
justify-content: center;
padding: 0.85rem 0;
}
.sidebar.collapsed .nav-icon {
margin-right: 0;
}
.sidebar.collapsed .user-profile-container {
padding: 0.75rem;
margin: 0 0.5rem 1rem 0.5rem;
border: none;
background: transparent;
}
.sidebar.collapsed .user-profile-content {
justify-content: center;
margin-bottom: 0;
}
.sidebar.collapsed .sign-out-btn span {
display: none;
}
.sidebar.collapsed .sign-out-btn {
padding: 0.5rem;
}
.sidebar.collapsed .nav-section,
.sidebar.collapsed .user-info,
.sidebar.collapsed .theme-toggle span { display: none; }
.sidebar.collapsed .nav-item { justify-content: center; padding: 0.7rem 0; }
.sidebar.collapsed .nav-icon { margin-right: 0; }
.sidebar.collapsed .sidebar-toggle-btn { transform: rotate(180deg); }
.sidebar.collapsed .theme-toggle { justify-content: center; }
/* NAV ITEM */
.nav-item {
display: flex;
align-items: center;
padding: 0.85rem 1rem;
border-radius: 0 var(--radius-md) var(--radius-md) 0;
transition: all 0.2s ease;
color: var(--text-secondary);
border-left: 3px solid transparent;
padding: 0.6rem 0.75rem;
margin: 1px 0;
border-radius: var(--r-sm);
transition: all 0.15s ease;
color: var(--text-2);
font-weight: 500;
font-size: 0.875rem;
white-space: nowrap;
/* Keep text on one line */
overflow: hidden;
position: relative;
}
.nav-item:hover {
color: var(--text-primary);
background: rgba(255, 255, 255, 0.03);
.nav-item:hover { color: var(--text); background: var(--surface-2); }
.nav-item.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.nav-item.active::before {
content: ""; position: absolute; left: 0; top: 50%; transform: translateY(-50%);
width: 3px; height: 18px; border-radius: 0 3px 3px 0; background: var(--accent);
}
.nav-icon { margin-right: 0.7rem; color: currentColor; min-width: 18px; width: 18px; height: 18px; }
.nav-item.active {
background: linear-gradient(90deg, rgba(59, 130, 246, 0.15), transparent);
border-left: 3px solid var(--primary);
color: var(--text-primary);
box-shadow: 0 4px 12px -4px rgba(59, 130, 246, 0.2);
font-weight: 600;
}
/* Main content offset */
#mainContent { transition: margin-left 0.3s ease; margin-left: var(--sidebar-width); }
body.sidebar-collapsed #mainContent { margin-left: var(--sidebar-collapsed-width); }
.nav-icon {
margin-right: 0.875rem;
color: currentColor;
min-width: 24px;
/* Fix icon size */
}
/* ===== Page header (shared) ===== */
.page-header { display: flex; justify-content: space-between; align-items: flex-end; flex-wrap: wrap; gap: 1rem; margin-bottom: 1.5rem; }
.page-header h1 { font-size: 1.5rem; font-weight: 700; letter-spacing: -0.4px; }
.page-header .sub { color: var(--text-3); font-size: 0.85rem; margin-top: 0.3rem; }
.page-header .actions { display: flex; gap: 0.6rem; align-items: center; }
.nav-item.active .nav-icon {
color: var(--primary);
filter: drop-shadow(0 0 8px var(--primary-glow));
}
/* Main Content Adjustment based on sidebar */
#mainContent {
transition: margin-left 0.3s ease;
margin-left: var(--sidebar-width);
}
body.sidebar-collapsed #mainContent {
margin-left: var(--sidebar-collapsed-width);
}
/* Responsive */
/* ===== Responsive ===== */
@media (max-width: 1024px) {
.sidebar {
width: var(--sidebar-collapsed-width);
}
.sidebar .brand-text,
.sidebar .nav-text,
.sidebar .user-info {
display: none;
}
.sidebar .nav-item {
justify-content: center;
padding: 0.85rem 0;
}
.sidebar .nav-icon {
margin-right: 0;
}
/* Auto-collapse helpers handled via JS class, but default CSS fallback */
#mainContent {
margin-left: var(--sidebar-collapsed-width);
}
.sidebar { width: var(--sidebar-collapsed-width); }
.sidebar .brand-text, .sidebar .nav-text, .sidebar .nav-section,
.sidebar .user-info, .sidebar .theme-toggle span { display: none; }
.sidebar .nav-item { justify-content: center; padding: 0.7rem 0; }
.sidebar .nav-icon { margin-right: 0; }
#mainContent { margin-left: var(--sidebar-collapsed-width); }
}
/* Mobile Responsiveness Improvements */
@media (max-width: 768px) {
.mobile-col { flex-direction: column !important; }
.mobile-w-full { width: 100% !important; }
.mobile-items-start { align-items: flex-start !important; }
.mobile-gap-4 { gap: 1rem !important; }
.mobile-border-0 { border: none !important; }
.container { padding: 0 1rem; }
/* Layout Helpers */
.mobile-col {
flex-direction: column !important;
}
:root { --sidebar-collapsed-width: 0px; }
.sidebar { transform: translateX(-100%); }
.sidebar:not(.collapsed) { transform: translateX(0); width: 100%; background: var(--surface); }
body.sidebar-collapsed #mainContent { margin-left: 0; }
#mainContent { margin-left: 0 !important; }
.mobile-w-full {
width: 100% !important;
}
.mobile-items-start {
align-items: flex-start !important;
}
.mobile-gap-4 {
gap: 1rem !important;
}
.mobile-border-0 {
border: none !important;
}
/* Container adjustments */
.container {
padding: 0 1rem;
}
/* Sidebar Overrides for Mobile */
:root {
--sidebar-collapsed-width: 0px;
/* Hide completely on mobile if collapsed */
}
.sidebar {
transform: translateX(-100%);
}
.sidebar:not(.collapsed) {
transform: translateX(0);
width: 100%;
/* Full width sidebar on mobile */
background: rgba(10, 10, 10, 0.95);
/* Darker opaque bg */
}
body.sidebar-collapsed #mainContent {
margin-left: 0;
}
#mainContent {
margin-left: 0 !important;
/* Always 0 on mobile unless overlay logic added, but for now stack */
}
/* Video Detail Specifics */
.video-header-image {
width: 100% !important;
height: auto !important;
aspect-ratio: 16/9;
margin-bottom: 1rem;
}
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
width: 100%;
}
/* Tabs */
.tabs-container {
overflow-x: auto;
padding-bottom: 0.5rem;
margin-bottom: 1rem;
-webkit-overflow-scrolling: touch;
}
.tab-btn {
white-space: nowrap;
}
/* Mobile Menu Toggle */
.video-header-image { width: 100% !important; height: auto !important; aspect-ratio: 16/9; margin-bottom: 1rem; }
.stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; width: 100%; }
.tabs-container { overflow-x: auto; padding-bottom: 0.5rem; margin-bottom: 1rem; -webkit-overflow-scrolling: touch; }
.tab-btn { white-space: nowrap; }
.mobile-menu-btn {
display: flex !important;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
margin-bottom: 1rem;
background: transparent;
color: white;
border: 1px solid var(--glass-border);
border-radius: var(--radius-md);
cursor: pointer;
display: flex !important; align-items: center; justify-content: center;
width: 40px; height: 40px; margin-bottom: 1rem;
background: var(--surface); color: var(--text);
border: 1px solid var(--border); border-radius: var(--radius-md); cursor: pointer;
}
}
.mobile-menu-btn { display: none; }
/* Default hidden for desktop */
.mobile-menu-btn {
display: none;
}
/* Mobile Responsive Tables (Card View) */
/* Mobile responsive tables (card view) */
@media (max-width: 768px) {
table,
thead,
tbody,
th,
td,
tr {
display: block;
}
/* Hide table headers (but not display:none for accessibility, usually, but here simplicity) */
thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
tr {
margin-bottom: 1rem;
background: rgba(255, 255, 255, 0.03);
border: 1px solid var(--glass-border);
border-radius: var(--radius-md);
padding: 1rem;
}
td {
border: none;
position: relative;
padding-left: 50% !important;
/* Make space for label */
padding-top: 0.5rem !important;
padding-bottom: 0.5rem !important;
min-height: 2rem;
display: flex;
align-items: center;
text-align: right;
justify-content: flex-end;
}
td::before {
content: attr(data-label);
position: absolute;
left: 1rem;
width: 45%;
padding-right: 10px;
white-space: nowrap;
text-align: left;
font-weight: bold;
color: var(--text-muted);
}
/* Specific adjustments for certain columns if needed */
td[data-label="Video"] {
padding-left: 0 !important;
display: block;
text-align: left;
}
td[data-label="Video"]::before {
display: none;
/* Hide label for video title/thumb block to give it full width */
}
td[data-label="Rank"] {
display: inline-block;
padding: 0 !important;
margin-bottom: 0.5rem;
width: auto;
}
td[data-label="Rank"]::before {
display: none;
}
td[data-label="Rank"] span {
font-size: 0.875rem !important;
background: rgba(var(--primary-rgb), 0.1);
/* fallback if var not ready */
padding: 2px 8px;
border-radius: 4px;
color: var(--text-muted);
border: 1px solid var(--glass-border);
}
/* Customize Rank appearance to look like a badge */
table, thead, tbody, th, td, tr { display: block; }
thead tr { position: absolute; top: -9999px; left: -9999px; }
tr { margin-bottom: 1rem; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 1rem; }
td { border: none; position: relative; padding-left: 50% !important; padding-top: 0.5rem !important; padding-bottom: 0.5rem !important; min-height: 2rem; display: flex; align-items: center; text-align: right; justify-content: flex-end; }
td::before { content: attr(data-label); position: absolute; left: 1rem; width: 45%; padding-right: 10px; white-space: nowrap; text-align: left; font-weight: bold; color: var(--text-muted); }
td[data-label="Video"] { padding-left: 0 !important; display: block; text-align: left; }
td[data-label="Video"]::before { display: none; }
td[data-label="Rank"] { display: inline-block; padding: 0 !important; margin-bottom: 0.5rem; width: auto; }
td[data-label="Rank"]::before { display: none; }
td[data-label="Rank"] span { font-size: 0.875rem !important; background: var(--accent-soft); padding: 2px 8px; border-radius: 4px; color: var(--text-muted); border: 1px solid var(--border); }
}
/* ===== Shared components (added) ===== */
/* ===== Shared components ===== */
/* Progress bar (dashboard funnel, distributions, …) */
.bar-track {
flex: 1;
height: 8px;
background: rgba(255, 255, 255, 0.06);
border-radius: var(--radius-full);
overflow: hidden;
}
.bar-fill {
height: 100%;
border-radius: var(--radius-full);
transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
@media (prefers-reduced-motion: reduce) {
.bar-fill { transition: none; }
}
/* Progress bar */
.bar-track { flex: 1; height: 8px; background: var(--inset); border-radius: var(--radius-full); overflow: hidden; }
.bar-fill { height: 100%; border-radius: var(--radius-full); transition: width 0.6s cubic-bezier(0.22, 1, 0.36, 1); }
@media (prefers-reduced-motion: reduce) { .bar-fill { transition: none; } }
/* Badge */
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 0.7rem;
font-weight: 600;
padding: 2px 8px;
border-radius: var(--radius-full);
border: 1px solid transparent;
line-height: 1.4;
display: inline-flex; align-items: center; gap: 4px;
font-size: 0.7rem; font-weight: 700;
padding: 2px 8px; border-radius: 6px;
border: 1px solid transparent; line-height: 1.4;
}
.badge-muted { background: rgba(255,255,255,0.06); color: var(--text-secondary); }
.badge-primary { background: rgba(var(--primary-rgb), 0.15); color: var(--primary); }
.badge-success { background: rgba(var(--success-rgb), 0.15); color: var(--success); }
.badge-warning { background: rgba(245, 158, 11, 0.15); color: var(--warning); }
.badge-danger { background: rgba(var(--danger-rgb), 0.15); color: var(--danger); }
.badge-muted { background: var(--surface-2); color: var(--text-secondary); }
.badge-primary { background: var(--accent-soft); color: var(--accent); }
.badge-success { background: rgba(var(--success-rgb), 0.14); color: var(--success); }
.badge-warning { background: rgba(245, 158, 11, 0.14); color: var(--warning); }
.badge-danger { background: rgba(var(--danger-rgb), 0.14); color: var(--danger); }
/* Skeleton shimmer */
.skeleton {
position: relative;
overflow: hidden;
background: rgba(255, 255, 255, 0.05);
border-radius: var(--radius-md);
}
.skeleton { position: relative; overflow: hidden; background: var(--surface-2); border-radius: var(--radius-md); }
.skeleton::after {
content: "";
position: absolute;
inset: 0;
transform: translateX(-100%);
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
content: ""; position: absolute; inset: 0; transform: translateX(-100%);
background: linear-gradient(90deg, transparent, var(--hover-strong), transparent);
animation: skeleton-shimmer 1.4s infinite;
}
@keyframes skeleton-shimmer { 100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
.skeleton::after { animation: none; }
}
@media (prefers-reduced-motion: reduce) { .skeleton::after { animation: none; } }
/* Dark form controls (unify select/input across pages) */
/* Form controls */
select, input[type="text"], input[type="search"], input[type="number"], input[type="date"], input[type="datetime-local"], textarea {
background: rgba(255, 255, 255, 0.04);
color: var(--text-primary);
border: 1px solid var(--glass-border);
border-radius: var(--radius-md);
padding: 0.5rem 0.75rem;
background: var(--surface);
color: var(--text);
border: 1px solid var(--border-strong);
border-radius: var(--r-sm);
padding: 0.5rem 0.7rem;
font-family: inherit;
font-size: 0.875rem;
transition: border-color 0.2s ease, background 0.2s ease;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
select:focus, input[type="text"]:focus, input[type="search"]:focus, input[type="number"]:focus, input[type="date"]:focus, input[type="datetime-local"]:focus, textarea:focus {
outline: none;
border-color: var(--primary);
background: rgba(255, 255, 255, 0.06);
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
select option { background: #0f172a; color: var(--text-primary); }
select option { background: var(--surface); color: var(--text); }
/* Table polish */
/* Table */
table { width: 100%; border-collapse: collapse; }
thead th {
text-align: left;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--text-muted);
padding: 0.6rem 0.75rem;
border-bottom: 1px solid var(--glass-border);
text-align: left; font-size: 0.75rem; font-weight: 600;
text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-muted);
padding: 0.65rem 0.75rem; border-bottom: 1px solid var(--border);
}
tbody td { padding: 0.6rem 0.75rem; border-bottom: 1px solid rgba(255, 255, 255, 0.04); }
tbody td { padding: 0.65rem 0.75rem; border-bottom: 1px solid var(--border); }
tbody tr { transition: background 0.15s ease; }
tbody tr:hover { background: rgba(255, 255, 255, 0.03); }
tbody tr:hover { background: var(--hover); }

View File

@ -1,60 +1,104 @@
/* ===========================================================================
Design tokens Refined SaaS, LIGHT default + DARK via [data-theme="dark"].
Old variable names are aliased to theme tokens so existing markup adapts.
=========================================================================== */
:root {
/* Premium Dark Theme Palette (Cyber/Space) */
/* Surfaces (light) */
--bg: #f6f7f9;
--surface: #ffffff;
--surface-2: #f1f3f6;
--inset: #eef0f3;
--border: #e6e8ec;
--border-strong: #d9dce1;
/* Backgrounds */
--bg-primary: #050508;
/* Ultra dark, almost black */
--bg-glass: rgba(15, 23, 42, 0.6);
/* Text (light) */
--text: #15181d;
--text-2: #5a626d;
--text-3: #9aa1ad;
/* Glassmorphism */
--glass-border: rgba(255, 255, 255, 0.08);
--glass-highlight: rgba(255, 255, 255, 0.15);
--backdrop-blur: 12px;
/* On-surface translucent (hover/active) */
--hover: rgba(16, 24, 40, .045);
--hover-strong: rgba(16, 24, 40, .07);
/* Typography */
--text-primary: #f1f5f9;
--text-secondary: #94a3b8;
--text-muted: #64748b;
/* Brand Colors (Neon/Vibrant) */
--primary: #3b82f6;
--primary-glow: rgba(59, 130, 246, 0.5);
--primary-gradient: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
/* Elevation */
--shadow: 0 1px 2px rgba(16, 24, 40, .04), 0 4px 12px rgba(16, 24, 40, .05);
--shadow-lg: 0 10px 30px rgba(16, 24, 40, .10);
--side-bg: #ffffff;
/* Accent + status (shared across themes) */
--accent: #4f7cff;
--accent-2: #7c5cff;
--accent-soft: rgba(79, 124, 255, .10);
--primary: #4f7cff;
--primary-gradient: linear-gradient(135deg, #4f7cff 0%, #7c5cff 100%);
--primary-glow: rgba(79, 124, 255, .35);
--success: #10b981;
--danger: #ef4444;
/* UI Elements */
--radius-md: 12px;
--radius-lg: 20px;
--radius-full: 9999px;
/* Semantic (added) */
--danger: #f43f5e;
--warning: #f59e0b;
--primary-rgb: 59, 130, 246; /* matches --primary #3b82f6; used by rgba(var(--primary-rgb), …) */
--green: #10b981;
--amber: #f59e0b;
--red: #f43f5e;
--purple: #8b5cf6;
--sky: #0ea5e9;
--primary-rgb: 79, 124, 255;
--success-rgb: 16, 185, 129;
--danger-rgb: 239, 68, 68;
--danger-rgb: 244, 63, 94;
/* Spacing scale */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
/* === Back-compat aliases (old names → theme tokens) === */
--bg-primary: var(--bg);
--bg-glass: var(--surface);
--glass-border: var(--border);
--glass-highlight: var(--border-strong);
--text-primary: var(--text);
--text-secondary: var(--text-2);
--text-muted: var(--text-3);
--bg-hover: var(--surface-2);
--backdrop-blur: 0px;
/* Radius / spacing / font */
--radius-md: 10px;
--radius-lg: 14px;
--radius-full: 9999px;
--r: 12px;
--r-sm: 9px;
--space-1: .25rem;
--space-2: .5rem;
--space-3: .75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}
/* ===== Dark theme overrides ===== */
:root[data-theme="dark"] {
--bg: #0a0b0e;
--surface: #131519;
--surface-2: #191c22;
--inset: #0e1014;
--border: rgba(255, 255, 255, .08);
--border-strong: rgba(255, 255, 255, .13);
--text: #e8eaef;
--text-2: #9aa1ad;
--text-3: #6b7280;
--hover: rgba(255, 255, 255, .05);
--hover-strong: rgba(255, 255, 255, .09);
--shadow: 0 1px 2px rgba(0, 0, 0, .3);
--shadow-lg: 0 12px 30px rgba(0, 0, 0, .45);
--side-bg: linear-gradient(180deg, #0c0e12, #0a0b0e);
--accent-soft: rgba(79, 124, 255, .16);
}
body {
background-color: var(--bg-primary);
background-image: radial-gradient(circle at 15% 50%, rgba(56, 189, 248, 0.08), transparent 25%),
radial-gradient(circle at 85% 30%, rgba(99, 102, 241, 0.08), transparent 25%);
background-attachment: fixed;
color: var(--text-primary);
background-color: var(--bg);
color: var(--text);
font-family: var(--font-sans);
font-feature-settings: "tnum" 1, "cv01" 1;
margin: 0;
-webkit-font-smoothing: antialiased;
min-height: 100vh;
}
transition: background-color .2s ease, color .2s ease;
}

View File

@ -1,3 +1,12 @@
// Theme toggle (light/dark). State applied pre-paint in base.html <head>.
function toggleTheme() {
const root = document.documentElement;
const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', next);
try { localStorage.setItem('theme', next); } catch (e) { /* ignore */ }
if (window.lucide) window.lucide.createIcons();
}
// Sidebar Logic
document.addEventListener('DOMContentLoaded', () => {
const sidebar = document.getElementById('sidebar');

View File

@ -193,7 +193,7 @@
else {
opList.innerHTML = op.map((v,i)=>{
const ratio = v.viewsPerSubRatio!=null ? Number(v.viewsPerSubRatio).toFixed(1)+'x' : '-';
return '<a href="https://www.youtube.com/watch?v='+v.videoId+'" target="_blank" class="flex items-center justify-between p-3" style="border-radius:var(--radius-md); background:rgba(255,255,255,0.03); text-decoration:none;">' +
return '<a href="https://www.youtube.com/watch?v='+v.videoId+'" target="_blank" class="flex items-center justify-between p-3" style="border-radius:var(--radius-md); background:var(--surface-2); text-decoration:none;">' +
'<div class="flex items-center gap-2" style="min-width:0;">' +
'<span class="badge badge-muted" style="flex-shrink:0;">'+(i+1)+'</span>' +
'<img src="'+esc(v.thumbnailUrl)+'" style="width:48px;height:27px;object-fit:cover;border-radius:4px;flex-shrink:0;">' +
@ -220,8 +220,8 @@
? '<p class="text-muted text-sm" style="margin-top:4px;">발행 패키지가 없습니다.</p>'
: '<div class="text-sm text-muted" style="margin:6px 0 2px;">최근</div>' + recent.map(p=>{
const st = PUB_ST[p.status] || { t:p.status, cls:'badge-muted' };
return '<a href="/rework/'+p.channelVideoId+'" class="flex items-center justify-between p-2" style="border-radius:6px; background:rgba(255,255,255,0.03); text-decoration:none;">' +
'<span class="text-sm truncate" style="max-width:170px; color:#e2e8f0;">'+esc(p.title||'(제목 없음)')+'</span>' +
return '<a href="/rework/'+p.channelVideoId+'" class="flex items-center justify-between p-2" style="border-radius:6px; background:var(--surface-2); text-decoration:none;">' +
'<span class="text-sm truncate" style="max-width:170px; color:var(--text);">'+esc(p.title||'(제목 없음)')+'</span>' +
'<span class="badge '+st.cls+'" style="flex-shrink:0;">'+st.t+'</span></a>';
}).join('');

View File

@ -6,6 +6,14 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>h-lab - Dashboard</title>
<!-- Theme init (before paint, no flash) -->
<script>
(function () {
try { document.documentElement.setAttribute('data-theme', localStorage.getItem('theme') || 'light'); }
catch (e) { document.documentElement.setAttribute('data-theme', 'light'); }
})();
</script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

View File

@ -3,118 +3,66 @@
<body th:fragment="sidebar">
<aside id="sidebar" class="sidebar">
<div class="p-6 mb-4 flex items-center justify-between">
<!-- Brand -->
<div class="flex items-center justify-between" style="padding: 1.1rem 1rem 0.5rem;">
<div class="flex items-center gap-3">
<div style="
width: 32px; height: 32px;
background: var(--primary-gradient);
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
box-shadow: 0 0 15px var(--primary-glow);
flex-shrink: 0;
">
<span style="font-weight: bold; color: white; font-size: 18px;">H</span>
<div style="width:30px;height:30px;background:var(--primary-gradient);border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
<span style="font-weight:800;color:#fff;font-size:15px;">H</span>
</div>
<div class="brand-text" style="white-space:nowrap;">
<div style="font-size:15px;font-weight:700;letter-spacing:-0.2px;color:var(--text);line-height:1.1;">h-lab</div>
<div style="font-size:11px;color:var(--text-3);font-weight:500;">yanalyst</div>
</div>
<h1 class="text-xl font-bold brand-text" style="
background: linear-gradient(to right, #fff, #94a3b8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
white-space: nowrap;
">
h-lab
</h1>
</div>
<!-- Toggle Button -->
<button id="sidebarToggle" class="sidebar-toggle-btn">
<i data-lucide="chevron-left" style="width: 18px; height: 18px;"></i>
<i data-lucide="chevron-left" style="width:18px;height:18px;"></i>
</button>
</div>
<nav style="flex: 1; padding: 0 1rem; overflow-y: auto;">
<ul class="flex-col gap-2">
<li>
<a th:href="@{/}" class="nav-item" th:classappend="${currentPage == 'dashboard'} ? 'active'">
<i data-lucide="layout-dashboard" class="nav-icon"></i>
<span class="nav-text">Dashboard</span>
</a>
</li>
<li>
<a th:href="@{/channels}" class="nav-item" th:classappend="${currentPage == 'channels'} ? 'active'">
<i data-lucide="users" class="nav-icon"></i>
<span class="nav-text">Channels</span>
</a>
</li>
<li>
<a th:href="@{/videos}" class="nav-item" th:classappend="${currentPage == 'videos'} ? 'active'">
<i data-lucide="video" class="nav-icon"></i>
<span class="nav-text">Videos</span>
</a>
</li>
<li>
<a th:href="@{/collection}" class="nav-item"
th:classappend="${currentPage == 'collection'} ? 'active'">
<i data-lucide="library" class="nav-icon"></i>
<span class="nav-text">수집함</span>
</a>
</li>
<li>
<a th:href="@{/discover}" class="nav-item"
th:classappend="${currentPage == 'discover'} ? 'active'">
<i data-lucide="radar" class="nav-icon"></i>
<span class="nav-text">발굴</span>
</a>
</li>
<li>
<a th:href="@{/board}" class="nav-item"
th:classappend="${currentPage == 'board'} ? 'active'">
<i data-lucide="kanban-square" class="nav-icon"></i>
<span class="nav-text">보드</span>
</a>
</li>
<li>
<a th:href="@{/publish}" class="nav-item"
th:classappend="${currentPage == 'publish'} ? 'active'">
<i data-lucide="send" class="nav-icon"></i>
<span class="nav-text">발행</span>
</a>
</li>
<li>
<a th:href="@{/production}" class="nav-item"
th:classappend="${currentPage == 'production'} ? 'active'">
<i data-lucide="clapperboard" class="nav-icon"></i>
<span class="nav-text">Production</span>
</a>
</li>
<li>
<a href="#" class="nav-item">
<i data-lucide="settings" class="nav-icon"></i>
<span class="nav-text">Settings</span>
</a>
</li>
</ul>
<nav style="flex:1;padding:0.5rem 0.75rem;overflow-y:auto;">
<div class="nav-section">분석</div>
<a th:href="@{/}" class="nav-item" th:classappend="${currentPage == 'dashboard'} ? 'active'">
<i data-lucide="layout-dashboard" class="nav-icon"></i><span class="nav-text">대시보드</span>
</a>
<a th:href="@{/discover}" class="nav-item" th:classappend="${currentPage == 'discover'} ? 'active'">
<i data-lucide="radar" class="nav-icon"></i><span class="nav-text">발굴</span>
</a>
<a th:href="@{/channels}" class="nav-item" th:classappend="${currentPage == 'channels'} ? 'active'">
<i data-lucide="users" class="nav-icon"></i><span class="nav-text">채널</span>
</a>
<div class="nav-section">파이프라인</div>
<a th:href="@{/collection}" class="nav-item" th:classappend="${currentPage == 'collection'} ? 'active'">
<i data-lucide="library" class="nav-icon"></i><span class="nav-text">수집함</span>
</a>
<a th:href="@{/board}" class="nav-item" th:classappend="${currentPage == 'board'} ? 'active'">
<i data-lucide="kanban-square" class="nav-icon"></i><span class="nav-text">칸반 보드</span>
</a>
<a th:href="@{/publish}" class="nav-item" th:classappend="${currentPage == 'publish'} ? 'active'">
<i data-lucide="send" class="nav-icon"></i><span class="nav-text">발행 큐</span>
</a>
<div class="nav-section">제작</div>
<a th:href="@{/production}" class="nav-item" th:classappend="${currentPage == 'production'} ? 'active'">
<i data-lucide="clapperboard" class="nav-icon"></i><span class="nav-text">프로덕션</span>
</a>
</nav>
<div class="user-profile-container">
<button class="theme-toggle" onclick="toggleTheme()" type="button" title="테마 전환">
<span class="moon"><i data-lucide="moon" style="width:15px;height:15px;"></i></span>
<span class="sun"><i data-lucide="sun" style="width:15px;height:15px;"></i></span>
<span class="nav-text">테마 전환</span>
</button>
<div class="user-profile-content">
<div style="
width: 36px; height: 36px; border-radius: 50%;
background: linear-gradient(135deg, #475569 0%, #0f172a 100%);
border: 2px solid rgba(255,255,255,0.1);
flex-shrink: 0;
"></div>
<div class="user-info">
<div class="text-sm font-bold" style="color: var(--text-primary); white-space: nowrap;">Admin User
</div>
<div class="text-sm" style="color: var(--text-muted); font-size: 0.75rem; white-space: nowrap;">Pro
Plan</div>
<div style="width:30px;height:30px;border-radius:50%;background:var(--surface-2);border:1px solid var(--border);flex-shrink:0;"></div>
<div class="user-info" style="white-space:nowrap;">
<div style="font-size:12.5px;font-weight:600;color:var(--text);">hehiho</div>
<div style="font-size:11px;color:var(--text-3);">개인 작업공간</div>
</div>
</div>
<button class="w-full btn-ghost sign-out-btn"
style="justify-content: center; padding: 0.5rem; gap: 0.5rem; border-radius: 4px; background: rgba(0,0,0,0.2);">
<i data-lucide="log-out" style="width: 14px; height: 14px;"></i> <span>Sign Out</span>
</button>
</div>
</aside>
</body>
</html>
</html>