acessibility

This commit is contained in:
sameeranageshwar
2026-04-11 20:06:06 -04:00
parent f7dbfc5251
commit 1f99b1278c
5 changed files with 128 additions and 11 deletions
@@ -1,15 +1,17 @@
<script lang="ts"> <script lang="ts">
import { mapState, UMD_LOCATIONS } from '$lib/states/map.svelte'; import { mapState, UMD_LOCATIONS } from '$lib/states/map.svelte';
import { themeState } from '$lib/states/theme.svelte';
let { showDropdown = true } = $props<{ showDropdown?: boolean }>(); let { showDropdown = true } = $props<{ showDropdown?: boolean }>();
const legendItems = [ const legendItems = $derived([
{ label: 'Harmful (80+ dB)', color: '#f38ba8' }, { label: 'Harmful (80+ dB)', color: themeState.isColorBlindFriendly ? '#e66100' : '#f38ba8' },
{ label: 'Disruptive (65-79 dB)', color: '#fab387' }, { label: 'Disruptive (65-79 dB)', color: themeState.isColorBlindFriendly ? '#ffb000' : '#fab387' },
{ label: 'Manageable (55-64 dB)', color: '#f9e2af' }, { label: 'Manageable (55-64 dB)', color: themeState.isColorBlindFriendly ? '#f0e442' : '#f9e2af' },
{ label: 'Great (40-54 dB)', color: '#94e2d5' }, { label: 'Great (40-54 dB)', color: themeState.isColorBlindFriendly ? '#56b4e9' : '#94e2d5' },
{ label: 'Ideal (0-39 dB)', color: '#89b4fa' } { label: 'Ideal (0-39 dB)', color: themeState.isColorBlindFriendly ? '#0072b2' : '#89b4fa' }
]; ]);
</script> </script>
<div class="absolute top-6 right-6 md:right-8 z-20 flex flex-col gap-3 items-end"> <div class="absolute top-6 right-6 md:right-8 z-20 flex flex-col gap-3 items-end">
+1 -1
View File
@@ -12,7 +12,7 @@ export const UMD_LOCATIONS: StudyLocation[] = [
{ 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: '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: '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: '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: 'clarice', name: 'Clarice Library', hasFloors: false, lng: -76.9500912552473, lat: 38.990547823732285 },
{ id: 'yahentamitsi', name: 'Yahentamitsi', hasFloors: false, lng: -76.9448027183373, lat: 38.99108961575231 }, { 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: 'iribe', name: 'Iribe', hasFloors: false, lng: -76.93643838603555, lat: 38.98933701397555 },
{ id: 'reckord', name: 'Reckord Armory', hasFloors: false, lng: -76.93897470250619, lat: 38.98609556181066 }, { id: 'reckord', name: 'Reckord Armory', hasFloors: false, lng: -76.93897470250619, lat: 38.98609556181066 },
+46
View File
@@ -2,15 +2,61 @@ import { browser } from '$app/environment';
class ThemeState { class ThemeState {
isLight = $state(false); isLight = $state(false);
isHighContrast = $state(false);
isColorBlindFriendly = $state(false);
constructor() {
if (browser) {
this.isLight = localStorage.getItem('theme') === 'light';
this.isHighContrast = localStorage.getItem('highContrast') === 'true';
this.isColorBlindFriendly = localStorage.getItem('colorBlind') === 'true';
this.updateDOM();
}
}
toggle() { toggle() {
this.isLight = !this.isLight; this.isLight = !this.isLight;
if (browser) {
localStorage.setItem('theme', this.isLight ? 'light' : 'dark');
this.updateDOM();
}
}
toggleHighContrast() {
this.isHighContrast = !this.isHighContrast;
if (browser) {
localStorage.setItem('highContrast', this.isHighContrast ? 'true' : 'false');
this.updateDOM();
}
}
toggleColorBlind() {
this.isColorBlindFriendly = !this.isColorBlindFriendly;
if (browser) {
localStorage.setItem('colorBlind', this.isColorBlindFriendly ? 'true' : 'false');
this.updateDOM();
}
}
private updateDOM() {
if (browser) { if (browser) {
if (this.isLight) { if (this.isLight) {
document.documentElement.classList.add('light-theme'); document.documentElement.classList.add('light-theme');
} else { } else {
document.documentElement.classList.remove('light-theme'); document.documentElement.classList.remove('light-theme');
} }
if (this.isHighContrast) {
document.documentElement.classList.add('high-contrast');
} else {
document.documentElement.classList.remove('high-contrast');
}
if (this.isColorBlindFriendly) {
document.documentElement.classList.add('color-blind');
} else {
document.documentElement.classList.remove('color-blind');
}
} }
} }
} }
+20 -1
View File
@@ -43,7 +43,26 @@
color: #585b70 !important; color: #585b70 !important;
} }
.light-theme .border-white { /* Color Blind (Protanopia/Deuteranopia safe swaps) */
:root.color-blind {
--color-neon-primary: #ffb86c;
--color-neon-blue: #8be9fd;
}
/* High Contrast: Pitch black backgrounds and pure white texts */
:root.high-contrast {
--color-crust: #000000;
--color-mantle: #0a0a0a;
--color-base: #111111;
}
:root.light-theme.high-contrast {
--color-crust: #ffffff;
--color-mantle: #f4f4f4;
--color-base: #ebebeb;
}
.light-theme border-white {
border-color: rgba(88, 91, 112, 0.1); border-color: rgba(88, 91, 112, 0.1);
} }
+52 -2
View File
@@ -9,6 +9,15 @@
<svelte:head> <svelte:head>
<title>EchoNode | Settings</title> <title>EchoNode | Settings</title>
<script>
window.googleTranslateElementInit = function() {
new window.google.translate.TranslateElement({
pageLanguage: 'en',
layout: window.google.translate.TranslateElement.InlineLayout.SIMPLE
}, 'google_translate_element');
};
</script>
<script src="https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit" async defer></script>
</svelte:head> </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"> <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">
@@ -27,12 +36,15 @@
<section class="glass-panel p-6 md:p-8 rounded-2xl border-white/10 shadow-xl"> <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"> <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" /> <Icon icon="mdi:palette-outline" class="text-neon-blue" />
Appearance Appearance and Accesibility
</h2> </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="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"> <div class="pr-4">
<h3 class="font-display font-medium text-white text-lg">Light / Dark Mode</h3> <h3 class="font-display font-medium text-white text-lg flex items-center gap-2">
<Icon icon="mdi:theme-light-dark" class="text-neon-primary" />
Light / Dark Mode
</h3>
</div> </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"> <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">
@@ -40,6 +52,44 @@
{themeState.isLight ? "Enable Dark Mode" : "Enable Light Mode"} {themeState.isLight ? "Enable Dark Mode" : "Enable Light Mode"}
</button> </button>
</div> </div>
<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 mt-4">
<div class="pr-4">
<h3 class="font-display font-medium text-white text-lg flex items-center gap-2">
<Icon icon="mdi:contrast-circle" class="text-neon-primary" />
High Contrast Mode
</h3>
</div>
<button onclick={() => themeState.toggleHighContrast()} class="shrink-0 px-5 py-2.5 bg-surface0 rounded-xl border border-white/10 hover:border-white/20 transition-all flex items-center justify-center gap-2 font-medium">
<Icon icon={themeState.isHighContrast ? "mdi:toggle-switch" : "mdi:toggle-switch-off-outline"} class="text-2xl {themeState.isHighContrast ? 'text-neon-primary' : 'text-slate-400'}" />
{themeState.isHighContrast ? "Enabled" : "Disabled"}
</button>
</div>
<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 mt-4">
<div class="pr-4">
<h3 class="font-display font-medium text-white text-lg flex items-center gap-2">
<Icon icon="mdi:eye-outline" class="text-neon-primary" />
Color Blind Friendly
</h3>
</div>
<button onclick={() => themeState.toggleColorBlind()} class="shrink-0 px-5 py-2.5 bg-surface0 rounded-xl border border-white/10 hover:border-white/20 transition-all flex items-center justify-center gap-2 font-medium">
<Icon icon={themeState.isColorBlindFriendly ? "mdi:toggle-switch" : "mdi:toggle-switch-off-outline"} class="text-2xl {themeState.isColorBlindFriendly ? 'text-neon-primary' : 'text-slate-400'}" />
{themeState.isColorBlindFriendly ? "Enabled" : "Disabled"}
</button>
</div>
<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 mt-4">
<div class="pr-4">
<h3 class="font-display font-medium text-white text-lg flex items-center gap-2"><Icon icon="mdi:translate" class="text-neon-primary" /> Global Translation</h3>
</div>
<div class="shrink-0 p-2 bg-surface0 border border-white/10 rounded-xl overflow-hidden min-h-[44px] flex items-center justify-center">
<div id="google_translate_element" class="scale-90 origin-right"></div>
</div>
</div>
</section> </section>
</div> </div>
</div> </div>