- {t.privacy.guarantees.map((item, i) => (
+ {t.privacy.guarantees.map((g, i) => (
diff --git a/site/src/tokens.ts b/site/src/tokens.ts
index 2df7de6..8aa616e 100644
--- a/site/src/tokens.ts
+++ b/site/src/tokens.ts
@@ -11,32 +11,33 @@
// ── Colors ──────────────────────────────────────────────
export const color = {
brand: {
- amber: '#f25b29',
- amberLight: '#ff7a4d',
- amberDark: '#c44a22',
- amberGlow: 'rgba(242,91,41,0.6)',
- amberDim: 'rgba(242,91,41,0.15)',
- amberMuted: 'rgba(242,91,41,0.08)',
+ blue: '#3b82f6',
+ blueLight: '#60a5fa',
+ blueDark: '#2563eb',
+ blueGlow: 'rgba(59,130,246,0.5)',
+ blueDim: 'rgba(59,130,246,0.14)',
+ blueMuted: 'rgba(59,130,246,0.08)',
},
surface: {
- 950: '#0a0a0c',
- 900: '#0d0d0f',
- 800: '#131315',
- 700: '#19191b',
- 600: '#1e1f21',
- 500: '#242427',
- 400: '#2a2a2d',
- 300: '#333338',
- 200: '#3a3b3f',
- 100: '#4a4b50',
+ 950: '#08090c',
+ 900: '#0d0f14',
+ 800: '#13161f',
+ 700: '#1a1e2b',
+ 600: '#222838',
+ 500: '#2b3245',
+ 400: '#363f57',
+ 300: '#434d69',
+ 200: '#535f7f',
+ 100: '#68769c',
},
neutral: {
- 50: '#fafafa',
- 100: '#f0f0f1',
- 200: '#d4d4d8',
- 300: '#a1a1aa',
- 400: '#71717a',
- 500: '#52525b',
+ 50: '#ffffff',
+ 100: '#f4f4f5',
+ 200: '#e4e4e7',
+ 300: '#d4d4d8',
+ 400: '#a1a1aa',
+ 500: '#71717a',
+ 600: '#52525b',
},
semantic: {
success: '#22c55e',
@@ -46,9 +47,10 @@ export const color = {
} as const
// ── Typography ──────────────────────────────────────────
+// 한+영 혼용 헤드라인의 스트로크 일관성을 위해 디스플레이도 Pretendard로 통일.
export const font = {
- display: '"Space Grotesk", sans-serif',
- sans: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
+ display: '"Pretendard Variable", Pretendard, -apple-system, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',
+ sans: '"Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Apple SD Gothic Neo", "Noto Sans KR", sans-serif',
mono: '"JetBrains Mono", "Fira Code", monospace',
} as const
@@ -61,18 +63,19 @@ export const space = {
// ── Shadows ─────────────────────────────────────────────
export const shadow = {
- insetPanel: 'inset 0 2px 6px rgba(0,0,0,0.6), 0 1px 1px rgba(255,255,255,0.04)',
- chassis: '0 1px 3px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.04)',
- glowSm: '0 0 10px rgba(242,91,41,0.3)',
- glowMd: '0 0 20px rgba(242,91,41,0.25), 0 0 40px rgba(242,91,41,0.1)',
- glowLg: '0 0 30px rgba(242,91,41,0.3), 0 0 60px rgba(242,91,41,0.15), 0 0 90px rgba(242,91,41,0.05)',
+ insetPanel: 'inset 0 1px 3px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.06)',
+ chassis: '0 4px 20px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06)',
+ glowSm: '0 0 12px rgba(59,130,246,0.3)',
+ glowMd: '0 0 24px rgba(59,130,246,0.25), 0 0 48px rgba(59,130,246,0.1)',
+ glowLg: '0 0 36px rgba(59,130,246,0.35), 0 0 72px rgba(59,130,246,0.15)',
} as const
// ── Radii ───────────────────────────────────────────────
export const radius = {
- instrument: '2px',
- panel: '6px',
- card: '12px',
+ instrument: '4px',
+ panel: '8px',
+ card: '16px',
+ cardLarge: '24px',
pill: '999px',
} as const
diff --git a/site/tailwind.config.js b/site/tailwind.config.js
index 3e2bea9..48ee54a 100644
--- a/site/tailwind.config.js
+++ b/site/tailwind.config.js
@@ -5,50 +5,88 @@ export default {
extend: {
colors: {
brand: {
- amber: '#f25b29',
- 'amber-light': '#ff7a4d',
- 'amber-dark': '#c44a22',
- 'amber-glow': 'rgba(242,91,41,0.6)',
- 'amber-dim': 'rgba(242,91,41,0.15)',
- 'amber-muted': 'rgba(242,91,41,0.08)',
+ // 브랜드 통일: 앱(Midnight Glass) blue 토큰과 동일 값 — 레거시 amber 폐기
+ blue: '#3b82f6',
+ 'blue-light': '#60a5fa',
+ 'blue-dark': '#2563eb',
+ 'blue-glow': 'rgba(59,130,246,0.5)',
+ 'blue-dim': 'rgba(59,130,246,0.14)',
+ 'blue-muted': 'rgba(59,130,246,0.08)',
},
surface: {
- 950: '#0a0a0c',
- 900: '#0d0d0f',
- 800: '#131315',
- 700: '#19191b',
- 600: '#1e1f21',
- 500: '#242427',
- 400: '#2a2a2d',
- 300: '#333338',
- 200: '#3a3b3f',
- 100: '#4a4b50',
+ 950: '#08090c',
+ 900: '#0d0f14',
+ 800: '#13161f',
+ 700: '#1a1e2b',
+ 600: '#222838',
+ 500: '#2b3245',
+ 400: '#363f57',
+ 300: '#434d69',
+ 200: '#535f7f',
+ 100: '#68769c',
},
neutral: {
- 50: '#fafafa',
- 100: '#f0f0f1',
- 200: '#d4d4d8',
- 300: '#a1a1aa',
- 400: '#71717a',
- 500: '#52525b',
- 600: '#3f3f46',
+ 50: '#ffffff',
+ 100: '#f4f4f5',
+ 200: '#e4e4e7',
+ 300: '#d4d4d8',
+ 400: '#a1a1aa',
+ 500: '#71717a',
+ 600: '#52525b',
+ 700: '#3f3f46',
+ 800: '#27272a',
+ 900: '#18181b',
},
},
fontFamily: {
- display: ['"Space Grotesk"', 'sans-serif'],
sans: [
- '-apple-system', 'BlinkMacSystemFont', '"Segoe UI"', 'Roboto',
- '"Helvetica Neue"', 'Arial', 'sans-serif',
+ '"Pretendard Variable"',
+ 'Pretendard',
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ 'system-ui',
+ 'Roboto',
+ '"Helvetica Neue"',
+ '"Segoe UI"',
+ '"Apple SD Gothic Neo"',
+ '"Noto Sans KR"',
+ '"Malgun Gothic"',
+ 'sans-serif',
+ ],
+ display: [
+ '"Pretendard Variable"',
+ 'Pretendard',
+ '-apple-system',
+ 'BlinkMacSystemFont',
+ 'system-ui',
+ '"Apple SD Gothic Neo"',
+ '"Noto Sans KR"',
+ 'sans-serif',
+ ],
+ mono: [
+ '"JetBrains Mono"',
+ '"Fira Code"',
+ 'ui-monospace',
+ 'SFMono-Regular',
+ 'Menlo',
+ 'Monaco',
+ 'Consolas',
+ 'monospace',
],
- mono: ['"JetBrains Mono"', '"Fira Code"', 'monospace'],
},
fontSize: {
- 'hero': ['clamp(2.5rem, 5vw, 4.25rem)', { lineHeight: '1.15', letterSpacing: '-0.03em' }],
- 'display': ['clamp(2rem, 3.5vw, 3.25rem)', { lineHeight: '1.2', letterSpacing: '-0.025em' }],
- 'micro': ['0.625rem', { lineHeight: '1.2', letterSpacing: '0.1em' }],
- 'nano': ['0.5625rem', { lineHeight: '1.2', letterSpacing: '0.12em' }],
+ // 한글 헤드라인: 행간 1.2+ (블록 글자의 숨 쉬는 공간), 자간 -0.01~-0.02em.
+ 'hero': ['clamp(2.75rem, 5.5vw, 4.5rem)', { lineHeight: '1.22', letterSpacing: '-0.02em' }],
+ 'display': ['clamp(2.25rem, 4vw, 3.5rem)', { lineHeight: '1.28', letterSpacing: '-0.015em' }],
+ 'h2': ['clamp(1.75rem, 3vw, 2.5rem)', { lineHeight: '1.32', letterSpacing: '-0.012em' }],
+ 'h3': ['1.375rem', { lineHeight: '1.4', letterSpacing: '-0.008em' }],
+ 'micro': ['0.75rem', { lineHeight: '1.4', letterSpacing: '0.04em' }],
+ 'nano': ['0.6875rem', { lineHeight: '1.4', letterSpacing: '0.05em' }],
},
spacing: {
+ '4.5': '1.125rem',
+ '13': '3.25rem',
+ '15': '3.75rem',
'18': '4.5rem',
'22': '5.5rem',
'26': '6.5rem',
@@ -56,22 +94,25 @@ export default {
'34': '8.5rem',
},
borderRadius: {
- 'instrument': '2px',
- 'panel': '6px',
- 'card': '12px',
+ 'instrument': '4px',
+ 'panel': '8px',
+ 'card': '16px',
+ '2card': '20px',
+ '3card': '24px',
},
boxShadow: {
- 'inset-panel': 'inset 0 2px 6px rgba(0,0,0,0.6), 0 1px 1px rgba(255,255,255,0.04)',
- 'chassis': '0 1px 3px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.04)',
- 'glow-sm': '0 0 10px rgba(242,91,41,0.3)',
- 'glow-md': '0 0 20px rgba(242,91,41,0.25), 0 0 40px rgba(242,91,41,0.1)',
- 'glow-lg': '0 0 30px rgba(242,91,41,0.3), 0 0 60px rgba(242,91,41,0.15), 0 0 90px rgba(242,91,41,0.05)',
+ 'inset-panel': 'inset 0 1px 3px rgba(0,0,0,0.6), inset 0 1px 0 rgba(255,255,255,0.06)',
+ 'chassis': '0 4px 20px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06)',
+ 'glow-sm': '0 0 12px rgba(59,130,246,0.3)',
+ 'glow-md': '0 0 24px rgba(59,130,246,0.25), 0 0 48px rgba(59,130,246,0.1)',
+ 'glow-lg': '0 0 36px rgba(59,130,246,0.35), 0 0 72px rgba(59,130,246,0.15)',
+ 'glass-card': '0 12px 32px -4px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.08)',
},
animation: {
'glow-pulse': 'glow-pulse 2s ease-in-out infinite',
'scan': 'scan 8s linear infinite',
'blink': 'blink 1.4s ease-in-out infinite',
- 'fade-in-up': 'fade-in-up 0.6s ease-out forwards',
+ 'fade-in-up': 'fade-in-up 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards',
},
keyframes: {
'glow-pulse': {
@@ -87,7 +128,7 @@ export default {
'50%': { opacity: '0.3' },
},
'fade-in-up': {
- '0%': { opacity: '0', transform: 'translateY(20px)' },
+ '0%': { opacity: '0', transform: 'translateY(16px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
},
diff --git a/site/vite.config.ts b/site/vite.config.ts
index cecf58e..4cf2b0c 100644
--- a/site/vite.config.ts
+++ b/site/vite.config.ts
@@ -1,7 +1,40 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
+import { cpSync, mkdirSync } from 'node:fs'
+import { dirname, join } from 'node:path'
+import { fileURLToPath } from 'node:url'
+
+const siteRoot = dirname(fileURLToPath(import.meta.url))
+const safePublicFiles = [
+ '.well-known/assetlinks.json',
+ 'accept-invite.css',
+ 'accept-invite.html',
+ 'accept-invite.js',
+ 'accept-invite/index.html',
+ 'download.html',
+ 'favicon.svg',
+ 'legal.css',
+ 'privacy/index.html',
+ 'terms/index.html',
+ 'delete-account/index.html',
+]
export default defineConfig({
- plugins: [react()],
+ // `public/releases` contains retained historical binaries. It is deliberately
+ // outside the build graph; only this non-binary allowlist can reach dist.
+ publicDir: false,
+ plugins: [
+ react(),
+ {
+ name: 'copy-safe-public-files',
+ closeBundle() {
+ for (const relativePath of safePublicFiles) {
+ const destination = join(siteRoot, 'dist', relativePath)
+ mkdirSync(dirname(destination), { recursive: true })
+ cpSync(join(siteRoot, 'public', relativePath), destination, { errorOnExist: true })
+ }
+ },
+ },
+ ],
base: './',
})