This commit is contained in:
sameeranageshwar
2026-04-11 18:45:43 -04:00
parent 22298fab1a
commit d2e4d78bda
22 changed files with 619 additions and 16 deletions
+4 -1
View File
@@ -4,9 +4,12 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="text-scale" content="scale" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<body data-sveltekit-preload-data="hover" class="bg-black text-slate-200 font-sans antialiased selection:bg-neon-primary/30 selection:text-neon-primary">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
@@ -0,0 +1,51 @@
<script lang="ts">
import Icon from '@iconify/svelte';
let isOpen = $state(false);
const mockAlerts = [
{ id: 1, loc: 'McKeldin 2nd Floor', level: 78, time: '2 mins ago' },
{ id: 2, loc: 'ESJ Atrium', level: 82, time: '15 mins ago' }
];
</script>
<div class="relative">
<!-- Notification Bell -->
<button onclick={() => isOpen = !isOpen} class="relative w-12 h-12 glass-panel flex items-center justify-center rounded-xl border border-white/10 hover:bg-white/10 transition-colors group {isOpen ? 'bg-white/10 ring-1 ring-neon-alert' : ''}">
<Icon icon="mdi:bell-outline" class="text-2xl text-slate-300 group-hover:text-white" />
<!-- Badge -->
{#if mockAlerts.length > 0}
<span class="absolute top-2 right-2 w-2.5 h-2.5 bg-neon-alert rounded-full shadow-[0_0_8px_rgba(255,7,58,0.8)] animate-pulse border border-slate-900"></span>
{/if}
</button>
<!-- Dropdown Panel -->
{#if isOpen}
<div class="absolute top-14 left-0 w-80 glass-panel rounded-2xl shadow-2xl border border-white/10 overflow-hidden z-30 animate-in fade-in slide-in-from-top-2">
<div class="px-4 py-3 border-b border-white/10 bg-black/40 flex justify-between items-center">
<h3 class="font-display font-medium text-white text-sm">Active Noise Alerts</h3>
<span class="text-xs text-slate-400 bg-white/5 py-0.5 px-2 rounded-full">{mockAlerts.length} New</span>
</div>
<div class="max-h-64 overflow-y-auto">
{#each mockAlerts as alert}
<button class="w-full text-left p-4 border-b border-white/5 hover:bg-white/5 transition-colors flex items-start gap-3">
<Icon icon="mdi:alert-rhombus-outline" class="text-neon-alert text-xl mt-0.5" />
<div class="flex-1">
<p class="text-sm font-medium text-red-200">Spike Detected: {alert.level} dB</p>
<p class="text-xs text-red-400/80 mt-1">{alert.loc}</p>
</div>
<p class="text-[10px] text-slate-500 font-mono mt-1">{alert.time}</p>
</button>
{/each}
{#if mockAlerts.length === 0}
<div class="p-6 text-center text-slate-500">
<Icon icon="mdi:check-circle-outline" class="text-3xl text-neon-primary mx-auto mb-2 opacity-50" />
<p class="text-sm">Campus is quiet right now.</p>
</div>
{/if}
</div>
</div>
{/if}
</div>
@@ -0,0 +1,124 @@
<script lang="ts">
import { onMount, untrack } from 'svelte';
import { browser } from '$app/environment';
import { mapState, UMD_LOCATIONS } from '$lib/states/map.svelte';
import { themeState } from '$lib/states/theme.svelte';
import 'maplibre-gl/dist/maplibre-gl.css';
let mapContainer: HTMLDivElement | undefined = $state();
let mapInstance: any = $state();
// Watch and react to mapState updates using a Svelte 5 $effect
$effect(() => {
const target = mapState.targetFlyTo;
if (mapInstance && target) {
untrack(() => {
mapInstance.flyTo({
center: [target.lng, target.lat],
zoom: target.zoom,
essential: true,
duration: 1500
});
});
}
});
$effect(() => {
if (mapInstance) {
const style = themeState.isLight
? 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'
: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';
untrack(() => { mapInstance.setStyle(style); });
}
});
onMount(async () => {
if (!browser || !mapContainer) return;
const { Map, NavigationControl } = await import('maplibre-gl');
const map = new Map({
container: mapContainer,
style: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',
center: [-76.9425, 38.9859], // UMD McKeldin Mall
zoom: 15.5,
pitch: 0,
bearing: 0,
attributionControl: false
});
map.addControl(new NavigationControl({
visualizePitch: true
}), 'bottom-right');
mapInstance = map;
map.on('load', () => {
map.addSource('study-locations', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: UMD_LOCATIONS.map(loc => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [loc.lng, loc.lat] },
properties: { name: loc.name }
}))
}
});
map.addLayer({
id: 'study-locations-labels',
type: 'symbol',
source: 'study-locations',
layout: {
'text-field': ['get', 'name'],
'text-size': 13,
'text-variable-anchor': ['top', 'bottom', 'left', 'right'],
'text-radial-offset': 1,
'text-justify': 'center'
},
paint: {
'text-color': '#ffffff',
'text-halo-color': '#000000',
'text-halo-width': 2
}
});
});
return () => {
map.remove();
};
});
</script>
<div class="absolute inset-0 z-0 bg-crust transition-colors duration-500">
<div bind:this={mapContainer} class="absolute inset-0 z-0 {themeState.isLight ? 'mix-blend-normal opacity-100 filter brightness-105 contrast-125 grayscale' : 'mix-blend-luminosity opacity-90'} transition-all duration-500" style="width: 100%; height: 100%;"></div>
<!-- Color Screen Wash -->
<div class="absolute inset-0 pointer-events-none {themeState.isLight ? 'mix-blend-screen bg-white opacity-20' : 'mix-blend-color bg-crust opacity-100'} z-10 transition-all duration-500"></div>
</div>
<style>
:global(.maplibregl-ctrl-group) {
background: rgba(15, 23, 42, 0.8) !important;
backdrop-filter: blur(12px) !important;
border: 1px solid rgba(255, 255, 255, 0.1) !important;
border-left: 2px solid var(--color-neon-primary) !important;
box-shadow: var(--shadow-glow-primary) !important;
border-radius: 12px !important;
overflow: hidden;
}
:global(.maplibregl-ctrl-group button) {
width: 40px !important;
height: 40px !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important;
}
:global(.maplibregl-ctrl-group button:last-child) {
border-bottom: none !important;
}
:global(.maplibregl-ctrl-icon) {
filter: invert(1) opacity(0.8) !important;
}
:global(.maplibregl-ctrl-group button:hover) {
background: rgba(255, 255, 255, 0.1) !important;
}
</style>
@@ -0,0 +1,48 @@
<script lang="ts">
import { mapState, UMD_LOCATIONS } from '$lib/states/map.svelte';
let { showDropdown = false } = $props<{ showDropdown?: boolean }>();
const legendItems = [
{ label: 'Harmful (80+ dB)', color: '#f38ba8' },
{ label: 'Disruptive (65-79 dB)', color: '#fab387' },
{ label: 'Manageable (55-64 dB)', color: '#f9e2af' },
{ label: 'Great (40-54 dB)', color: '#94e2d5' },
{ label: 'Ideal (0-39 dB)', color: '#89b4fa' }
];
</script>
<div class="absolute top-6 right-6 md:right-8 z-20 flex flex-col gap-3 items-end">
<div class="glass-panel p-4 md:p-5 rounded-2xl border-l-2 border-l-neon-primary flex flex-col gap-3" style="box-shadow: var(--shadow-glow-primary)">
<h3 class="font-display font-medium text-white text-xs md:text-sm tracking-wider uppercase mb-1 border-b border-white/5 pb-2">Noise Levels</h3>
<div class="flex flex-col gap-2.5">
{#each legendItems as item}
<div class="flex items-center gap-3">
<div class="w-3.5 h-3.5 rounded shadow-sm shrink-0 border border-white/20" style="background-color: {item.color}; box-shadow: 0 0 8px {item.color}60"></div>
<span class="text-xs font-mono text-slate-300 font-medium whitespace-nowrap">{item.label}</span>
</div>
{/each}
</div>
</div>
{#if showDropdown}
<div class="glass-panel rounded-xl border-l-2 border-l-neon-primary overflow-hidden flex flex-col w-64" style="box-shadow: var(--shadow-glow-primary)">
<select class="bg-transparent text-white font-display text-sm p-4 border-none outline-none focus:ring-0 cursor-pointer w-full font-medium" onchange={(e) => {
const val = (e.target as HTMLSelectElement).value;
if (val === 'default') {
mapState.flyHome();
} else if (val) {
const loc = UMD_LOCATIONS.find(l => l.name === val);
if (loc) {
mapState.flyTo(loc.lng, loc.lat, 18);
}
}
}}>
<option value="default" class="bg-crust text-white font-semibold text-center hidden md:block">Jump to Campus Area</option>
{#each UMD_LOCATIONS as loc}
<option value={loc.name} class="bg-crust text-white">{loc.name}</option>
{/each}
</select>
</div>
{/if}
</div>
+36
View File
@@ -0,0 +1,36 @@
export interface StudyLocation {
id: string;
name: string;
hasFloors: boolean;
floors?: string[];
lng: number;
lat: number;
}
export const UMD_LOCATIONS: StudyLocation[] = [
{ id: 'esj', name: 'Edward St. John (ESJ)', hasFloors: true, floors: ['Floor 1', 'Floor 2', 'Floor 3'], lng: -76.94209511596014, lat: 38.987133359608755 },
{ id: 'mckeldin', name: 'McKeldin Library', hasFloors: true, floors: ['Floor 1', 'Floor 2', 'Floor 3', 'Floor 4', 'Floor 5', 'Floor 6', 'Floor 7'], lng: -76.94494907523277, lat: 38.986021017749366 },
{ id: 'hornbake', name: 'Hornbake Library', hasFloors: false, lng: -76.94161787005467, lat: 38.988233373664826 },
{ id: 'stem', name: 'STEM Library', hasFloors: false, lng: -76.93942003731279, lat: 38.988991437126195 },
{ id: 'clarice', name: 'Clarice Library', hasFloors: false, lng: -76.94967344225785, lat: 38.99029330703877 },
{ id: 'yahentamitsi', name: 'Yahentamitsi', hasFloors: false, lng: -76.9448027183373, lat: 38.99108961575231 },
{ id: 'iribe', name: 'Iribe', hasFloors: false, lng: -76.93643838603555, lat: 38.98933701397555 },
{ id: 'reckord', name: 'Reckord Armory', hasFloors: false, lng: -76.93897470250619, lat: 38.98609556181066 }
];
export const DEFAULT_VIEW = { lng: -76.9425, lat: 38.9859, zoom: 15.5 };
class MapState {
// The target coordinates the map should fly to
targetFlyTo = $state<{ lng: number; lat: number; zoom: number; timestamp: number } | null>(null);
flyTo(lng: number, lat: number, zoom: number = 18) {
this.targetFlyTo = { lng, lat, zoom, timestamp: Date.now() }; // timestamp ensures reactivity even if same coords
}
flyHome() {
this.flyTo(DEFAULT_VIEW.lng, DEFAULT_VIEW.lat, DEFAULT_VIEW.zoom);
}
}
export const mapState = new MapState();
+18
View File
@@ -0,0 +1,18 @@
import { browser } from '$app/environment';
class ThemeState {
isLight = $state(false);
toggle() {
this.isLight = !this.isLight;
if (browser) {
if (this.isLight) {
document.documentElement.classList.add('light-theme');
} else {
document.documentElement.classList.remove('light-theme');
}
}
}
}
export const themeState = new ThemeState();
+38 -1
View File
@@ -1,9 +1,46 @@
<script lang="ts">
import './layout.css';
import favicon from '$lib/assets/favicon.svg';
// Make sure you have $app/environment or similar if you need active states,
// for now we use simple routing links using sveltekit's native anchor tags.
import Icon from '@iconify/svelte';
import { page } from '$app/stores';
let { children } = $props();
</script>
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
{@render children()}
<div class="h-screen w-full overflow-hidden bg-black text-slate-200 flex flex-col md:flex-row relative">
<!-- Floating Sidebar (Desktop) / Bottom Bar (Mobile) -->
<nav class="absolute md:static bottom-4 left-1/2 md:left-4 -translate-x-1/2 md:translate-x-0 md:my-auto z-50 rounded-3xl glass-panel md:w-16 w-11/12 md:h-auto py-3 md:py-6 px-4 md:px-0 flex md:flex-col items-center justify-around md:justify-center gap-6 shadow-2xl border-white/10 md:mr-4 overflow-hidden relative">
<!-- Shell Pattern Background -->
<div class="absolute inset-0 pointer-events-none opacity-[0.08] z-0" style="background-image: url('data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%2228%22 height=%2249%22 viewBox=%220 0 28 49%22%3E%3Cg fill-rule=%22evenodd%22%3E%3Cg id=%22hexagons%22 fill=%22%23ffffff%22 fill-opacity=%221%22 fill-rule=%22nonzero%22%3E%3Cpath d=%22M13.99 9.25l13 7.5v15l-13 7.5L1 31.75v-15l12.99-7.5zM3 17.9v12.7l10.99 6.34 11-6.35V17.9l-11-6.34L3 17.9zM0 15l12.98-7.5V0h-2v6.35L0 12.69v2.3zm0 18.5L12.98 41v8h-2v-6.85L0 35.81v-2.3zM15 0v7.5L27.99 15H28v-2.31h-.01L17 6.35V0h-2zm0 49v-8l12.99-7.5H28v2.31h-.01L17 42.15V49h-2z%22/%3E%3C/g%3E%3C/g%3E%3C/svg%3E'); background-repeat: repeat;"></div>
<!-- Nav Item: Live Map -->
<a href="/" class="relative flex items-center justify-center p-3 rounded-2xl transition-all duration-300 hover:bg-white/10 group {$page.url.pathname === '/' ? 'text-neon-blue drop-shadow-[0_0_10px_rgba(0,243,255,0.6)] bg-white/5' : 'text-slate-400 hover:text-white'}">
<Icon icon="mdi:map" class="text-2xl" />
<span class="absolute left-full ml-4 px-2 py-1 bg-black/80 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none md:block hidden border border-white/10">Live Map</span>
</a>
<!-- Nav Item: History Map -->
<a href="/history" class="relative flex items-center justify-center p-3 rounded-2xl transition-all duration-300 hover:bg-white/10 group {$page.url.pathname === '/history' ? 'text-neon-blue drop-shadow-[0_0_10px_rgba(0,243,255,0.6)]' : 'text-slate-400 hover:text-white'}">
<Icon icon="mdi:history" class="text-2xl" />
<span class="absolute left-full ml-4 px-2 py-1 bg-black/80 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none md:block hidden border border-white/10">History</span>
</a>
<div class="w-px h-6 md:w-6 md:h-px bg-white/10 my-2"></div>
<!-- Nav Item: Settings -->
<a href="/settings" class="relative flex items-center justify-center p-3 rounded-2xl transition-all duration-300 hover:bg-white/10 group {$page.url.pathname === '/settings' ? 'text-neon-blue drop-shadow-[0_0_10px_rgba(0,243,255,0.6)]' : 'text-slate-400 hover:text-white'}">
<Icon icon="mdi:cog-outline" class="text-2xl" />
<span class="absolute left-full ml-4 px-2 py-1 bg-black/80 rounded text-xs opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap pointer-events-none md:block hidden border border-white/10">Settings</span>
</a>
</nav>
<!-- Main Content Area -->
<main class="flex-1 relative w-full h-full overflow-hidden">
{@render children()}
</main>
</div>
+26 -2
View File
@@ -1,2 +1,26 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
<script lang="ts">
import Icon from '@iconify/svelte';
import AlertsDropdown from '$lib/components/AlertsDropdown.svelte';
import MapControls from '$lib/components/MapControls.svelte';
import InteractiveMap from '$lib/components/InteractiveMap.svelte';
</script>
<svelte:head>
<title>EchoNode | Live Map</title>
</svelte:head>
<div class="relative w-full h-full bg-crust border-l border-white/5">
<InteractiveMap />
<MapControls showDropdown={true} />
<div class="absolute top-6 left-6 md:left-8 z-10 flex gap-4 items-center">
<div class="glass-panel flex items-center gap-4 px-6 py-3 rounded-2xl shadow-lg border-l-2 border-l-neon-primary h-12" style="box-shadow: var(--shadow-glow-primary)">
<h1 class="font-display font-semibold text-white tracking-wider flex items-center gap-2">
<Icon icon="mdi:access-point-network" class="text-neon-primary text-lg" style="filter: drop-shadow(var(--shadow-glow-primary))" />
LIVE DATA
</h1>
</div>
<AlertsDropdown />
</div>
</div>
+59
View File
@@ -0,0 +1,59 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import MapControls from '$lib/components/MapControls.svelte';
import InteractiveMap from '$lib/components/InteractiveMap.svelte';
</script>
<svelte:head>
<title>EchoNode | History</title>
</svelte:head>
<div class="relative w-full h-full bg-crust border-l border-white/5">
<!-- Map Engine Engine -->
<InteractiveMap />
<MapControls showDropdown={true} />
<!-- Top Bar / History Mode Overlay -->
<header class="absolute top-6 left-6 md:left-8 z-10">
<div class="glass-panel flex items-center justify-between md:justify-start gap-4 px-6 py-3 rounded-2xl shadow-lg border-l-2 border-l-neon-blue drop-shadow-[0_0_10px_rgba(0,243,255,0.2)] h-12">
<div>
<h1 class="font-display font-semibold text-white tracking-wider flex items-center gap-2">
<Icon icon="mdi:history" class="text-neon-blue drop-shadow-[0_0_5px_rgba(0,243,255,0.8)] text-lg" />
HISTORICAL DATA
</h1>
</div>
</div>
</header>
<!-- Time Control Bottom Bar -->
<div class="absolute bottom-24 md:bottom-8 left-4 md:left-8 right-4 md:right-8 z-10 flex justify-center">
<div class="glass-panel rounded-2xl p-6 border-l-2 border-l-neon-primary w-full max-w-4xl" style="box-shadow: var(--shadow-glow-primary)">
<div class="flex items-center justify-between mb-2">
<h2 class="font-display font-medium text-lg text-white">Playback Controls</h2>
<span class="text-neon-blue font-mono text-sm tracking-wider drop-shadow-[0_0_5px_rgba(0,243,255,0.5)]">Tue, Oct 14 - 14:00</span>
</div>
<div class="flex items-center gap-6 mt-6">
<button class="w-12 h-12 rounded-full bg-neon-blue/10 hover:bg-neon-blue/20 flex items-center justify-center text-neon-blue transition-colors border border-neon-blue/30 shrink-0">
<Icon icon="mdi:play" class="text-2xl" />
</button>
<!-- Slider Track -->
<div class="flex-1 relative group cursor-pointer h-8 flex items-center">
<div class="w-full h-1.5 bg-slate-800 rounded-full overflow-hidden">
<div class="h-full bg-neon-blue w-[60%] shadow-[0_0_10px_rgba(0,243,255,0.8)]"></div>
</div>
<!-- Slider Thumb -->
<div class="absolute top-1/2 left-[60%] -translate-x-1/2 -translate-y-1/2 w-4 h-4 bg-white rounded-full shadow-[0_0_10px_rgba(255,255,255,0.8)] border-2 border-neon-blue group-hover:scale-125 transition-transform"></div>
</div>
<div class="text-xs text-slate-400 font-mono shrink-0">
<p>24H Window</p>
</div>
</div>
</div>
</div>
</div>
+58 -2
View File
@@ -1,3 +1,59 @@
@import 'tailwindcss';
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';
@theme {
--font-sans: 'Comic Relief', 'Comic Neue', 'Comic Sans MS', cursive, system-ui, sans-serif;
--font-display: 'Comic Relief', 'Comic Neue', 'Comic Sans MS', cursive, system-ui, sans-serif;
/* Catppuccin Theme */
--color-crust: #11111b;
--color-mantle: #181825;
--color-base: #1e1e2e;
--color-surface0: #313244;
--color-surface1: #45475a;
--color-surface2: #585b70;
--color-red: #f38ba8;
--color-neon-primary: #f38ba8;
--color-neon-alert: #f38ba8;
--color-neon-blue: #f38ba8;
--color-panel-glass: rgba(24, 24, 37, 0.7); /* Mantle Glass */
/* Glowing effects around elements */
--shadow-glow-primary: 0 0 15px rgba(243, 139, 168, 0.4);
--shadow-glow-alert: 0 0 15px rgba(243, 139, 168, 0.4);
--shadow-glow-blue: 0 0 15px rgba(243, 139, 168, 0.4);
}
.light-theme {
--color-crust: #ffffff; /* Pure white background */
--color-mantle: #cdd6f4;
--color-base: #bac2de;
--color-surface0: #9399b2; /* Darker buttons */
--color-surface1: #7f849c;
--color-surface2: #6c7086;
--color-red: #e07290;
--color-neon-primary: #e07290; /* Darker Pink */
--color-neon-alert: #f38ba8;
--color-neon-blue: #e07290; /* Darker Pink Accent */
--color-panel-glass: rgba(255, 255, 255, 0.95); /* Bright White Glass for sidebar */
color: #585b70 !important; /* Darkest Text */
}
/* Override text colors globally for light mode */
.light-theme .text-white, .light-theme .text-slate-200, .light-theme .text-slate-300, .light-theme .text-slate-400, .light-theme .text-slate-500 {
color: #585b70 !important;
}
.light-theme border-white {
border-color: rgba(88, 91, 112, 0.1);
}
@utility glass-panel {
background-color: var(--color-panel-glass);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.05);
}
@utility text-glow {
text-shadow: 0 0 10px currentColor;
}
+45
View File
@@ -0,0 +1,45 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import { themeState } from '$lib/states/theme.svelte';
function handleLightModeClick() {
themeState.toggle();
}
</script>
<svelte:head>
<title>EchoNode | Settings</title>
</svelte:head>
<div class="relative w-full h-full bg-crust border-l border-white/5 p-6 md:p-12 overflow-y-auto duration-500 transition-colors">
<!-- Background Mesh -->
<div class="absolute inset-0 bg-[radial-gradient(ellipse_at_top_right,_var(--tw-gradient-stops))] from-surface0/30 via-crust to-crust z-0 pointer-events-none transition-colors duration-500"></div>
<div class="relative z-10 max-w-4xl mx-auto">
<header class="mb-10">
<h1 class="font-display font-semibold text-3xl text-white tracking-wider flex items-center gap-3">
<Icon icon="mdi:cog" class="text-slate-300" />
SYSTEM SETTINGS
</h1>
<p class="text-sm text-slate-400 mt-2">Configure your interface preferences.</p>
</header>
<section class="glass-panel p-6 md:p-8 rounded-2xl border-white/10 shadow-xl">
<h2 class="font-display text-xl text-white mb-6 flex items-center gap-2 border-b border-white/10 pb-3">
<Icon icon="mdi:palette-outline" class="text-neon-blue" />
Appearance
</h2>
<div class="flex flex-col md:flex-row md:items-center justify-between p-4 md:p-6 bg-mantle/40 rounded-xl border border-white/5 hover:border-white/10 transition-colors gap-4">
<div class="pr-4">
<h3 class="font-display font-medium text-white text-lg">Light / Dark Mode</h3>
</div>
<button onclick={handleLightModeClick} class="shrink-0 px-5 py-2.5 bg-surface0 rounded-xl border border-white/10 hover:border-yellow-400/50 hover:text-yellow-400 hover:bg-surface1 transition-all flex items-center justify-center gap-2 font-medium">
<Icon icon={themeState.isLight ? "mdi:weather-night" : "mdi:weather-sunny"} class="text-lg" />
{themeState.isLight ? "Enable Dark Mode" : "Enable Light Mode"}
</button>
</div>
</section>
</div>
</div>