From 31eb600b52d6701e41f187026d4c4e2534e3cd80 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Tue, 31 Mar 2026 21:07:40 +0800 Subject: [PATCH 01/37] feat: create `@theme` block --- apps/web/app/dashboard/page.tsx | 24 +------- apps/web/app/globals.css | 101 +++++++++++++------------------- apps/web/lib/utils.ts | 6 ++ 3 files changed, 51 insertions(+), 80 deletions(-) create mode 100644 apps/web/lib/utils.ts diff --git a/apps/web/app/dashboard/page.tsx b/apps/web/app/dashboard/page.tsx index 44d6d64..9cb117b 100644 --- a/apps/web/app/dashboard/page.tsx +++ b/apps/web/app/dashboard/page.tsx @@ -266,16 +266,9 @@ export default function Dashboard() {

YOUR PROFILE

- - {data?.userRole === "admin" ? "ADMIN" : "REGULAR USER"} - + + {data?.userRole === "admin" ? "ADMIN" : "REGULAR USER"} +
@@ -841,17 +834,6 @@ export default function Dashboard() { } /* Badges */ - .status-badge { - background: color-mix(in srgb, var(--status-success) 15%, transparent); - color: var(--status-success); - border: 1px solid var(--status-success); - padding: 4px 8px; - border-radius: 4px; - font-family: var(--font-chakra); - font-size: 10px; - font-weight: 700; - letter-spacing: 0.1em; - } .count-badge { background: color-mix(in srgb, var(--status-warning) 15%, transparent); diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 3ebd735..07eff2f 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -1,5 +1,42 @@ @import "tailwindcss"; +@theme { + --font-chakra: var(--font-chakra), sans-serif; + --font-nunito: var(--font-nunito), sans-serif; + --font-cubao-wide: var(--font-cubao-wide), sans-serif; + + --color-base: var(--bg-base); + --color-panel: var(--bg-panel); + --color-panel-hover: var(--bg-panel-hover); + + --color-primary: var(--text-primary); + --color-secondary: var(--text-secondary); + --color-border-color: var(--border-color); + + --color-neon-blue: var(--neon-blue); + --color-neon-red: var(--neon-red); + --color-neon-yellow: var(--neon-yellow); + + --color-status-success: var(--status-success); + --color-status-warning: var(--status-warning); + --color-status-danger: var(--status-danger); + + --color-pin-academic: var(--pin-academic); + --color-pin-food: var(--pin-food); + --color-pin-social: var(--pin-social); + --color-pin-transit: var(--pin-transit); + --color-pin-utility: var(--pin-utility); + + --animate-pan: autoPan 20s linear infinite; + --animate-scroll-text: scrollText 5s ease-in-out infinite alternate; + --animate-slide-up: slideUp 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; + --animate-fade-in: fadeIn 0.2s ease-out forwards; + --animate-fade-up: fadeUp 0.3s ease-out forwards; + --animate-pulse-glow: pulseGlow 2s infinite alternate; + --animate-radar: radarPing 2s cubic-bezier(0, 0, 0.2, 1) infinite; + --animate-slide-right: slideRight 0.3s ease-out forwards; +} + @keyframes spin { 100% { transform: rotate(360deg); } } @@ -87,10 +124,6 @@ from { transform: translateX(-100%); } to { transform: translateX(0); } } -@keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } -} :root { --bg-base: #f0f2f5; @@ -174,28 +207,11 @@ p { } .tactical-panel { - background: var(--bg-panel); - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); - border: 1px solid var(--border-color); - border-radius: 16px; - color: var(--text-primary); - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); - transition: - background 0.3s ease, - border-color 0.3s ease, - color 0.3s ease; + @apply bg-panel backdrop-blur-md border border-border-color rounded-2xl text-primary shadow-[0_8px_32px_rgba(0,0,0,0.15)] transition-colors duration-300; } .tactical-button { - background: var(--bg-panel); - border: 1px solid var(--border-color); - color: var(--text-primary); - border-radius: 8px; - font-family: var(--font-chakra), sans-serif; - font-weight: 600; - cursor: pointer; - transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); + @apply bg-panel border border-border-color text-primary rounded-lg font-chakra font-semibold cursor-pointer transition-all duration-200 hover:bg-panel-hover hover:-translate-y-[2px] active:translate-y-[1px]; } .tactical-button:hover { @@ -208,15 +224,7 @@ p { } .tactical-button-primary { - background: rgba(0, 229, 255, 0.15); - border: 1px solid var(--neon-blue); - color: var(--neon-blue); - box-shadow: 0 0 10px var(--shadow-glow); - border-radius: 8px; - font-family: var(--font-chakra), sans-serif; - font-weight: 600; - cursor: pointer; - transition: all 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); + @apply bg-neon-blue/15 border border-neon-blue text-neon-blue shadow-[0_0_10px_var(--shadow-glow)] rounded-lg font-chakra font-semibold cursor-pointer transition-all duration-200 hover:bg-neon-blue hover:text-base hover:shadow-[0_0_20px_var(--color-neon-blue)] active:translate-y-[1px]; } .tactical-button-primary:hover { @@ -354,19 +362,7 @@ p { /* --- SIDE CONTROLS --- */ .icon-button, .control-button { - width: 44px; - height: 44px; - background: var(--bg-panel); - backdrop-filter: blur(12px); - border: 1px solid var(--border-color); - border-radius: 12px; - color: var(--text-primary); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: transform 0.1s; - font-family: var(--font-chakra); + @apply w-11 h-11 bg-panel backdrop-blur-md border border-border-color rounded-xl text-primary flex items-center justify-center cursor-pointer transition-transform duration-100 font-chakra active:scale-95; } .icon-button:active, @@ -395,8 +391,7 @@ p { } .control-button { - border: none; - border-radius: 0; + @apply border-none rounded-none; } .divider { @@ -455,18 +450,6 @@ p { color: var(--text-primary) !important; } -@keyframes extrudeRight { - from { - opacity: 0; - transform: translateX(-15px) scale(0.9); - } - - to { - opacity: 1; - transform: translateX(0) scale(1); - } -} - /* --- HUD & ANIMATIONS --- */ @media (max-width: 768px) { diff --git a/apps/web/lib/utils.ts b/apps/web/lib/utils.ts new file mode 100644 index 0000000..daab5de --- /dev/null +++ b/apps/web/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} \ No newline at end of file From 98552c01d54138cfe46995b594f37eccbf371d64 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Tue, 31 Mar 2026 21:35:23 +0800 Subject: [PATCH 02/37] refactor: use Tailwind classes for `TopBar` --- apps/web/app/globals.css | 141 +-------------------------------- apps/web/components/TopBar.tsx | 53 +++++++------ 2 files changed, 28 insertions(+), 166 deletions(-) diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 07eff2f..a0b5b87 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -233,114 +233,6 @@ p { box-shadow: 0 0 20px var(--neon-blue); } -/* --- UI LAYER --- */ -.ui-layer { - position: absolute; - inset: 0; - pointer-events: none; - display: flex; - justify-content: space-between; - padding: 8px; - z-index: 100; - overflow: hidden; -} - -/* --- ZONES --- */ -.zone-left, -.zone-right { - pointer-events: auto; -} - -.zone-left { - position: absolute; - top: 8px; - left: 8px; - z-index: 20; -} - -.zone-right { - position: absolute; - top: 8px; - right: 8px; - bottom: 24px; - display: flex; - flex-direction: column; - justify-content: space-between; - width: 44px; - z-index: 20; -} - -.zone-center { - position: absolute; - top: 8px; - left: 50%; - transform: translateX(-50%); - width: max-content; - max-width: calc(100vw - 120px); - display: flex; - flex-direction: column; - align-items: stretch; - gap: 8px; - pointer-events: none; - z-index: 10; -} - -/* --- CENTER CONTENTS --- */ -.search-block { - position: relative; - width: 100%; - margin: 0 auto; - pointer-events: auto; - transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275); - display: flex; - align-items: center; -} - -.search-block:focus-within { - transform: scale(1.05); -} - -.search-input { - width: 100%; - height: 44px; - background: var(--bg-panel); - backdrop-filter: blur(12px); - border: 1px solid var(--border-color); - border-radius: 14px; - box-sizing: border-box; - padding: 0 40px 0 14px; - color: var(--text-primary); - font-size: 13px; - font-weight: 700; - font-family: var(--font-chakra); -} - -.search-input:focus { - outline: none; - border-color: rgba(255, 255, 255, 0.3); -} - -.search-icon-right { - position: absolute; - right: 12px; - top: 50%; - transform: translateY(-50%); - color: #aaa; - pointer-events: none; - z-index: 5; -} - -.filter-row { - display: flex; - justify-content: center; - gap: 8px; - width: 100%; - overflow-x: auto; - padding-top: 4px; - padding-bottom: 4px; - pointer-events: auto; -} - .no-scrollbar::-webkit-scrollbar { display: none; } @@ -370,16 +262,6 @@ p { transform: scale(0.92); } -.tool-group { - display: flex; - flex-direction: column; - gap: 8px; -} - -.bottom-align { - margin-top: auto; -} - .zoom-stack { display: flex; flex-direction: column; @@ -448,25 +330,4 @@ p { transform: scale(1.1); background: var(--bg-panel-hover) !important; color: var(--text-primary) !important; -} - -/* --- HUD & ANIMATIONS --- */ - -@media (max-width: 768px) { - .profile-btn, - .theme-toggle { - display: none; - } - - .zone-left { - top: auto; - bottom: 24px; - display: flex; - flex-direction: column-reverse; - gap: 12px; - } - - .zone-left .transit-system-container { - margin-top: 0; - } -} +} \ No newline at end of file diff --git a/apps/web/components/TopBar.tsx b/apps/web/components/TopBar.tsx index b25554e..268e032 100644 --- a/apps/web/components/TopBar.tsx +++ b/apps/web/components/TopBar.tsx @@ -84,16 +84,16 @@ export function TopBar({ ]; return ( -
+
{/* === LEFT ZONE === */}
+ className="absolute left-2 z-20 pointer-events-auto bottom-6 top-auto flex flex-col-reverse gap-3 md:top-2 md:bottom-auto md:block" + style={{ + opacity: hideControls ? 0 : 1, + pointerEvents: hideControls ? "none" : "auto", + transition: "opacity 0.3s ease", + }} + >
- {/* === CENTER ZONE (Search + Filters) === */} -
-
- onSearchChange(e.target.value)} - className="search-input" - /> -
+ {/* === CENTER ZONE (Search + Filters) === */} +
+ +
+ onSearchChange(e.target.value)} + className="w-full h-11 bg-panel backdrop-blur-md border border-border-color rounded-[14px] px-3.5 pr-10 text-primary text-[13px] font-bold font-chakra focus:outline-none focus:border-white/30" + /> +
-
+
{filters.map((filter) => { const color = getPinColor(filter); const isActive = activeFilter === filter; @@ -280,12 +281,12 @@ export function TopBar({
{/* === RIGHT ZONE (Full Height Tool Stack) === */} -
- {/* Top Group */} -
+
+ {/* Top Group */} +
-
+
{isZoneMenuOpen && ( -
+
{ZONE_CATEGORIES.map((category) => { const isActive = activeZoneCategories.includes(category.id); @@ -206,7 +206,7 @@ export function TopBar({ key={category.id} type="button" onClick={() => onToggleZoneCategory(category.id)} - className="route-node" + className="w-8 h-8 rounded-full flex items-center justify-center font-chakra font-bold text-[14px] cursor-pointer border border-transparent transition-all duration-200 hover:scale-110 hover:!bg-panel-hover hover:!text-primary" title={category.label} style={{ backgroundColor: isActive @@ -265,7 +265,9 @@ export function TopBar({ type="button" key={filter} onClick={() => onFilterChange(filter)} - className={`filter-chip ${isActive ? "active" : ""}`} + className={`px-3.5 py-1.5 border rounded-full text-[10px] font-black whitespace-nowrap cursor-pointer font-chakra transition-all duration-200 ease-[cubic-bezier(0.175,0.885,0.32,1.275)] ${ + isActive ? "scale-110" : "scale-100" + }`} style={{ borderColor: isActive ? color : "var(--border-color)", color: isActive ? "var(--bg-base)" : color, @@ -376,7 +378,7 @@ export function TopBar({ -
+
-
+
{data?.userRole === "admin" && ( )} -
-
- DISPLAY SETTINGS +
+ DISPLAY SETTINGS
@@ -593,131 +589,6 @@ export default function Dashboard() {
); From ab88684df38fb6d46a422ef162b6285be43a0ad3 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Tue, 31 Mar 2026 23:14:53 +0800 Subject: [PATCH 06/37] refactor: use Tailwind classes for `dashboard` --- apps/web/app/dashboard/page.tsx | 689 +++++--------------------------- 1 file changed, 99 insertions(+), 590 deletions(-) diff --git a/apps/web/app/dashboard/page.tsx b/apps/web/app/dashboard/page.tsx index 697c165..438b8b1 100644 --- a/apps/web/app/dashboard/page.tsx +++ b/apps/web/app/dashboard/page.tsx @@ -255,29 +255,29 @@ export default function Dashboard() { {/* --- DASHBOARD GRID --- */}
-
-
-

YOUR PROFILE

+
+
+

YOUR PROFILE

{data?.userRole === "admin" ? "ADMIN" : "REGULAR USER"}
-
-
-
-
- {data?.name ? data.name.charAt(0).toUpperCase() : "O"} -
-
-
- - {data?.name || "UNKNOWN NAME"} - - - {(data as any)?.email || "UNKNOWN EMAIL"} - +
+
+
+
+ {data?.name ? data.name.charAt(0).toUpperCase() : "O"} +
+
+ + {data?.name || "UNKNOWN NAME"} + + + {(data as any)?.email || "UNKNOWN EMAIL"} + +
{/*
@@ -344,55 +344,47 @@ export default function Dashboard() {
-
-
-

YOUR STATISTICS

+
+
+

YOUR STATISTICS

-
+
{/* Top Stats Grid */} -
-
- TOTAL PINS ADDED - {stats.totalPins} +
+
+ TOTAL PINS ADDED + {stats.totalPins}
-
- TOTAL COMMENTS - {stats.comments} +
+ TOTAL COMMENTS + {stats.comments}
{/* Verification Integrity Bar */} -
-
- VERIFICATIONS - - {verificationRate}% - -
-
-
-
-
- - {stats.verifiedPins} VERIFIED - - - {stats.pendingPins} PENDING - - - {stats.rejectedPins} REJECTED - +
+
+ VERIFICATIONS + {verificationRate}%
+
+
+
+
+ {stats.verifiedPins} VERIFIED + {stats.pendingPins} PENDING + {stats.rejectedPins} REJECTED +
{/* Category Distribution */} -
- CATEGORY DISTRIBUTION -
+
+ CATEGORY DISTRIBUTION +
{PIN_CATEGORIES.map((category) => { const count = stats.categoryBreakdown[ @@ -404,26 +396,21 @@ export default function Dashboard() { : 0; return ( -
-
- - {category.label} - - {count} -
-
-
-
+
+
+ {category.label} + {count} +
+
+
+
); })} @@ -432,41 +419,37 @@ export default function Dashboard() {
-
-
-

YOUR PENDING PINS

- +
+
+

YOUR PENDING PINS

+ {stats.pendingList?.length}
-
-
+
+
{stats.pendingList?.map((pin) => { const color = getPinColor( pin.pinTags?.[0]?.tag.title || "", ); return ( -
-
-
- +
+
+
+ {pin.title.charAt(0).toUpperCase()}
-
- {pin.title} - - {pin.latitude.toFixed(4)},{" "} - {pin.longitude.toFixed(4)} - -
+
+ {pin.title} + + {pin.latitude.toFixed(4)}, {pin.longitude.toFixed(4)} + +
-
-
-

YOUR RECENT PINS

- +
+
+

YOUR RECENT PINS

+ {stats.recentList?.length}
-
-
+
+
{stats.recentList?.map((pin) => { const color = getPinColor( pin.pinTags?.[0]?.tag.title || "", ); return ( -
-
-
- +
+
+
+ {pin.title.charAt(0).toUpperCase()}
-
- {pin.title} - - {pin.latitude.toFixed(4)},{" "} - {pin.longitude.toFixed(4)} - -
+
+ {pin.title} + + {pin.latitude.toFixed(4)}, {pin.longitude.toFixed(4)} + +
- -
); } From 16cb1eb6097ee56c742d7cfb7491917dfca32bb9 Mon Sep 17 00:00:00 2001 From: ptrn23 Date: Tue, 31 Mar 2026 23:55:01 +0800 Subject: [PATCH 07/37] refactor: use Tailwind classes for `admin` --- apps/web/app/admin/page.tsx | 335 +++++------------------------------- 1 file changed, 45 insertions(+), 290 deletions(-) diff --git a/apps/web/app/admin/page.tsx b/apps/web/app/admin/page.tsx index 8ac3308..44c62e8 100644 --- a/apps/web/app/admin/page.tsx +++ b/apps/web/app/admin/page.tsx @@ -134,30 +134,31 @@ export default function AdminDashboard() { }; return ( -
+
{/* --- MOBILE OVERLAY --- */} {isSidebarOpen && ( // biome-ignore lint/a11y/noStaticElementInteractions: // biome-ignore lint/a11y/useKeyWithClickEvents: -
setIsSidebarOpen(false)} - /> +
setIsSidebarOpen(false)} /> )} {/* --- SIDEBAR --- */} -