feat: implement user preference management with persistence, reactive CSS variables, and layout customization

This commit is contained in:
2026-08-21 10:25:00 +09:00
parent 8212f16d28
commit fa15956a90
23 changed files with 2915 additions and 1146 deletions
+283 -52
View File
@@ -4,47 +4,47 @@
DESIGN TOKENS
============================================ */
:root {
/* Brand */
--accent: #4B7BF5;
--accent-hover: #3A6AE4;
--accent-light: rgba(75, 123, 245, 0.1);
--accent-medium: rgba(75, 123, 245, 0.18);
/* Brand — muted, desaturated pastel accent */
--accent: hsl(220, 68%, 56%);
--accent-hover: hsl(220, 68%, 47%);
--accent-light: hsla(220, 68%, 56%, 0.1);
--accent-medium: hsla(220, 68%, 56%, 0.18);
/* Backgrounds */
--bg-primary: #FFFFFF;
--bg-secondary: #F7F8FC;
--bg-sidebar: #F2F4F8;
--bg-hover: rgba(0, 0, 0, 0.04);
--bg-active: rgba(75, 123, 245, 0.08);
--bg-overlay: rgba(0, 0, 0, 0.5);
/* Backgrounds — warm off-white, matte */
--bg-primary: #FAFAF9;
--bg-secondary: #F4F4F2;
--bg-sidebar: #EFEFF0;
--bg-hover: rgba(0, 0, 0, 0.045);
--bg-active: hsla(220, 68%, 56%, 0.09);
--bg-overlay: rgba(0, 0, 0, 0.45);
/* Text */
--text-primary: #1A1A2E;
--text-secondary: #6B7280;
--text-tertiary: #9CA3AF;
/* Text — slightly warm */
--text-primary: #18181B;
--text-secondary: #71717A;
--text-tertiary: #A1A1AA;
--text-on-accent: #FFFFFF;
/* Border */
--border: rgba(0, 0, 0, 0.08);
--border-medium: rgba(0, 0, 0, 0.12);
--border-strong: rgba(0, 0, 0, 0.2);
/* Border — barely visible */
--border: rgba(0, 0, 0, 0.07);
--border-medium: rgba(0, 0, 0, 0.11);
--border-strong: rgba(0, 0, 0, 0.19);
/* Priority colors */
--priority-high: #EF4444;
--priority-medium: #F59E0B;
--priority-low: #10B981;
/* Priority colors — desaturated pastels */
--priority-high: #E05252;
--priority-medium: #E8931A;
--priority-low: #3DAD84;
/* Status */
--success: #10B981;
--danger: #EF4444;
--warning: #F59E0B;
--success: #3DAD84;
--danger: #E05252;
--warning: #E8931A;
/* Shadows */
--shadow-xs: 0 1px 2px rgba(0,0,0,0.06);
--shadow-sm: 0 2px 8px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 16px rgba(0,0,0,0.1), 0 2px 4px rgba(0,0,0,0.06);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.12), 0 4px 8px rgba(0,0,0,0.06);
--shadow-panel: -4px 0 24px rgba(0,0,0,0.08);
/* Shadows — very soft */
--shadow-xs: 0 1px 2px rgba(0,0,0,0.05);
--shadow-sm: 0 2px 8px rgba(0,0,0,0.07), 0 1px 2px rgba(0,0,0,0.04);
--shadow-md: 0 4px 16px rgba(0,0,0,0.09), 0 2px 4px rgba(0,0,0,0.05);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.10), 0 4px 8px rgba(0,0,0,0.05);
--shadow-panel: -4px 0 24px rgba(0,0,0,0.07);
/* Radius */
--radius-xs: 4px;
@@ -63,27 +63,32 @@
/* Layout */
--sidebar-width: 260px;
--detail-width: min(580px, 50vw);
--detail-width: 520px;
--header-height: 56px;
/* Dynamic vars (overridden by prefs) */
--font-size-base: 14px;
--task-item-padding: 10px 20px;
--task-item-gap: 4px;
}
/* Dark mode */
/* VSCode-inspired Achromatic Dark Theme */
[data-theme="dark"] {
--bg-primary: #111827;
--bg-secondary: #1F2937;
--bg-sidebar: #161D2B;
--bg-hover: rgba(255, 255, 255, 0.05);
--bg-active: rgba(75, 123, 245, 0.12);
--text-primary: #F9FAFB;
--text-secondary: #9CA3AF;
--text-tertiary: #6B7280;
--border: rgba(255, 255, 255, 0.08);
--border-medium: rgba(255, 255, 255, 0.12);
--border-strong: rgba(255, 255, 255, 0.2);
--shadow-sm: 0 2px 8px rgba(0,0,0,0.3);
--shadow-md: 0 4px 16px rgba(0,0,0,0.4);
--shadow-lg: 0 8px 32px rgba(0,0,0,0.5);
--shadow-panel: -4px 0 24px rgba(0,0,0,0.4);
--bg-primary: #1e1e1e;
--bg-secondary: #252526;
--bg-sidebar: #181818;
--bg-hover: rgba(255, 255, 255, 0.07);
--bg-active: rgba(59, 130, 246, 0.16);
--text-primary: #e2e8f0;
--text-secondary: #94a3b8;
--text-tertiary: #64748b;
--border: #2d2d2d;
--border-medium: #383838;
--border-strong: #4a4a4a;
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.6);
--shadow-panel: -4px 0 24px rgba(0, 0, 0, 0.5);
}
/* ============================================
@@ -103,7 +108,7 @@ html {
body {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 14px;
font-size: var(--font-size-base, 14px);
line-height: 1.5;
color: var(--text-primary);
background: var(--bg-primary);
@@ -165,6 +170,7 @@ a { color: inherit; text-decoration: none; }
transition: width var(--dur-slow) var(--ease-out),
transform var(--dur-slow) var(--ease-out);
z-index: 20;
min-width: 0;
}
.sidebar.collapsed {
@@ -413,7 +419,7 @@ a { color: inherit; text-decoration: none; }
display: flex;
align-items: flex-start;
gap: 10px;
padding: 10px 20px;
padding: var(--task-item-padding, 10px 20px);
cursor: pointer;
transition: background var(--dur-fast);
position: relative;
@@ -629,6 +635,8 @@ a { color: inherit; text-decoration: none; }
transform: translateX(0);
transition: width var(--dur-slow) var(--ease-out),
transform var(--dur-slow) var(--ease-out);
min-width: 280px;
max-width: 800px;
}
.detail-panel.closed {
@@ -672,6 +680,17 @@ a { color: inherit; text-decoration: none; }
gap: 16px;
}
/* The main scrolling content area inside the detail panel */
.detail-scroll {
flex: 1;
overflow-y: auto;
padding: 14px 16px 20px;
display: flex;
flex-direction: column;
gap: 10px;
min-height: 0;
}
.detail-title-input {
font-size: 18px;
font-weight: 600;
@@ -1796,3 +1815,215 @@ a { color: inherit; text-decoration: none; }
color: var(--text-primary);
border-color: var(--border-medium);
}
.tick-meta-chip.active {
background: var(--accent-light);
border-color: var(--accent);
color: var(--accent);
font-weight: 600;
}
.desktop-only {
display: inline;
}
@media (max-width: 768px) {
.desktop-only {
display: none;
}
}
/* ============================================
ADD TASK BAR & DROPDOWNS
============================================ */
.add-task-bar {
padding: 12px 16px;
border-top: 1px solid var(--border);
background: var(--bg-primary);
position: relative;
z-index: 30;
}
.add-task-input {
width: 100%;
padding: 8px 12px;
font-size: 14px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text-primary);
outline: none;
transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.add-task-input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent-light);
background: var(--bg-primary);
}
.dropdown {
position: absolute;
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: var(--radius-md);
box-shadow: var(--shadow-lg);
backdrop-filter: blur(16px);
z-index: 200;
animation: dropdownIn var(--dur-fast) var(--ease-out);
}
/* ============================================
MODULAR SPLIT RESIZER & DETAIL BLOCKS
============================================ */
.detail-block-card {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: var(--radius-md);
display: flex;
flex-direction: column;
overflow: hidden;
transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.detail-block-card:hover {
border-color: var(--border-medium);
}
.detail-block-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
user-select: none;
}
.detail-split-resizer {
height: 6px;
margin: 2px 0;
cursor: row-resize;
display: flex;
align-items: center;
justify-content: center;
transition: background var(--dur-fast);
border-radius: 3px;
flex-shrink: 0;
}
.detail-split-resizer:hover,
.detail-split-resizer.dragging {
background: var(--accent-light);
}
.detail-split-resizer-line {
width: 32px;
height: 2px;
background: var(--border-medium);
border-radius: 1px;
transition: width var(--dur-fast), background var(--dur-fast);
}
.detail-split-resizer:hover .detail-split-resizer-line,
.detail-split-resizer.dragging .detail-split-resizer-line {
width: 48px;
background: var(--accent);
}
/* ============================================
KANBAN BOARD VIEW
============================================ */
.kanban-container {
display: flex;
gap: 16px;
height: 100%;
padding: 16px 20px;
overflow-x: auto;
align-items: flex-start;
}
.kanban-column {
flex: 1;
min-width: 280px;
max-width: 360px;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
display: flex;
flex-direction: column;
max-height: 100%;
}
.kanban-column-header {
padding: 12px 16px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid var(--border);
}
.kanban-column-body {
padding: 12px;
display: flex;
flex-direction: column;
gap: 10px;
overflow-y: auto;
flex: 1;
}
.kanban-card {
background: var(--bg-primary);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 12px 14px;
cursor: pointer;
box-shadow: var(--shadow-xs);
transition: transform var(--dur-fast), box-shadow var(--dur-fast), border-color var(--dur-fast);
}
.kanban-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-sm);
border-color: var(--accent);
}
.kanban-card.selected {
border-color: var(--accent);
box-shadow: 0 0 0 2px var(--accent-light);
}
/* View Switcher Button */
.view-switcher-group {
display: inline-flex;
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 2px;
gap: 2px;
}
.view-switcher-btn {
padding: 4px 8px;
font-size: 12px;
font-weight: 500;
border: none;
background: transparent;
color: var(--text-secondary);
border-radius: var(--radius-xs);
cursor: pointer;
transition: all var(--dur-fast);
display: inline-flex;
align-items: center;
gap: 4px;
}
.view-switcher-btn.active {
background: var(--bg-primary);
color: var(--text-primary);
font-weight: 600;
box-shadow: var(--shadow-xs);
}