Skip to content

Commit 012589e

Browse files
committed
refactor(design): migrate to Tailwind v4 @theme tokens + accessible focus rings
Surgical design upgrade from Gemini 3.1 Pro audit. No feature changes. - globals.css: add @theme OKLCH tokens (light + .dark), radius scale, Pretendard-first --font-sans stack. KaTeX dark override preserved. - Button variants: semantic tokens (primary/secondary/destructive/accent) replace slate-* + dark: pairs; focus ring ring-1 -> ring-2 with offset. - DashboardShell: main bg-slate-50/dark:bg-slate-950 -> bg-muted/40. - ItemCard: rounded-lg -> rounded-xl, semantic bg-card, unified focus ring. - SkillItemsPanel ItemCard: one-off blue hover -> theme accent + focus-visible. - SearchBar ClearButton: 44x44 min tap target (WCAG AA). - SearchResults EmptyResults: dashed border boundary + muted icon. Audit proposal tracked in plan/gemini-design-upgrade-{prompt,output}.md.
1 parent 383592f commit 012589e

9 files changed

Lines changed: 448 additions & 20 deletions

File tree

apps/web/src/app/globals.css

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,81 @@
22

33
@custom-variant dark (&:where(.dark *));
44

5-
/* Dark mode: ensure KaTeX formulas are visible */
5+
@theme {
6+
--color-background: oklch(0.99 0.01 255);
7+
--color-foreground: oklch(0.15 0.02 255);
8+
9+
--color-card: oklch(1 0 0);
10+
--color-card-foreground: oklch(0.15 0.02 255);
11+
12+
--color-popover: oklch(1 0 0);
13+
--color-popover-foreground: oklch(0.15 0.02 255);
14+
15+
--color-primary: oklch(0.2 0.05 255);
16+
--color-primary-foreground: oklch(0.98 0 0);
17+
18+
--color-secondary: oklch(0.96 0.01 255);
19+
--color-secondary-foreground: oklch(0.2 0.05 255);
20+
21+
--color-muted: oklch(0.96 0.01 255);
22+
--color-muted-foreground: oklch(0.55 0.02 255);
23+
24+
--color-accent: oklch(0.96 0.01 255);
25+
--color-accent-foreground: oklch(0.2 0.05 255);
26+
27+
--color-destructive: oklch(0.6 0.15 20);
28+
--color-destructive-foreground: oklch(0.98 0 0);
29+
30+
--color-border: oklch(0.92 0.01 255);
31+
--color-input: oklch(0.92 0.01 255);
32+
--color-ring: oklch(0.2 0.05 255);
33+
34+
--radius-lg: 0.5rem;
35+
--radius-md: calc(var(--radius-lg) - 2px);
36+
--radius-sm: calc(var(--radius-md) - 2px);
37+
38+
--font-sans: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
39+
}
40+
41+
.dark {
42+
--color-background: oklch(0.15 0.02 255);
43+
--color-foreground: oklch(0.98 0.01 255);
44+
45+
--color-card: oklch(0.15 0.02 255);
46+
--color-card-foreground: oklch(0.98 0.01 255);
47+
48+
--color-popover: oklch(0.15 0.02 255);
49+
--color-popover-foreground: oklch(0.98 0.01 255);
50+
51+
--color-primary: oklch(0.98 0.01 255);
52+
--color-primary-foreground: oklch(0.2 0.05 255);
53+
54+
--color-secondary: oklch(0.22 0.03 255);
55+
--color-secondary-foreground: oklch(0.98 0.01 255);
56+
57+
--color-muted: oklch(0.22 0.03 255);
58+
--color-muted-foreground: oklch(0.65 0.02 255);
59+
60+
--color-accent: oklch(0.22 0.03 255);
61+
--color-accent-foreground: oklch(0.98 0.01 255);
62+
63+
--color-destructive: oklch(0.4 0.15 20);
64+
--color-destructive-foreground: oklch(0.98 0 0);
65+
66+
--color-border: oklch(0.22 0.03 255);
67+
--color-input: oklch(0.22 0.03 255);
68+
--color-ring: oklch(0.8 0.05 255);
69+
}
70+
671
@layer base {
72+
* {
73+
@apply border-border;
74+
}
75+
body {
76+
@apply bg-background text-foreground;
77+
}
78+
79+
/* Dark mode: ensure KaTeX formulas are visible */
780
.dark .katex,
881
.dark .katex .base,
982
.dark .katex .strut,

apps/web/src/components/dashboard/shell.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function DashboardShell({ children }: { children: React.ReactNode }) {
2121
{/* 메인 콘텐츠 영역 */}
2222
<div className="flex flex-1 flex-col overflow-hidden">
2323
<Header />
24-
<main id="main-content" className="flex-1 overflow-y-auto bg-slate-50 p-6 dark:bg-slate-950">
24+
<main id="main-content" className="flex-1 overflow-y-auto bg-muted/40 p-6">
2525
{children}
2626
</main>
2727
<HelpFab />

apps/web/src/components/items/item-card.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,10 @@ const ItemCard = memo(function ItemCard({
156156
onClick={onClick ? handleClick : undefined}
157157
onKeyDown={onClick ? handleKeyDown : undefined}
158158
className={cn(
159-
"flex flex-col gap-3 rounded-lg border border-slate-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-900",
159+
"flex flex-col gap-3 rounded-xl border bg-card text-card-foreground p-4",
160160
"transition-shadow duration-150",
161-
onClick && "cursor-pointer hover:border-slate-300 hover:shadow-md dark:hover:border-slate-600",
162-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-slate-950",
161+
onClick && "cursor-pointer hover:shadow-md hover:border-accent",
162+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
163163
className,
164164
)}
165165
>

apps/web/src/components/search/search-bar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ function ClearButton({ onClick }: Readonly<{ onClick: () => void }>) {
3232
<button
3333
type="button"
3434
onClick={onClick}
35-
className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full p-0.5 text-slate-400 hover:text-slate-600 focus:outline-none focus:ring-2 focus:ring-slate-400"
35+
className="absolute right-3 top-1/2 -translate-y-1/2 flex min-h-[44px] min-w-[44px] items-center justify-center rounded-full text-muted-foreground hover:text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
3636
aria-label="검색어 지우기"
3737
>
3838
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">

apps/web/src/components/search/search-results.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ function ResultsSkeleton() {
163163
// --- 빈 결과 ---
164164
function EmptyResults() {
165165
return (
166-
<div className="flex flex-col items-center justify-center py-16 text-slate-400">
167-
<svg className="mb-3 h-12 w-12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
166+
<div className="flex flex-col items-center justify-center rounded-xl border border-dashed py-16 text-muted-foreground">
167+
<svg className="mb-4 h-10 w-10 text-muted-foreground/50" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
168168
<circle cx="11" cy="11" r="8" />
169169
<line x1="21" y1="21" x2="16.65" y2="16.65" />
170170
</svg>

apps/web/src/components/skills/skill-items-panel.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ const ItemCard = memo(function ItemCard({
167167
onClick={handleClick}
168168
disabled={!onItemClick}
169169
className={cn(
170-
"w-full rounded-lg border border-slate-200 p-3 text-left transition-colors",
171-
onItemClick ? "cursor-pointer hover:border-blue-300 hover:bg-blue-50/50" : "cursor-default",
170+
"w-full rounded-lg border bg-card p-3 text-left transition-colors",
171+
onItemClick ? "cursor-pointer hover:border-primary/50 hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" : "cursor-default",
172172
)}
173173
>
174174
<div className="line-clamp-2 min-h-[2.5rem] overflow-hidden">

apps/web/src/components/ui/button.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,16 @@ import { cva, type VariantProps } from "class-variance-authority";
33
import { cn } from "@/lib/utils";
44

55
const buttonVariants = cva(
6-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-slate-950 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 dark:focus-visible:ring-slate-300",
6+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
77
{
88
variants: {
99
variant: {
10-
default: "bg-slate-900 text-slate-50 shadow hover:bg-slate-900/90 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/90",
11-
destructive:
12-
"bg-red-500 text-slate-50 shadow-sm hover:bg-red-500/90 dark:bg-red-600 dark:hover:bg-red-600/90",
13-
outline:
14-
"border border-slate-200 bg-white shadow-sm hover:bg-slate-100 hover:text-slate-900 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 dark:hover:bg-slate-800 dark:hover:text-slate-100",
15-
secondary:
16-
"bg-slate-100 text-slate-900 shadow-sm hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-slate-800/80",
17-
ghost: "hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-100",
18-
link: "text-slate-900 underline-offset-4 hover:underline dark:text-slate-100",
10+
default: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
11+
destructive: "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
12+
outline: "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
13+
secondary: "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
14+
ghost: "hover:bg-accent hover:text-accent-foreground",
15+
link: "text-primary underline-offset-4 hover:underline",
1916
},
2017
size: {
2118
default: "h-9 px-4 py-2",

0 commit comments

Comments
 (0)