Files
ai-code-app/src/views/play/apps/template1/Template1PlayAppView.tsx
2026-05-29 17:42:33 +09:00

3008 lines
100 KiB
TypeScript

import {
AppstoreOutlined,
AppstoreAddOutlined,
BellOutlined,
CheckCircleOutlined,
CompassOutlined,
LogoutOutlined,
CloseOutlined,
LeftOutlined,
SettingOutlined,
RightOutlined,
ThunderboltOutlined,
} from '@ant-design/icons';
import {
useEffect,
useMemo,
useState,
type CSSProperties,
type PointerEvent as ReactPointerEvent,
type ReactNode,
} from 'react';
import './Template1PlayAppView.css';
type Template1PlayAppViewProps = {
onBack: () => void;
launchContext?: 'direct' | 'embedded';
};
type Template1SectionId = 'home' | 'projects' | 'resources' | 'automation';
type Template1Section = {
id: Template1SectionId;
label: string;
description: string;
chips: string[];
};
type Template1MenuItem = {
title: string;
description: string;
icon: ReactNode;
screen?: Template1SectionId;
actionLabel?: string;
actionType?: 'navigate' | 'exit' | 'local';
settingAction?: 'theme' | 'notifications' | 'reset-state';
};
type Template1ThemeId =
| 'theme-default'
| 'theme-sky'
| 'theme-citrus'
| 'theme-forest'
| 'theme-violet'
| 'theme-graphite'
| 'theme-sunset'
| 'theme-coral'
| 'theme-olive'
| 'theme-mint'
| 'theme-ocean'
| 'theme-berry'
| 'theme-latte'
| 'theme-choco'
| 'theme-aurora'
| 'theme-electric'
| 'theme-tide'
| 'theme-peach-night'
| 'theme-graphite-cool'
| 'theme-ember'
| 'theme-small-text'
| 'theme-base-text'
| 'theme-large-text'
| 'theme-xsmall-text'
| 'theme-reading-text'
| 'theme-comfort-text'
| 'theme-comfortable-readable-text'
| 'theme-micro-text'
| 'theme-cursor-text'
| 'theme-compact-structure'
| 'theme-wide-structure'
| 'theme-list-structure'
| 'theme-focus-structure'
| 'theme-dense-grid-structure'
| 'theme-stagger-structure'
| 'theme-split-card-structure'
| 'theme-stats-column-structure'
| 'theme-mosaic-structure'
| 'theme-timeline-structure'
| 'theme-dashboard-structure'
| 'theme-kanban-structure'
| 'theme-glass-accent'
| 'theme-soft-glass'
| 'theme-glass-ink'
| 'theme-sunrise-glow'
| 'theme-slate-readability'
| 'theme-ocean-office'
| 'theme-sunset-contrast'
| 'theme-berry-focus'
| 'theme-aurora-clean'
| 'theme-micro-precision'
| 'theme-warm-glass'
| 'theme-citrus-minimal';
type Template1ThemeCategory = '색상' | '폰트' | '구조' | '혼합';
type Template1ThemePanelClass = 'color' | 'font' | 'layout' | 'mix';
type Template1LayoutPattern =
| 'compact'
| 'wide'
| 'list'
| 'focus'
| 'dense-grid'
| 'stagger'
| 'split'
| 'stats'
| 'mosaic'
| 'timeline'
| 'dashboard'
| 'kanban';
type Template1LayoutVisualKind =
| 'table-tight'
| 'table-card'
| 'list'
| 'focus'
| 'stagger'
| 'split'
| 'chess'
| 'timeline'
| 'board';
type Template1LayoutVisual = {
label: string;
kind: Template1LayoutVisualKind;
tileCount: number;
};
type Template1ThemePreset = {
id: Template1ThemeId;
title: string;
category: Template1ThemeCategory;
description: string;
fontScale: number;
cardColumns: number;
homeColumns: number;
gap: string;
padding: string;
shellRadius: string;
cardRadius: string;
cardMinHeight: string;
cardIconSize: string;
chipGap: string;
cssVars: Record<string, string>;
};
const TEMPLATE1_DEFAULT_THEME_VARIABLES: Record<string, string> = {
'--template1-background-start': '#f8fafc',
'--template1-background-end': '#f4f7fe',
'--template1-surface': '#ffffff',
'--template1-text': '#0f172a',
'--template1-subtext': '#64748b',
'--template1-muted': '#94a3b8',
'--template1-border': 'rgba(15, 23, 42, 0.1)',
'--template1-theme-item-min-width': '196px',
'--template1-theme-item-height': '56px',
'--template1-theme-item-max-width': '256px',
'--template1-theme-item-max-height': '74px',
'--template1-theme-window-width': '272px',
'--template1-theme-window-height': '302px',
'--template1-card-grid-template': 'auto 1fr auto',
'--template1-card-action-column': 'auto',
'--template1-card-action-justify': 'end',
'--template1-home-item-layout': 'auto minmax(0, 1fr) auto',
'--template1-home-item-gap': '14px',
'--template1-home-item-padding': '16px',
'--template1-home-item-padding-inline': '18px',
'--template1-card-max-height': 'none',
'--template1-topbar-start': 'rgba(248, 250, 252, 0.96)',
'--template1-topbar-end': 'rgba(248, 250, 252, 0.75)',
'--template1-topbar-text': '#0c4a9e',
'--template1-badge-bg': '#dbeafe',
'--template1-badge-text': '#0c4a9e',
'--template1-button-bg': '#dbeafe',
'--template1-button-text': '#0c4a9e',
'--template1-button-hover': 'rgba(37, 99, 235, 0.2)',
'--template1-settings-bg': '#ffffff',
'--template1-settings-border': '#e2e8f0',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#dbe4f2',
'--template1-card-text': '#0f172a',
'--template1-card-subtext': '#475569',
'--template1-card-icon-bg': '#e2e8f0',
'--template1-card-action': '#3b82f6',
'--template1-danger-bg': '#fff1f2',
'--template1-danger-border': '#fecaca',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(15, 23, 42, 0.12)',
};
const THEME_PRESETS: Template1ThemePreset[] = [
{
id: 'theme-default',
title: '기본 라이트',
category: '색상',
description: '기존 느낌의 안정적인 블루 계열',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#f8fafc',
'--template1-background-end': '#f4f7fe',
'--template1-surface': '#ffffff',
'--template1-text': '#0f172a',
'--template1-subtext': '#64748b',
'--template1-muted': '#94a3b8',
'--template1-border': 'rgba(15, 23, 42, 0.1)',
'--template1-topbar-start': 'rgba(248, 250, 252, 0.96)',
'--template1-topbar-end': 'rgba(248, 250, 252, 0.75)',
'--template1-topbar-text': '#0c4a9e',
'--template1-badge-bg': '#dbeafe',
'--template1-badge-text': '#0c4a9e',
'--template1-button-bg': '#dbeafe',
'--template1-button-text': '#0c4a9e',
'--template1-button-hover': 'rgba(37, 99, 235, 0.2)',
'--template1-settings-bg': '#ffffff',
'--template1-settings-border': '#e2e8f0',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#dbe4f2',
'--template1-card-text': '#0f172a',
'--template1-card-subtext': '#475569',
'--template1-card-icon-bg': '#e2e8f0',
'--template1-card-action': '#3b82f6',
'--template1-danger-bg': '#fff1f2',
'--template1-danger-border': '#fecaca',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(15, 23, 42, 0.12)',
},
},
{
id: 'theme-sky',
title: '스카이 라이트',
category: '색상',
description: '푸른 그라데이션과 선명한 포인트',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '24px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#f0f9ff',
'--template1-background-end': '#e0f2fe',
'--template1-surface': '#f8fdff',
'--template1-text': '#0f172a',
'--template1-subtext': '#0f3f5f',
'--template1-muted': '#1d5a83',
'--template1-border': 'rgba(12, 74, 158, 0.16)',
'--template1-topbar-start': 'rgba(238, 250, 255, 0.96)',
'--template1-topbar-end': 'rgba(238, 250, 255, 0.7)',
'--template1-topbar-text': '#075985',
'--template1-badge-bg': '#bae6fd',
'--template1-badge-text': '#0c4a6e',
'--template1-button-bg': '#bae6fd',
'--template1-button-text': '#075985',
'--template1-button-hover': 'rgba(14, 165, 233, 0.2)',
'--template1-settings-bg': '#ecfeff',
'--template1-settings-border': '#bae6fd',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#bae6fd',
'--template1-card-text': '#0f172a',
'--template1-card-subtext': '#075985',
'--template1-card-icon-bg': '#bae6fd',
'--template1-card-action': '#0284c7',
'--template1-danger-bg': '#e0f2fe',
'--template1-danger-border': '#7dd3fc',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(2, 132, 199, 0.12)',
},
},
{
id: 'theme-citrus',
title: '시트러스',
category: '색상',
description: '온건한 오렌지톤과 고명도 카드',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '20px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#fffbeb',
'--template1-background-end': '#fff7ed',
'--template1-surface': '#fffdf7',
'--template1-text': '#431407',
'--template1-subtext': '#854d0e',
'--template1-muted': '#b45309',
'--template1-border': 'rgba(180, 83, 9, 0.25)',
'--template1-topbar-start': 'rgba(255, 247, 237, 0.96)',
'--template1-topbar-end': 'rgba(255, 247, 237, 0.74)',
'--template1-topbar-text': '#92400e',
'--template1-badge-bg': '#fde68a',
'--template1-badge-text': '#92400e',
'--template1-button-bg': '#fde68a',
'--template1-button-text': '#92400e',
'--template1-button-hover': 'rgba(202, 138, 4, 0.2)',
'--template1-settings-bg': '#fffbeb',
'--template1-settings-border': '#fde68a',
'--template1-card-bg': '#fffdf7',
'--template1-card-border': '#fdba74',
'--template1-card-text': '#431407',
'--template1-card-subtext': '#7c2d12',
'--template1-card-icon-bg': '#fde68a',
'--template1-card-action': '#d97706',
'--template1-danger-bg': '#fff7ed',
'--template1-danger-border': '#fed7aa',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(180, 83, 9, 0.12)',
},
},
{
id: 'theme-forest',
title: '포레스트',
category: '색상',
description: '그린 포인트와 무광 카드',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#f0fdf4',
'--template1-background-end': '#ecfccb',
'--template1-surface': '#f0fdf4',
'--template1-text': '#052e16',
'--template1-subtext': '#166534',
'--template1-muted': '#14532d',
'--template1-border': 'rgba(34, 197, 94, 0.25)',
'--template1-topbar-start': 'rgba(240, 253, 244, 0.96)',
'--template1-topbar-end': 'rgba(236, 252, 203, 0.72)',
'--template1-topbar-text': '#166534',
'--template1-badge-bg': '#bbf7d0',
'--template1-badge-text': '#166534',
'--template1-button-bg': '#bbf7d0',
'--template1-button-text': '#166534',
'--template1-button-hover': 'rgba(22, 163, 74, 0.2)',
'--template1-settings-bg': '#f0fdf4',
'--template1-settings-border': '#bbf7d0',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#86efac',
'--template1-card-text': '#052e16',
'--template1-card-subtext': '#166534',
'--template1-card-icon-bg': '#dcfce7',
'--template1-card-action': '#16a34a',
'--template1-danger-bg': '#ecfccb',
'--template1-danger-border': '#a3e635',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(21, 128, 61, 0.12)',
},
},
{
id: 'theme-violet',
title: '바이올렛',
category: '색상',
description: '보라 기조의 고대비 강조형',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '24px',
cardRadius: '20px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#faf5ff',
'--template1-background-end': '#ede9fe',
'--template1-surface': '#fcfaff',
'--template1-text': '#312e81',
'--template1-subtext': '#5b21b6',
'--template1-muted': '#6d28d9',
'--template1-border': 'rgba(109, 40, 217, 0.25)',
'--template1-topbar-start': 'rgba(250, 245, 255, 0.96)',
'--template1-topbar-end': 'rgba(237, 233, 254, 0.7)',
'--template1-topbar-text': '#5b21b6',
'--template1-badge-bg': '#ddd6fe',
'--template1-badge-text': '#5b21b6',
'--template1-button-bg': '#ddd6fe',
'--template1-button-text': '#4c1d95',
'--template1-button-hover': 'rgba(109, 40, 217, 0.2)',
'--template1-settings-bg': '#faf5ff',
'--template1-settings-border': '#ddd6fe',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#c4b5fd',
'--template1-card-text': '#312e81',
'--template1-card-subtext': '#4c1d95',
'--template1-card-icon-bg': '#ddd6fe',
'--template1-card-action': '#6d28d9',
'--template1-danger-bg': '#f3e8ff',
'--template1-danger-border': '#c4b5fd',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(91, 33, 182, 0.12)',
},
},
{
id: 'theme-graphite',
title: '그래파이트',
category: '색상',
description: '차분한 다크 모노톤',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '20px',
cardRadius: '12px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#f8fafc',
'--template1-background-end': '#e2e8f0',
'--template1-surface': '#f1f5f9',
'--template1-text': '#0f172a',
'--template1-subtext': '#334155',
'--template1-muted': '#475569',
'--template1-border': 'rgba(71, 85, 105, 0.28)',
'--template1-topbar-start': 'rgba(241, 245, 249, 0.96)',
'--template1-topbar-end': 'rgba(226, 232, 240, 0.72)',
'--template1-topbar-text': '#0f172a',
'--template1-badge-bg': '#cbd5e1',
'--template1-badge-text': '#0f172a',
'--template1-button-bg': '#cbd5e1',
'--template1-button-text': '#0f172a',
'--template1-button-hover': 'rgba(71, 85, 105, 0.2)',
'--template1-settings-bg': '#f8fafc',
'--template1-settings-border': '#cbd5e1',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#cbd5e1',
'--template1-card-text': '#0f172a',
'--template1-card-subtext': '#334155',
'--template1-card-icon-bg': '#e2e8f0',
'--template1-card-action': '#334155',
'--template1-danger-bg': '#e2e8f0',
'--template1-danger-border': '#94a3b8',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(15, 23, 42, 0.16)',
},
},
{
id: 'theme-sunset',
title: '썬셋',
category: '색상',
description: '붉은 톤과 따뜻한 대비',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '20px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#fff1f2',
'--template1-background-end': '#ffe4e6',
'--template1-surface': '#fff7f8',
'--template1-text': '#7f1d1d',
'--template1-subtext': '#9f1239',
'--template1-muted': '#be123c',
'--template1-border': 'rgba(190, 18, 60, 0.28)',
'--template1-topbar-start': 'rgba(255, 241, 242, 0.96)',
'--template1-topbar-end': 'rgba(254, 205, 211, 0.75)',
'--template1-topbar-text': '#9f1239',
'--template1-badge-bg': '#fecdd3',
'--template1-badge-text': '#9f1239',
'--template1-button-bg': '#fecdd3',
'--template1-button-text': '#9f1239',
'--template1-button-hover': 'rgba(190, 18, 60, 0.18)',
'--template1-settings-bg': '#fff7f8',
'--template1-settings-border': '#fecdd3',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#fda4af',
'--template1-card-text': '#7f1d1d',
'--template1-card-subtext': '#9f1239',
'--template1-card-icon-bg': '#fee2e2',
'--template1-card-action': '#be123c',
'--template1-danger-bg': '#fecdd3',
'--template1-danger-border': '#fda4af',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(190, 18, 60, 0.14)',
},
},
{
id: 'theme-coral',
title: '코랄',
category: '색상',
description: '밝은 톤에 강한 액션 포인트',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#fff5f5',
'--template1-background-end': '#fce7f3',
'--template1-surface': '#fff8fb',
'--template1-text': '#831843',
'--template1-subtext': '#9d174d',
'--template1-muted': '#be185d',
'--template1-border': 'rgba(219, 39, 119, 0.25)',
'--template1-topbar-start': 'rgba(255, 245, 245, 0.96)',
'--template1-topbar-end': 'rgba(252, 231, 243, 0.72)',
'--template1-topbar-text': '#9d174d',
'--template1-badge-bg': '#f9a8d4',
'--template1-badge-text': '#9d174d',
'--template1-button-bg': '#f9a8d4',
'--template1-button-text': '#9d174d',
'--template1-button-hover': 'rgba(236, 72, 153, 0.2)',
'--template1-settings-bg': '#fff8fb',
'--template1-settings-border': '#f9a8d4',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#f9a8d4',
'--template1-card-text': '#831843',
'--template1-card-subtext': '#9d174d',
'--template1-card-icon-bg': '#fbcfe8',
'--template1-card-action': '#be185d',
'--template1-danger-bg': '#f9a8d4',
'--template1-danger-border': '#f472b6',
'--template1-danger-icon': '#be123c',
'--template1-danger-text': '#be123c',
'--template1-shadow': 'rgba(190, 24, 93, 0.15)',
},
},
{
id: 'theme-olive',
title: '올리브',
category: '색상',
description: '차분한 올리브 + 브라운의 조합',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '20px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#f7fee7',
'--template1-background-end': '#ecfccb',
'--template1-surface': '#f7fee7',
'--template1-text': '#365314',
'--template1-subtext': '#365314',
'--template1-muted': '#4d7c0f',
'--template1-border': 'rgba(77, 124, 15, 0.24)',
'--template1-topbar-start': 'rgba(247, 254, 231, 0.96)',
'--template1-topbar-end': 'rgba(236, 252, 203, 0.72)',
'--template1-topbar-text': '#365314',
'--template1-badge-bg': '#d9f99d',
'--template1-badge-text': '#365314',
'--template1-button-bg': '#d9f99d',
'--template1-button-text': '#365314',
'--template1-button-hover': 'rgba(101, 163, 13, 0.2)',
'--template1-settings-bg': '#ecfccb',
'--template1-settings-border': '#d9f99d',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#bef264',
'--template1-card-text': '#365314',
'--template1-card-subtext': '#4d7c0f',
'--template1-card-icon-bg': '#dcfce7',
'--template1-card-action': '#65a30d',
'--template1-danger-bg': '#fef9c3',
'--template1-danger-border': '#facc15',
'--template1-danger-icon': '#713f12',
'--template1-danger-text': '#713f12',
'--template1-shadow': 'rgba(101, 163, 13, 0.14)',
},
},
{
id: 'theme-mint',
title: '민트',
category: '색상',
description: '초록빛이 얇은 차트와 함께하는 쿨 톤',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#f0fdfa',
'--template1-background-end': '#ccfbf1',
'--template1-surface': '#f0fdf4',
'--template1-text': '#115e59',
'--template1-subtext': '#115e59',
'--template1-muted': '#0f766e',
'--template1-border': 'rgba(13, 148, 136, 0.26)',
'--template1-topbar-start': 'rgba(240, 253, 250, 0.96)',
'--template1-topbar-end': 'rgba(204, 251, 241, 0.72)',
'--template1-topbar-text': '#115e59',
'--template1-badge-bg': '#99f6e4',
'--template1-badge-text': '#115e59',
'--template1-button-bg': '#99f6e4',
'--template1-button-text': '#115e59',
'--template1-button-hover': 'rgba(13, 148, 136, 0.2)',
'--template1-settings-bg': '#f0fdfa',
'--template1-settings-border': '#99f6e4',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#5eead4',
'--template1-card-text': '#115e59',
'--template1-card-subtext': '#0f766e',
'--template1-card-icon-bg': '#99f6e4',
'--template1-card-action': '#0f766e',
'--template1-danger-bg': '#ccfbf1',
'--template1-danger-border': '#5eead4',
'--template1-danger-icon': '#115e59',
'--template1-danger-text': '#115e59',
'--template1-shadow': 'rgba(13, 148, 136, 0.13)',
},
},
{
id: 'theme-ocean',
title: '오션',
category: '색상',
description: '진한 블루와 선명한 대비 포인트',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '20px',
cardRadius: '17px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#eff6ff',
'--template1-background-end': '#dbeafe',
'--template1-surface': '#f1f5ff',
'--template1-text': '#1e3a8a',
'--template1-subtext': '#1d4ed8',
'--template1-muted': '#312e81',
'--template1-border': 'rgba(30, 64, 175, 0.22)',
'--template1-topbar-start': 'rgba(219, 234, 254, 0.96)',
'--template1-topbar-end': 'rgba(191, 219, 254, 0.72)',
'--template1-topbar-text': '#1d4ed8',
'--template1-badge-bg': '#dbeafe',
'--template1-badge-text': '#1d4ed8',
'--template1-button-bg': '#dbeafe',
'--template1-button-text': '#1d4ed8',
'--template1-button-hover': 'rgba(37, 99, 235, 0.16)',
'--template1-settings-bg': '#eff6ff',
'--template1-settings-border': '#bfdbfe',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#93c5fd',
'--template1-card-text': '#1e3a8a',
'--template1-card-subtext': '#1e40af',
'--template1-card-icon-bg': '#dbeafe',
'--template1-card-action': '#2563eb',
'--template1-danger-bg': '#dbeafe',
'--template1-danger-border': '#93c5fd',
'--template1-danger-icon': '#1d4ed8',
'--template1-danger-text': '#1d4ed8',
'--template1-shadow': 'rgba(30, 64, 175, 0.12)',
},
},
{
id: 'theme-berry',
title: '버리',
category: '색상',
description: '보라 계열과 높은 대비로 강조',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '11px',
padding: '14px',
shellRadius: '21px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#faf5ff',
'--template1-background-end': '#f5d0fe',
'--template1-surface': '#fdf2ff',
'--template1-text': '#4c1d95',
'--template1-subtext': '#6b21a8',
'--template1-muted': '#7e22ce',
'--template1-border': 'rgba(109, 40, 217, 0.24)',
'--template1-topbar-start': 'rgba(250, 245, 255, 0.96)',
'--template1-topbar-end': 'rgba(245, 208, 254, 0.72)',
'--template1-topbar-text': '#6b21a8',
'--template1-badge-bg': '#e9d5ff',
'--template1-badge-text': '#5b21b6',
'--template1-button-bg': '#e9d5ff',
'--template1-button-text': '#5b21b6',
'--template1-button-hover': 'rgba(124, 58, 237, 0.18)',
'--template1-settings-bg': '#faf5ff',
'--template1-settings-border': '#e9d5ff',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#ddd6fe',
'--template1-card-text': '#4c1d95',
'--template1-card-subtext': '#6d28d9',
'--template1-card-icon-bg': '#e9d5ff',
'--template1-card-action': '#6d28d9',
'--template1-danger-bg': '#f5d0fe',
'--template1-danger-border': '#f0abfc',
'--template1-danger-icon': '#6b21a8',
'--template1-danger-text': '#6b21a8',
'--template1-shadow': 'rgba(109, 40, 217, 0.14)',
},
},
{
id: 'theme-latte',
title: '라떼',
category: '색상',
description: '따뜻한 라이트 톤과 부드러운 대비',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '11px',
padding: '14px',
shellRadius: '20px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#fff7ed',
'--template1-background-end': '#ffedd5',
'--template1-surface': '#fffbeb',
'--template1-text': '#7c2d12',
'--template1-subtext': '#9a3412',
'--template1-muted': '#c2410c',
'--template1-border': 'rgba(194, 65, 12, 0.2)',
'--template1-topbar-start': 'rgba(255, 247, 237, 0.96)',
'--template1-topbar-end': 'rgba(254, 215, 170, 0.7)',
'--template1-topbar-text': '#9a3412',
'--template1-badge-bg': '#fed7aa',
'--template1-badge-text': '#9a3412',
'--template1-button-bg': '#fed7aa',
'--template1-button-text': '#9a3412',
'--template1-button-hover': 'rgba(234, 88, 12, 0.22)',
'--template1-settings-bg': '#fff7ed',
'--template1-settings-border': '#fdba74',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#fdba74',
'--template1-card-text': '#7c2d12',
'--template1-card-subtext': '#9a3412',
'--template1-card-icon-bg': '#fed7aa',
'--template1-card-action': '#ea580c',
'--template1-danger-bg': '#ffedd5',
'--template1-danger-border': '#fdba74',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(234, 88, 12, 0.14)',
},
},
{
id: 'theme-choco',
title: '초코',
category: '색상',
description: '다크 베이스에 단단한 하이라이트',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '11px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#262626',
'--template1-background-end': '#3f3f46',
'--template1-surface': '#1f2937',
'--template1-text': '#f8fafc',
'--template1-subtext': '#cbd5e1',
'--template1-muted': '#94a3b8',
'--template1-border': 'rgba(148, 163, 184, 0.35)',
'--template1-topbar-start': 'rgba(31, 41, 55, 0.96)',
'--template1-topbar-end': 'rgba(55, 65, 81, 0.75)',
'--template1-topbar-text': '#f8fafc',
'--template1-badge-bg': '#334155',
'--template1-badge-text': '#f8fafc',
'--template1-button-bg': '#334155',
'--template1-button-text': '#f8fafc',
'--template1-button-hover': 'rgba(148, 163, 184, 0.24)',
'--template1-settings-bg': '#262626',
'--template1-settings-border': '#475569',
'--template1-card-bg': '#1e293b',
'--template1-card-border': '#64748b',
'--template1-card-text': '#f8fafc',
'--template1-card-subtext': '#cbd5e1',
'--template1-card-icon-bg': '#334155',
'--template1-card-action': '#93c5fd',
'--template1-danger-bg': 'rgba(148, 163, 184, 0.18)',
'--template1-danger-border': '#64748b',
'--template1-danger-icon': '#fca5a5',
'--template1-danger-text': '#fca5a5',
'--template1-shadow': 'rgba(15, 23, 42, 0.45)',
},
},
{
id: 'theme-aurora',
title: '오로라',
category: '색상',
description: '차갑고 선명한 푸른 계열',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#eff6ff',
'--template1-background-end': '#dbeafe',
'--template1-surface': '#eef2ff',
'--template1-text': '#312e81',
'--template1-subtext': '#1d4ed8',
'--template1-muted': '#4338ca',
'--template1-border': 'rgba(59, 130, 246, 0.28)',
'--template1-topbar-start': 'rgba(238, 242, 255, 0.96)',
'--template1-topbar-end': 'rgba(224, 242, 254, 0.76)',
'--template1-topbar-text': '#312e81',
'--template1-badge-bg': '#bfdbfe',
'--template1-badge-text': '#312e81',
'--template1-button-bg': '#bfdbfe',
'--template1-button-text': '#312e81',
'--template1-button-hover': 'rgba(59, 130, 246, 0.2)',
'--template1-settings-bg': '#eff6ff',
'--template1-settings-border': '#bfdbfe',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#93c5fd',
'--template1-card-text': '#312e81',
'--template1-card-subtext': '#1e40af',
'--template1-card-icon-bg': '#dbeafe',
'--template1-card-action': '#2563eb',
'--template1-danger-bg': '#dbeafe',
'--template1-danger-border': '#93c5fd',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(30, 64, 175, 0.18)',
},
},
{
id: 'theme-electric',
title: '일렉트릭',
category: '색상',
description: '강한 대비의 보라·청록 조합',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '20px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#312e81',
'--template1-background-end': '#0f172a',
'--template1-surface': '#1e1b4b',
'--template1-text': '#f8fafc',
'--template1-subtext': '#cbd5e1',
'--template1-muted': '#a5b4fc',
'--template1-border': 'rgba(168, 85, 247, 0.36)',
'--template1-topbar-start': 'rgba(30, 27, 75, 0.96)',
'--template1-topbar-end': 'rgba(15, 23, 42, 0.78)',
'--template1-topbar-text': '#f8fafc',
'--template1-badge-bg': '#5b21b6',
'--template1-badge-text': '#f8fafc',
'--template1-button-bg': '#5b21b6',
'--template1-button-text': '#f8fafc',
'--template1-button-hover': 'rgba(147, 51, 234, 0.24)',
'--template1-settings-bg': '#312e81',
'--template1-settings-border': '#7c3aed',
'--template1-card-bg': '#0f172a',
'--template1-card-border': '#6d28d9',
'--template1-card-text': '#f8fafc',
'--template1-card-subtext': '#c7d2fe',
'--template1-card-icon-bg': '#312e81',
'--template1-card-action': '#22d3ee',
'--template1-danger-bg': '#1e293b',
'--template1-danger-border': '#7c3aed',
'--template1-danger-icon': '#fca5a5',
'--template1-danger-text': '#fca5a5',
'--template1-shadow': 'rgba(17, 24, 39, 0.58)',
},
},
{
id: 'theme-tide',
title: '타이드',
category: '색상',
description: '청록 바다 톤의 부드러운 잔향',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#f0fdfa',
'--template1-background-end': '#cffafe',
'--template1-surface': '#ecfeff',
'--template1-text': '#0f766e',
'--template1-subtext': '#0f766e',
'--template1-muted': '#115e59',
'--template1-border': 'rgba(13, 148, 136, 0.24)',
'--template1-topbar-start': 'rgba(236, 254, 255, 0.96)',
'--template1-topbar-end': 'rgba(207, 250, 254, 0.72)',
'--template1-topbar-text': '#0f766e',
'--template1-badge-bg': '#99f6e4',
'--template1-badge-text': '#134e4a',
'--template1-button-bg': '#99f6e4',
'--template1-button-text': '#134e4a',
'--template1-button-hover': 'rgba(13, 148, 136, 0.2)',
'--template1-settings-bg': '#ecfeff',
'--template1-settings-border': '#5eead4',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#2dd4bf',
'--template1-card-text': '#0f766e',
'--template1-card-subtext': '#115e59',
'--template1-card-icon-bg': '#a7f3d0',
'--template1-card-action': '#0d9488',
'--template1-danger-bg': '#cffafe',
'--template1-danger-border': '#2dd4bf',
'--template1-danger-icon': '#b91c1c',
'--template1-danger-text': '#b91c1c',
'--template1-shadow': 'rgba(13, 148, 136, 0.14)',
},
},
{
id: 'theme-peach-night',
title: '피치 나이트',
category: '색상',
description: '핑크와 다크 브라운의 조용한 대비',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '11px',
padding: '14px',
shellRadius: '21px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#2d1a33',
'--template1-background-end': '#4c1d95',
'--template1-surface': '#2e1065',
'--template1-text': '#fdf2f8',
'--template1-subtext': '#f5d0fe',
'--template1-muted': '#e9d5ff',
'--template1-border': 'rgba(232, 121, 249, 0.3)',
'--template1-topbar-start': 'rgba(46, 16, 101, 0.96)',
'--template1-topbar-end': 'rgba(124, 58, 237, 0.74)',
'--template1-topbar-text': '#fdf2f8',
'--template1-badge-bg': '#c026d3',
'--template1-badge-text': '#fdf2f8',
'--template1-button-bg': '#c026d3',
'--template1-button-text': '#fdf2f8',
'--template1-button-hover': 'rgba(236, 72, 153, 0.24)',
'--template1-settings-bg': '#312e81',
'--template1-settings-border': '#a855f7',
'--template1-card-bg': '#1e1b4b',
'--template1-card-border': '#c026d3',
'--template1-card-text': '#fdf2f8',
'--template1-card-subtext': '#ddd6fe',
'--template1-card-icon-bg': '#4c1d95',
'--template1-card-action': '#fb7185',
'--template1-danger-bg': '#2e1065',
'--template1-danger-border': '#c026d3',
'--template1-danger-icon': '#fca5a5',
'--template1-danger-text': '#fca5a5',
'--template1-shadow': 'rgba(30, 27, 75, 0.52)',
},
},
{
id: 'theme-graphite-cool',
title: '쿨 그래파이트',
category: '색상',
description: '은은한 회색계열에 청색 포인트',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '11px',
padding: '14px',
shellRadius: '20px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#111827',
'--template1-background-end': '#1f2937',
'--template1-surface': '#0f172a',
'--template1-text': '#e2e8f0',
'--template1-subtext': '#cbd5e1',
'--template1-muted': '#94a3b8',
'--template1-border': 'rgba(148, 163, 184, 0.3)',
'--template1-topbar-start': 'rgba(15, 23, 42, 0.96)',
'--template1-topbar-end': 'rgba(30, 41, 59, 0.76)',
'--template1-topbar-text': '#f8fafc',
'--template1-badge-bg': '#334155',
'--template1-badge-text': '#f8fafc',
'--template1-button-bg': '#334155',
'--template1-button-text': '#f8fafc',
'--template1-button-hover': 'rgba(148, 163, 184, 0.24)',
'--template1-settings-bg': '#111827',
'--template1-settings-border': '#475569',
'--template1-card-bg': '#1e293b',
'--template1-card-border': '#64748b',
'--template1-card-text': '#e2e8f0',
'--template1-card-subtext': '#d1d5db',
'--template1-card-icon-bg': '#334155',
'--template1-card-action': '#67e8f9',
'--template1-danger-bg': '#334155',
'--template1-danger-border': '#64748b',
'--template1-danger-icon': '#fda4af',
'--template1-danger-text': '#fda4af',
'--template1-shadow': 'rgba(15, 23, 42, 0.58)',
},
},
{
id: 'theme-ember',
title: '엠버',
category: '색상',
description: '따뜻한 주황과 카카오 브라운',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#431407',
'--template1-background-end': '#7c2d12',
'--template1-surface': '#3b1905',
'--template1-text': '#fef3c7',
'--template1-subtext': '#fed7aa',
'--template1-muted': '#fdba74',
'--template1-border': 'rgba(180, 83, 9, 0.32)',
'--template1-topbar-start': 'rgba(59, 25, 5, 0.96)',
'--template1-topbar-end': 'rgba(124, 45, 18, 0.75)',
'--template1-topbar-text': '#fef3c7',
'--template1-badge-bg': '#b45309',
'--template1-badge-text': '#fef3c7',
'--template1-button-bg': '#b45309',
'--template1-button-text': '#fef3c7',
'--template1-button-hover': 'rgba(217, 119, 6, 0.24)',
'--template1-settings-bg': '#431407',
'--template1-settings-border': '#d97706',
'--template1-card-bg': '#7c2d12',
'--template1-card-border': '#f59e0b',
'--template1-card-text': '#fef3c7',
'--template1-card-subtext': '#fed7aa',
'--template1-card-icon-bg': '#92400e',
'--template1-card-action': '#fcd34d',
'--template1-danger-bg': '#3b1905',
'--template1-danger-border': '#d97706',
'--template1-danger-icon': '#fca5a5',
'--template1-danger-text': '#fca5a5',
'--template1-shadow': 'rgba(69, 26, 3, 0.52)',
},
},
{
id: 'theme-small-text',
title: '폰트 소형',
category: '폰트',
description: '요소 간 간격은 유지, 텍스트만 축소',
fontScale: 0.88,
cardColumns: 3,
homeColumns: 1,
gap: '11px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '80px',
cardIconSize: '36px',
chipGap: '4px',
cssVars: TEMPLATE1_DEFAULT_THEME_VARIABLES,
},
{
id: 'theme-base-text',
title: '폰트 기본',
category: '폰트',
description: '기본 텍스트 스케일 유지',
fontScale: 1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: TEMPLATE1_DEFAULT_THEME_VARIABLES,
},
{
id: 'theme-large-text',
title: '폰트 대형',
category: '폰트',
description: '가독성 중심 대형 타이포',
fontScale: 1.18,
cardColumns: 3,
homeColumns: 1,
gap: '13px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '86px',
cardIconSize: '40px',
chipGap: '4px',
cssVars: TEMPLATE1_DEFAULT_THEME_VARIABLES,
},
{
id: 'theme-xsmall-text',
title: '폰트 초소형',
category: '폰트',
description: '많은 항목을 한 화면에 넣고 싶을 때',
fontScale: 0.75,
cardColumns: 4,
homeColumns: 1,
gap: '10px',
padding: '12px',
shellRadius: '18px',
cardRadius: '14px',
cardMinHeight: '72px',
cardIconSize: '30px',
chipGap: '3px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-grid-template': 'auto 1fr auto',
'--template1-card-max-height': '86px',
},
},
{
id: 'theme-reading-text',
title: '폰트 리딩',
category: '폰트',
description: '긴 문장을 안정적으로 읽기 좋은 배율',
fontScale: 1.12,
cardColumns: 3,
homeColumns: 1,
gap: '13px',
padding: '14px',
shellRadius: '22px',
cardRadius: '20px',
cardMinHeight: '88px',
cardIconSize: '40px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-max-height': '122px',
},
},
{
id: 'theme-comfort-text',
title: '폰트 편안',
category: '폰트',
description: '장문 콘텐츠 위주의 완만한 가독성',
fontScale: 1.04,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '86px',
cardIconSize: '39px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-home-item-padding': '15px',
'--template1-home-item-padding-inline': '16px',
},
},
{
id: 'theme-comfortable-readable-text',
title: '폰트 라운드',
category: '폰트',
description: '긴 문장을 위해 라운드 라벨/여백 강화',
fontScale: 1.06,
cardColumns: 3,
homeColumns: 2,
gap: '11px',
padding: '12px',
shellRadius: '20px',
cardRadius: '16px',
cardMinHeight: '78px',
cardIconSize: '35px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-max-height': '112px',
},
},
{
id: 'theme-micro-text',
title: '폰트 마이크로',
category: '폰트',
description: '컴팩트한 인터페이스에서 정보 밀도 강화',
fontScale: 0.84,
cardColumns: 4,
homeColumns: 1,
gap: '9px',
padding: '11px',
shellRadius: '16px',
cardRadius: '14px',
cardMinHeight: '70px',
cardIconSize: '30px',
chipGap: '3px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-max-height': '88px',
'--template1-card-grid-template': 'auto 1fr auto',
},
},
{
id: 'theme-cursor-text',
title: '폰트 리듬',
category: '폰트',
description: '제목과 본문의 대비를 더 분명하게',
fontScale: 1.1,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '13px',
shellRadius: '20px',
cardRadius: '16px',
cardMinHeight: '86px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-max-height': '118px',
},
},
{
id: 'theme-compact-structure',
title: '구조 컴팩트',
category: '구조',
description: '카드 폭은 줄이고 여백을 촘촘히',
fontScale: 1,
cardColumns: 2,
homeColumns: 2,
gap: '9px',
padding: '10px',
shellRadius: '16px',
cardRadius: '12px',
cardMinHeight: '72px',
cardIconSize: '32px',
chipGap: '3px',
cssVars: TEMPLATE1_DEFAULT_THEME_VARIABLES,
},
{
id: 'theme-wide-structure',
title: '구조 와이드',
category: '구조',
description: '큰 카드와 넓은 패딩으로 시인성 강화',
fontScale: 1,
cardColumns: 2,
homeColumns: 1,
gap: '14px',
padding: '16px',
shellRadius: '26px',
cardRadius: '20px',
cardMinHeight: '92px',
cardIconSize: '42px',
chipGap: '5px',
cssVars: TEMPLATE1_DEFAULT_THEME_VARIABLES,
},
{
id: 'theme-list-structure',
title: '구조 리스트',
category: '구조',
description: '목록형 단일 카드를 우선 노출',
fontScale: 1,
cardColumns: 1,
homeColumns: 1,
gap: '10px',
padding: '12px',
shellRadius: '18px',
cardRadius: '14px',
cardMinHeight: '78px',
cardIconSize: '34px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-bg': '#f8fafc',
'--template1-card-border': '#cbd5e1',
'--template1-topbar-text': '#475569',
},
},
{
id: 'theme-focus-structure',
title: '구조 포커스',
category: '구조',
description: '중요 항목의 우선순위를 카드 크기로 표현',
fontScale: 1,
cardColumns: 2,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '96px',
cardIconSize: '40px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#c7d2fe',
'--template1-card-action': '#4f46e5',
'--template1-card-icon-bg': '#e0e7ff',
},
},
{
id: 'theme-dense-grid-structure',
title: '구조 더블그리드',
category: '구조',
description: '카드 수를 급증시키는 촘촘한 4열 배치',
fontScale: 1,
cardColumns: 4,
homeColumns: 2,
gap: '8px',
padding: '10px',
shellRadius: '14px',
cardRadius: '12px',
cardMinHeight: '64px',
cardIconSize: '30px',
chipGap: '3px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-max-height': '84px',
},
},
{
id: 'theme-stagger-structure',
title: '구조 스태거',
category: '구조',
description: '텍스트 길이 우선 배열과 액션 하단 배치',
fontScale: 1,
cardColumns: 2,
homeColumns: 2,
gap: '12px',
padding: '12px',
shellRadius: '20px',
cardRadius: '16px',
cardMinHeight: '94px',
cardIconSize: '36px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-grid-template': '1fr',
'--template1-card-action-column': '1 / -1',
'--template1-card-action-justify': 'stretch',
'--template1-card-max-height': '130px',
'--template1-home-item-layout': 'auto minmax(0, 1fr)',
'--template1-home-item-gap': '10px',
},
},
{
id: 'theme-split-card-structure',
title: '구조 카드분할',
category: '구조',
description: '상세는 크게, 아이콘은 작게 배치',
fontScale: 1,
cardColumns: 2,
homeColumns: 1,
gap: '13px',
padding: '12px',
shellRadius: '24px',
cardRadius: '18px',
cardMinHeight: '96px',
cardIconSize: '42px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-grid-template': 'auto 1fr',
'--template1-card-action-column': '1 / -1',
'--template1-card-action-justify': 'flex-end',
'--template1-home-item-padding': '14px',
'--template1-home-item-padding-inline': '16px',
},
},
{
id: 'theme-stats-column-structure',
title: '구조 통계열',
category: '구조',
description: '항목 수가 많을 때 가독성을 위한 넓은 좌우 여백',
fontScale: 1,
cardColumns: 1,
homeColumns: 2,
gap: '14px',
padding: '16px',
shellRadius: '18px',
cardRadius: '14px',
cardMinHeight: '82px',
cardIconSize: '36px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-grid-template': 'auto 1fr auto',
'--template1-card-action-column': '3 / 3',
'--template1-home-item-layout': 'auto minmax(0, 1fr) auto',
},
},
{
id: 'theme-mosaic-structure',
title: '구조 모자이크',
category: '구조',
description: '작은 카드와 넓은 화면에서 분산 배치',
fontScale: 1,
cardColumns: 3,
homeColumns: 2,
gap: '8px',
padding: '10px',
shellRadius: '16px',
cardRadius: '12px',
cardMinHeight: '68px',
cardIconSize: '30px',
chipGap: '3px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-grid-template': 'auto 1fr',
'--template1-card-action-column': '1 / -1',
'--template1-card-action-justify': 'start',
'--template1-card-max-height': '98px',
'--template1-home-item-layout': 'auto minmax(0, 1fr)',
'--template1-home-item-gap': '8px',
'--template1-home-item-padding': '11px',
'--template1-home-item-padding-inline': '12px',
},
},
{
id: 'theme-timeline-structure',
title: '구조 타임라인',
category: '구조',
description: '목록처럼 수직이 강조되는 정렬',
fontScale: 1,
cardColumns: 2,
homeColumns: 1,
gap: '13px',
padding: '14px',
shellRadius: '22px',
cardRadius: '17px',
cardMinHeight: '90px',
cardIconSize: '37px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-grid-template': '1fr',
'--template1-card-action-column': '1 / -1',
'--template1-card-action-justify': 'flex-start',
'--template1-card-max-height': '118px',
},
},
{
id: 'theme-dashboard-structure',
title: '구조 대시보드',
category: '구조',
description: '카드 간격을 크게 잡아 분석 레이아웃에 최적화',
fontScale: 1,
cardColumns: 3,
homeColumns: 2,
gap: '10px',
padding: '12px',
shellRadius: '20px',
cardRadius: '14px',
cardMinHeight: '76px',
cardIconSize: '33px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-grid-template': 'auto minmax(0, 1.2fr) auto',
'--template1-card-action-column': '3 / 3',
'--template1-card-action-justify': 'end',
'--template1-card-bg': '#ffffff',
'--template1-card-border': '#bfdbfe',
'--template1-card-max-height': '104px',
},
},
{
id: 'theme-kanban-structure',
title: '구조 칸반',
category: '구조',
description: '작업 보드형 배치에서 항목 구분이 뚜렷',
fontScale: 1,
cardColumns: 2,
homeColumns: 1,
gap: '12px',
padding: '12px',
shellRadius: '22px',
cardRadius: '16px',
cardMinHeight: '88px',
cardIconSize: '36px',
chipGap: '4px',
cssVars: {
...TEMPLATE1_DEFAULT_THEME_VARIABLES,
'--template1-card-grid-template': 'minmax(0, 1.2fr) auto',
'--template1-card-action-column': '1 / -1',
'--template1-card-action-justify': 'stretch',
'--template1-home-item-layout': 'auto minmax(0, 1fr)',
'--template1-home-item-gap': '10px',
'--template1-card-max-height': '124px',
},
},
{
id: 'theme-glass-accent',
title: '글래스 믹스',
category: '혼합',
description: '잔잔한 색상과 폰트 강조를 함께 적용',
fontScale: 1.06,
cardColumns: 3,
homeColumns: 1,
gap: '13px',
padding: '15px',
shellRadius: '24px',
cardRadius: '17px',
cardMinHeight: '86px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#111827',
'--template1-background-end': '#0b1220',
'--template1-surface': '#1f2937',
'--template1-text': '#f8fafc',
'--template1-subtext': '#cbd5e1',
'--template1-muted': '#94a3b8',
'--template1-border': 'rgba(148, 163, 184, 0.36)',
'--template1-topbar-start': '#334155',
'--template1-topbar-end': '#1f2937',
'--template1-topbar-text': '#f8fafc',
'--template1-badge-bg': 'rgba(148, 163, 184, 0.28)',
'--template1-badge-text': '#e2e8f0',
'--template1-button-bg': 'rgba(148, 163, 184, 0.3)',
'--template1-button-text': '#f8fafc',
'--template1-button-hover': 'rgba(148, 163, 184, 0.5)',
'--template1-settings-bg': '#111827',
'--template1-settings-border': 'rgba(148, 163, 184, 0.5)',
'--template1-card-bg': '#111827',
'--template1-card-border': 'rgba(148, 163, 184, 0.42)',
'--template1-card-text': '#f8fafc',
'--template1-card-subtext': '#cbd5e1',
'--template1-card-icon-bg': 'rgba(148, 163, 184, 0.28)',
'--template1-card-action': '#93c5fd',
'--template1-danger-bg': 'rgba(220, 38, 38, 0.18)',
'--template1-danger-border': 'rgba(220, 38, 38, 0.38)',
'--template1-danger-icon': '#fca5a5',
'--template1-danger-text': '#fecdd3',
'--template1-shadow': 'rgba(2, 6, 23, 0.4)',
},
},
{
id: 'theme-soft-glass',
title: '글래스 라이트',
category: '혼합',
description: '차분한 블루 톤의 부드러운 글래스 텍스처',
fontScale: 1.02,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#f8fafc',
'--template1-background-end': '#dbeafe',
'--template1-surface': '#eef2ff',
'--template1-text': '#1e3a8a',
'--template1-subtext': '#334155',
'--template1-muted': '#64748b',
'--template1-border': 'rgba(96, 165, 250, 0.36)',
'--template1-topbar-start': '#dbeafe',
'--template1-topbar-end': '#bfdbfe',
'--template1-topbar-text': '#1e3a8a',
'--template1-badge-bg': 'rgba(96, 165, 250, 0.2)',
'--template1-badge-text': '#1d4ed8',
'--template1-button-bg': 'rgba(96, 165, 250, 0.28)',
'--template1-button-text': '#1d4ed8',
'--template1-button-hover': 'rgba(59, 130, 246, 0.25)',
'--template1-settings-bg': '#eef2ff',
'--template1-settings-border': 'rgba(96, 165, 250, 0.45)',
'--template1-card-bg': '#ffffff',
'--template1-card-border': 'rgba(147, 197, 253, 0.6)',
'--template1-card-text': '#1e3a8a',
'--template1-card-subtext': '#334155',
'--template1-card-icon-bg': 'rgba(191, 219, 254, 0.65)',
'--template1-card-action': '#2563eb',
'--template1-danger-bg': 'rgba(96, 165, 250, 0.12)',
'--template1-danger-border': 'rgba(96, 165, 250, 0.45)',
'--template1-danger-icon': '#1d4ed8',
'--template1-danger-text': '#1d4ed8',
'--template1-shadow': 'rgba(30, 58, 138, 0.16)',
},
},
{
id: 'theme-glass-ink',
title: '글래스 인크',
category: '혼합',
description: '연한 잉크톤과 다크 텍스트 강조',
fontScale: 1.02,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '22px',
cardRadius: '16px',
cardMinHeight: '84px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#020617',
'--template1-background-end': '#0f172a',
'--template1-surface': '#111827',
'--template1-text': '#e2e8f0',
'--template1-subtext': '#94a3b8',
'--template1-muted': '#cbd5e1',
'--template1-border': 'rgba(148, 163, 184, 0.3)',
'--template1-topbar-start': '#0b1120',
'--template1-topbar-end': '#1e293b',
'--template1-topbar-text': '#e2e8f0',
'--template1-badge-bg': 'rgba(148, 163, 184, 0.18)',
'--template1-badge-text': '#e2e8f0',
'--template1-button-bg': 'rgba(148, 163, 184, 0.2)',
'--template1-button-text': '#e2e8f0',
'--template1-button-hover': 'rgba(148, 163, 184, 0.38)',
'--template1-settings-bg': '#020617',
'--template1-settings-border': 'rgba(148, 163, 184, 0.34)',
'--template1-card-bg': '#111827',
'--template1-card-border': 'rgba(148, 163, 184, 0.4)',
'--template1-card-text': '#e2e8f0',
'--template1-card-subtext': '#cbd5e1',
'--template1-card-icon-bg': 'rgba(148, 163, 184, 0.25)',
'--template1-card-action': '#93c5fd',
'--template1-danger-bg': 'rgba(248, 113, 113, 0.16)',
'--template1-danger-border': 'rgba(248, 113, 113, 0.36)',
'--template1-danger-icon': '#fca5a5',
'--template1-danger-text': '#fecdd3',
'--template1-shadow': 'rgba(2, 6, 23, 0.45)',
},
},
{
id: 'theme-sunrise-glow',
title: '새벽 네온',
category: '혼합',
description: '라이트/다크 대비를 넘나드는 글래스 하이라이트',
fontScale: 1.08,
cardColumns: 2,
homeColumns: 1,
gap: '12px',
padding: '13px',
shellRadius: '24px',
cardRadius: '18px',
cardMinHeight: '88px',
cardIconSize: '39px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#0f172a',
'--template1-background-end': '#1e1b4b',
'--template1-surface': '#1e293b',
'--template1-text': '#f8fafc',
'--template1-subtext': '#93c5fd',
'--template1-muted': '#c7d2fe',
'--template1-border': 'rgba(167, 139, 250, 0.34)',
'--template1-topbar-start': '#1f2937',
'--template1-topbar-end': '#312e81',
'--template1-topbar-text': '#f8fafc',
'--template1-badge-bg': 'rgba(129, 140, 248, 0.22)',
'--template1-badge-text': '#ddd6fe',
'--template1-button-bg': 'rgba(129, 140, 248, 0.22)',
'--template1-button-text': '#ddd6fe',
'--template1-button-hover': 'rgba(129, 140, 248, 0.32)',
'--template1-settings-bg': '#0f172a',
'--template1-settings-border': 'rgba(129, 140, 248, 0.34)',
'--template1-card-bg': '#1e293b',
'--template1-card-border': 'rgba(165, 180, 252, 0.42)',
'--template1-card-text': '#f8fafc',
'--template1-card-subtext': '#c7d2fe',
'--template1-card-icon-bg': 'rgba(129, 140, 248, 0.2)',
'--template1-card-action': '#c4b5fd',
'--template1-danger-bg': 'rgba(129, 140, 248, 0.15)',
'--template1-danger-border': 'rgba(129, 140, 248, 0.34)',
'--template1-danger-icon': '#fda4af',
'--template1-danger-text': '#fda4af',
'--template1-shadow': 'rgba(30, 27, 75, 0.45)',
},
},
{
id: 'theme-slate-readability',
title: '슬레이트 가독성',
category: '혼합',
description: '차분한 다크톤에 중간 글꼴 스케일을 결합한 실사용형',
fontScale: 1.01,
cardColumns: 2,
homeColumns: 1,
gap: '13px',
padding: '14px',
shellRadius: '22px',
cardRadius: '16px',
cardMinHeight: '86px',
cardIconSize: '37px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#0f172a',
'--template1-background-end': '#0b1326',
'--template1-surface': '#1f2937',
'--template1-text': '#e2e8f0',
'--template1-subtext': '#94a3b8',
'--template1-muted': '#cbd5e1',
'--template1-border': 'rgba(148, 163, 184, 0.36)',
'--template1-topbar-start': '#1f2937',
'--template1-topbar-end': '#0f172a',
'--template1-topbar-text': '#e2e8f0',
'--template1-badge-bg': 'rgba(148, 163, 184, 0.25)',
'--template1-badge-text': '#f8fafc',
'--template1-button-bg': 'rgba(148, 163, 184, 0.24)',
'--template1-button-text': '#f8fafc',
'--template1-button-hover': 'rgba(148, 163, 184, 0.45)',
'--template1-settings-bg': '#111827',
'--template1-settings-border': 'rgba(148, 163, 184, 0.5)',
'--template1-card-bg': '#1f2937',
'--template1-card-border': 'rgba(148, 163, 184, 0.38)',
'--template1-card-text': '#e2e8f0',
'--template1-card-subtext': '#cbd5e1',
'--template1-card-icon-bg': 'rgba(148, 163, 184, 0.22)',
'--template1-card-action': '#93c5fd',
'--template1-danger-bg': 'rgba(248, 113, 113, 0.16)',
'--template1-danger-border': 'rgba(248, 113, 113, 0.36)',
'--template1-danger-icon': '#fca5a5',
'--template1-danger-text': '#fecdd3',
'--template1-shadow': 'rgba(2, 6, 23, 0.44)',
},
},
{
id: 'theme-ocean-office',
title: '오션 오피스',
category: '혼합',
description: '차분한 시안 계열에 넓은 카드 텍스트 정렬을 추가한 업무형',
fontScale: 1.03,
cardColumns: 2,
homeColumns: 2,
gap: '12px',
padding: '13px',
shellRadius: '24px',
cardRadius: '16px',
cardMinHeight: '88px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#e0f2fe',
'--template1-background-end': '#bae6fd',
'--template1-surface': '#f1f5f9',
'--template1-text': '#0f172a',
'--template1-subtext': '#334155',
'--template1-muted': '#475569',
'--template1-border': 'rgba(14, 116, 144, 0.28)',
'--template1-topbar-start': '#e0f2fe',
'--template1-topbar-end': '#bae6fd',
'--template1-topbar-text': '#0c4a6e',
'--template1-badge-bg': 'rgba(14, 116, 144, 0.22)',
'--template1-badge-text': '#0c4a6e',
'--template1-button-bg': 'rgba(14, 116, 144, 0.24)',
'--template1-button-text': '#0c4a6e',
'--template1-button-hover': 'rgba(14, 116, 144, 0.3)',
'--template1-settings-bg': '#f8fafc',
'--template1-settings-border': 'rgba(14, 116, 144, 0.45)',
'--template1-card-bg': '#ffffff',
'--template1-card-border': 'rgba(14, 165, 233, 0.35)',
'--template1-card-text': '#0f172a',
'--template1-card-subtext': '#334155',
'--template1-card-icon-bg': 'rgba(14, 165, 233, 0.2)',
'--template1-card-action': '#0284c7',
'--template1-danger-bg': 'rgba(14, 116, 144, 0.12)',
'--template1-danger-border': 'rgba(14, 116, 144, 0.35)',
'--template1-danger-icon': '#0369a1',
'--template1-danger-text': '#0369a1',
'--template1-shadow': 'rgba(14, 116, 144, 0.18)',
},
},
{
id: 'theme-sunset-contrast',
title: '선셋 강세',
category: '혼합',
description: '낮은 채도의 주황/네온 대비를 통한 즉시 인지형 구성',
fontScale: 1.04,
cardColumns: 2,
homeColumns: 1,
gap: '11px',
padding: '13px',
shellRadius: '22px',
cardRadius: '18px',
cardMinHeight: '84px',
cardIconSize: '37px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#fde68a',
'--template1-background-end': '#ffedd5',
'--template1-surface': '#fff7ed',
'--template1-text': '#7c2d12',
'--template1-subtext': '#9a3412',
'--template1-muted': '#c2410c',
'--template1-border': 'rgba(217, 119, 6, 0.34)',
'--template1-topbar-start': '#fef3c7',
'--template1-topbar-end': '#fed7aa',
'--template1-topbar-text': '#7c2d12',
'--template1-badge-bg': 'rgba(217, 119, 6, 0.24)',
'--template1-badge-text': '#7c2d12',
'--template1-button-bg': 'rgba(217, 119, 6, 0.24)',
'--template1-button-text': '#7c2d12',
'--template1-button-hover': 'rgba(217, 119, 6, 0.3)',
'--template1-settings-bg': '#fef3c7',
'--template1-settings-border': 'rgba(217, 119, 6, 0.45)',
'--template1-card-bg': '#fff7ed',
'--template1-card-border': 'rgba(245, 158, 11, 0.4)',
'--template1-card-text': '#7c2d12',
'--template1-card-subtext': '#9a3412',
'--template1-card-icon-bg': 'rgba(245, 158, 11, 0.24)',
'--template1-card-action': '#ea580c',
'--template1-danger-bg': 'rgba(251, 146, 60, 0.18)',
'--template1-danger-border': 'rgba(251, 146, 60, 0.45)',
'--template1-danger-icon': '#dc2626',
'--template1-danger-text': '#dc2626',
'--template1-shadow': 'rgba(120, 53, 15, 0.24)',
},
},
{
id: 'theme-berry-focus',
title: '베리 포커스',
category: '혼합',
description: '강한 보조색과 집중형 가독성을 함께 조율한 구성',
fontScale: 1.06,
cardColumns: 2,
homeColumns: 1,
gap: '12px',
padding: '13px',
shellRadius: '20px',
cardRadius: '16px',
cardMinHeight: '90px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#111827',
'--template1-background-end': '#1e1b4b',
'--template1-surface': '#312e81',
'--template1-text': '#f8fafc',
'--template1-subtext': '#c7d2fe',
'--template1-muted': '#ddd6fe',
'--template1-border': 'rgba(167, 139, 250, 0.38)',
'--template1-topbar-start': '#312e81',
'--template1-topbar-end': '#4c1d95',
'--template1-topbar-text': '#f5d0fe',
'--template1-badge-bg': 'rgba(216, 180, 254, 0.24)',
'--template1-badge-text': '#ddd6fe',
'--template1-button-bg': 'rgba(216, 180, 254, 0.24)',
'--template1-button-text': '#ddd6fe',
'--template1-button-hover': 'rgba(216, 180, 254, 0.32)',
'--template1-settings-bg': '#312e81',
'--template1-settings-border': 'rgba(216, 180, 254, 0.35)',
'--template1-card-bg': '#4338ca',
'--template1-card-border': 'rgba(216, 180, 254, 0.42)',
'--template1-card-text': '#f8fafc',
'--template1-card-subtext': '#ddd6fe',
'--template1-card-icon-bg': 'rgba(216, 180, 254, 0.2)',
'--template1-card-action': '#f472b6',
'--template1-danger-bg': 'rgba(244, 114, 182, 0.2)',
'--template1-danger-border': 'rgba(244, 114, 182, 0.4)',
'--template1-danger-icon': '#f9a8d4',
'--template1-danger-text': '#fce7f3',
'--template1-shadow': 'rgba(76, 29, 149, 0.33)',
},
},
{
id: 'theme-aurora-clean',
title: '오로라 클래린',
category: '혼합',
description: '차가운 블루그레이 베이스에 깔끔한 타이포우선 배치',
fontScale: 0.98,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '14px',
shellRadius: '24px',
cardRadius: '14px',
cardMinHeight: '82px',
cardIconSize: '36px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#e2e8f0',
'--template1-background-end': '#f1f5f9',
'--template1-surface': '#ffffff',
'--template1-text': '#1e293b',
'--template1-subtext': '#334155',
'--template1-muted': '#475569',
'--template1-border': 'rgba(71, 85, 105, 0.34)',
'--template1-topbar-start': '#f8fafc',
'--template1-topbar-end': '#e2e8f0',
'--template1-topbar-text': '#0f172a',
'--template1-badge-bg': 'rgba(148, 163, 184, 0.25)',
'--template1-badge-text': '#334155',
'--template1-button-bg': 'rgba(71, 85, 105, 0.24)',
'--template1-button-text': '#1e293b',
'--template1-button-hover': 'rgba(71, 85, 105, 0.28)',
'--template1-settings-bg': '#ffffff',
'--template1-settings-border': 'rgba(71, 85, 105, 0.4)',
'--template1-card-bg': '#ffffff',
'--template1-card-border': 'rgba(148, 163, 184, 0.5)',
'--template1-card-text': '#1e293b',
'--template1-card-subtext': '#334155',
'--template1-card-icon-bg': 'rgba(148, 163, 184, 0.25)',
'--template1-card-action': '#334155',
'--template1-danger-bg': 'rgba(71, 85, 105, 0.14)',
'--template1-danger-border': 'rgba(71, 85, 105, 0.4)',
'--template1-danger-icon': '#475569',
'--template1-danger-text': '#475569',
'--template1-shadow': 'rgba(30, 41, 59, 0.2)',
},
},
{
id: 'theme-micro-precision',
title: '마이크로 정밀',
category: '혼합',
description: '작고 촘촘한 정보뷰를 위한 고밀도 조합',
fontScale: 0.92,
cardColumns: 4,
homeColumns: 2,
gap: '9px',
padding: '11px',
shellRadius: '18px',
cardRadius: '12px',
cardMinHeight: '72px',
cardIconSize: '28px',
chipGap: '3px',
cssVars: {
'--template1-background-start': '#0f172a',
'--template1-background-end': '#020617',
'--template1-surface': '#111827',
'--template1-text': '#e2e8f0',
'--template1-subtext': '#94a3b8',
'--template1-muted': '#cbd5e1',
'--template1-border': 'rgba(148, 163, 184, 0.32)',
'--template1-topbar-start': '#1e293b',
'--template1-topbar-end': '#0f172a',
'--template1-topbar-text': '#e2e8f0',
'--template1-badge-bg': 'rgba(148, 163, 184, 0.24)',
'--template1-badge-text': '#e2e8f0',
'--template1-button-bg': 'rgba(148, 163, 184, 0.24)',
'--template1-button-text': '#e2e8f0',
'--template1-button-hover': 'rgba(148, 163, 184, 0.32)',
'--template1-settings-bg': '#020617',
'--template1-settings-border': 'rgba(148, 163, 184, 0.4)',
'--template1-card-bg': '#0f172a',
'--template1-card-border': 'rgba(148, 163, 184, 0.4)',
'--template1-card-text': '#e2e8f0',
'--template1-card-subtext': '#94a3b8',
'--template1-card-icon-bg': 'rgba(148, 163, 184, 0.22)',
'--template1-card-action': '#38bdf8',
'--template1-danger-bg': 'rgba(59, 130, 246, 0.16)',
'--template1-danger-border': 'rgba(59, 130, 246, 0.36)',
'--template1-danger-icon': '#7dd3fc',
'--template1-danger-text': '#bfdbfe',
'--template1-shadow': 'rgba(2, 6, 23, 0.45)',
},
},
{
id: 'theme-warm-glass',
title: '웜 글래스',
category: '혼합',
description: '따뜻한 톤의 글래스와 중립 텍스트를 조합한 대화형 스타일',
fontScale: 1.02,
cardColumns: 3,
homeColumns: 1,
gap: '12px',
padding: '13px',
shellRadius: '23px',
cardRadius: '16px',
cardMinHeight: '86px',
cardIconSize: '38px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#ffedd5',
'--template1-background-end': '#fde68a',
'--template1-surface': '#fef3c7',
'--template1-text': '#431407',
'--template1-subtext': '#9a3412',
'--template1-muted': '#b45309',
'--template1-border': 'rgba(217, 119, 6, 0.3)',
'--template1-topbar-start': '#ffedd5',
'--template1-topbar-end': '#fde68a',
'--template1-topbar-text': '#7c2d12',
'--template1-badge-bg': 'rgba(217, 119, 6, 0.24)',
'--template1-badge-text': '#7c2d12',
'--template1-button-bg': 'rgba(217, 119, 6, 0.22)',
'--template1-button-text': '#7c2d12',
'--template1-button-hover': 'rgba(217, 119, 6, 0.3)',
'--template1-settings-bg': '#ffedd5',
'--template1-settings-border': 'rgba(217, 119, 6, 0.45)',
'--template1-card-bg': '#fefce8',
'--template1-card-border': 'rgba(217, 119, 6, 0.5)',
'--template1-card-text': '#7c2d12',
'--template1-card-subtext': '#9a3412',
'--template1-card-icon-bg': 'rgba(217, 119, 6, 0.2)',
'--template1-card-action': '#ea580c',
'--template1-danger-bg': 'rgba(249, 115, 22, 0.18)',
'--template1-danger-border': 'rgba(249, 115, 22, 0.46)',
'--template1-danger-icon': '#c2410c',
'--template1-danger-text': '#c2410c',
'--template1-shadow': 'rgba(120, 53, 15, 0.3)',
},
},
{
id: 'theme-citrus-minimal',
title: '시트러스 미니멀',
category: '혼합',
description: '밝은 크림베이스에 선명 텍스트 스케일로 정돈한 조합',
fontScale: 0.99,
cardColumns: 3,
homeColumns: 1,
gap: '11px',
padding: '12px',
shellRadius: '21px',
cardRadius: '15px',
cardMinHeight: '84px',
cardIconSize: '35px',
chipGap: '4px',
cssVars: {
'--template1-background-start': '#fffbeb',
'--template1-background-end': '#fef3c7',
'--template1-surface': '#fffbeb',
'--template1-text': '#3f3f46',
'--template1-subtext': '#44403c',
'--template1-muted': '#57534e',
'--template1-border': 'rgba(180, 83, 9, 0.25)',
'--template1-topbar-start': '#fff7ed',
'--template1-topbar-end': '#fde68a',
'--template1-topbar-text': '#7c2d12',
'--template1-badge-bg': 'rgba(180, 83, 9, 0.18)',
'--template1-badge-text': '#7c2d12',
'--template1-button-bg': 'rgba(180, 83, 9, 0.2)',
'--template1-button-text': '#7c2d12',
'--template1-button-hover': 'rgba(180, 83, 9, 0.28)',
'--template1-settings-bg': '#fffbeb',
'--template1-settings-border': 'rgba(180, 83, 9, 0.35)',
'--template1-card-bg': '#ffffff',
'--template1-card-border': 'rgba(180, 83, 9, 0.38)',
'--template1-card-text': '#3f3f46',
'--template1-card-subtext': '#57534e',
'--template1-card-icon-bg': 'rgba(245, 158, 11, 0.2)',
'--template1-card-action': '#ca8a04',
'--template1-danger-bg': 'rgba(146, 64, 14, 0.12)',
'--template1-danger-border': 'rgba(146, 64, 14, 0.35)',
'--template1-danger-icon': '#a16207',
'--template1-danger-text': '#a16207',
'--template1-shadow': 'rgba(120, 53, 15, 0.22)',
},
},
];
const THEME_PRESET_BY_ID = Object.fromEntries(THEME_PRESETS.map((item) => [item.id, item]));
const THEME_LAYOUT_PATTERN: Record<Template1ThemeId, Template1LayoutPattern> = {
'theme-default': 'mosaic',
'theme-sky': 'mosaic',
'theme-citrus': 'mosaic',
'theme-forest': 'mosaic',
'theme-violet': 'mosaic',
'theme-graphite': 'mosaic',
'theme-sunset': 'mosaic',
'theme-coral': 'mosaic',
'theme-olive': 'mosaic',
'theme-mint': 'mosaic',
'theme-ocean': 'mosaic',
'theme-berry': 'mosaic',
'theme-choco': 'mosaic',
'theme-aurora': 'mosaic',
'theme-electric': 'mosaic',
'theme-tide': 'mosaic',
'theme-peach-night': 'mosaic',
'theme-graphite-cool': 'mosaic',
'theme-ember': 'mosaic',
'theme-small-text': 'mosaic',
'theme-base-text': 'mosaic',
'theme-large-text': 'mosaic',
'theme-xsmall-text': 'mosaic',
'theme-reading-text': 'mosaic',
'theme-comfort-text': 'mosaic',
'theme-comfortable-readable-text': 'mosaic',
'theme-micro-text': 'mosaic',
'theme-cursor-text': 'mosaic',
'theme-compact-structure': 'compact',
'theme-wide-structure': 'wide',
'theme-list-structure': 'list',
'theme-focus-structure': 'focus',
'theme-dense-grid-structure': 'dense-grid',
'theme-stagger-structure': 'stagger',
'theme-split-card-structure': 'split',
'theme-stats-column-structure': 'stats',
'theme-mosaic-structure': 'mosaic',
'theme-timeline-structure': 'timeline',
'theme-dashboard-structure': 'dashboard',
'theme-kanban-structure': 'kanban',
'theme-glass-accent': 'mosaic',
'theme-soft-glass': 'mosaic',
'theme-glass-ink': 'mosaic',
'theme-sunrise-glow': 'mosaic',
'theme-slate-readability': 'mosaic',
'theme-ocean-office': 'dashboard',
'theme-sunset-contrast': 'split',
'theme-berry-focus': 'kanban',
'theme-aurora-clean': 'mosaic',
'theme-micro-precision': 'dense-grid',
'theme-warm-glass': 'wide',
'theme-citrus-minimal': 'compact',
};
const THEME_LAYOUT_VISUALS: Record<Template1LayoutPattern, Template1LayoutVisual> = {
compact: { label: '테이블형(저패딩)', kind: 'table-tight', tileCount: 8 },
wide: { label: '카드형(저패딩)', kind: 'table-card', tileCount: 6 },
list: { label: '리스트형', kind: 'list', tileCount: 4 },
focus: { label: '포커스형', kind: 'focus', tileCount: 6 },
'dense-grid': { label: '바둑판형', kind: 'chess', tileCount: 12 },
stagger: { label: '스태거형', kind: 'stagger', tileCount: 8 },
split: { label: '분할형', kind: 'split', tileCount: 10 },
stats: { label: '통계형', kind: 'split', tileCount: 9 },
mosaic: { label: '바둑판형', kind: 'chess', tileCount: 14 },
timeline: { label: '타임라인형', kind: 'timeline', tileCount: 7 },
dashboard: { label: '대시보드형', kind: 'board', tileCount: 10 },
kanban: { label: '칸반형', kind: 'board', tileCount: 8 },
};
const THEME_CATEGORIES: Template1ThemeCategory[] = ['색상', '폰트', '혼합'];
const THEME_TAB_CLASS_BY_CATEGORY: Record<Template1ThemeCategory, Template1ThemePanelClass> = {
: 'color',
: 'font',
: 'layout',
: 'mix',
};
const THEME_CATEGORY_BY_ID = Object.fromEntries(THEME_PRESETS.map((theme) => [theme.id, theme.category])) as Record<
Template1ThemeId,
Template1ThemeCategory
>;
const THEME_WINDOW_DRAG_PADDING = 8;
const THEME_WINDOW_HALF_SIZE = {
width: 272,
height: 302,
};
const SETTINGS: Template1MenuItem[] = [
{
title: '테마',
description: '배경/카드 스타일을 선택',
icon: <AppstoreOutlined />,
actionType: 'local',
settingAction: 'theme',
},
{
title: '알림',
description: '알림 영역 설정 진입점',
icon: <BellOutlined />,
actionType: 'local',
settingAction: 'notifications',
},
{
title: '상태 초기화',
description: '현재 화면을 홈 상태로 복원',
icon: <CheckCircleOutlined />,
actionType: 'local',
settingAction: 'reset-state',
},
{
title: '앱 종료',
description: '설정을 닫고 앱 화면을 종료합니다.',
icon: <LogoutOutlined />,
actionType: 'exit',
},
];
const THEME_STYLE_KEY = '--template1-background-start';
const DEFAULT_THEME_ID: Template1ThemeId = 'theme-default';
const TEMPLATE1_SECTIONS: Template1Section[] = [
{
id: 'home',
label: '홈',
description: '빠른 네비게이션으로 주요 기능으로 이동',
chips: ['요약', '최근 작업', '바로가기'],
},
{
id: 'projects',
label: '작업',
description: '프로젝트 관리나 문맥 전환 진입점',
chips: ['레이아웃', '요청', '검수'],
},
{
id: 'resources',
label: '리소스',
description: '문서/데이터/관리 화면 모음',
chips: ['문서', '리소스', '로그'],
},
{
id: 'automation',
label: '자동화',
description: '자동화와 상태 확인 흐름',
chips: ['플랜', '체크리스트', '실행 이력'],
},
];
const TEMPLATE1_MENUS: Record<Template1SectionId, Template1MenuItem[]> = {
home: [
{
title: '오늘 개요',
description: '최근 작업 수와 진행 상태를 한 화면에서 확인',
icon: <CompassOutlined />,
screen: 'home',
actionLabel: '열기',
actionType: 'navigate',
},
{
title: '요청 보드',
description: '채팅/플랜 타입의 빠른 이동',
icon: <ThunderboltOutlined />,
screen: 'projects',
actionLabel: '이동',
actionType: 'navigate',
},
{
title: '앱 바코드',
description: '새 앱 연결 전에 기본 레이아웃 점검',
icon: <AppstoreAddOutlined />,
screen: 'projects',
actionLabel: '확인',
actionType: 'navigate',
},
],
projects: [
{
title: 'Layout Editor',
description: '레이아웃 편집 흐름 컴포넌트',
icon: <CompassOutlined />,
screen: 'projects',
actionLabel: '열기',
actionType: 'navigate',
},
{
title: 'Layout Draw',
description: '컴포넌트 샘플 배치 화면',
icon: <ThunderboltOutlined />,
screen: 'projects',
actionLabel: '열기',
actionType: 'navigate',
},
{
title: '저장된 레이아웃',
description: '저장 기록과 재열기 진입점',
icon: <CheckCircleOutlined />,
screen: 'projects',
actionLabel: '열기',
actionType: 'navigate',
},
],
resources: [
{
title: '문서',
description: '문서 목록과 상세 진입 구성',
icon: <CompassOutlined />,
screen: 'resources',
actionLabel: '열기',
actionType: 'navigate',
},
{
title: '리소스 관리',
description: '공유 리소스 목록과 승인 상태',
icon: <AppstoreAddOutlined />,
screen: 'resources',
actionLabel: '열기',
actionType: 'navigate',
},
{
title: '활동 로그',
description: '최근 변경 이력 요약',
icon: <BellOutlined />,
screen: 'automation',
actionLabel: '열기',
actionType: 'navigate',
},
],
automation: [
{
title: '자동화',
description: 'Plan/작업 진행 목록',
icon: <AppstoreAddOutlined />,
screen: 'automation',
actionLabel: '열기',
actionType: 'navigate',
},
{
title: '체크리스트',
description: '요청 단계 추적',
icon: <CheckCircleOutlined />,
screen: 'automation',
actionLabel: '확인',
actionType: 'navigate',
},
{
title: '에러 로그',
description: '실패/경고 대응 화면',
icon: <CloseOutlined />,
screen: 'automation',
actionLabel: '확인',
actionType: 'navigate',
},
],
};
type Template1ActiveScreen = Template1SectionId;
type ThemeWindowPosition = {
left: number;
top: number;
};
type Template1HomeScreenProps = {
items: Pick<Template1MenuItem, 'title' | 'description' | 'icon' | 'actionLabel'> & {
chips: string[];
screen: Template1SectionId;
}[];
onItemSelect: (item: Template1MenuItem) => void;
};
type Template1SectionScreenProps = {
title: string;
chips: string[];
cards: Template1MenuItem[];
onItemSelect: (item: Template1MenuItem) => void;
};
function Template1HomeScreen({ items, onItemSelect }: Template1HomeScreenProps) {
return (
<>
<header className="template1-app__section-head">
<h2> </h2>
</header>
<div className="template1-app__home-list" aria-label="콘텐츠 목록">
{items.map((item) => (
<button
type="button"
key={item.title}
className="template1-app__home-item"
onClick={() => onItemSelect({ ...item, screen: item.screen, actionType: 'navigate' })}
>
<span className="template1-app__card-icon">{item.icon}</span>
<div>
<strong>{item.title}</strong>
<span>{item.description}</span>
<small>{item.chips.join(' · ')}</small>
</div>
<span className="template1-app__card-action">{item.actionLabel ?? '열기'}</span>
</button>
))}
</div>
</>
);
}
function Template1SectionScreen({ title, chips, cards, onItemSelect }: Template1SectionScreenProps) {
return (
<>
<div className="template1-app__chips-text">{chips.join(' · ')}</div>
<div className="template1-app__cards" aria-label={title}>
<div className="template1-app__title-row">
<h2>{title}</h2>
</div>
{cards.map((item) => (
<button
type="button"
key={item.title}
className="template1-app__card"
onClick={() => onItemSelect(item)}
>
<span className="template1-app__card-icon">{item.icon}</span>
<div>
<strong>{item.title}</strong>
<span>{item.description}</span>
</div>
<span className="template1-app__card-action">{item.actionLabel ?? '열기'}</span>
</button>
))}
</div>
</>
);
}
function Template1SettingsDropdown({
onItemSelect,
onThemeOpen,
}: {
onItemSelect: (item: Template1MenuItem) => void;
onThemeOpen: () => void;
}) {
return (
<div className="template1-app__settings-dropdown" role="menu" aria-label="설정 메뉴">
<div className="template1-app__settings-list">
{SETTINGS.map((item) => (
<button
type="button"
key={item.title}
className={`template1-app__settings-item${item.actionType === 'exit' ? ' template1-app__settings-item--danger' : ''}`}
onClick={() => {
if (item.settingAction === 'theme') {
onThemeOpen();
return;
}
onItemSelect(item);
}}
>
<span
className={`template1-app__card-icon template1-app__settings-item-icon${item.actionType === 'exit' ? ' template1-app__settings-item-icon--danger' : ''}`}
>
{item.icon}
</span>
<div className="template1-app__settings-item-content">
<strong>{item.title}</strong>
<span
className={item.actionType === 'exit' ? 'template1-app__settings-item-description--muted' : undefined}
>
{item.description}
</span>
</div>
</button>
))}
</div>
</div>
);
}
function Template1ThemeSettingsPanel({
activeThemeId,
onClose,
onThemeSelect,
onWindowMoveStart,
style,
isDragging,
}: {
activeThemeId: Template1ThemeId;
onClose: () => void;
onThemeSelect: (themeId: Template1ThemeId) => void;
onWindowMoveStart: (event: ReactPointerEvent<HTMLDivElement>) => void;
style: CSSProperties;
isDragging: boolean;
}) {
const activeThemeCategory = THEME_CATEGORY_BY_ID[activeThemeId] ?? '색상';
const [activeTab, setActiveTab] = useState<Template1ThemeCategory>(
THEME_CATEGORIES.includes(activeThemeCategory) ? activeThemeCategory : '색상',
);
const [themePage, setThemePage] = useState(0);
useEffect(() => {
setActiveTab(THEME_CATEGORIES.includes(activeThemeCategory) ? activeThemeCategory : '색상');
setThemePage(0);
}, [activeThemeCategory]);
useEffect(() => {
setThemePage(0);
}, [activeTab]);
const renderThemePreview = (tabClass: Template1ThemePanelClass, theme: Template1ThemePreset) => {
if (tabClass === 'font') {
return (
<span className="template1-app__theme-item-preview__font" style={{ color: theme.cssVars['--template1-card-text'] }}>
<span
className="template1-app__theme-item-preview__font-glyph"
style={{ fontSize: `${Math.max(12, Math.round(16 * theme.fontScale))}px` }}
>
Aa
</span>
<span className="template1-app__theme-item-preview__font-line" />
</span>
);
}
if (tabClass === 'layout') {
const layoutPattern = THEME_LAYOUT_PATTERN[theme.id];
const layoutVisual = THEME_LAYOUT_VISUALS[layoutPattern];
return (
<span className="template1-app__theme-item-preview__layout-shell">
<span className="template1-app__theme-item-preview__layout-shell-title">
: {layoutVisual.label}
</span>
<span
className={`template1-app__theme-item-preview__layout template1-app__theme-item-preview__layout--${layoutVisual.kind}`}
aria-label={`${layoutVisual.label} 미리보기`}
>
{Array.from({ length: layoutVisual.tileCount }, (_, index) => (
<span
key={`${theme.id}-${index}`}
className="template1-app__theme-item-preview__layout-tile"
/>
))}
</span>
<span className="template1-app__theme-item-preview__text-row">
<span>: {theme.cardColumns}</span>
<span>: {theme.homeColumns}</span>
<span>: {theme.gap}</span>
<span>: {theme.padding}</span>
</span>
</span>
);
}
if (tabClass === 'mix') {
const mixColor = theme.cssVars['--template1-background-start'];
const mixSurface = theme.cssVars['--template1-surface'];
const mixAction = theme.cssVars['--template1-card-action'];
const mixText = theme.cssVars['--template1-card-text'];
return (
<span className="template1-app__theme-item-preview__mix-combo">
<span className="template1-app__theme-item-preview__mix-colors" aria-hidden="true">
<span
className="template1-app__theme-item-preview__mix-swatch template1-app__theme-item-preview__mix-swatch--start"
style={{ background: mixColor, borderColor: theme.cssVars['--template1-card-border'] }}
/>
<span
className="template1-app__theme-item-preview__mix-swatch template1-app__theme-item-preview__mix-swatch--surface"
style={{ background: mixSurface, borderColor: theme.cssVars['--template1-card-border'] }}
/>
<span
className="template1-app__theme-item-preview__mix-swatch template1-app__theme-item-preview__mix-swatch--action"
style={{ background: mixAction, borderColor: theme.cssVars['--template1-card-border'] }}
/>
</span>
<span className="template1-app__theme-item-preview__text-card">
<span className="template1-app__theme-item-preview__text-title" style={{ color: mixText }}>
</span>
<span className="template1-app__theme-item-preview__text-row">
<span>: {Math.round(theme.fontScale * 100)}%</span>
<span>: {theme.cardIconSize}</span>
</span>
<span className="template1-app__theme-item-preview__text-row">
<span>: {theme.title.includes('글래스') ? '글래스' : '강조형'}</span>
<span> : {theme.chipGap}</span>
</span>
</span>
</span>
);
}
return (
<span className="template1-app__theme-item-preview--color">
<span className="template1-app__theme-item-preview__swatch">
<span
className="template1-app__theme-item-preview__swatch-main"
style={{ background: theme.cssVars[THEME_STYLE_KEY], borderColor: theme.cssVars['--template1-card-border'] }}
/>
<span style={{ background: theme.cssVars['--template1-card-action'], opacity: 0.8 }} />
<span style={{ background: theme.cssVars['--template1-card-text'], opacity: 0.25 }} />
</span>
</span>
);
};
const groupedThemes = useMemo(() => {
const map: Record<Template1ThemeCategory, Template1ThemePreset[]> = {
: [],
: [],
: [],
: [],
};
THEME_PRESETS.forEach((theme) => {
map[theme.category].push(theme);
});
return map;
}, []);
const activeThemes = groupedThemes[activeTab];
const activeTabClass = THEME_TAB_CLASS_BY_CATEGORY[activeTab];
const isPagingTab = activeTab === '혼합';
const themePageSize = isPagingTab ? 1 : activeThemes.length;
const themePageCount = Math.max(1, Math.ceil(activeThemes.length / themePageSize));
const clampThemePage = Math.min(themePage, themePageCount - 1);
const safeThemePage = clampThemePage < 0 ? 0 : clampThemePage;
const visibleThemes = isPagingTab
? activeThemes.slice(safeThemePage * themePageSize, (safeThemePage + 1) * themePageSize)
: activeThemes;
const canPrevThemePage = isPagingTab && safeThemePage > 0;
const canNextThemePage = isPagingTab && safeThemePage < themePageCount - 1;
return (
<section
className={`template1-app__theme-window${isDragging ? ' template1-app__theme-window--dragging' : ''}`}
role="region"
aria-label="테마 설정 창"
style={style}
>
<header
className="template1-app__theme-window-head"
onPointerDown={onWindowMoveStart}
>
<div className="template1-app__theme-window-title">
<p></p>
</div>
<button
type="button"
className="template1-app__theme-window-close"
aria-label="테마 창 닫기"
onClick={onClose}
>
<CloseOutlined />
</button>
</header>
<div className="template1-app__theme-page">
<div
className="template1-app__theme-tabs"
role="tablist"
aria-label="테마 그룹"
>
{THEME_CATEGORIES.map((category) => (
<button
key={category}
type="button"
role="tab"
aria-selected={activeTab === category}
className={`template1-app__theme-tab${activeTab === category ? ' template1-app__theme-tab--active' : ''}`}
onClick={() => setActiveTab(category)}
>
{category}
</button>
))}
</div>
<div className={`template1-app__theme-page-body template1-app__theme-page-body--${activeTabClass}`}>
{visibleThemes.map((theme, index) => {
const isActive = theme.id === activeThemeId;
const tabClass = activeTabClass;
const tabBadge = activeTab === '폰트'
? `${Math.round(theme.fontScale * 100)}%`
: activeTab === '혼합'
? '구성형'
: '톤';
const tabSummary = activeTab === '혼합'
? `${theme.title} / 폰트 ${Math.round(theme.fontScale * 100)}%`
: '';
const tabDescription = activeTab === '색상' || activeTab === '폰트' || activeTab === '혼합' ? theme.description : '';
return (
<button
key={theme.id}
type="button"
aria-pressed={isActive}
className={`template1-app__theme-item${isActive ? ' template1-app__theme-item--active' : ''} template1-app__theme-item--${THEME_TAB_CLASS_BY_CATEGORY[activeTab]}`}
onClick={() => onThemeSelect(theme.id)}
>
<span className={`template1-app__theme-item-preview template1-app__theme-item-preview--${tabClass}`}>
{renderThemePreview(tabClass, theme)}
</span>
<span className={`template1-app__theme-info template1-app__theme-info--${tabClass}`}>
<strong title={theme.description}>{theme.title}</strong>
{tabSummary ? <span className="template1-app__theme-item-summary">{tabSummary}</span> : null}
{tabDescription ? <span className="template1-app__theme-info-desc">{tabDescription}</span> : null}
<span className="template1-app__theme-info-chip">
<em>{tabBadge}</em>
{isActive ? (
<span className="template1-app__theme-active-check">
<CheckCircleOutlined />
</span>
) : null}
</span>
</span>
</button>
);
})}
{isPagingTab && themePageCount > 1 ? (
<div className="template1-app__theme-pager">
<button
type="button"
className="template1-app__theme-pager-btn"
onClick={() => setThemePage((value) => Math.max(0, value - 1))}
disabled={!canPrevThemePage}
>
<LeftOutlined />
</button>
<span className="template1-app__theme-pager-label">
{safeThemePage + 1} / {themePageCount}
</span>
<button
type="button"
className="template1-app__theme-pager-btn"
onClick={() => setThemePage((value) => Math.min(themePageCount - 1, value + 1))}
disabled={!canNextThemePage}
>
<RightOutlined />
</button>
</div>
) : null}
</div>
</div>
</section>
);
}
function buildThemeStyle(theme: Template1ThemePreset): CSSProperties {
return {
'--template1-font-scale': `${theme.fontScale}`,
'--template1-card-columns': `${theme.cardColumns}`,
'--template1-home-columns': `${theme.homeColumns}`,
'--template1-gap': theme.gap,
'--template1-padding': theme.padding,
'--template1-shell-radius': theme.shellRadius,
'--template1-card-radius': theme.cardRadius,
'--template1-card-min-height': theme.cardMinHeight,
'--template1-card-icon-size': theme.cardIconSize,
'--template1-chip-gap': theme.chipGap,
...theme.cssVars,
} as CSSProperties;
}
export function Template1PlayAppView({ onBack, launchContext = 'direct' }: Template1PlayAppViewProps) {
const [activeScreen, setActiveScreen] = useState<Template1ActiveScreen>('home');
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [activeThemeId, setActiveThemeId] = useState<Template1ThemeId>(DEFAULT_THEME_ID);
const [isThemeWindowOpen, setIsThemeWindowOpen] = useState(false);
const [themeWindowPos, setThemeWindowPos] = useState<ThemeWindowPosition>({ left: 40, top: 88 });
const [themeWindowDragStart, setThemeWindowDragStart] = useState({ x: 0, y: 0 });
const [isThemeWindowDragging, setIsThemeWindowDragging] = useState(false);
void launchContext;
const activeSectionItem = useMemo(
() => TEMPLATE1_SECTIONS.find((item) => item.id === activeScreen) ?? TEMPLATE1_SECTIONS[0],
[activeScreen],
);
const menuItems = TEMPLATE1_MENUS[activeScreen] ?? [];
const homeMenuItems = useMemo(
() =>
TEMPLATE1_SECTIONS.filter((item) => item.id !== 'home')
.map((item) => ({
title: item.label,
description: item.description,
icon:
item.id === 'projects' ? <ThunderboltOutlined /> :
item.id === 'resources' ? <AppstoreAddOutlined /> :
<BellOutlined />,
screen: item.id,
actionLabel: '열기',
chips: item.chips,
actionType: 'navigate',
})),
[],
);
const goSection = (section: Template1SectionId) => {
setActiveScreen(section);
setIsThemeWindowOpen(false);
setIsSettingsOpen(false);
};
useEffect(() => {
if (!isThemeWindowDragging) {
return;
}
const stopWindowDrag = () => setIsThemeWindowDragging(false);
const moveWindow = (event: PointerEvent) => {
const nextLeft = event.clientX - themeWindowDragStart.x;
const nextTop = event.clientY - themeWindowDragStart.y;
const dragAreaWidth = Math.max(120, window.innerWidth - 20);
const dragAreaHeight = Math.max(120, window.innerHeight - 20);
const windowWidth = Math.min(THEME_WINDOW_HALF_SIZE.width, dragAreaWidth);
const windowHeight = Math.min(THEME_WINDOW_HALF_SIZE.height, dragAreaHeight);
const maxLeft = Math.max(THEME_WINDOW_DRAG_PADDING, window.innerWidth - windowWidth - THEME_WINDOW_DRAG_PADDING);
const maxTop = Math.max(THEME_WINDOW_DRAG_PADDING, window.innerHeight - windowHeight - THEME_WINDOW_DRAG_PADDING);
setThemeWindowPos({
left: Math.max(THEME_WINDOW_DRAG_PADDING, Math.min(nextLeft, maxLeft)),
top: Math.max(THEME_WINDOW_DRAG_PADDING, Math.min(nextTop, maxTop)),
});
};
window.addEventListener('pointermove', moveWindow);
window.addEventListener('pointerup', stopWindowDrag);
return () => {
window.removeEventListener('pointermove', moveWindow);
window.removeEventListener('pointerup', stopWindowDrag);
};
}, [isThemeWindowDragging, themeWindowDragStart]);
const goThemeScreen = () => {
setIsThemeWindowOpen(true);
setIsSettingsOpen(false);
};
const closeThemeWindow = () => {
setIsThemeWindowOpen(false);
};
const openItem = (item: Template1MenuItem) => {
if (item.actionType === 'exit') {
onBack();
setIsSettingsOpen(false);
return;
}
if (item.settingAction === 'reset-state') {
goSection('home');
setIsSettingsOpen(false);
return;
}
if (item.actionType === 'local') {
if (item.settingAction === 'theme') {
goThemeScreen();
return;
}
setIsSettingsOpen(false);
return;
}
if (item.screen) {
goSection(item.screen);
}
};
const handleHomeIconClick = () => {
setIsThemeWindowOpen(false);
goSection('home');
};
const renderActiveScreen = () => {
if (activeScreen === 'home') {
return <Template1HomeScreen items={homeMenuItems} onItemSelect={openItem} />;
}
return (
<Template1SectionScreen
title={activeSectionItem.label}
chips={activeSectionItem.chips}
cards={menuItems}
onItemSelect={openItem}
/>
);
};
return (
<section
className="template1-app"
style={buildThemeStyle(THEME_PRESET_BY_ID[activeThemeId] ?? THEME_PRESET_BY_ID[DEFAULT_THEME_ID])}
>
<header className="template1-app__topbar">
<div className="template1-app__brand">
<button
type="button"
className="template1-app__badge"
aria-label="Template1 홈으로 이동"
onClick={handleHomeIconClick}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
handleHomeIconClick();
}
}}
>
<AppstoreOutlined />
</button>
<div>
<h1>Template1 </h1>
<p> </p>
</div>
</div>
<button
type="button"
className="template1-app__topbar-action"
aria-label="Template1 설정 열기"
aria-expanded={isSettingsOpen}
onClick={() => {
if (isSettingsOpen) {
setIsSettingsOpen(false);
return;
}
setIsSettingsOpen(true);
}}
title="설정"
>
<SettingOutlined />
</button>
</header>
{isSettingsOpen ? (
<Template1SettingsDropdown
onItemSelect={(item) => {
if (item.settingAction === 'theme') {
goThemeScreen();
return;
}
openItem(item);
}}
onThemeOpen={() => {
goThemeScreen();
}}
/>
) : null}
<main className="template1-app__main">
<div className="template1-app__scroll-area">
<div key={`template1-screen-${activeScreen}-${isSettingsOpen ? 'settings' : 'screen'}`} className="template1-app__screen-transition">
{renderActiveScreen()}
</div>
</div>
</main>
{isThemeWindowOpen ? (
<Template1ThemeSettingsPanel
activeThemeId={activeThemeId}
onClose={closeThemeWindow}
onThemeSelect={(themeId) => {
setActiveThemeId(themeId);
}}
isDragging={isThemeWindowDragging}
onWindowMoveStart={(event) => {
const clickedTarget = event.target as Element | null;
if (
event.button !== 0 ||
!(clickedTarget instanceof Element) ||
clickedTarget.closest('.template1-app__theme-window-close')
) {
return;
}
setThemeWindowDragStart({
x: event.clientX - Number(themeWindowPos.left),
y: event.clientY - Number(themeWindowPos.top),
});
setIsThemeWindowDragging(true);
event.preventDefault();
}}
style={{
left: themeWindowPos.left,
top: themeWindowPos.top,
}}
/>
) : null}
</section>
);
}