diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css index 82ed1df..400218f 100644 --- a/apps/web/app/globals.css +++ b/apps/web/app/globals.css @@ -3,28 +3,33 @@ @tailwind utilities; :root { - /* DARK MODE */ - --bg-color: #0f1115; - --map-grid: rgba(255, 255, 255, 0.03); - --hud-glass: rgba(15, 17, 21, 0.85); - --hud-glass-solid: rgba(20, 22, 26, 0.95); - --hud-border: 1px solid rgba(255, 255, 255, 0.15); - - /* NEON PALETTE */ - --neon-maroon: #FF0055; - --neon-green: #00FF99; - --neon-blue: #00E5FF; - --neon-yellow: #FFD700; - --neon-pink: #FF00FF; + --bg-base: #f0f2f5; + --bg-panel: rgba(255, 255, 255, 0.9); + --bg-panel-hover: rgba(0, 0, 0, 0.05); + + --text-primary: #121212; + --text-secondary: #555555; + + --border-color: rgba(0, 0, 0, 0.15); + --shadow-glow: rgba(0, 136, 255, 0.2); +} - --text-main: #FFFFFF; - --text-muted: #8899A6; - --shadow-hard: 0 10px 30px rgba(0, 0, 0, 0.6); +.dark { + --bg-base: #0f1115; + --bg-panel: rgba(10, 10, 12, 0.85); + --bg-panel-hover: rgba(255, 255, 255, 0.1); + + --text-primary: #ffffff; + --text-secondary: #8899A6; + + --border-color: rgba(255, 255, 255, 0.15); + --shadow-glow: rgba(0, 229, 255, 0.2); } body { - background-color: var(--bg-color); - color: var(--text-main); + background-color: var(--bg-base); + color: var(--text-primary); + transition: background-color 0.3s ease, color 0.3s ease; margin: 0; padding: 0; overflow: hidden; @@ -44,6 +49,55 @@ p, .font-desc, .details-card p { z-index: 0; } +.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; +} + +.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); +} + +.tactical-button:hover { + background: var(--bg-panel-hover); + transform: translateY(-2px); +} + +.tactical-button:active { + transform: translateY(1px); +} + +.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); +} + +.tactical-button-primary:hover { + background: var(--neon-blue); + color: var(--bg-base); + box-shadow: 0 0 20px var(--neon-blue); +} + /* --- UI LAYER --- */ .ui-layer { position: absolute; @@ -102,14 +156,13 @@ p, .font-desc, .details-card p { .search-input { width: 100%; height: 44px; - background: rgba(10, 10, 12, 0.9); + background: var(--bg-panel); backdrop-filter: blur(12px); - border: 1px solid rgba(255, 255, 255, 0.1); + border: 1px solid var(--border-color); border-radius: 14px; box-sizing: border-box; padding: 0 40px 0 14px; - - color: white; font-size: 13px; font-weight: 700; + 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); } @@ -146,11 +199,11 @@ p, .font-desc, .details-card p { /* --- SIDE CONTROLS --- */ .icon-button, .control-button { width: 44px; height: 44px; - background: rgba(10, 10, 12, 0.9); + background: var(--bg-panel); backdrop-filter: blur(12px); - border: 1px solid rgba(255, 255, 255, 0.1); + border: 1px solid var(--border-color); border-radius: 12px; - color: white; + color: var(--text-primary); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.1s; @@ -166,13 +219,13 @@ p, .font-desc, .details-card p { .zoom-stack { display: flex; flex-direction: column; - background: rgba(10, 10, 12, 0.9); + background: var(--bg-panel); backdrop-filter: blur(12px); - border: 1px solid rgba(255, 255, 255, 0.1); + border: 1px solid var(--border-color); border-radius: 12px; overflow: hidden; } .control-button { border: none; border-radius: 0; } -.divider { height: 1px; background: rgba(255, 255, 255, 0.1); width: 80%; margin: 0 auto; } +.divider { height: 1px; background: var(--border-color); width: 80%; margin: 0 auto; } .transit-system-container { position: relative; @@ -193,10 +246,10 @@ p, .font-desc, .details-card p { margin-left: 12px; display: flex; gap: 8px; - background: rgba(10, 10, 12, 0.85); + background: var(--bg-panel); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); - border: 1px solid rgba(255, 255, 255, 0.1); + border: 1px solid var(--border-color); border-radius: 20px; padding: 6px; animation: extrudeRight 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; @@ -219,8 +272,8 @@ p, .font-desc, .details-card p { .route-node:hover { transform: scale(1.1); - background: rgba(255, 255, 255, 0.1) !important; - color: white !important; + background: var(--bg-panel-hover) !important; + color: var(--text-primary) !important; } @keyframes extrudeRight { diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 2486d8d..0f574e4 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -3,6 +3,7 @@ import { Chakra_Petch, Nunito } from "next/font/google"; import localFont from "next/font/local"; import "./globals.css"; import { TRPCProvider } from "@/components/TRPCProvider"; +import { ThemeProvider } from "@/lib/ThemeContext"; const chakra = Chakra_Petch({ weight: ["300", "400", "500", "600", "700"], @@ -40,17 +41,19 @@ export const metadata: Metadata = { }; export default function RootLayout({ - children, + children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode; }>) { - return ( - - - - {children} - - - - ); + return ( + + + + + {children} + + + + + ); } diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx index fdef184..f2b34a0 100644 --- a/apps/web/app/page.tsx +++ b/apps/web/app/page.tsx @@ -4,6 +4,8 @@ import { APIProvider, Map as GoogleMap, AdvancedMarker, + ColorScheme, + MapCameraChangedEvent, } from "@vis.gl/react-google-maps"; import { HeadsUpDisplay } from "@/components/HeadsUpDisplay"; import { NeonPin } from "@/components/NeonPin"; @@ -15,7 +17,8 @@ import { TargetLine } from "@/components/TargetLine"; import { Polyline } from "@/components/Polyline"; import { Polygon } from "@/components/Polygon"; import { JEEPNEY_ROUTES, CAMPUS_ZONES, ZONE_CATEGORIES } from "@/data/map-layers"; -import { useState, useEffect, useRef, useMemo } from "react"; +import { useState, useEffect, useRef, useMemo, useCallback } from "react"; +import { useTheme } from "@/lib/ThemeContext"; import { trpc } from "@/lib/trpc"; export default function Home() { @@ -54,6 +57,20 @@ export default function Home() { ); }; + const [cameraProps, setCameraProps] = useState({ + center: { lat: 14.6549, lng: 121.0645 }, + zoom: 19, + }); + + const handleCameraChange = useCallback((ev: MapCameraChangedEvent) => { + setCameraProps({ + center: ev.detail.center, + zoom: ev.detail.zoom, + }); + }, []); + + const { theme } = useTheme(); + const [pendingPinCoords, setPendingPinCoords] = useState<{ lat: number; lng: number; @@ -99,10 +116,12 @@ export default function Home() { > {/* MAP LAYER */} { @@ -216,7 +235,7 @@ export default function Home() { onSearchChange={setSearchQuery} activeRoutes={activeRoutes} onToggleRoute={handleToggleRoute} - activeZoneCategories={activeZoneCategories} + activeZoneCategories={activeZoneCategories} onToggleZoneCategory={handleToggleZoneCategory} /> diff --git a/apps/web/components/AddPinModal.tsx b/apps/web/components/AddPinModal.tsx index 4a238e2..f6d0d56 100644 --- a/apps/web/components/AddPinModal.tsx +++ b/apps/web/components/AddPinModal.tsx @@ -45,7 +45,7 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { const utils = trpc.useUtils(); const createPin = trpc.pin.userCreate.useMutation({ onSuccess: (newPin) => { - utils.pin.getAll.invalidate(); // this forces a refresh on the main page + utils.pin.getAll.invalidate(); if (!newPin) return; onSave(newPin.id); }, @@ -87,7 +87,7 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { return (
-
+
- -
@@ -188,11 +188,7 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { .modal-card { width: 100%; max-width: 400px; - background: rgba(10, 10, 12, 0.85); - border: 1px solid rgba(0, 229, 255, 0.3); - border-radius: 16px; padding: 24px; - box-shadow: 0 0 30px rgba(0, 229, 255, 0.1); animation: slideUp 0.3s cubic-bezier(0.16, 1, 0.3, 1); } @@ -201,13 +197,13 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { align-items: center; gap: 12px; margin-bottom: 24px; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); + border-bottom: 1px solid var(--border-color); padding-bottom: 16px; } .modal-title { font-family: var(--font-cubao-wide), sans-serif; - color: white; + color: var(--text-primary); font-size: 20px; margin: 0; letter-spacing: 0.05em; @@ -225,7 +221,7 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { gap: 8px; } - label { + .input-group span { font-family: var(--font-chakra), sans-serif; font-size: 12px; color: var(--neon-blue, #00E5FF); @@ -234,11 +230,11 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { } input, textarea { - background: rgba(0, 0, 0, 0.5); - border: 1px solid rgba(255, 255, 255, 0.2); + background: var(--bg-base); + border: 1px solid var(--border-color); border-radius: 8px; padding: 12px; - color: white; + color: var(--text-primary); font-family: var(--font-nunito), sans-serif; font-size: 14px; outline: none; @@ -247,7 +243,7 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { input:focus, textarea:focus { border-color: var(--neon-blue, #00E5FF); - box-shadow: 0 0 10px rgba(0, 229, 255, 0.2); + box-shadow: 0 0 10px var(--shadow-glow); } .type-selector { @@ -257,9 +253,9 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { } .type-btn { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - color: #aaa; + background: transparent; + border: 1px solid var(--border-color); + color: var(--text-secondary); padding: 10px; border-radius: 6px; font-family: var(--font-chakra), sans-serif; @@ -269,14 +265,15 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { } .type-btn:hover { - background: rgba(255, 255, 255, 0.1); + background: var(--bg-panel-hover); + color: var(--text-primary); } .type-btn.active { background: rgba(0, 229, 255, 0.15); border-color: var(--neon-blue, #00E5FF); color: var(--neon-blue, #00E5FF); - box-shadow: inset 0 0 8px rgba(0, 229, 255, 0.2); + box-shadow: inset 0 0 8px var(--shadow-glow); } .action-row { @@ -285,39 +282,11 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { margin-top: 12px; } - .cancel-btn, .save-btn { + .action-row button { flex: 1; padding: 14px; - border-radius: 8px; - font-family: var(--font-chakra), sans-serif; - font-weight: 700; font-size: 13px; letter-spacing: 0.05em; - cursor: pointer; - transition: all 0.2s; - } - - .cancel-btn { - background: transparent; - border: 1px solid rgba(255, 255, 255, 0.2); - color: #aaa; - } - - .cancel-btn:hover { - background: rgba(255, 255, 255, 0.1); - color: white; - } - - .save-btn { - background: rgba(0, 229, 255, 0.1); - border: 1px solid var(--neon-blue, #00E5FF); - color: var(--neon-blue, #00E5FF); - } - - .save-btn:hover:not(:disabled) { - background: var(--neon-blue, #00E5FF); - color: black; - box-shadow: 0 0 15px rgba(0, 229, 255, 0.4); } .save-btn:disabled { @@ -332,4 +301,4 @@ export function AddPinModal({ coords, onSave, onCancel }: AddPinModalProps) { `}
); -} +} \ No newline at end of file diff --git a/apps/web/components/ExpandedPinView.tsx b/apps/web/components/ExpandedPinView.tsx index c569e32..88969fe 100644 --- a/apps/web/components/ExpandedPinView.tsx +++ b/apps/web/components/ExpandedPinView.tsx @@ -67,29 +67,11 @@ const CommentNode = ({ {new Date(comment.createdAt).toLocaleString("default")} - {/* {depth === 0 && comment.rating && ( - - {"★".repeat(comment.rating)} {comment.rating}/5 - - )} */}

{comment.message}

- {/* */} {!isReplying ? ( sessionData && depth < 3 && ( @@ -102,10 +84,14 @@ const CommentNode = ({ ) ) : ( -
- - - +
@@ -123,19 +109,37 @@ const CommentNode = ({
); @@ -183,11 +187,7 @@ export function ExpandedPinView({ pinId, onClose }: ExpandedPinViewProps) { ); return ( - // biome-ignore lint/a11y/noStaticElementInteractions: - // biome-ignore lint/a11y/useKeyWithClickEvents:
- {/** biome-ignore lint/a11y/noStaticElementInteractions: */} - {/** biome-ignore lint/a11y/useKeyWithClickEvents: */}
e.stopPropagation()}>
{/* HEADER */} @@ -220,20 +220,6 @@ export function ExpandedPinView({ pinId, onClose }: ExpandedPinViewProps) { {pin?.images?.map((img) => (
- {/* - - - - */}
))}
@@ -273,12 +259,6 @@ export function ExpandedPinView({ pinId, onClose }: ExpandedPinViewProps) {
- {/* RATING */} - {/*
- AVG RATING - ★ 5.0 / 5.0 -
*/} - {/* TIMESTAMPS */}
CREATED AT @@ -302,28 +282,22 @@ export function ExpandedPinView({ pinId, onClose }: ExpandedPinViewProps) {
+ {/* OWNER ACTIONS */} {!isDeleting && sessionData?.user.id === pin?.ownerId && ( - )} {isDeleting && ( -
- Are you sure you want to delete? -
- -
@@ -338,15 +312,19 @@ export function ExpandedPinView({ pinId, onClose }: ExpandedPinViewProps) { className="action-btn" onClick={() => setIsReplying(true)} > - COMMENT + + ADD COMMENT ) ) : ( -
- - - +
)} @@ -371,20 +349,17 @@ export function ExpandedPinView({ pinId, onClose }: ExpandedPinViewProps) { } .modal-content { - background: rgba(10, 10, 12, 0.95); - border: 1px solid rgba(255, 255, 255, 0.15); + background: var(--bg-panel); + border: 1px solid var(--border-color); border-top: 4px solid ${color}; border-radius: 24px; width: 100%; max-width: 500px; - height: 70vh; - display: flex; flex-direction: column; - box-shadow: 0 30px 60px rgba(0, 0, 0, 0.8); + box-shadow: 0 30px 60px rgba(0, 0, 0, 0.3); animation: scalePop 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); - overflow: hidden; position: relative; padding: 0; @@ -403,157 +378,120 @@ export function ExpandedPinView({ pinId, onClose }: ExpandedPinViewProps) { left: 0; width: 100%; height: 80px; - /* Fades from solid background color to transparent */ - background: linear-gradient(to top, rgba(10, 10, 12, 1) 10%, rgba(10, 10, 12, 0) 100%); - pointer-events: none; /* Ensures you can still click text behind the fade */ + /* Fades to transparent seamlessly in both Light and Dark mode */ + background: linear-gradient(to top, var(--bg-base) 10%, transparent 100%); + pointer-events: none; border-bottom-left-radius: 24px; border-bottom-right-radius: 24px; } - .custom-vertical-scrollbar::-webkit-scrollbar { - width: 6px; - } - .custom-vertical-scrollbar::-webkit-scrollbar-track { - background: transparent; - margin-top: 24px; - margin-bottom: 24px; - } - .custom-vertical-scrollbar::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.15); - border-radius: 10px; - } - .custom-vertical-scrollbar::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.3); - } + /* Custom Scrollbars */ + .custom-vertical-scrollbar::-webkit-scrollbar { width: 6px; } + .custom-vertical-scrollbar::-webkit-scrollbar-track { background: transparent; margin: 24px 0; } + .custom-vertical-scrollbar::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 10px; } + .custom-vertical-scrollbar::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } + .custom-scrollbar::-webkit-scrollbar { height: 6px; } + .custom-scrollbar::-webkit-scrollbar-track { background: transparent; } + .custom-scrollbar::-webkit-scrollbar-thumb { background: var(--border-color); border-radius: 10px; transition: background 0.2s; } + .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } + + /* Header & Images */ .modal-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; } .badge { font-family: var(--font-cubao-wide); font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; } - h2 { font-family: var(--font-chakra); color: white; font-size: 26px; font-weight: 800; margin: 4px 0 0 0; } + h2 { font-family: var(--font-chakra); color: var(--text-primary); font-size: 26px; font-weight: 800; margin: 4px 0 0 0; } .close-btn { - background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 50%; width: 44px; height: 44px; color: white; cursor: pointer; + background: transparent; border: 1px solid var(--border-color); + border-radius: 50%; width: 44px; height: 44px; color: var(--text-primary); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; flex-shrink: 0; } - .close-btn:active { transform: scale(0.9); background: rgba(255, 255, 255, 0.1); } + .close-btn:hover { background: var(--bg-panel-hover); } + .close-btn:active { transform: scale(0.9); } .photo-gallery { - display: grid; - grid-template-rows: repeat(2, 90px); - grid-auto-flow: column; - gap: 12px; - margin-bottom: 24px; - overflow-x: auto; - overscroll-behavior-x: contain; - padding-bottom: 8px; - scroll-snap-type: x mandatory; + display: grid; grid-template-rows: repeat(2, 90px); grid-auto-flow: column; + gap: 12px; margin-bottom: 24px; overflow-x: auto; overscroll-behavior-x: contain; + padding-bottom: 8px; scroll-snap-type: x mandatory; } .no-scrollbar::-webkit-scrollbar { display: none; } .photo-placeholder { - background: rgba(255, 255, 255, 0.03); - border: 1px solid rgba(255, 255, 255, 0.05); + background: var(--bg-panel-hover); + border: 1px solid var(--border-color); border-radius: 16px; - display: flex; - align-items: center; - justify-content: center; - scroll-snap-align: start; - position: relative; - overflow: hidden; - } - - .photo-placeholder::after { - content: ''; position: absolute; top: 0; left: -100%; width: 50%; height: 100%; - background: linear-gradient(90deg, transparent, rgba(255,255,255,0.05), transparent); - animation: shimmer 2s infinite; - } - - .photo-placeholder.large { - grid-row: span 2; /* Spans both rows (192px tall including gap) */ - width: 192px; - } - - .photo-placeholder.small { - grid-row: span 1; /* Spans 1 row (90px tall) */ - width: 140px; - } - - .custom-scrollbar::-webkit-scrollbar { - height: 6px; /* Thin horizontal scrollbar */ - } - .custom-scrollbar::-webkit-scrollbar-track { - background: rgba(255, 255, 255, 0.02); - border-radius: 10px; - } - .custom-scrollbar::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.15); - border-radius: 10px; - transition: background 0.2s; - } - .custom-scrollbar::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.3); /* Highlights when hovered */ + display: flex; align-items: center; justify-content: center; + scroll-snap-align: start; position: relative; overflow: hidden; } + .photo-placeholder.large { grid-row: span 2; width: 192px; } + /* Body Data */ .modal-body { display: flex; flex-direction: column; gap: 24px; } - .description { font-family: var(--font-nunito); font-size: 15px; color: #ccc; line-height: 1.5; margin: 0; } + .description { font-family: var(--font-nunito); font-size: 15px; color: var(--text-primary); line-height: 1.5; margin: 0; } .meta-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; - background: rgba(0, 0, 0, 0.3); border: 1px solid rgba(255, 255, 255, 0.05); + background: var(--bg-panel-hover); border: 1px solid var(--border-color); border-radius: 16px; padding: 20px; } .meta-item { display: flex; flex-direction: column; gap: 4px; } + .col-span-2 { grid-column: span 2; } + .meta-label { font-family: var(--font-chakra); font-size: 10px; font-weight: 800; color: var(--text-secondary); letter-spacing: 0.1em; } + .meta-value { font-family: var(--font-nunito); font-size: 14px; font-weight: 700; color: var(--text-primary); } + + .font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; letter-spacing: 0.05em; } + .font-cubao-wide { font-family: var(--font-cubao-wide); font-weight: 100; letter-spacing: 0.1em;} + .text-muted { color: var(--text-secondary); font-style: italic; } + .text-neon-green { color: var(--neon-green, #00FF99); text-shadow: 0 0 10px rgba(0, 255, 153, 0.3); } + + .danger-btn { border-color: #ff4d4d; color: #ff4d4d; padding: 12px; } + .danger-btn:hover { background: rgba(255, 77, 77, 0.1); transform: none;} + + .delete-confirm-box { + background: rgba(255, 77, 77, 0.05); + border: 1px solid #ff4d4d; + border-radius: 12px; + padding: 16px; + display: flex; + flex-direction: column; + gap: 12px; + } + .delete-confirm-box p { margin: 0; color: var(--text-primary); font-family: var(--font-nunito); font-size: 14px; } + .delete-actions { display: flex; gap: 8px; } + .danger-btn-solid { background: rgba(255, 77, 77, 0.2); border-color: #ff4d4d; color: #ff4d4d; box-shadow: none; flex: 1; padding: 10px;} + .danger-btn-solid:hover { background: #ff4d4d; color: white; box-shadow: 0 0 15px rgba(255, 77, 77, 0.4); } + .forum-section { - margin-top: 16px; - border-top: 1px solid rgba(255, 255, 255, 0.05); + margin-top: 8px; + border-top: 1px solid var(--border-color); padding-top: 24px; } - .section-title { - font-family: var(--font-chakra); - font-size: 12px; - font-weight: 900; - letter-spacing: 0.15em; - color: #666; - margin-bottom: 20px; + font-family: var(--font-chakra); font-size: 12px; font-weight: 900; + letter-spacing: 0.15em; color: var(--text-secondary); margin-bottom: 20px; } .action-btn { - background: none; - border: none; - color: #888; - font-family: var(--font-chakra); - font-size: 11px; - font-weight: 700; - cursor: pointer; - display: flex; - align-items: center; - gap: 4px; - padding: 0; - transition: color 0.2s; + background: none; border: none; color: var(--text-secondary); + font-family: var(--font-chakra); font-size: 11px; font-weight: 700; + cursor: pointer; display: flex; align-items: center; gap: 4px; padding: 0; transition: color 0.2s; } + .action-btn:hover { color: var(--text-primary); } - .action-btn:hover { - color: #fff; + .reply-form { display: flex; gap: 8px; width: 100%; align-items: center; margin-bottom: 24px; } + .reply-input { + flex: 1; background: var(--bg-base); border: 1px solid var(--border-color); + border-radius: 6px; padding: 10px 12px; color: var(--text-primary); + font-family: var(--font-nunito); font-size: 13px; outline: none; } - - .col-span-2 { grid-column: span 2; } - - .meta-label { font-family: var(--font-chakra); font-size: 10px; font-weight: 800; color: #666; letter-spacing: 0.1em; } - .meta-value { font-family: var(--font-nunito); font-size: 14px; font-weight: 700; color: #eee; } - - .font-mono { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; letter-spacing: 0.05em; } - .font-cubao-wide { font-family: var(--font-cubao-wide); font-weight: 100; letter-spacing: 0.1em;} - .text-muted { color: #888; font-style: italic; } - .text-neon-green { color: var(--neon-green, #00FF99); text-shadow: 0 0 10px rgba(0, 255, 153, 0.3); } - .text-neon-yellow { color: var(--neon-yellow, #FFD700); text-shadow: 0 0 10px rgba(255, 215, 0, 0.3); } + .reply-input:focus { border-color: var(--neon-blue, #00E5FF); } + .form-btn { padding: 10px 16px; font-size: 11px; border-radius: 6px; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes scalePop { from { opacity: 0; transform: scale(0.95) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } } - @keyframes shimmer { 100% { left: 200%; } } `}
); -} +} \ No newline at end of file diff --git a/apps/web/components/HeadsUpDisplay.tsx b/apps/web/components/HeadsUpDisplay.tsx index 9971667..a73aede 100644 --- a/apps/web/components/HeadsUpDisplay.tsx +++ b/apps/web/components/HeadsUpDisplay.tsx @@ -1,7 +1,6 @@ "use client"; import { useMemo, useState } from "react"; -import { getFilterColor } from "@/components/TopBar"; import { PinDetailsCard } from "@/components/PinDetailsCard"; import { ExpandedPinView } from "@/components/ExpandedPinView"; import { useSession } from "@/lib/auth-client"; @@ -29,7 +28,8 @@ export function HeadsUpDisplay({ }; const { data: sessionData } = useSession(); - const isLoggedIn = useMemo(() => !!sessionData?.user.id, [sessionData]); + const isLoggedIn = useMemo(() => !!sessionData?.user?.id, [sessionData]); + return (
); -} +} \ No newline at end of file diff --git a/apps/web/components/MapCursor.tsx b/apps/web/components/MapCursor.tsx index 58d2aff..2cad9e4 100644 --- a/apps/web/components/MapCursor.tsx +++ b/apps/web/components/MapCursor.tsx @@ -41,7 +41,7 @@ export function MapCursor({ heading = 0 }: MapCursorProps) { .cursor-arrow { position: relative; z-index: 10; - filter: drop-shadow(0 0 8px var(--neon-blue, #00E5FF)); + filter: drop-shadow(0 0 8px var(--neon-blue)); transition: transform 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); } diff --git a/apps/web/components/NeonPin.tsx b/apps/web/components/NeonPin.tsx index 6324b82..d5a9d7f 100644 --- a/apps/web/components/NeonPin.tsx +++ b/apps/web/components/NeonPin.tsx @@ -51,7 +51,7 @@ export function NeonPin({ height: "100%", transform: "rotate(45deg)", border: `2px solid ${color}`, - backgroundColor: isSelected ? color : "rgba(15, 17, 21, 0.6)", + backgroundColor: isSelected ? color : "var(--bg-panel)", boxShadow: isLocked ? `0 0 30px 5px ${color}` : isSelected @@ -68,7 +68,7 @@ export function NeonPin({ style={{ transform: "rotate(-45deg)", fontSize: "18px", - color: isSelected ? "#000" : color, + color: isSelected ? "var(--bg-base)" : color, fontFamily: "var(--font-cubao-wide), sans-serif", }} > @@ -77,4 +77,4 @@ export function NeonPin({
); -} +} \ No newline at end of file diff --git a/apps/web/components/PinDetailsCard.tsx b/apps/web/components/PinDetailsCard.tsx index 13f4157..494fc4f 100644 --- a/apps/web/components/PinDetailsCard.tsx +++ b/apps/web/components/PinDetailsCard.tsx @@ -71,7 +71,8 @@ export function PinDetailsCard({ className="lock-button" onClick={onLockClick} style={{ - background: isLocked ? "var(--neon-blue, #00D1FF)" : "white", + background: isLocked ? "var(--neon-blue)" : "var(--text-primary)", + color: "var(--bg-base)", }} > {isLocked ? "TARGET LOCKED" : "LOCK TARGET"} @@ -80,20 +81,21 @@ export function PinDetailsCard({ ); -} +} \ No newline at end of file diff --git a/apps/web/components/TopBar.tsx b/apps/web/components/TopBar.tsx index 9fbb527..29116ea 100644 --- a/apps/web/components/TopBar.tsx +++ b/apps/web/components/TopBar.tsx @@ -3,6 +3,7 @@ import { useState } from "react"; import { useRouter } from "next/navigation"; import { trpc } from "@/lib/trpc"; +import { useTheme } from "@/lib/ThemeContext"; import { useSession } from "@/lib/auth-client"; import { JEEPNEY_ROUTES, ZONE_CATEGORIES } from "@/data/map-layers"; @@ -35,6 +36,7 @@ export function TopBar({ const { data: sessionData } = useSession(); const [isTransitMenuOpen, setIsTransitMenuOpen] = useState(false); const [isZoneMenuOpen, setIsZoneMenuOpen] = useState(false); + const { theme, toggleTheme } = useTheme(); const handleProfileClick = () => { if (sessionData?.user) { @@ -105,7 +107,7 @@ export function TopBar({ className="route-node" title={route.name} style={{ - backgroundColor: isActive ? `${route.color}20` : 'rgba(255, 255, 255, 0.05)', + backgroundColor: isActive ? `${route.color}20` : 'var(--bg-panel-hover)', color: isActive ? route.color : '#aaa', borderColor: isActive ? route.color : 'transparent', boxShadow: isActive ? `0 0 10px ${route.color}40` : 'none', @@ -146,7 +148,7 @@ export function TopBar({ className="route-node" title={category.label} style={{ - backgroundColor: isActive ? `${category.color}20` : 'rgba(255, 255, 255, 0.05)', + backgroundColor: isActive ? `${category.color}20` : 'var(--bg-panel-hover)', color: isActive ? category.color : '#aaa', borderColor: isActive ? category.color : 'transparent', boxShadow: isActive ? `0 0 10px ${category.color}40` : 'none', @@ -199,10 +201,9 @@ export function TopBar({ onClick={() => onFilterChange(filter)} className={`filter-chip ${isActive ? "active" : ""}`} style={{ - borderColor: isActive ? color : "rgba(255,255,255,0.15)", - color: isActive ? "#000" : color, - backgroundColor: isActive ? color : "rgba(10, 10, 12, 0.9)", - // The Bouncy Scale Animation + borderColor: isActive ? color : "var(--border-color)", + color: isActive ? "var(--bg-base)" : color, + backgroundColor: isActive ? color : "var(--bg-panel)", transform: isActive ? "scale(1.1)" : "scale(1)", }} > @@ -239,17 +240,24 @@ export function TopBar({ - @@ -315,6 +323,6 @@ export const getFilterColor = (type: string) => { case "utility": return "#00d1ff"; default: - return "#ffffff"; + return "var(--text-primary)"; } }; diff --git a/apps/web/lib/ThemeContext.tsx b/apps/web/lib/ThemeContext.tsx new file mode 100644 index 0000000..08792c1 --- /dev/null +++ b/apps/web/lib/ThemeContext.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { createContext, useContext, useEffect, useState } from "react"; + +type Theme = "dark" | "light"; + +interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; +} + +const ThemeContext = createContext(undefined); + +export function ThemeProvider({ children }: { children: React.ReactNode }) { + const [theme, setTheme] = useState("light"); + + useEffect(() => { + const savedTheme = localStorage.getItem("waypoint-theme") as Theme; + if (savedTheme) { + setTheme(savedTheme); + } + }, []); + + useEffect(() => { + const root = document.documentElement; + if (theme === "dark") { + root.classList.add("dark"); + } else { + root.classList.remove("dark"); + } + localStorage.setItem("waypoint-theme", theme); + }, [theme]); + + const toggleTheme = () => { + setTheme((prev) => (prev === "dark" ? "light" : "dark")); + }; + + return ( + + {children} + + ); +} + +export function useTheme() { + const context = useContext(ThemeContext); + if (!context) throw new Error("useTheme must be used within a ThemeProvider"); + return context; +} \ No newline at end of file