ARCANE BREW 개발기 (2) - Vue 3 + Tailwind v4 기반 구축
목차 보기
시리즈 목차 보기2 / 7
ARCANE BREW 개발기
2 / 7
- 아이디어와 세계관 설계
- Vue 3 + Tailwind v4 기반 구축
- Canvas 파티클 엔진과 랜딩 페이지
- 재료 시스템과 데이터 아키텍처
- 가마솥과 포션 조제 시스템
- 지팡이 공방과 업적 시스템
- 폴리싱과 배포
이전 편 요약
1편에서는 ARCANE BREW의 세계관과 기술 스택을 설계했다. 이번 편에서는 Phase 1 — 실제로 프로젝트를 생성하고, 판타지 테마를 CSS로 구현하고, 타입 시스템과 라우팅의 골격을 잡는 과정을 다룬다.
Vite + Vue 3 프로젝트 초기화
create-vue로 프로젝트를 생성한 뒤, Vite 설정을 정리했다.
// 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 안에서 직접 테마를 정의한다.
/* 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에 글래스모피즘은 꽤 잘 어울린다. 반투명한 유리 재질이 마법적인 분위기를 더해준다.
.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원소 체계를 타입으로 잡아두면 이후 작업이 훨씬 안전해진다. 핵심 인터페이스들을 간략히 소개한다.
// 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 형태로 라우팅되니 새로고침 문제가 없다.
// 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단 구성이다.
<!-- App.vue -->
<template>
<div class="min-h-screen bg-arcane-950 text-arcane-100">
<TheNavbar />
<main>
<RouterView />
</main>
<TheFooter />
</div>
</template>bg-arcane-950과 text-arcane-100 — 아까 @theme에서 정의한 커스텀 컬러가 바로 여기서 유틸리티 클래스로 쓰인다. 별도의 CSS 파일 없이 Tailwind 클래스만으로 다크 판타지 배경이 적용된다.
Pinia store는 기능별로 분리했다. useIngredientStore, useRecipeStore, useAchievementStore, usePlayerStore — 각각이 독립적인 도메인을 담당하고, usePlayerStore에서 LocalStorage 영속화를 처리한다. 자세한 구현은 이후 편에서 기능별로 다룬다.
기반을 세우고
Vite + Vue 3 프로젝트를 만들고 Tailwind CSS v4의 @theme으로 판타지 색상을 코드에 옮겼다. 글래스모피즘 유틸리티와 TypeScript 타입, Vue Router + Pinia 골격도 이때 함께 잡았다.
특히 Tailwind v4의 @theme 디렉티브는 이번 프로젝트에서 핵심이었다. 세계관의 색상 체계를 CSS 변수로 선언하면 Tailwind 유틸리티가 자동으로 따라오는 구조가 디자인 일관성을 유지하는 데 크게 도움이 됐다.
다음 편에서는 Phase 2 — Canvas 파티클 엔진과 랜딩 페이지를 다룬다. 가마솥에서 마법 파티클이 피어오르는 장면을 Canvas API로 구현하고, GSAP ScrollTrigger로 극적인 스크롤 애니메이션을 만드는 과정이다.