목록으로
ARCANE BREW 개발기

ARCANE BREW 개발기 (2) - Vue 3 + Tailwind v4 기반 구축

2025년 12월 28일9 min read
#Vue#Tailwind CSS#TypeScript#Vite#ARCANE BREW

목차 보기
시리즈 목차 보기2 / 7

이전 편 요약

1편에서는 ARCANE BREW의 세계관과 기술 스택을 설계했다. 이번 편에서는 Phase 1 — 실제로 프로젝트를 생성하고, 판타지 테마를 CSS로 구현하고, 타입 시스템과 라우팅의 골격을 잡는 과정을 다룬다.

🛠️ Vite + Vue 3 프로젝트 초기화

create-vue로 프로젝트를 생성한 뒤, Vite 설정을 정리했다.

ts
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import tailwindcss from '@tailwindcss/vite'
import { fileURLToPath, URL } from 'node:url'

export default defineConfig({
  base: '/RealRealFun/',
  plugins: [vue(), tailwindcss()],
  resolve: {
    alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) },
  },
})

base: '/RealRealFun/'은 GitHub Pages 배포를 위한 설정이다. 레포지토리명이 곧 서브 경로가 되기 때문에 빌드 시점에 base path를 잡아줘야 한다. @tailwindcss/vite 플러그인은 Tailwind CSS v4의 Vite 통합인데, PostCSS 설정 없이 플러그인 하나로 동작한다. v3 대비 설정이 훨씬 간결해졌다.

🎨 Tailwind CSS v4 커스텀 테마

이번 프로젝트에서 가장 공들인 부분이다. Tailwind v4의 @theme 디렉티브는 CSS 변수 기반으로 디자인 토큰을 선언하는 새로운 방식이다. tailwind.config.js 파일이 사라지고, CSS 안에서 직접 테마를 정의한다.

css
/* main.css */
@import "tailwindcss";

@theme {
  --color-arcane-50: #f5f3ff;
  --color-arcane-100: #ede9fe;
  --color-arcane-200: #ddd6fe;
  --color-arcane-300: #c4b5fd;
  --color-arcane-400: #a78bfa;
  --color-arcane-500: #8b5cf6;
  --color-arcane-600: #7c3aed;
  --color-arcane-700: #6d28d9;
  --color-arcane-800: #5b21b6;
  --color-arcane-900: #1e1338;
  --color-arcane-950: #0f0a1e;

  --color-gold-50: #fffbeb;
  --color-gold-100: #fef3c7;
  --color-gold-200: #fde68a;
  --color-gold-300: #fcd34d;
  --color-gold-400: #fbbf24;
  --color-gold-500: #f59e0b;
  --color-gold-600: #d97706;

  --color-elem-fire: #ef4444;
  --color-elem-water: #06b6d4;
  --color-elem-earth: #10b981;
  --color-elem-air: #a78bfa;
  --color-elem-spirit: #fbbf24;

  --font-display: 'Cinzel', serif;
  --font-body: 'Noto Sans KR', sans-serif;
  --font-serif: 'Noto Serif KR', serif;
}

--color-arcane-*는 딥 퍼플 기반의 메인 팔레트다. 950(#0f0a1e)이 배경색, 50(#f5f3ff)이 텍스트용 밝은 톤이다. 다크 판타지 분위기를 내려면 배경이 충분히 어두워야 하는데, 일반적인 gray 계열보다 보라빛이 감도는 950이 훨씬 분위기가 살았다.

--color-gold-*는 골드/앰버 악센트다. 연금술의 황금 변환을 연상시키는 색으로, 버튼 호버, 활성 상태, 강조 텍스트에 사용한다.

--color-elem-*는 1편에서 설계한 5원소 컬러를 그대로 가져왔다. bg-elem-fire, border-elem-water 같은 유틸리티가 자동 생성되니, 원소별 스타일링이 매우 직관적이다.

폰트는 세 가지를 조합했다. Cinzel은 로마 비문 스타일의 디스플레이 서체로 제목과 네비게이션에 사용한다. Noto Sans KR은 한국어 본문용, Noto Serif KR은 세계관 설명 텍스트처럼 고풍스러운 느낌이 필요한 곳에 쓴다.

✨ 글래스모피즘 유틸리티

다크 판타지 UI에 글래스모피즘은 꽤 잘 어울린다. 반투명한 유리 재질이 마법적인 분위기를 더해준다.

css
.glass {
  background: rgba(30, 19, 56, 0.6);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(139, 92, 246, 0.15);
}

.glass-card {
  background: rgba(30, 19, 56, 0.5);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(139, 92, 246, 0.1);
  transition: all 0.3s ease;
}

.glass-card:hover {
  background: rgba(30, 19, 56, 0.7);
  border-color: rgba(139, 92, 246, 0.3);
  box-shadow: 0 0 20px rgba(139, 92, 246, 0.1);
}

glass는 네비게이션 바나 모달 같은 고정 요소에, glass-card는 재료 카드나 레시피 카드처럼 호버 인터랙션이 필요한 요소에 사용한다. rgba(30, 19, 56, ...)arcane-900의 반투명 버전이다. 보라빛 반투명 배경 + 보라빛 테두리 조합이 다크 판타지 톤을 일관되게 유지해준다.

호버 시 배경 불투명도가 올라가고 테두리가 밝아지면서 은은한 box-shadow가 추가되는데, 카드가 마법적으로 활성화되는 느낌을 준다.

📐 TypeScript 타입 시스템

33종의 재료, 22개 레시피, 5원소 체계를 타입으로 잡아두면 이후 작업이 훨씬 안전해진다. 핵심 인터페이스들을 간략히 소개한다.

ts
// 5원소 유니온 타입
type Element = 'fire' | 'water' | 'earth' | 'air' | 'spirit'

// 마법 재료
interface Ingredient {
  id: string
  name: string
  element: Element
  rarity: 'common' | 'uncommon' | 'rare' | 'legendary'
  description: string
  icon: string
}

// 물약 레시피
interface Recipe {
  id: string
  name: string
  ingredients: string[]   // Ingredient ID 배열
  resultElement: Element
  difficulty: number       // 1~5
  description: string
}

// 업적
interface Achievement {
  id: string
  name: string
  category: 'explore' | 'brew' | 'collect'
  condition: string
  reward?: string
}

Element 유니온 타입이 시스템 전체를 관통한다. Ingredient의 원소 속성, Recipe의 결과 원소, 지팡이 코어의 원소 — 모두 동일한 타입을 참조한다. 나중에 원소를 추가하거나 이름을 변경할 때 한 곳만 수정하면 컴파일러가 관련 코드를 전부 잡아준다.

Wand(지팡이) 인터페이스도 있는데, 코어/나무/장식의 조합으로 108가지 변형이 가능하다. 이 부분은 Phase 6에서 상세히 다룬다.

🧭 Vue Router + Pinia 기본 구조

라우터는 Hash History를 선택했다. GitHub Pages에 정적 배포할 예정이라 서버 사이드 URL 리라이팅이 불가능하기 때문이다. Hash 모드에서는 /#/ingredients, /#/cauldron 형태로 라우팅되니 새로고침 문제가 없다.

ts
// router/index.ts
import { createRouter, createWebHashHistory } from 'vue-router'

const router = createRouter({
  history: createWebHashHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/', component: () => import('@/pages/HomePage.vue') },
    { path: '/ingredients', component: () => import('@/pages/IngredientsPage.vue') },
    { path: '/cauldron', component: () => import('@/pages/CauldronPage.vue') },
    { path: '/recipes', component: () => import('@/pages/RecipesPage.vue') },
    { path: '/wand', component: () => import('@/pages/WandPage.vue') },
    { path: '/profile', component: () => import('@/pages/ProfilePage.vue') },
  ],
})

모든 페이지를 lazy import로 처리했다. 판타지 세계의 각 "장소"가 하나의 라우트에 대응하는 구조다.

레이아웃은 단순하게 TheNavbar + RouterView + TheFooter 3단 구성이다.

vue
<!-- App.vue -->
<template>
  <div class="min-h-screen bg-arcane-950 text-arcane-100">
    <TheNavbar />
    <main>
      <RouterView />
    </main>
    <TheFooter />
  </div>
</template>

bg-arcane-950text-arcane-100 — 아까 @theme에서 정의한 커스텀 컬러가 바로 여기서 유틸리티 클래스로 쓰인다. 별도의 CSS 파일 없이 Tailwind 클래스만으로 다크 판타지 배경이 적용된다.

Pinia store는 기능별로 분리했다. useIngredientStore, useRecipeStore, useAchievementStore, usePlayerStore — 각각이 독립적인 도메인을 담당하고, usePlayerStore에서 LocalStorage 영속화를 처리한다. 자세한 구현은 이후 편에서 기능별로 다룬다.

정리

이번 편에서는 ARCANE BREW의 기술적 토대를 놓았다. Vite + Vue 3 프로젝트 생성, Tailwind CSS v4의 @theme으로 판타지 디자인 시스템 구축, 글래스모피즘 CSS 유틸리티, TypeScript 타입 정의, 그리고 Vue Router + Pinia의 기본 골격까지.

특히 Tailwind v4의 @theme 디렉티브는 이번 프로젝트에서 핵심이었다. 세계관의 색상 체계를 CSS 변수로 선언하면 Tailwind 유틸리티가 자동으로 따라오는 구조가 디자인 일관성을 유지하는 데 크게 도움이 됐다.

다음 편에서는 Phase 2 — Canvas 파티클 엔진과 랜딩 페이지를 다룬다. 가마솥에서 마법 파티클이 피어오르는 장면을 Canvas API로 구현하고, GSAP ScrollTrigger로 극적인 스크롤 애니메이션을 만드는 과정이다.

검색...

검색...