From 47ece399f9cc6823983ca38ca512157011463f0b Mon Sep 17 00:00:00 2001 From: AkashJana18 Date: Tue, 7 Jul 2026 15:41:45 +0530 Subject: [PATCH] feat: Added dark theme for better accessibility Changes: - Added a navbar theme toggle at the right edge in index.html:64. - Added Light/Dark controls inside the accessibility panel in index.html:923. - Added warm dark-theme tokens, dark note/tag colors, responsive nav ordering, and cache-busted CSS in styles.css:64. - Wired both toggles to shared persisted state and reset behavior in app.js:65. Verified: - node --check app.js - git diff --check - Static asset serving for styles.css?v=20 and app.js?v=6 - Headless Chrome toggle behavior, localStorage persistence, reset behavior - Responsive dark-mode pass at 375, 768, and 1280 px with no horizontal overflow - Key contrast samples pass AA --- app.js | 49 ++++++++++++++++++ index.html | 28 +++++++++-- styles.css | 142 +++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 189 insertions(+), 30 deletions(-) diff --git a/app.js b/app.js index 6ea6ae9..e9f5712 100644 --- a/app.js +++ b/app.js @@ -62,6 +62,54 @@ navLinks?.addEventListener('click', (e) => { } }); +// --- Theme toggle (shared by navbar and accessibility panel) --- +(function themeControls() { + const root = document.documentElement; + const store = window.localStorage; + const get = (k, d) => { try { return store.getItem(k) ?? d; } catch { return d; } }; + const set = (k, v) => { try { store.setItem(k, v); } catch {} }; + const remove = (k) => { try { store.removeItem(k); } catch {} }; + + function currentTheme() { + return root.dataset.theme === 'dark' ? 'dark' : 'light'; + } + + function applyTheme(theme, persist = true) { + const next = theme === 'dark' ? 'dark' : 'light'; + root.dataset.theme = next; + if (persist) set('guide-theme', next); + + document.querySelectorAll('[data-theme-choice]').forEach((button) => { + button.setAttribute('aria-pressed', String(button.dataset.themeChoice === next)); + }); + + document.querySelectorAll('[data-theme-toggle]').forEach((button) => { + const isDark = next === 'dark'; + const label = isDark ? 'Switch to light mode' : 'Switch to dark mode'; + button.setAttribute('aria-pressed', String(isDark)); + button.setAttribute('aria-label', label); + button.title = label; + const icon = button.querySelector('.theme-icon'); + if (icon) icon.textContent = isDark ? '☀' : '☾'; + }); + } + + document.querySelectorAll('[data-theme-choice]').forEach((button) => { + button.addEventListener('click', () => applyTheme(button.dataset.themeChoice)); + }); + + document.querySelectorAll('[data-theme-toggle]').forEach((button) => { + button.addEventListener('click', () => applyTheme(currentTheme() === 'dark' ? 'light' : 'dark')); + }); + + window.resetGuideTheme = () => { + remove('guide-theme'); + applyTheme('light', false); + }; + + applyTheme(get('guide-theme', 'light'), false); +})(); + // --- Accessibility toolbar (persisted preferences) --- (function a11y() { const fab = document.getElementById('a11yFab'); @@ -108,6 +156,7 @@ navLinks?.addEventListener('click', (e) => { document.getElementById('a11yReset')?.addEventListener('click', () => { ['a11y-size', 'a11y-hc', 'a11y-ul-links', 'a11y-reduce-motion'].forEach((k) => { try { store.removeItem(k); } catch {} }); applySize('1'); ['hc', 'ul-links', 'reduce-motion'].forEach((c) => applyToggle(c, false)); + window.resetGuideTheme?.(); }); // restore saved prefs (default reduce-motion to the OS setting if unset) diff --git a/index.html b/index.html index 8adacad..79383d1 100644 --- a/index.html +++ b/index.html @@ -21,7 +21,16 @@ - + + @@ -40,7 +49,6 @@ @@ -906,6 +920,14 @@

Accessibility

+
+ Theme +
+ + +
+
+
Underline links
@@ -925,6 +947,6 @@

Accessibility

- + diff --git a/styles.css b/styles.css index ca64805..608fcc2 100644 --- a/styles.css +++ b/styles.css @@ -21,7 +21,39 @@ --line: rgba(51,40,27,0.12); --line-2: rgba(51,40,27,0.2); --shadow: 0 10px 30px -20px rgba(51,40,27,0.32); + --shadow-hover: 0 18px 40px -24px rgba(43,39,51,0.4); + --panel-shadow: 0 24px 60px -22px rgba(43,39,51,0.45); --landmark: #5a4530; + --nav-bg: rgba(242,230,208,0.85); + --accent-wash: rgba(197,97,46,0.1); + --on-accent: #160d08; + --on-coral: #fff; + --on-ink: #fff; + + --note-warn-bg: #f7e7c5; + --note-warn-border: #e6cd98; + --note-warn-label: #a9791b; + --note-stop-bg: #f6dcc7; + --note-stop-border: #e6b393; + --note-ok-bg: #ecefd3; + --note-ok-border: #d2d9a8; + + --tag-veg-bg: #e6f6ea; + --tag-veg-ink: #1c7a3f; + --tag-vegan-bg: #e1f5f0; + --tag-vegan-ink: #0d7d6e; + --tag-jain-bg: #fcefd8; + --tag-jain-ink: #9a6000; + --tag-df-bg: #ece9fd; + --tag-df-ink: #5039c9; + --tag-kosher-bg: #e6eefe; + --tag-kosher-ink: #2155c4; + --tag-halal-bg: #e6f5ec; + --tag-halal-ink: #167a4a; + --tag-nonveg-bg: #fde6ee; + --tag-nonveg-ink: #c01060; + --reading-bg: #fff2c4; + --reading-ring: #fff2c4; --maxw: 1200px; --mono: "Space Mono", ui-monospace, Menlo, monospace; @@ -29,8 +61,58 @@ --body: "Inter", system-ui, -apple-system, sans-serif; } +html[data-theme="dark"] { + color-scheme: dark; + --bg: #17120d; + --bg-2: #211910; + --card: #261e15; + --ink: #f4ead8; + --ink-2: #d7c6aa; + --muted: #aa9476; + --violet: #e18a58; + --teal: #b0bb72; + --marigold: #e2ad54; + --coral: #df734b; + --line: rgba(244,234,216,0.13); + --line-2: rgba(244,234,216,0.25); + --shadow: 0 16px 42px -28px rgba(0,0,0,0.75); + --shadow-hover: 0 24px 52px -30px rgba(0,0,0,0.86); + --panel-shadow: 0 24px 70px -26px rgba(0,0,0,0.85); + --landmark: #e7c99c; + --nav-bg: rgba(23,18,13,0.88); + --accent-wash: rgba(225,138,88,0.16); + --on-accent: #1b120d; + --on-coral: #1b120d; + --on-ink: #17120d; + + --note-warn-bg: #352814; + --note-warn-border: #71572b; + --note-warn-label: #e2ad54; + --note-stop-bg: #361e16; + --note-stop-border: #7d4128; + --note-ok-bg: #263018; + --note-ok-border: #5b6935; + + --tag-veg-bg: #1d3424; + --tag-veg-ink: #9be2b2; + --tag-vegan-bg: #183431; + --tag-vegan-ink: #85ded0; + --tag-jain-bg: #3a2c12; + --tag-jain-ink: #f2c76a; + --tag-df-bg: #292646; + --tag-df-ink: #bdb3ff; + --tag-kosher-bg: #1d2b47; + --tag-kosher-ink: #9fbfff; + --tag-halal-bg: #1a3326; + --tag-halal-ink: #94ddb2; + --tag-nonveg-bg: #3a1f2a; + --tag-nonveg-ink: #ff9dc4; + --reading-bg: #5b431d; + --reading-ring: #7c5a25; +} + * { box-sizing: border-box; margin: 0; padding: 0; } -html { scroll-behavior: smooth; scroll-padding-top: 78px; } +html { color-scheme: light; scroll-behavior: smooth; scroll-padding-top: 78px; } body { font-family: var(--body); background: var(--bg); color: var(--ink); font-size: 17px; line-height: 1.7; -webkit-font-smoothing: antialiased; overflow-x: hidden; } h1,h2,h3,h4 { font-family: var(--disp); font-weight: 700; line-height: 1.1; letter-spacing: -0.02em; } a { color: inherit; } @@ -38,7 +120,7 @@ a { color: inherit; } .mono { font-family: var(--mono); font-size: 12.5px; letter-spacing: 0.04em; text-transform: uppercase; } a:focus-visible, button:focus-visible { outline: 3px solid var(--violet); outline-offset: 3px; border-radius: 4px; } -.skip { position: absolute; left: -9999px; z-index: 200; background: var(--ink); color: #fff; padding: 12px 18px; font-weight: 700; border-radius: 0 0 10px 0; } +.skip { position: absolute; left: -9999px; z-index: 200; background: var(--ink); color: var(--on-ink); padding: 12px 18px; font-weight: 700; border-radius: 0 0 10px 0; } .skip:focus { left: 0; top: 0; } /* ---------- Landmark silhouettes (faint, in the background) ---------- */ @@ -51,7 +133,7 @@ a:focus-visible, button:focus-visible { outline: 3px solid var(--violet); outlin body.hc .landmark { display: none; } /* ---------- Nav ---------- */ -.nav { position: sticky; top: 0; z-index: 50; background: rgba(242,230,208,0.85); backdrop-filter: blur(12px); border-bottom: 1px solid var(--line); } +.nav { position: sticky; top: 0; z-index: 50; background: var(--nav-bg); backdrop-filter: blur(12px); border-bottom: 1px solid var(--line); } .nav-inner { display: flex; align-items: center; height: 64px; gap: 18px; } .brand { display: flex; align-items: baseline; gap: 9px; font-family: var(--disp); font-weight: 700; font-size: 16px; white-space: nowrap; } .brand .dc { color: var(--violet); } @@ -59,8 +141,12 @@ body.hc .landmark { display: none; } .brand .pl { font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); } .nav-links { margin-left: auto; display: flex; gap: 2px; flex-wrap: wrap; } .nav-links a { font-family: var(--mono); font-size: 12px; text-transform: uppercase; letter-spacing: 0.03em; text-decoration: none; padding: 9px 12px; border-radius: 8px; color: var(--ink-2); transition: 0.14s; } -.nav-links a:hover { color: var(--violet); background: rgba(197,97,46,0.1); } -.nav-toggle { display: none; margin-left: auto; background: var(--violet); color: #fff; border: 0; border-radius: 8px; padding: 9px 15px; font-family: var(--mono); font-size: 11px; text-transform: uppercase; font-weight: 700; cursor: pointer; } +.nav-links a:hover { color: var(--violet); background: var(--accent-wash); } +.nav-actions { display: flex; align-items: center; gap: 8px; } +.theme-toggle { width: 42px; height: 42px; flex: 0 0 42px; display: inline-grid; place-items: center; border: 1px solid var(--line-2); border-radius: 50%; background: var(--card); color: var(--ink); cursor: pointer; box-shadow: var(--shadow); transition: background-color 0.14s, border-color 0.14s, color 0.14s, transform 0.14s; } +.theme-toggle:hover { border-color: var(--violet); color: var(--violet); background: var(--accent-wash); transform: translateY(-1px); } +.theme-icon { font-size: 18px; line-height: 1; transform: translateY(-1px); } +.nav-toggle { display: none; background: var(--violet); color: var(--on-accent); border: 0; border-radius: 8px; padding: 9px 15px; font-family: var(--mono); font-size: 11px; text-transform: uppercase; font-weight: 700; cursor: pointer; } /* ---------- Hero ---------- */ .hero { position: relative; overflow: hidden; padding: clamp(70px, 11vw, 122px) 0 56px; } @@ -110,7 +196,7 @@ section.block:nth-of-type(even) { background: var(--bg-2); } .g3 { grid-template-columns: repeat(3,1fr); } .g4 { grid-template-columns: repeat(4,1fr); } .cell { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 24px; display: flex; flex-direction: column; box-shadow: var(--shadow); transition: 0.18s; } -.cell:hover { transform: translateY(-3px); box-shadow: 0 18px 40px -24px rgba(43,39,51,0.4); } +.cell:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); } .cell .top { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; } .cell h3 { font-size: 21px; } .cell .tier { font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); white-space: nowrap; } @@ -129,16 +215,16 @@ section.block:nth-of-type(even) { background: var(--bg-2); } /* ---------- Notes (soft tinted, no left bar, no glow) ---------- */ .note { border: 1px solid var(--line); border-radius: 16px; padding: 24px; background: var(--card); } -.note.warn { background: #f7e7c5; border-color: #e6cd98; } -.note.stop { background: #f6dcc7; border-color: #e6b393; } -.note.ok { background: #ecefd3; border-color: #d2d9a8; } +.note.warn { background: var(--note-warn-bg); border-color: var(--note-warn-border); } +.note.stop { background: var(--note-stop-bg); border-color: var(--note-stop-border); } +.note.ok { background: var(--note-ok-bg); border-color: var(--note-ok-border); } .note h3 { font-size: 20px; margin-bottom: 8px; } .note p, .note li { font-size: 16px; color: var(--ink-2); } .note ul { padding-left: 19px; margin-top: 8px; } .note li { margin-bottom: 7px; } .note li::marker { color: var(--violet); } .note .lbl { font-family: var(--mono); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--violet); display: block; margin-bottom: 8px; } -.note.warn .lbl { color: #a9791b; } +.note.warn .lbl { color: var(--note-warn-label); } .note.stop .lbl { color: var(--coral); } .note.ok .lbl { color: var(--teal); } @@ -157,11 +243,11 @@ section.block:nth-of-type(even) { background: var(--bg-2); } .filterbar .flabel { font-family: var(--mono); font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin-right: 6px; } .filter-btn { border: 1px solid var(--line-2); background: var(--card); cursor: pointer; color: var(--ink); padding: 9px 16px; border-radius: 999px; font-family: var(--mono); font-size: 12.5px; text-transform: uppercase; letter-spacing: 0.03em; display: inline-flex; align-items: center; gap: 8px; transition: 0.14s; } .filter-btn:hover { border-color: var(--violet); color: var(--violet); } -.filter-btn[aria-pressed="true"] { background: var(--violet); color: #fff; border-color: var(--violet); } +.filter-btn[aria-pressed="true"] { background: var(--violet); color: var(--on-accent); border-color: var(--violet); } .filter-btn .dot { width: 8px; height: 8px; border-radius: 50%; } .food-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 16px; } .food { background: var(--card); border: 1px solid var(--line); border-radius: 16px; padding: 22px; display: flex; flex-direction: column; box-shadow: var(--shadow); transition: 0.18s; } -.food:hover { transform: translateY(-3px); box-shadow: 0 18px 40px -24px rgba(43,39,51,0.4); } +.food:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); } .food.hide { display: none; } .food .top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; } .food h4 { font-size: 20px; } @@ -171,13 +257,13 @@ section.block:nth-of-type(even) { background: var(--bg-2); } .food .foot { margin-top: 16px; display: flex; align-items: center; justify-content: space-between; gap: 10px; } .tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; } .tag { font-family: var(--mono); font-size: 11px; text-transform: uppercase; padding: 4px 9px; border-radius: 999px; font-weight: 700; } -.tag.veg { background: #e6f6ea; color: #1c7a3f; } -.tag.vegan { background: #e1f5f0; color: #0d7d6e; } -.tag.jain { background: #fcefd8; color: #9a6000; } -.tag.df { background: #ece9fd; color: #5039c9; } -.tag.kosher { background: #e6eefe; color: #2155c4; } -.tag.halal { background: #e6f5ec; color: #167a4a; } -.tag.nonveg { background: #fde6ee; color: #c01060; } +.tag.veg { background: var(--tag-veg-bg); color: var(--tag-veg-ink); } +.tag.vegan { background: var(--tag-vegan-bg); color: var(--tag-vegan-ink); } +.tag.jain { background: var(--tag-jain-bg); color: var(--tag-jain-ink); } +.tag.df { background: var(--tag-df-bg); color: var(--tag-df-ink); } +.tag.kosher { background: var(--tag-kosher-bg); color: var(--tag-kosher-ink); } +.tag.halal { background: var(--tag-halal-bg); color: var(--tag-halal-ink); } +.tag.nonveg { background: var(--tag-nonveg-bg); color: var(--tag-nonveg-ink); } .heat { display: inline-flex; gap: 3px; align-items: center; } .heat i { width: 16px; height: 5px; background: var(--line-2); border-radius: 2px; } .heat.l1 i:nth-child(1), .heat.l2 i:nth-child(-n+2), .heat.l3 i:nth-child(-n+3) { background: var(--marigold); } @@ -212,9 +298,9 @@ footer .disc b { color: var(--ink-2); } .totop.show { opacity: 1; pointer-events: auto; } /* ---------- Accessibility toolbar ---------- */ -.a11y-fab { position: fixed; left: 22px; bottom: 22px; z-index: 70; display: inline-flex; align-items: center; gap: 9px; background: var(--ink); color: #fff; border: 0; cursor: pointer; padding: 12px 16px; border-radius: 999px; font-family: var(--body); font-weight: 700; font-size: 14px; box-shadow: var(--shadow); } +.a11y-fab { position: fixed; left: 22px; bottom: 22px; z-index: 70; display: inline-flex; align-items: center; gap: 9px; background: var(--ink); color: var(--on-ink); border: 0; cursor: pointer; padding: 12px 16px; border-radius: 999px; font-family: var(--body); font-weight: 700; font-size: 14px; box-shadow: var(--shadow); } .a11y-fab .icon { font-size: 16px; } -.a11y-panel { position: fixed; left: 22px; bottom: 80px; z-index: 71; width: 290px; background: var(--card); border: 1px solid var(--line-2); border-radius: 16px; padding: 18px; box-shadow: 0 24px 60px -22px rgba(43,39,51,0.45); display: none; } +.a11y-panel { position: fixed; left: 22px; bottom: 80px; z-index: 71; width: 290px; background: var(--card); border: 1px solid var(--line-2); border-radius: 16px; padding: 18px; box-shadow: var(--panel-shadow); display: none; } .a11y-panel.open { display: block; } .a11y-panel h3 { font-size: 18px; margin-bottom: 4px; } .a11y-panel .hint { font-size: 13px; color: var(--ink-2); margin-bottom: 14px; } @@ -223,19 +309,19 @@ footer .disc b { color: var(--ink-2); } .a11y-row .opts { display: flex; gap: 6px; } .a11y-opt { flex: 1; border: 1px solid var(--line-2); background: transparent; color: var(--ink); cursor: pointer; padding: 9px 8px; border-radius: 10px; font-family: var(--body); font-weight: 600; font-size: 14px; transition: 0.14s; } .a11y-opt:hover { border-color: var(--violet); } -.a11y-opt[aria-pressed="true"] { background: var(--violet); color: #fff; border-color: var(--violet); } +.a11y-opt[aria-pressed="true"] { background: var(--violet); color: var(--on-accent); border-color: var(--violet); } .a11y-opt:disabled { opacity: 0.4; cursor: default; } -.a11y-opt.speaking { background: var(--coral); color: #fff; border-color: var(--coral); } +.a11y-opt.speaking { background: var(--coral); color: var(--on-coral); border-color: var(--coral); } .a11y-reset { width: 100%; margin-top: 4px; border: 0; background: transparent; color: var(--violet); font-weight: 700; font-size: 13.5px; cursor: pointer; text-decoration: underline; text-underline-offset: 3px; } /* High contrast */ -body.hc { --bg:#fff; --bg-2:#fff; --card:#fff; --ink:#000; --ink-2:#161616; --muted:#333; --line:rgba(0,0,0,0.45); --line-2:rgba(0,0,0,0.6); --violet:#9c3f12; --coral:#a3401c; --teal:#4c5a23; } +body.hc { color-scheme: light; --bg:#fff; --bg-2:#fff; --card:#fff; --ink:#000; --ink-2:#161616; --muted:#333; --line:rgba(0,0,0,0.45); --line-2:rgba(0,0,0,0.6); --violet:#9c3f12; --coral:#a3401c; --teal:#4c5a23; --nav-bg:rgba(255,255,255,0.96); --accent-wash:#f1e4db; --on-accent:#fff; --on-coral:#fff; --on-ink:#fff; --note-warn-bg:#fff; --note-warn-border:#000; --note-warn-label:#000; --note-stop-bg:#fff; --note-stop-border:#000; --note-ok-bg:#fff; --note-ok-border:#000; --reading-bg:#fff2a0; --reading-ring:#000; } body.hc .cell, body.hc .food, body.hc .note, body.hc .route, body.hc .emerg .e { box-shadow: none; border-width: 2px; } body.ul-links a:not(.a11y-fab):not(.totop) { text-decoration: underline; text-underline-offset: 3px; } body.reduce-motion * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; } body.reduce-motion .ticker-track { animation: none; } -.reading { background: #fff2c4; box-shadow: 0 0 0 3px #fff2c4; border-radius: 5px; } -body.hc .reading { background: #fff2a0; color: #000; box-shadow: 0 0 0 3px #000; } +.reading { background: var(--reading-bg); box-shadow: 0 0 0 3px var(--reading-ring); border-radius: 5px; } +body.hc .reading { color: #000; } @media (prefers-reduced-motion: reduce) { .ticker-track, .hero .shape { animation: none; } } /* ---------- Responsive ---------- */ @@ -245,7 +331,9 @@ body.hc .reading { background: #fff2a0; color: #000; box-shadow: 0 0 0 3px #000; /* swipeable link strip instead of a hamburger */ .nav-toggle { display: none; } .nav-inner { flex-wrap: wrap; height: auto; min-height: 58px; padding: 9px 0; gap: 6px; } - .nav-links { margin-left: 0; width: 100%; overflow-x: auto; flex-wrap: nowrap; gap: 2px; padding-bottom: 2px; scrollbar-width: none; -webkit-overflow-scrolling: touch; } + .brand { order: 0; } + .nav-actions { order: 1; margin-left: auto; } + .nav-links { order: 2; margin-left: 0; width: 100%; overflow-x: auto; flex-wrap: nowrap; gap: 2px; padding-bottom: 2px; scrollbar-width: none; -webkit-overflow-scrolling: touch; } .nav-links::-webkit-scrollbar { display: none; } .nav-links a { white-space: nowrap; flex: 0 0 auto; } .sec-head { grid-template-columns: 1fr; gap: 6px; }