Files
slot/docs/04-theme-architecture.md
chpark 980d7d905d Add Next.js + PostgreSQL rewrite scaffold with 4-theme system
Stack
- Next.js 15 (App Router) + TypeScript + Drizzle ORM + postgres-js
- Node scrypt for password hashing; PBKDF2 verifier for legacy gnuboard5 hashes
- pnpm workspace monorepo: apps/web + packages/{db,auth,themes,...}

Themes (admin-selectable at /admin/themes)
- basic     : 그누보드 default reproduction (light, blue accent)
- eyoom     : eb4_maga_005 매거진 reproduction (dark, orange accent, ranking sidebar)
- amina     : Aminam Builder reproduction (light, violet gradient, card grid)
- youngcart : 영카트 shop reproduction (red accent, search bar, category nav)

DB
- New schema (12 tables) pushed to PG via drizzle-kit: members, sessions, boards,
  posts, point_ledger, app_settings, bacara_*, lottery_tickets, roulette_spins,
  game_points, board_groups
- Legacy data still readable from inspection2 schema via @slot/db/legacy

Verified end-to-end against the migrated DB on localhost:3000:
- Home renders with active theme tokens injected as CSS variables
- /free lists 442K real posts from inspection2.g5_write_free
- Login (testlogin/test1234) issues session cookie, header switches to
  "테스트님 환영합니다 / 로그아웃"
- Switching app_settings.theme.global from eyoom → amina swaps colors,
  layout, and Korean nav labels site-wide on next request

Migration docs added: 03-migration-plan, 04-theme-architecture,
05-local-dev-setup, 06-feature-inventory.
2026-04-27 18:51:32 +09:00

8.6 KiB

Theme Architecture — 4-Theme Built-in System

0. 요구사항 (사용자 명시)

"테마 관리에서 리액트 기반 한 가지 — 기본 / 이윰빌더 / 아미나빌더 / 영카드 — 이렇게 선택할 수 있게 구현"

→ 운영자가 어드민 화면에서 사이트 전체 테마를 한 번에 전환 가능. 페이지 단위 / 게시판 단위 오버라이드도 지원.

1. 4종 테마 정의

코드 한글명 모티프 활용 시나리오
basic 기본 그누보드5 default skin (responsive bootstrap-ish) 미니멀, 신규 도메인 시작
eyoom 이윰빌더 현재 운영 중인 eb4_maga_005 (매거진 + 사이드바 + 위젯) 컨텐츠 중심 커뮤니티
amina 아미나빌더 그누보드용 Amina Builder (모던 카드형 + 메가메뉴) 트래픽 큰 종합 커뮤니티
youngcart 영카드(영카트) 영카트 쇼핑몰 + 게시판 + 마이페이지 쇼핑 중심 사이트

2. 패키지 구조 — packages/themes/

packages/themes/
├── core/                          # 테마 공통 인터페이스
│   ├── ThemeProvider.tsx          # context, 토큰, css var 주입
│   ├── ThemeRegistry.ts           # 모든 테마 메타데이터 + lazy loader
│   ├── types.ts                   # ThemeManifest, Slot, Layout 타입
│   └── slots.ts                   # Header/Footer/Side/Banner/PostList/PostView 슬롯
├── basic/
│   ├── manifest.ts
│   ├── tokens.ts                  # CSS variables (--color-primary, --space-md, etc.)
│   ├── layouts/{root,board,post,shop}.tsx
│   ├── components/{Header,Footer,Sidebar,PostCard,...}.tsx
│   ├── styles/                    # tailwind.config 또는 sass
│   └── index.ts                   # default export = ThemeManifest
├── eyoom/
│   ├── manifest.ts
│   ├── tokens.ts                  # 이윰 매거진 컬러/타이포 (참고: src/theme/eb4_maga_005)
│   ├── layouts/...
│   ├── components/{MegaMenu,RankingSidebar,LatestWidget,...}.tsx
│   └── index.ts
├── amina/                         # Aminam Builder reproduction
│   └── ...
└── youngcart/                     # YoungCart shop-centric
    ├── components/{ProductGrid,CartDrawer,CategoryNav,...}.tsx
    └── ...

3. 테마 인터페이스

// packages/themes/core/types.ts
export interface ThemeManifest {
  id: 'basic' | 'eyoom' | 'amina' | 'youngcart';
  name: string;                       // 한글 표시명
  preview: string;                    // 썸네일 URL
  tokens: ThemeTokens;                // CSS 변수 (런타임 주입)
  layouts: {
    root:   ComponentType<LayoutProps>;
    board:  ComponentType<BoardLayoutProps>;
    post:   ComponentType<PostLayoutProps>;
    shop:   ComponentType<ShopLayoutProps>;
    page:   ComponentType<PageLayoutProps>;
  };
  slots: {
    Header:    ComponentType<HeaderProps>;
    Footer:    ComponentType<FooterProps>;
    Sidebar:   ComponentType<SidebarProps>;
    PostList:  ComponentType<PostListProps>;
    PostCard:  ComponentType<PostCardProps>;
    PostView:  ComponentType<PostViewProps>;
    LoginForm: ComponentType<LoginFormProps>;
    NewWindow: ComponentType<NewWindowProps>;
    // ...20개 정도 슬롯 표준화
  };
  features?: {
    shop?: boolean;            // youngcart 만 true
    magazineWidgets?: boolean; // eyoom 만 true
    megaMenu?: boolean;        // amina, eyoom
  };
}

4. Slot/Override 메커니즘

4.1 글로벌 테마

  • app_settings.theme = 'eyoom' (DB) 가 활성 테마
  • apps/web/src/app/layout.tsxThemeRegistry.load(activeTheme) 으로 동적 import
  • React Server Component 단계에서 결정 → 첫 페인트부터 올바른 레이아웃

4.2 페이지 단위 override

  • 게시판/카테고리/페이지에 theme_override 컬럼
  • 예: 자유게시판은 eyoom, 쇼핑은 youngcart 동시 운영 가능

4.3 사용자 토글 (옵션)

  • 회원이 mb_theme_pref 로 본인 선호 테마 선택 가능
  • cookie + DB 저장
  • 관리자가 기능 on/off 제어

5. 관리자 — 테마 선택 UI

apps/admin/src/app/themes/page.tsx:

┌─ 테마 관리 ──────────────────────────────┐
│  [기본] [이윰빌더] [아미나빌더] [영카드]   │   ← 4개 카드, 활성 테마 강조
│                                          │
│  ◉ 기본 사이트 테마: [이윰빌더 ▼]          │
│                                          │
│  ─ 영역별 오버라이드 ─                     │
│  · 자유게시판: (글로벌)                    │
│  · 쇼핑몰: 영카드                          │
│  · /event 페이지: 아미나빌더               │
│                                          │
│  ─ 미리보기 ─                              │
│  https://localhost:3000/?_preview=amina   │
│                                          │
│  [저장] [기본값 복원]                       │
└──────────────────────────────────────────┘

DB:

CREATE TABLE app_settings (
  key   TEXT PRIMARY KEY,
  value JSONB
);
INSERT INTO app_settings VALUES
 ('theme.global', '"eyoom"'::jsonb),
 ('theme.overrides', '[{"path":"/shop/*","theme":"youngcart"}]');

6. 디자인 토큰 표준

각 테마는 동일한 토큰 스키마를 만족 → CSS 변수로 주입 → 컴포넌트는 토큰만 참조 (매직 컬러 금지):

// tokens.ts (예: eyoom)
export const tokens: ThemeTokens = {
  color: {
    primary:        '#ff5722',
    primaryDark:    '#e64a19',
    bg:             '#0e0f12',
    bgSurface:      '#16181d',
    text:           '#e6e6e6',
    textMuted:      '#9aa0a6',
    border:         '#2a2d34',
    success:        '#21d07a',
    danger:         '#e74c3c',
    warning:        '#f5b041',
  },
  font: {
    sans: '"Noto Sans KR", system-ui, sans-serif',
    mono: '"JetBrains Mono", monospace',
  },
  radius:  { sm: '4px', md: '8px', lg: '14px', pill: '999px' },
  space:   { xs: '4px', sm: '8px', md: '16px', lg: '24px', xl: '40px' },
  shadow:  { sm: '...', md: '...', lg: '...' },
  z:       { sticky: 100, modal: 1000, toast: 2000 },
};

각 테마는 동일 키를 채우되 값만 다르게 (basic 은 라이트, eyoom 은 다크 매거진, amina 는 그라디언트, youngcart 는 화이트 + 액센트)

7. 마이그레이션 매핑 (이윰 → React)

기존 (theme/eb4_maga_005/) 신규 (packages/themes/eyoom/)
head.html.php layouts/root.tsx (Header)
head.sub.html.php slots/Header.tsx
tail.html.php slots/Footer.tsx
side.html.php slots/Sidebar.tsx
index.html.php layouts/root.tsx (홈 위젯)
skin/board/<X>/list.skin.html.php slots/PostList.tsx
skin/board/<X>/view.skin.html.php slots/PostView.tsx
css/eyoom.css, css/eyoom-style.css styles/*.css (CSS module 또는 tailwind)
js/eyoom.js React hooks / event handlers

매거진 위젯 (이윰 코어)

  • 최신글 슬라이더 (eyoom_slider) → <LatestSlider />
  • 회원 랭킹 (eyoom_ranking) → <MemberRanking type="bacara|slots|..." />
  • 출석 (eyoom_attendance) → <AttendanceWidget />
  • 배너 (eyoom_banner) → <RotatingBanner />

8. 아미나빌더 디자인 노트

  • 메가메뉴 (1단 큰 카테고리 + 2단 서브)
  • 카드형 그리드 (3열 → 모바일 1열)
  • 섹션별 스킨 시스템 (홈에 여러 게시판 위젯 동시 렌더)
  • 라이트/다크 테마 토글
  • 인기 회원 랭킹, 실시간 댓글, 인기글 사이드바

9. 영카드 디자인 노트

  • 상단 카테고리 가로 네비
  • 메인 = 베스트 상품 그리드 + 쿠폰존 + 이벤트 슬라이더
  • 마이페이지: 주문/배송/쿠폰/적립금
  • 게시판은 영카트 기본 룩 (테이블형 리스트)

10. 신규 테마 추가 절차 (확장성)

  1. packages/themes/<id>/ 폴더 생성, manifest 작성
  2. ThemeRegistry.register({ id: '<id>', loader: () => import('./<id>') }) 추가
  3. app_settings.theme.global 후보에 자동 노출
  4. 별도 빌드 없이 hot reload (Next.js dev 모드)

11. 성능

  • 각 테마 번들은 lazy chunk → 활성 테마만 다운로드
  • 테마 토큰은 <style>:root{--color-primary: ...}</style> 인라인 → FOUC 방지
  • RSC 단에서 테마 결정 → 클라이언트 hydration 최소화