From 65c0adfd769b9ef11b897c96a3634c61120055b8 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Mon, 8 Dec 2025 00:18:03 +0900
Subject: feat(client): redesign frontend with TailwindCSS v4
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Replace inline styles with TailwindCSS, implementing a cohesive Japanese-inspired
design system with custom colors (cream, teal primary), typography (Fraunces,
DM Sans), and animations. Update all pages and components with consistent styling,
improve accessibility by adding aria-hidden to decorative SVGs, and configure
Biome for Tailwind CSS syntax support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---
 src/client/components/SyncStatusIndicator.tsx | 116 +++++++++++++++++++-------
 1 file changed, 84 insertions(+), 32 deletions(-)

(limited to 'src/client/components/SyncStatusIndicator.tsx')

diff --git a/src/client/components/SyncStatusIndicator.tsx b/src/client/components/SyncStatusIndicator.tsx
index 23e3ec6..0f555ca 100644
--- a/src/client/components/SyncStatusIndicator.tsx
+++ b/src/client/components/SyncStatusIndicator.tsx
@@ -20,63 +20,115 @@ export function SyncStatusIndicator() {
 		return "Synced";
 	};
 
-	const getStatusColor = (): string => {
+	const getStatusStyles = (): string => {
 		if (!isOnline) {
-			return "#6c757d"; // gray
+			return "bg-muted/10 text-muted";
 		}
 		if (isSyncing) {
-			return "#007bff"; // blue
+			return "bg-info/10 text-info";
 		}
 		if (status === SyncStatus.Error) {
-			return "#dc3545"; // red
+			return "bg-error/10 text-error";
 		}
 		if (pendingCount > 0) {
-			return "#ffc107"; // yellow
+			return "bg-warning/10 text-warning";
 		}
-		return "#28a745"; // green
+		return "bg-success/10 text-success";
 	};
 
-	const getStatusIcon = (): string => {
+	const getStatusIcon = () => {
 		if (!isOnline) {
-			return "\u25CB"; // hollow circle
+			return (
+				<svg
+					className="w-3.5 h-3.5"
+					fill="currentColor"
+					viewBox="0 0 20 20"
+					aria-hidden="true"
+				>
+					<circle cx="10" cy="10" r="4" />
+				</svg>
+			);
 		}
 		if (isSyncing) {
-			return "\u21BB"; // rotating arrows
+			return (
+				<svg
+					className="w-3.5 h-3.5 animate-spin"
+					fill="none"
+					viewBox="0 0 24 24"
+					aria-hidden="true"
+				>
+					<circle
+						className="opacity-25"
+						cx="12"
+						cy="12"
+						r="10"
+						stroke="currentColor"
+						strokeWidth="4"
+					/>
+					<path
+						className="opacity-75"
+						fill="currentColor"
+						d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
+					/>
+				</svg>
+			);
 		}
 		if (status === SyncStatus.Error) {
-			return "\u2717"; // cross mark
+			return (
+				<svg
+					className="w-3.5 h-3.5"
+					fill="currentColor"
+					viewBox="0 0 20 20"
+					aria-hidden="true"
+				>
+					<path
+						fillRule="evenodd"
+						d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
+						clipRule="evenodd"
+					/>
+				</svg>
+			);
 		}
 		if (pendingCount > 0) {
-			return "\u25D4"; // partial circle
+			return (
+				<svg
+					className="w-3.5 h-3.5"
+					fill="currentColor"
+					viewBox="0 0 20 20"
+					aria-hidden="true"
+				>
+					<path
+						fillRule="evenodd"
+						d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z"
+						clipRule="evenodd"
+					/>
+				</svg>
+			);
 		}
-		return "\u2713"; // check mark
+		return (
+			<svg
+				className="w-3.5 h-3.5"
+				fill="currentColor"
+				viewBox="0 0 20 20"
+				aria-hidden="true"
+			>
+				<path
+					fillRule="evenodd"
+					d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
+					clipRule="evenodd"
+				/>
+			</svg>
+		);
 	};
 
 	return (
 		<div
 			data-testid="sync-status-indicator"
-			style={{
-				display: "inline-flex",
-				alignItems: "center",
-				gap: "0.25rem",
-				padding: "0.25rem 0.5rem",
-				borderRadius: "4px",
-				backgroundColor: "#f8f9fa",
-				border: "1px solid #dee2e6",
-				fontSize: "0.875rem",
-			}}
+			className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium transition-colors ${getStatusStyles()}`}
 			title={lastError || undefined}
 		>
-			<span
-				style={{
-					color: getStatusColor(),
-					fontWeight: "bold",
-				}}
-				aria-hidden="true"
-			>
-				{getStatusIcon()}
-			</span>
-			<span style={{ color: getStatusColor() }}>{getStatusText()}</span>
+			{getStatusIcon()}
+			<span>{getStatusText()}</span>
 		</div>
 	);
 }
-- 
cgit v1.2.3-70-g09d2

