فهرست منبع

M1-D6: design tokens, WIDE 3-column shell, dark-first theme

- tokens: 15-step gray ramp, price up-red/down-blue, single accent, density vars
- fluid 3-col layout + MarketSidebar placeholder + header search/ticker skeleton
- dark default with fixed dark header, FOUC guard, az-theme migration
- tsc 0 errors, npm build green

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
KIM-JINO5 3 هفته پیش
والد
کامیت
1dd7cf22be

+ 52 - 10
app/component/Header.tsx

@@ -1,7 +1,7 @@
 'use client';
 
 import Styles from '../styles/layout.module.scss';
-import { useCallback } from 'react';
+import { useCallback, useEffect, useRef } from 'react';
 import Link from 'next/link';
 import { usePathname } from 'next/navigation';
 import useAuth from '@/hooks/useAuth';
@@ -44,6 +44,34 @@ export default function Header({ sidebarOpen, onToggle }: Props)
 	const { totalCount: cartCount } = useCart();
 	const pathname = usePathname();
 	const dragScroll = useDragScroll<HTMLDivElement>();
+	const searchRef = useRef<HTMLInputElement>(null);
+
+	// 통합 검색 포커스 단축키: Ctrl+K(⌘+K) / '/' (finviz 관례) — 검색 동작 자체는 후속
+	useEffect(() => {
+		const handler = (e: KeyboardEvent) => {
+			if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
+				e.preventDefault();
+				searchRef.current?.focus();
+				return;
+			}
+
+			if (e.key === '/' && !e.ctrlKey && !e.metaKey && !e.altKey) {
+				const target = e.target as HTMLElement|null;
+				const tag = target?.tagName;
+				if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || target?.isContentEditable) {
+					return;
+				}
+
+				e.preventDefault();
+				searchRef.current?.focus();
+			}
+		};
+
+		window.addEventListener('keydown', handler);
+		return () => {
+			window.removeEventListener('keydown', handler);
+		};
+	}, []);
 
 	const scrollTabs = useCallback((dir: 'left' | 'right') => {
 		const el = dragScroll.ref.current;
@@ -66,19 +94,17 @@ export default function Header({ sidebarOpen, onToggle }: Props)
 
 			{/* 1줄: 로고 + 우측 아이콘 (PC에서는 전체 내비) */}
 			<div className={Styles.headerRow1}>
-				{/* 햄버거는 채널 사이드바 토글 전용 — 채널 기능 OFF 시 미노출 */}
-				{FEATURE_CHANNEL && (
-					<button type='button' className={Styles.hamburger} onClick={onToggle} aria-label='메뉴'>
-						<FontAwesomeIcon icon={sidebarOpen ? faXmark : faBars} />
-					</button>
-				)}
+				{/* 햄버거 — 좌측 사이드바(채널 or 관심종목 자리표시) 슬라이드 토글 (모바일 전용) */}
+				<button type='button' className={Styles.hamburger} onClick={onToggle} aria-label='메뉴' aria-expanded={sidebarOpen}>
+					<FontAwesomeIcon icon={sidebarOpen ? faXmark : faBars} />
+				</button>
 
 				<Link href='/' className={Styles.logo} aria-label="개미투자 홈">
 					<Logo size="sm" />
 				</Link>
 
 				{/* PC 내비게이션 */}
-				<nav className={Styles.pcNav}>
+				<nav className={Styles.pcNav} aria-label='주요 메뉴'>
 					<ul className='flex gap-4'>
 						{navItems.map(item => (
 							<li key={item.label}>
@@ -88,6 +114,19 @@ export default function Header({ sidebarOpen, onToggle }: Props)
 					</ul>
 				</nav>
 
+				{/* 중앙 통합 검색 — UI + Ctrl+K/'/' 포커스만 (자동완성·커맨드 팔레트는 후속 D6-4) */}
+				<div className={Styles.searchBox} role='search'>
+					<input
+						ref={searchRef}
+						type='search'
+						className={Styles.searchInput}
+						placeholder='종목·글·유저 검색'
+						aria-label='통합 검색'
+						autoComplete='off'
+					/>
+					<kbd className={Styles.searchKbd} aria-hidden='true'>Ctrl K</kbd>
+				</div>
+
 				{/* 우측 아이콘 (항상 표시) */}
 				<div className={Styles.headerActions}>
 					<Link href="/cart" className={Styles.cartBtn} title="장바구니" aria-label="장바구니">
@@ -109,7 +148,7 @@ export default function Header({ sidebarOpen, onToggle }: Props)
 			</div>
 
 			{/* 2줄: 모바일 전용 가로 스크롤 탭 */}
-			<div className={Styles.headerRow2}>
+			<nav className={Styles.headerRow2} aria-label='주요 메뉴 (모바일)'>
 				<button type='button' className={Styles.scrollArrow} onClick={() => scrollTabs('left')} aria-label='왼쪽 스크롤'>
 					<FontAwesomeIcon icon={faChevronLeft} />
 				</button>
@@ -134,7 +173,10 @@ export default function Header({ sidebarOpen, onToggle }: Props)
 				<button type='button' className={Styles.scrollArrow} onClick={() => scrollTabs('right')} aria-label='오른쪽 스크롤'>
 					<FontAwesomeIcon icon={faChevronRight} />
 				</button>
-			</div>
+			</nav>
+
+			{/* 지수 티커 스트립 — TradingView 임베드 컨테이너 (규격만, 내용 주입 전까지 :empty 로 미표시) */}
+			<div id='header-ticker' className={Styles.tickerStrip} aria-hidden='true' />
 		</header>
 	);
 }

+ 22 - 7
app/component/Layout.tsx

@@ -5,15 +5,18 @@ import Styles from '../styles/layout.module.scss';
 import Header from './Header';
 import Footer from './Footer'
 import ChannelSidebar from '@/app/component/channel/ChannelSidebar';
+import MarketSidebar from '@/app/component/MarketSidebar';
 import { ChannelListItem } from '@/types/channel';
 import { FEATURE_CHANNEL } from '@/constants/features';
 
 type Props = {
 	children: React.ReactNode;
 	initialChannels?: ChannelListItem[];
+	// 우측 유틸 슬롯 (D6 3열 그리드) — 제공 페이지만 desktop 이상에서 3열 렌더
+	utility?: React.ReactNode;
 };
 
-export default function Layout({ children, initialChannels }: Props) {
+export default function Layout({ children, initialChannels, utility }: Props) {
 	const [sidebarOpen, setSidebarOpen] = useState(false);
 
 	const toggleSidebar = useCallback(() => {
@@ -26,25 +29,37 @@ export default function Layout({ children, initialChannels }: Props) {
 
 	return (
 		<>
-			<div id='container' className={`${Styles.container}${sidebarOpen ? ` ${Styles.sidebarOpen}` : ''}${FEATURE_CHANNEL ? '' : ` ${Styles.noAside}`}`}>
+			{/* 접근성: 스킵 링크 — Tab 첫 포커스 시 노출 */}
+			<a href='#main' className={Styles.skipLink}>본문 바로가기</a>
+
+			<div id='container' className={`${Styles.container}${sidebarOpen ? ` ${Styles.sidebarOpen}` : ''}${utility ? ` ${Styles.hasUtility}` : ''}`}>
 
 				<Header sidebarOpen={sidebarOpen} onToggle={toggleSidebar} />
 
 				{/* 모바일 오버레이 */}
 				<div className={Styles.overlay} onClick={closeOverlay} />
 
-				{/* 좌측 채널 사이드바 — 채널 기능 OFF 시 미노출 */}
-				{FEATURE_CHANNEL && (
-					<aside id='aside' className={Styles.aside}>
+				{/* 좌측 사이드바 — 채널 기능 ON: 채널 목록 / OFF: 관심종목 자리표시 (MarketSidebar) */}
+				<aside id='aside' className={Styles.aside} aria-label='사이드바'>
+					{FEATURE_CHANNEL ? (
 						<ChannelSidebar initialChannels={initialChannels} />
-					</aside>
-				)}
+					) : (
+						<MarketSidebar />
+					)}
+				</aside>
 
 				{/* 메인 내용 */}
 				<main id='main' className={`${Styles.main} relative`}>
 					{children}
 				</main>
 
+				{/* 우측 유틸 슬롯 (desktop 이상 전용) */}
+				{utility && (
+					<aside id='utility' className={Styles.utility} aria-label='보조 콘텐츠'>
+						{utility}
+					</aside>
+				)}
+
 				{/* 하단 */}
 				<Footer />
 

+ 86 - 0
app/component/MarketSidebar.tsx

@@ -0,0 +1,86 @@
+'use client';
+
+import '@/app/styles/market-sidebar.scss';
+import { useCallback, useEffect, useState } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faAnglesLeft, faAnglesRight, faStar, faSun, faMoon } from '@fortawesome/free-solid-svg-icons';
+import useTheme from '@/hooks/useTheme';
+
+const STORAGE_KEY = 'az-sidebar-collapsed';
+const WIDTH_EXPANDED = '220px';
+const WIDTH_COLLAPSED = '64px';
+
+// 좌측 사이드바 자리표시 (D6 그리드 셸) — 관심종목/게시판 즐겨찾기 본구현은 후속(MarketSidebar 본체)
+// 접기 메커니즘은 ChannelSidebar 와 동일: html 루트의 --antooza-sidebar-width 로 layout grid 열 폭 반응
+export default function MarketSidebar()
+{
+	// 자리표시 단계라 기본 접힘(64px 아이콘 레일) — 본문 우선. 토글 시 localStorage 영속.
+	const [collapsed, setCollapsed] = useState(true);
+	const { toggleTheme, isDark } = useTheme();
+
+	// 최초 mount 시 localStorage에서 collapsed 상태 복원
+	useEffect(() => {
+		try {
+			const saved = window.localStorage.getItem(STORAGE_KEY);
+			if (saved === 'false') {
+				setCollapsed(false);
+			}
+		} catch {}
+	}, []);
+
+	// collapsed 변경 시 html 루트 CSS 변수 동기화
+	useEffect(() => {
+		const root = document.documentElement;
+		root.style.setProperty('--antooza-sidebar-width', collapsed ? WIDTH_COLLAPSED : WIDTH_EXPANDED);
+		try {
+			window.localStorage.setItem(STORAGE_KEY, String(collapsed));
+		} catch {}
+		return () => {
+			// unmount 시 원복 (breakpoint 별 --az-sidebar-w 기본값으로 복귀)
+			root.style.removeProperty('--antooza-sidebar-width');
+		};
+	}, [collapsed]);
+
+	const handleToggleCollapse = useCallback(() => {
+		setCollapsed(prev => !prev);
+	}, []);
+
+	return (
+		<div className={`market-sidebar${collapsed ? ' market-sidebar--collapsed' : ''}`}>
+			{/* 헤더: 타이틀 + 접기 토글 */}
+			<div className='market-sidebar__header'>
+				{!collapsed && (
+					<span className='market-sidebar__title'>관심종목</span>
+				)}
+				<button
+					type='button'
+					className='market-sidebar__collapse-btn'
+					onClick={handleToggleCollapse}
+					aria-label={collapsed ? '사이드바 펼치기' : '사이드바 접기'}
+					aria-expanded={!collapsed}
+					title={collapsed ? '사이드바 펼치기' : '사이드바 접기'}
+				>
+					<FontAwesomeIcon icon={collapsed ? faAnglesRight : faAnglesLeft} />
+				</button>
+			</div>
+
+			{/* 자리표시 본문 */}
+			<div className='market-sidebar__body'>
+				{collapsed ? (
+					<div className='market-sidebar__icon-placeholder' title='관심종목 (준비 중)' aria-hidden='true'>
+						<FontAwesomeIcon icon={faStar} />
+					</div>
+				) : (
+					<p className='market-sidebar__empty'>관심종목 기능을 준비 중입니다</p>
+				)}
+			</div>
+
+			{/* 하단: 테마 전환 (기존 ChannelSidebar 토글이 feature-off 로 미노출되어 여기서 제공) */}
+			<div className='market-sidebar__footer'>
+				<button type='button' className='market-sidebar__icon-btn' onClick={toggleTheme} aria-label={isDark ? '라이트 모드' : '다크 모드'} title={isDark ? '라이트 모드' : '다크 모드'}>
+					<FontAwesomeIcon icon={isDark ? faSun : faMoon} />
+				</button>
+			</div>
+		</div>
+	);
+}

+ 16 - 178
app/globals.scss

@@ -1,3 +1,5 @@
+@use './styles/tokens';
+
 @tailwind base;
 @tailwind components;
 @tailwind utilities;
@@ -20,182 +22,7 @@ body {
     padding: 0;
 }
 
-@layer base {
-  	:root {
-        --background: 0 0% 100%;
-        --foreground: 0 0% 3.9%;
-        --card: 0 0% 100%;
-        --card-foreground: 0 0% 3.9%;
-        --popover: 0 0% 100%;
-        --popover-foreground: 0 0% 3.9%;
-        --primary: 0 0% 9%;
-        --primary-foreground: 0 0% 98%;
-        --secondary: 0 0% 96.1%;
-        --secondary-foreground: 0 0% 9%;
-        --muted: 0 0% 96.1%;
-        --muted-foreground: 0 0% 45.1%;
-        --accent: 0 0% 96.1%;
-        --accent-foreground: 0 0% 9%;
-        --destructive: 0 84.2% 60.2%;
-        --destructive-foreground: 0 0% 98%;
-        --border: 0 0% 89.8%;
-        --input: 0 0% 89.8%;
-        --ring: 215 100% 50%;
-        --chart-1: 12 76% 61%;
-        --chart-2: 173 58% 39%;
-        --chart-3: 197 37% 24%;
-        --chart-4: 43 74% 66%;
-        --chart-5: 27 87% 67%;
-        --radius: 0.5rem;
-		--crypto-up: 0 70% 55%;
-		--crypto-down: 220 70% 55%;
-		--crypto-neutral: 0 0% 50%;
-
-		/* 시맨틱 색상 변수 */
-        --text-normal: #f1f1f1;
-		--text-primary: #141414;
-		--text-secondary: #666;
-		--text-muted: #999;
-		--text-link: #0060a9;
-		--text-link-hover: #c7511f;
-        --text-head: #e5e5e5;
-		--bg-page: #fff;
-		--bg-elevated: #f9f9f9;
-		--bg-subtle: #f4f4f4;
-		--bg-input: #fff;
-        --bg-head: #007bc3;
-        --bg-icon: #004f7c;
-		--border-default: #eaeaea;
-		--border-light: #eee;
-		--border-strong: #ccc;
-		--shadow-color: rgba(0,0,0,0.12);
-		--btn-primary: #007bc3;
-		--btn-primary-hover: #0066a3;
-		--btn-primary-shadow: #00558a;
-		--color-accent: #F7931A;
-		--color-accent-hover: #e5851a;
-		--color-danger: #d51b28;
-		--color-success: #4caf50;
-		--color-warning-bg: #fefce8;
-		--color-warning-text: #92400e;
-		--overlay-color: rgba(0,0,0,0.5);
-		--sidebar-active-bg: #fff3e0;
-		--list-row-active: #faffd1;
-		--btn-default-border: #b3b3b3;
-		--btn-default-hover: #e3e3e3;
-        --outline-default: #c2c2c2;
-		--fg-default: #0f0f0f;
-		--color-blue: #3b82f6;
-		--color-blue-hover: #2563eb;
-		--color-blue-bg: rgba(59, 130, 246, 0.06);
-		--color-purple: #8b5cf6;
-		--color-purple-hover: #7c3aed;
-		--color-purple-bg: rgba(139, 92, 246, 0.06);
-		--color-danger-bg: #fee2e2;
-		--color-danger-border: #fca5a5;
-		--color-success-bg: #dcfce7;
-		--color-success-text: #16a34a;
-		--bg-subtle-hover: rgba(0, 0, 0, 0.05);
-		/* FAQ Q/A 라벨 색상 */
-		--faq-q-color: #0b671b;
-		--faq-a-color: #ff4727;
-		--sidebar-background: 0 0% 98%;
-		--sidebar-foreground: 240 5.3% 26.1%;
-		--sidebar-primary: 240 5.9% 10%;
-		--sidebar-primary-foreground: 0 0% 98%;
-		--sidebar-accent: 240 4.8% 95.9%;
-		--sidebar-accent-foreground: 240 5.9% 10%;
-		--sidebar-border: 220 13% 91%;
-		--sidebar-ring: 217.2 91.2% 59.8%;
-	}
-
-  	.dark {
-        --background: 0 0% 3.9%;
-        --foreground: 0 0% 98%;
-        --card: 0 0% 3.9%;
-        --card-foreground: 0 0% 98%;
-        --popover: 0 0% 3.9%;
-        --popover-foreground: 0 0% 98%;
-        --primary: 0 0% 98%;
-        --primary-foreground: 0 0% 9%;
-        --secondary: 0 0% 14.9%;
-        --secondary-foreground: 0 0% 98%;
-        --muted: 0 0% 14.9%;
-        --muted-foreground: 0 0% 63.9%;
-        --accent: 0 0% 14.9%;
-        --accent-foreground: 0 0% 98%;
-        --destructive: 0 62.8% 30.6%;
-        --destructive-foreground: 0 0% 98%;
-        --border: 0 0% 14.9%;
-        --input: 0 0% 14.9%;
-        --ring: 215 100% 60%;
-        --chart-1: 220 70% 50%;
-        --chart-2: 160 60% 45%;
-        --chart-3: 30 80% 55%;
-        --chart-4: 280 65% 60%;
-        --chart-5: 340 75% 55%;
-		--crypto-up: 0 70% 60%;
-		--crypto-down: 220 70% 60%;
-		--crypto-neutral: 0 0% 63.9%;
-
-		/* 시맨틱 색상 변수 */
-        --text-normal: #f1f1f1;
-		--text-primary: #cacaca;
-		--text-secondary: #a3a3a3;
-		--text-muted: #737373;
-		--text-link: #60a5fa;
-		--text-link-hover: #3b82f6;
-        --text-head: #cacaca;
-		--bg-page: #171717;
-		--bg-elevated: #1e1e1e;
-		--bg-subtle: #262626;
-		--bg-input: #262626;
-        --bg-head: #5603a3;
-        --bg-icon: #3c00aa;
-		--border-default: #333;
-		--border-light: #2a2a2a;
-		--border-strong: #444;
-		--shadow-color: rgba(0,0,0,0.4);
-		--btn-primary: #8b5cf6;
-		--btn-primary-hover: #7c3aed;
-		--btn-primary-shadow: #6d28d9;
-		--color-accent: #F7931A;
-		--color-accent-hover: #f59e0b;
-		--color-danger: #ef4444;
-		--color-success: #22c55e;
-		--color-warning-bg: #422006;
-		--color-warning-text: #fef08a;
-		--overlay-color: rgba(0,0,0,0.7);
-		--sidebar-active-bg: #3d2800;
-		--list-row-active: #2a2a00;
-		--btn-default-border: #555;
-		--btn-default-hover: #333;
-		--fg-default: #f1f1f1;
-		--color-blue: #60a5fa;
-		--color-blue-hover: #3b82f6;
-		--color-blue-bg: rgba(96, 165, 250, 0.1);
-		--color-purple: #a78bfa;
-		--color-purple-hover: #8b5cf6;
-		--color-purple-bg: rgba(167, 139, 250, 0.15);
-		--color-danger-bg: #3f1212;
-		--color-danger-border: #7f1d1d;
-		--color-success-bg: #0f2d1a;
-		--color-success-text: #4ade80;
-		--bg-subtle-hover: rgba(255, 255, 255, 0.05);
-		/* FAQ Q/A 라벨 색상 — dark 배경에서도 시인성 확보를 위해 light hue 유지하며 밝기 ↑ */
-		--faq-q-color: #3fbd54;
-		--faq-a-color: #ff6b4a;
-		--sidebar-background: 240 5.9% 10%;
-		--sidebar-foreground: 240 4.8% 95.9%;
-		--sidebar-primary: 224.3 76.3% 48%;
-		--sidebar-primary-foreground: 0 0% 100%;
-		--sidebar-accent: 240 3.7% 15.9%;
-		--sidebar-accent-foreground: 240 4.8% 95.9%;
-		--sidebar-border: 240 3.7% 15.9%;
-		--sidebar-ring: 217.2 91.2% 59.8%;
-    }
-}
-
+// 컬러/레이아웃/밀도 CSS 변수는 app/styles/tokens/* 로 이동 (D6 디자인 토큰 — 다크 기본)
 @layer base {
     * {
         @apply border-border;
@@ -206,6 +33,17 @@ body {
     }
 }
 
+// 접근성 — 키보드 포커스 링 (마우스 클릭에는 미적용, 포인트 컬러 토큰)
+:focus-visible {
+	outline: var(--focus-ring-width) solid var(--focus-ring-color);
+	outline-offset: 1px;
+}
+
+// 숫자 정렬 — 테이블 전역 tabular figures (폰트 추가 없이, D6 §3.3)
+table {
+	font-variant-numeric: tabular-nums;
+}
+
 select, input, textarea {
     font-size: 1rem;
     padding: 5px;
@@ -269,9 +107,9 @@ select, input, textarea {
     }
 }
 
-// 제출/Primary 버튼
+// 제출/Primary 버튼 — 포인트 컬러 1개 원칙: --btn-primary 는 --az-accent 로 수렴 (tokens/_colors.scss)
 .btn-primary {
-    color: #fff;
+    color: var(--btn-primary-text, #fff);
     background: var(--btn-primary);
 	border: 1px solid var(--btn-primary);
     -webkit-box-shadow: inset 0 -2px 0 0 var(--btn-primary-shadow);

+ 6 - 0
app/layout.tsx

@@ -107,6 +107,12 @@ export default async function RootLayout({
     return (
         <html lang="ko" suppressHydrationWarning>
             <head>
+                {/* 테마/밀도 FOUC 방지 — 첫 페인트 전에 html class/data 속성 확정 (기본 dark/normal, localStorage 영속) */}
+                <script
+                    dangerouslySetInnerHTML={{
+                        __html: "(function(){var e=document.documentElement;try{var t=localStorage.getItem('az-theme')||localStorage.getItem('bitforum-theme');t=t==='light'?'light':'dark';var d=localStorage.getItem('az-density');if(d!=='compact'&&d!=='expanded'){d='normal';}e.classList.add(t);e.dataset.theme=t;e.dataset.density=d;}catch(r){e.classList.add('dark');e.dataset.theme='dark';e.dataset.density='normal';}})();"
+                    }}
+                />
                 <link rel="manifest" href="/manifest.json" />
                 <Script src="https://www.googletagmanager.com/gtag/js?id=G-DY6YFW4CTM" strategy="afterInteractive" />
                 <Script id="gtag-init" strategy="afterInteractive">

+ 147 - 24
app/styles/layout.module.scss

@@ -1,17 +1,18 @@
 // ── 헤더 (Layout + Studio 공용) ──
+// 헤더는 라이트 테마에서도 다크 고정 (amazon 방식) — --bg-head/--text-head/--head-* 는 tokens/_colors.scss 에서 양 테마 동일 값
 .header {
 	background: var(--bg-head);
 	border-bottom: 1px solid var(--border-default);
 
-	// 1줄: 로고 + PC네비 + 우측 아이콘
+	// 1줄: 로고 + PC네비 + 중앙 검색 + 우측 아이콘
 	.headerRow1 {
 		display: flex;
 		align-items: center;
-		height: 56px;
+		height: var(--header-h, 56px);
 		padding: 0 16px;
 	}
 
-	// 햄버거 메뉴 (모바일 전용)
+	// 햄버거 메뉴 (모바일 전용 — 좌측 사이드바 슬라이드 토글)
 	.hamburger {
 		display: none;
 		align-items: center;
@@ -31,7 +32,7 @@
 			color: #fff;
 		}
 
-		@media (max-width: 1125px) {
+		@media (max-width: 1123.98px) {
 			display: flex;
 		}
 	}
@@ -56,9 +57,9 @@
 	// PC 네비게이션 (모바일에서 숨김)
 	.pcNav {
 		display: flex;
-		flex-grow: 1;
 		align-items: center;
 		font-weight: 500;
+		flex-shrink: 0;
 
 		ul {
 			display: flex;
@@ -76,17 +77,73 @@
 			}
 		}
 
-		@media (max-width: 1125px) {
+		@media (max-width: 1123.98px) {
+			display: none;
+		}
+	}
+
+	// 중앙 통합 검색 (D6 §5.2 골격 — UI + Ctrl+K / '/' 포커스만, 자동완성·이동은 후속)
+	.searchBox {
+		position: relative;
+		flex: 1 1 auto;
+		max-width: 560px;
+		margin: 0 auto;
+		padding: 0 16px;
+
+		@media (max-width: 1123.98px) {
 			display: none;
 		}
 	}
 
+	.searchInput {
+		width: 100%;
+		height: 36px;
+		padding: 0 72px 0 12px;
+		font-size: 0.875rem;
+		color: var(--head-input-text);
+		background: var(--head-input-bg);
+		border: 1px solid var(--head-input-border);
+		border-radius: 6px;
+		transition: border-color 0.15s ease;
+
+		&::placeholder {
+			color: var(--head-input-placeholder);
+		}
+
+		&:hover {
+			border-color: var(--outline-default);
+		}
+
+		&:focus {
+			outline: none;
+			border-color: var(--az-accent);
+			-webkit-box-shadow: 0 0 0 1px var(--az-accent);
+			box-shadow: 0 0 0 1px var(--az-accent);
+		}
+	}
+
+	.searchKbd {
+		position: absolute;
+		top: 50%;
+		right: 26px;
+		transform: translateY(-50%);
+		padding: 1px 6px;
+		font-size: 11px;
+		font-family: inherit;
+		color: var(--head-input-placeholder);
+		border: 1px solid var(--head-input-border);
+		border-radius: 4px;
+		pointer-events: none;
+		white-space: nowrap;
+	}
+
 	// 우측 아이콘 영역
 	.headerActions {
 		display: flex;
 		align-items: center;
 		gap: 8px;
 		margin-left: auto;
+		flex-shrink: 0;
 	}
 
 	.chargeBtn {
@@ -158,7 +215,7 @@
 	.headerRow2 {
 		display: none;
 
-		@media (max-width: 1125px) {
+		@media (max-width: 1123.98px) {
 			display: flex;
 			align-items: center;
 			background: rgba(0, 0, 0, 0.12);
@@ -170,7 +227,7 @@
 	.scrollArrow {
 		display: none;
 
-		@media (max-width: 1125px) {
+		@media (max-width: 1123.98px) {
 			display: flex;
 			align-items: center;
 			justify-content: center;
@@ -240,8 +297,27 @@
 		}
 	}
 
+	// 지수 티커 스트립 — TradingView 임베드 컨테이너 규격만 (D0 §3-A 단기안, 실제 임베드는 후속)
+	// 내용이 주입되기 전(:empty)에는 미표시
+	.tickerStrip {
+		height: var(--ticker-h, 32px);
+		overflow: hidden;
+		background: var(--bg-head);
+		border-top: 1px solid var(--head-input-border);
+
+		&:empty {
+			display: none;
+		}
+
+		iframe {
+			width: 100%;
+			height: 100%;
+			border: 0;
+		}
+	}
+
 	// 모바일 헤더 패딩 조정
-	@media (max-width: 1125px) {
+	@media (max-width: 1123.98px) {
 		.headerRow1 {
 			padding: 0 8px;
 		}
@@ -253,17 +329,37 @@
 	}
 }
 
-// ── 메인 레이아웃 컨테이너 ──
+// ── 접근성: 스킵 링크 (Tab 첫 포커스 시 노출) ──
+.skipLink {
+	position: fixed;
+	top: -100px;
+	left: 8px;
+	z-index: 3000;
+	padding: 8px 14px;
+	border-radius: 4px;
+	background: var(--az-accent);
+	color: #201503;
+	font-weight: 700;
+	transition: top 0.15s ease;
+
+	&:focus {
+		top: 8px;
+	}
+}
+
+// ── 메인 레이아웃 컨테이너 — WIDE fluid 3단 그리드 (max-width 없음) ──
+// 브레이크포인트 4단: mobile <768 / tablet 768~1123.98 / desktop 1124~1535.98 / wide ≥1536
+// (기존 min-width:1124 + max-width:1125 의 1124~1125px 이중 매칭 → 1123.98px 경계로 통일)
 .container {
 	display: grid;
 	height: 100vh;
 	height: 100dvh;
 	grid-template-rows: auto 1fr auto;
 
-	// PC: 좌측 채널 사이드바 + 메인 (2열)
+	// desktop 이상: 좌 사이드바 + 메인 (2열 기본)
 	@media (min-width: 1124px) {
 		grid-template-areas: "header header" "aside main" "aside footer";
-		grid-template-columns: var(--antooza-sidebar-width, 220px) minmax(0, 1fr);
+		grid-template-columns: var(--antooza-sidebar-width, var(--az-sidebar-w, 220px)) minmax(0, 1fr);
 		transition: grid-template-columns 0.2s ease;
 	}
 
@@ -271,18 +367,33 @@
 		transition: none;
 	}
 
-	// 모바일/태블릿
-	@media (max-width: 1125px) {
+	// 모바일/태블릿: 1열 (사이드바는 슬라이드 오버레이, 유틸 슬롯 미표시 — 페이지가 하단 적층 결정)
+	@media (max-width: 1123.98px) {
 		grid-template-areas: "header" "main" "footer";
 		grid-template-columns: minmax(0, 1fr);
 	}
 
-	// 채널 기능 OFF (NEXT_PUBLIC_FEATURE_CHANNEL=false): 사이드바 없는 1열 레이아웃
+	// 우 유틸 슬롯 제공 페이지: 3열 (desktop 이상에서만)
+	&.hasUtility {
+		@media (min-width: 1124px) {
+			grid-template-areas: "header header header" "aside main utility" "aside footer footer";
+			grid-template-columns: var(--antooza-sidebar-width, var(--az-sidebar-w, 220px)) minmax(0, 1fr) var(--az-utility-w, 280px);
+		}
+	}
+
+	// 사이드바 없는 변형 (보존 — 필요 페이지에서 재사용)
 	&.noAside {
 		@media (min-width: 1124px) {
 			grid-template-areas: "header" "main" "footer";
 			grid-template-columns: minmax(0, 1fr);
 		}
+
+		&.hasUtility {
+			@media (min-width: 1124px) {
+				grid-template-areas: "header header" "main utility" "footer footer";
+				grid-template-columns: minmax(0, 1fr) var(--az-utility-w, 280px);
+			}
+		}
 	}
 
 	> .header {
@@ -303,13 +414,13 @@
 		}
 
 		// 모바일: 왼쪽에서 슬라이드
-		@media (max-width: 1125px) {
+		@media (max-width: 1123.98px) {
 			position: fixed;
-			top: 56px;
+			top: var(--header-h, 56px);
 			left: 0;
 			width: min(320px, 85vw);
-			height: calc(100vh - 56px);
-			height: calc(100dvh - 56px);
+			height: calc(100vh - var(--header-h, 56px));
+			height: calc(100dvh - var(--header-h, 56px));
 			z-index: 1000;
 			transform: translateX(-100%);
 			border-right: 1px solid var(--border-default);
@@ -320,14 +431,14 @@
 	// 사이드바 열림 상태
 	&.sidebarOpen {
 		> .aside {
-			@media (max-width: 1125px) {
+			@media (max-width: 1123.98px) {
 				transform: translateX(0);
 				box-shadow: 4px 0 12px var(--shadow-color);
 			}
 		}
 
 		> .overlay {
-			@media (max-width: 1125px) {
+			@media (max-width: 1123.98px) {
 				display: block;
 			}
 		}
@@ -341,13 +452,25 @@
 		border-bottom: 1px solid var(--border-default);
 	}
 
+	// 우측 유틸 슬롯 (desktop 이상 전용 — mobile/tablet 은 미표시)
+	> .utility {
+		grid-area: utility;
+		overflow-y: auto;
+		background: var(--bg-page);
+		border-left: 1px solid var(--border-default);
+
+		@media (max-width: 1123.98px) {
+			display: none;
+		}
+	}
+
 	// 오버레이 (모바일)
 	> .overlay {
 		display: none;
 
-		@media (max-width: 1125px) {
+		@media (max-width: 1123.98px) {
 			position: fixed;
-			top: 56px;
+			top: var(--header-h, 56px);
 			left: 0;
 			right: 0;
 			bottom: 0;
@@ -357,7 +480,7 @@
 	}
 
 	// 하단 내용
-	// - Mobile/Tablet (≤1125px): Amazon 스타일 — 중앙 정렬 링크 줄 + 별도 카피라이트 줄
+	// - Mobile/Tablet (≤1123.98px): Amazon 스타일 — 중앙 정렬 링크 줄 + 별도 카피라이트 줄
 	// - PC (≥1124px): 한 줄 — 좌측 링크 나열 + 우측 카피라이트
 	> .footer {
 		grid-area: footer;

+ 95 - 0
app/styles/market-sidebar.scss

@@ -0,0 +1,95 @@
+// 좌측 사이드바 자리표시 (MarketSidebar) — 관심종목/즐겨찾기 본구현 전 골격 (D6 그리드 셸)
+.market-sidebar {
+	display: flex;
+	flex-direction: column;
+	height: 100%;
+	min-height: 0;
+
+	&__header {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding: 10px 12px;
+		border-bottom: 1px solid var(--border-light);
+	}
+
+	&__title {
+		font-size: var(--fs-sm);
+		font-weight: 700;
+		color: var(--text-secondary);
+	}
+
+	&__collapse-btn {
+		display: inline-flex;
+		align-items: center;
+		justify-content: center;
+		width: 28px;
+		height: 28px;
+		border: none;
+		border-radius: 4px;
+		background: transparent;
+		color: var(--text-muted);
+		cursor: pointer;
+
+		&:hover {
+			background: var(--bg-subtle);
+			color: var(--text-primary);
+		}
+	}
+
+	&__body {
+		flex: 1;
+		min-height: 0;
+		overflow-y: auto;
+		padding: var(--sp-4);
+	}
+
+	&__empty {
+		padding: var(--sp-5) 0;
+		font-size: var(--fs-xs);
+		color: var(--text-muted);
+		text-align: center;
+	}
+
+	&__icon-placeholder {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		height: 36px;
+		color: var(--text-muted);
+	}
+
+	&__footer {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		gap: var(--sp-3);
+		padding: var(--sp-3);
+		border-top: 1px solid var(--border-light);
+	}
+
+	&__icon-btn {
+		display: inline-flex;
+		align-items: center;
+		justify-content: center;
+		width: 32px;
+		height: 32px;
+		border: none;
+		border-radius: 4px;
+		background: transparent;
+		color: var(--text-muted);
+		cursor: pointer;
+
+		&:hover {
+			background: var(--bg-subtle);
+			color: var(--text-primary);
+		}
+	}
+
+	&--collapsed {
+		.market-sidebar__header {
+			justify-content: center;
+			padding: 10px 6px;
+		}
+	}
+}

+ 233 - 0
app/styles/tokens/_colors.scss

@@ -0,0 +1,233 @@
+// ─────────────────────────────────────────────────────────────
+// D6 디자인 토큰 — 컬러 (다크 기본 + 라이트)
+// 테마 스위칭: html 클래스 `.dark`/`.light` + `data-theme` 속성 — app/layout.tsx 의 FOUC 방지 인라인 스크립트가 첫 페인트 전 세팅
+// 원칙 (d6-design-system.md §3):
+//  - 기존 시맨틱 변수명(--bg-page, --text-primary 등)은 전부 보존 — 값만 그레이 램프 기반으로 재정의 (기존 ~50개 style.scss 무수정 호환)
+//  - 포인트 컬러는 --az-accent 1개 (개미 오렌지). 기존 --color-accent / --btn-primary 는 alias 로 수렴
+//    ※ 설계 문서의 `--accent` 이름은 shadcn HSL triplet(--accent)과 충돌하여 --az-accent 로 명명
+//  - 상승 빨강(--price-up) / 하락 파랑(--price-down) — 국내 관례, WCAG AA 대비 검증값
+//  - 헤더 토큰(--bg-head/--text-head/--bg-icon/--head-*)은 라이트 테마에서도 다크 고정 (amazon 방식)
+// ─────────────────────────────────────────────────────────────
+
+// ── 다크 (기본) ──
+:root {
+	color-scheme: dark;
+
+	// 그레이 램프 15단 (배경 → 전경)
+	--gray-0: #0b0c0f;
+	--gray-1: #111318;
+	--gray-2: #16181d;
+	--gray-3: #1c1f26;
+	--gray-4: #22262e;
+	--gray-5: #2a2f38;
+	--gray-6: #353b46;
+	--gray-7: #454c59;
+	--gray-8: #5a6270;
+	--gray-9: #717a88;
+	--gray-10: #8b93a1;
+	--gray-11: #a6adb9;
+	--gray-12: #c2c8d1;
+	--gray-13: #dde1e7;
+	--gray-14: #f2f4f7;
+
+	// 기능 컬러 — 등락 (색+부호 병행 원칙, 등락 표시에 --az-accent 사용 금지)
+	--price-up: #f2555f;
+	--price-down: #4a8df2;
+	--price-flat: var(--gray-10);
+
+	// 포인트 컬러 (유일) — 액션 버튼·활성 탭·포커스 링 전용
+	--az-accent: #f7931a;
+	--az-accent-hover: #f59e0b;
+
+	// 시맨틱 — 램프 참조 (테마 전환 시 램프 재정의로 자동 반영, 기존 변수명 보존)
+	--bg-page: var(--gray-0);
+	--bg-elevated: var(--gray-2);
+	--bg-subtle: var(--gray-3);
+	--bg-input: var(--gray-2);
+	--bg-card: var(--gray-2); // 기존 미정의 참조 보정
+	--bg-default: var(--gray-0); // 기존 미정의 참조 보정
+	--border-light: var(--gray-3);
+	--border-subtle: var(--gray-3); // 기존 미정의 참조 보정
+	--border-default: var(--gray-4);
+	--border-strong: var(--gray-6);
+	--text-muted: var(--gray-9);
+	--text-secondary: var(--gray-11);
+	--text-primary: var(--gray-13);
+	--text-normal: var(--gray-14);
+	--fg-default: var(--gray-14);
+	--btn-default-border: var(--gray-6);
+	--btn-default-hover: var(--gray-4);
+
+	// 헤더 — 양 테마 다크 고정 (라이트 블록에서 재정의하지 않음)
+	--bg-head: #111318;
+	--text-head: #c2c8d1;
+	--bg-icon: #22262e;
+	--outline-default: #454c59;
+	--head-input-bg: #1c1f26;
+	--head-input-text: #dde1e7;
+	--head-input-placeholder: #717a88;
+	--head-input-border: #353b46;
+
+	// 포인트 alias (기존 변수명 보존)
+	--color-accent: var(--az-accent);
+	--color-accent-hover: var(--az-accent-hover);
+	--btn-primary: var(--az-accent);
+	--btn-primary-hover: var(--az-accent-hover);
+	--btn-primary-shadow: #b96504;
+	--btn-primary-text: #201503; // 오렌지 위 잉크 텍스트 — 흰색은 AA 미달
+
+	// 테마별 리터럴
+	--text-link: #60a5fa;
+	--text-link-hover: #3b82f6;
+	--shadow-color: rgba(0, 0, 0, 0.45);
+	--color-danger: #ef4444;
+	--color-success: #22c55e;
+	--color-warning-bg: #422006;
+	--color-warning-text: #fef08a;
+	--overlay-color: rgba(0, 0, 0, 0.7);
+	--sidebar-active-bg: #3d2800;
+	--list-row-active: #2a2a00;
+	--color-blue: #60a5fa;
+	--color-blue-hover: #3b82f6;
+	--color-blue-bg: rgba(96, 165, 250, 0.1);
+	--color-purple: #a78bfa;
+	--color-purple-hover: #8b5cf6;
+	--color-purple-bg: rgba(167, 139, 250, 0.15);
+	--color-danger-bg: #3f1212;
+	--color-danger-border: #7f1d1d;
+	--color-success-bg: #0f2d1a;
+	--color-success-text: #4ade80;
+	--bg-subtle-hover: rgba(255, 255, 255, 0.05);
+	// FAQ Q/A 라벨 — dark 배경 시인성용 밝기 ↑
+	--faq-q-color: #3fbd54;
+	--faq-a-color: #ff6b4a;
+
+	// shadcn HSL triplet — 그레이 램프 파생값 (시각적 단일화, 통합은 장기 과제)
+	--background: 225 15% 5%;
+	--foreground: 218 17% 89%;
+	--card: 220 12% 10%;
+	--card-foreground: 218 17% 89%;
+	--popover: 220 12% 10%;
+	--popover-foreground: 218 17% 89%;
+	--primary: 218 17% 89%;
+	--primary-foreground: 222 13% 13%;
+	--secondary: 221 14% 16%;
+	--secondary-foreground: 218 17% 89%;
+	--muted: 221 14% 16%;
+	--muted-foreground: 216 10% 59%;
+	--accent: 221 14% 16%;
+	--accent-foreground: 218 17% 89%;
+	--destructive: 0 63% 31%;
+	--destructive-foreground: 0 0% 98%;
+	--border: 221 14% 16%;
+	--input: 221 14% 16%;
+	--ring: 33 93% 54%; // 포인트 컬러 1개 원칙 — 포커스 링도 개미 오렌지
+	--chart-1: 220 70% 50%;
+	--chart-2: 160 60% 45%;
+	--chart-3: 30 80% 55%;
+	--chart-4: 280 65% 60%;
+	--chart-5: 340 75% 55%;
+	--crypto-up: 356 86% 64%; // = --price-up (레거시 alias, 신규 코드는 --price-* 사용)
+	--crypto-down: 216 87% 62%; // = --price-down
+	--crypto-neutral: 216 10% 59%;
+	--sidebar-background: 240 5.9% 10%;
+	--sidebar-foreground: 240 4.8% 95.9%;
+	--sidebar-primary: 224.3 76.3% 48%;
+	--sidebar-primary-foreground: 0 0% 100%;
+	--sidebar-accent: 240 3.7% 15.9%;
+	--sidebar-accent-foreground: 240 4.8% 95.9%;
+	--sidebar-border: 240 3.7% 15.9%;
+	--sidebar-ring: 217.2 91.2% 59.8%;
+}
+
+// ── 라이트 ──
+// 램프를 역할 등가로 뒤집어 재정의 → 시맨틱 매핑(--bg-page: var(--gray-0) 등)은 공통 유지
+// 헤더 토큰(--bg-head 등)은 여기서 재정의하지 않음 = 다크 고정
+html.light,
+[data-theme='light'] {
+	color-scheme: light;
+
+	--gray-0: #ffffff;
+	--gray-1: #f8f9fb;
+	--gray-2: #f2f4f7;
+	--gray-3: #e9ecf1;
+	--gray-4: #dde1e7;
+	--gray-5: #cfd4dc;
+	--gray-6: #b6bcc7;
+	--gray-7: #99a1ae;
+	--gray-8: #7e8695;
+	--gray-9: #667080;
+	--gray-10: #545d6b;
+	--gray-11: #424a57;
+	--gray-12: #313843;
+	--gray-13: #21262e;
+	--gray-14: #14171c;
+
+	--price-up: #d02330;
+	--price-down: #1861d8;
+	--price-flat: var(--gray-9);
+
+	--az-accent: #d97800;
+	--az-accent-hover: #b96504;
+	--btn-primary-shadow: #a35a00;
+
+	--text-link: #0060a9;
+	--text-link-hover: #c7511f;
+	--shadow-color: rgba(0, 0, 0, 0.12);
+	--color-danger: #d51b28;
+	--color-success: #4caf50;
+	--color-warning-bg: #fefce8;
+	--color-warning-text: #92400e;
+	--overlay-color: rgba(0, 0, 0, 0.5);
+	--sidebar-active-bg: #fff3e0;
+	--list-row-active: #faffd1;
+	--color-blue: #3b82f6;
+	--color-blue-hover: #2563eb;
+	--color-blue-bg: rgba(59, 130, 246, 0.06);
+	--color-purple: #8b5cf6;
+	--color-purple-hover: #7c3aed;
+	--color-purple-bg: rgba(139, 92, 246, 0.06);
+	--color-danger-bg: #fee2e2;
+	--color-danger-border: #fca5a5;
+	--color-success-bg: #dcfce7;
+	--color-success-text: #16a34a;
+	--bg-subtle-hover: rgba(0, 0, 0, 0.05);
+	--faq-q-color: #0b671b;
+	--faq-a-color: #ff4727;
+
+	--background: 0 0% 100%;
+	--foreground: 222 13% 15%;
+	--card: 0 0% 100%;
+	--card-foreground: 222 13% 15%;
+	--popover: 0 0% 100%;
+	--popover-foreground: 222 13% 15%;
+	--primary: 222 13% 15%;
+	--primary-foreground: 216 24% 96%;
+	--secondary: 216 22% 93%;
+	--secondary-foreground: 222 13% 15%;
+	--muted: 216 22% 93%;
+	--muted-foreground: 216 11% 45%;
+	--accent: 216 22% 93%;
+	--accent-foreground: 222 13% 15%;
+	--destructive: 0 84.2% 60.2%;
+	--destructive-foreground: 0 0% 98%;
+	--border: 213 17% 89%;
+	--input: 213 17% 89%;
+	--ring: 33 100% 43%;
+	--chart-1: 12 76% 61%;
+	--chart-2: 173 58% 39%;
+	--chart-3: 197 37% 24%;
+	--chart-4: 43 74% 66%;
+	--chart-5: 27 87% 67%;
+	--crypto-up: 356 71% 48%;
+	--crypto-down: 217 80% 47%;
+	--crypto-neutral: 216 11% 45%;
+	--sidebar-background: 0 0% 98%;
+	--sidebar-foreground: 240 5.3% 26.1%;
+	--sidebar-primary: 240 5.9% 10%;
+	--sidebar-primary-foreground: 0 0% 98%;
+	--sidebar-accent: 240 4.8% 95.9%;
+	--sidebar-accent-foreground: 240 5.9% 10%;
+	--sidebar-border: 220 13% 91%;
+	--sidebar-ring: 217.2 91.2% 59.8%;
+}

+ 24 - 0
app/styles/tokens/_density.scss

@@ -0,0 +1,24 @@
+// ─────────────────────────────────────────────────────────────
+// D6 디자인 토큰 — 밀도 3단 (compact / normal / expanded)
+// html[data-density] 로 전역 제어 (기본 normal) — ThemeProvider 가 localStorage(az-density) 영속
+// 이번 단계는 CSS 변수 골격 + 전환 훅만 제공. 개별 컴포넌트(DataTable 등) 적용은 후속(D6-3)
+// ─────────────────────────────────────────────────────────────
+
+:root {
+	// normal (기본)
+	--row-h: 38px;
+	--cell-fs: 13px;
+	--cell-px: 10px;
+}
+
+html[data-density='compact'] {
+	--row-h: 30px;
+	--cell-fs: 12px;
+	--cell-px: 6px;
+}
+
+html[data-density='expanded'] {
+	--row-h: 50px;
+	--cell-fs: 14px;
+	--cell-px: 14px;
+}

+ 4 - 0
app/styles/tokens/_index.scss

@@ -0,0 +1,4 @@
+// D6 디자인 토큰 진입점 — globals.scss 에서 `@use './styles/tokens';` 로 로드
+@use 'colors';
+@use 'layout';
+@use 'density';

+ 52 - 0
app/styles/tokens/_layout.scss

@@ -0,0 +1,52 @@
+// ─────────────────────────────────────────────────────────────
+// D6 디자인 토큰 — 레이아웃 / 타이포 / 간격 (d6-design-system.md §3.3, §4)
+// WIDE fluid 원칙: max-width 없음, 브레이크포인트 4단
+//  mobile <768 / tablet 768~1123.98 / desktop 1124~1535.98 / wide ≥1536
+// ─────────────────────────────────────────────────────────────
+
+:root {
+	// 그리드 셸
+	--header-h: 56px;
+	--ticker-h: 32px;
+	--az-sidebar-w: 220px; // 좌 사이드바 기본 폭 — 런타임 토글(--antooza-sidebar-width)이 우선
+	--az-utility-w: 280px; // 우 유틸 슬롯 폭
+
+	// 간격 스케일 (2/4/8/12/16/24/32)
+	--sp-1: 2px;
+	--sp-2: 4px;
+	--sp-3: 8px;
+	--sp-4: 12px;
+	--sp-5: 16px;
+	--sp-6: 24px;
+	--sp-7: 32px;
+
+	// 타이포 스케일 (base 14px — tailwind fontSize.base 와 동일)
+	--fs-2xs: 11px;
+	--fs-xs: 12px;
+	--fs-sm: 13px;
+	--fs-base: 14px;
+	--fs-lg: 16px;
+	--fs-xl: 18px;
+	--fs-2xl: 22px;
+
+	// 포커스 링 (접근성 — :focus-visible 전역 적용은 globals.scss)
+	--focus-ring-width: 2px;
+	--focus-ring-color: var(--az-accent);
+
+	// shadcn radius (테마 무관 — 기존 값 승계)
+	--radius: 0.5rem;
+}
+
+// desktop(1124~1535.98px): 좌 사이드바 아이콘 모드(64px)가 기본
+@media (min-width: 1124px) and (max-width: 1535.98px) {
+	:root {
+		--az-sidebar-w: 64px;
+	}
+}
+
+// wide(≥1536px): 우 유틸 확장
+@media (min-width: 1536px) {
+	:root {
+		--az-utility-w: 320px;
+	}
+}

+ 43 - 9
contexts/themeProvider.tsx

@@ -2,47 +2,81 @@
 
 import { createContext, useContext, useEffect, useState, useCallback } from 'react';
 
-type Theme = 'light' | 'dark';
+type Theme = 'light'|'dark';
+type Density = 'compact'|'normal'|'expanded';
+
+const THEME_KEY = 'az-theme';
+const LEGACY_THEME_KEY = 'bitforum-theme'; // 구 키 마이그레이션용
+const DENSITY_KEY = 'az-density';
 
 const ThemeContext = createContext<{
 	theme: Theme;
 	toggleTheme: () => void;
+	density: Density;
+	setDensity: (density: Density) => void;
 }>({
-	theme: 'light',
+	theme: 'dark',
 	toggleTheme: () => {},
+	density: 'normal',
+	setDensity: () => {},
 });
 
 export function useThemeContext() {
 	return useContext(ThemeContext);
 }
 
+// html 루트에 테마 반영 — .dark/.light 클래스(tailwind dark: variant 용) + data-theme(토큰 스위칭 용)
+function applyTheme(theme: Theme) {
+	const root = document.documentElement;
+	root.classList.toggle('dark', theme === 'dark');
+	root.classList.toggle('light', theme === 'light');
+	root.dataset.theme = theme;
+}
+
 export function ThemeProvider({ children }: { children: React.ReactNode }) {
-	const [theme, setTheme] = useState<Theme>('light');
+	// 기본 다크 (D6 확정) — 첫 페인트는 app/layout.tsx 의 인라인 스크립트가 결정 (FOUC 방지)
+	const [theme, setTheme] = useState<Theme>('dark');
+	const [density, setDensityState] = useState<Density>('normal');
 	const [mounted, setMounted] = useState(false);
 
 	useEffect(() => {
-		const stored = localStorage.getItem('bitforum-theme') as Theme | null;
-		const initial = stored ?? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
+		const stored = localStorage.getItem(THEME_KEY) ?? localStorage.getItem(LEGACY_THEME_KEY);
+		const initial: Theme = stored === 'light' ? 'light' : 'dark';
+		if (!localStorage.getItem(THEME_KEY) && stored) {
+			localStorage.setItem(THEME_KEY, initial);
+		}
+
+		const storedDensity = localStorage.getItem(DENSITY_KEY);
+		const initialDensity: Density = storedDensity === 'compact' || storedDensity === 'expanded' ? storedDensity : 'normal';
+
 		setTheme(initial);
-		document.documentElement.classList.toggle('dark', initial === 'dark');
+		setDensityState(initialDensity);
+		applyTheme(initial);
+		document.documentElement.dataset.density = initialDensity;
 		setMounted(true);
 	}, []);
 
 	const toggleTheme = useCallback(() => {
 		setTheme((prev) => {
 			const next = prev === 'light' ? 'dark' : 'light';
-			localStorage.setItem('bitforum-theme', next);
-			document.documentElement.classList.toggle('dark', next === 'dark');
+			localStorage.setItem(THEME_KEY, next);
+			applyTheme(next);
 			return next;
 		});
 	}, []);
 
+	const setDensity = useCallback((next: Density) => {
+		setDensityState(next);
+		localStorage.setItem(DENSITY_KEY, next);
+		document.documentElement.dataset.density = next;
+	}, []);
+
 	if (!mounted) {
 		return <>{children}</>;
 	}
 
 	return (
-		<ThemeContext.Provider value={{ theme, toggleTheme }}>
+		<ThemeContext.Provider value={{ theme, toggleTheme, density, setDensity }}>
 			{children}
 		</ThemeContext.Provider>
 	);

+ 2 - 2
hooks/useTheme.ts

@@ -3,6 +3,6 @@
 import { useThemeContext } from '@/contexts/themeProvider';
 
 export default function useTheme() {
-	const { theme, toggleTheme } = useThemeContext();
-	return { theme, toggleTheme, isDark: theme === 'dark' };
+	const { theme, toggleTheme, density, setDensity } = useThemeContext();
+	return { theme, toggleTheme, isDark: theme === 'dark', density, setDensity };
 }

+ 1 - 0
tailwind.config.ts

@@ -78,6 +78,7 @@ export default {
     			md: '768px',
     			lgm: '896px',
     			lg: '1024px',
+    			desk: '1124px', // SCSS 레이아웃 분기점(1124/1123.98)과 정합 (D6)
     			xlm: '1152px',
     			xl: '1280px',
     			'2xl': '1536px',