Add support for dark/light mode.
This commit is contained in:
1 parent
631541a23e
commit
dd4d89ef02
3 files changed
+90
-30
No files matched your search
+42
-14
@@ -1,5 +1,22 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const $ = s => document.querySelector(s), $$ = s => document.querySelectorAll(s);
|
||||
|
||||
// Dark/light theme.
|
||||
const darkQuery = matchMedia('(prefers-color-scheme: dark)');
|
||||
const themeListeners = [];
|
||||
const isDark = () =>
|
||||
(document.body.dataset.theme || (darkQuery.matches ? 'dark' : 'light')) === 'dark';
|
||||
const notifyTheme = () => themeListeners.forEach(fn => fn(isDark()));
|
||||
|
||||
$('#theme-toggle')?.addEventListener('click', () => {
|
||||
document.body.dataset.theme = isDark() ? 'light' : 'dark';
|
||||
try { localStorage.setItem('ooru-theme', document.body.dataset.theme); } catch {}
|
||||
notifyTheme();
|
||||
});
|
||||
|
||||
// Fall back to system if there's no saved pref.
|
||||
darkQuery.addEventListener('change', () => { if (!document.body.dataset.theme) notifyTheme(); });
|
||||
|
||||
const items = $$('[data-space]');
|
||||
if (!items.length) {
|
||||
return;
|
||||
@@ -60,25 +77,36 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
map = L.map('map', { maxBounds: [[5, 67], [38, 98]], maxBoundsViscosity: 1, minZoom: 4 }).setView([22, 79], 5);
|
||||
map.on('moveend', onMapMove);
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png', {
|
||||
attribution: '© <a href="https://openstreetmap.org/copyright">OpenStreetMap</a>, © <a href="https://carto.com/">CARTO</a>',
|
||||
subdomains: 'abcd'
|
||||
}).addTo(map);
|
||||
|
||||
const addLabels = () => L.tileLayer(
|
||||
'https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png',
|
||||
{ subdomains: 'abcd', pane: 'shadowPane' }
|
||||
).addTo(map);
|
||||
// Make the country border brighter in the dark mode.
|
||||
const dimStyle = () => isDark()
|
||||
? { fillColor: '#000', fillOpacity: 0.6, stroke: true, color: '#7a8390', weight: 1, opacity: 0.8 }
|
||||
: { fillColor: '#ddd', fillOpacity: 0.6, stroke: false };
|
||||
|
||||
// Fetch India's boundaries and apply to the map.
|
||||
// Swap CARTO basemap + labels + dim styling to match the active theme.
|
||||
let baseLayer, labelLayer, dimLayer;
|
||||
function applyMapTheme() {
|
||||
const v = isDark() ? 'dark' : 'light';
|
||||
baseLayer?.remove();
|
||||
labelLayer?.remove();
|
||||
baseLayer = L.tileLayer(`https://{s}.basemaps.cartocdn.com/${v}_nolabels/{z}/{x}/{y}{r}.png`, {
|
||||
attribution: '© <a href="https://openstreetmap.org/copyright">OpenStreetMap</a>, © <a href="https://carto.com/">CARTO</a>',
|
||||
subdomains: 'abcd'
|
||||
}).addTo(map);
|
||||
labelLayer = L.tileLayer(`https://{s}.basemaps.cartocdn.com/${v}_only_labels/{z}/{x}/{y}{r}.png`,
|
||||
{ subdomains: 'abcd', pane: 'shadowPane' }).addTo(map);
|
||||
dimLayer?.setStyle(dimStyle());
|
||||
}
|
||||
applyMapTheme();
|
||||
themeListeners.push(applyMapTheme);
|
||||
|
||||
// Fetch India's boundaries and dim everything else outside it.
|
||||
fetch('/india.geojson').then(r => r.json()).then(india => {
|
||||
const world = [[-90, -180], [-90, 180], [90, 180], [90, -180]];
|
||||
const polys = india.geometry.type === 'Polygon' ? [india.geometry.coordinates] : india.geometry.coordinates;
|
||||
L.polygon([world, ...polys.map(p => p[0].map(c => [c[1], c[0]]))], {
|
||||
color: 'none', fillColor: '#ddd', fillOpacity: 0.6, interactive: false
|
||||
}).addTo(map);
|
||||
addLabels();
|
||||
}).catch(addLabels);
|
||||
dimLayer = L.polygon([world, ...polys.map(p => p[0].map(c => [c[1], c[0]]))],
|
||||
{ interactive: false, ...dimStyle() }).addTo(map);
|
||||
}).catch(() => {});
|
||||
|
||||
items.forEach(item => {
|
||||
const lat = parseFloat(item.dataset.lat), lng = parseFloat(item.dataset.lng);
|
||||
|
||||
+40
-15
@@ -1,14 +1,23 @@
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
|
||||
--text-heading: 1.1rem;
|
||||
--text-regular: 0.9rem;
|
||||
--text-small: 0.8rem;
|
||||
--text-xsmall: 0.7rem;
|
||||
--color-primary: blue;
|
||||
--color-muted: #888;
|
||||
--color-border: #ddd;
|
||||
--color-hover: #f5f5f5;
|
||||
|
||||
--color-bg: light-dark(#ffffff, #16181c);
|
||||
--color-text: light-dark(#1c1e21, #e3e5e8);
|
||||
--color-primary: light-dark(#1a53d8, #7aa2ff);
|
||||
--color-visited: light-dark(#7b3ff2, #c0a6ff);
|
||||
--color-muted: light-dark(#6b7075, #979ca3);
|
||||
--color-border: light-dark(#dddddd, #2c3037);
|
||||
--color-hover: light-dark(#f5f5f5, #22262d);
|
||||
}
|
||||
|
||||
:root:has(body[data-theme="dark"]) { color-scheme: dark; }
|
||||
:root:has(body[data-theme="light"]) { color-scheme: light; }
|
||||
|
||||
*, *::before, *::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
@@ -24,12 +33,14 @@ body {
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--color-primary);
|
||||
&:visited { color: purple; }
|
||||
&:visited { color: var(--color-visited); }
|
||||
&:hover { color: inherit; }
|
||||
|
||||
&.light {
|
||||
@@ -57,7 +68,9 @@ input[type="checkbox"] {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 6L5 9.5L10 2.5' stroke='blue' stroke-width='2.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/80% no-repeat;
|
||||
background: var(--color-primary);
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 6L5 9.5L10 2.5' stroke='%23000' stroke-width='2.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/80% no-repeat;
|
||||
mask: url("data:image/svg+xml,%3Csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2 6L5 9.5L10 2.5' stroke='%23000' stroke-width='2.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center/80% no-repeat;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
@@ -70,7 +83,7 @@ input[type="checkbox"] {
|
||||
header {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
@@ -84,9 +97,28 @@ header {
|
||||
font-size: var(--text-xsmall);
|
||||
color: var(--color-muted);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
background: none;
|
||||
border: none;
|
||||
line-height: 0;
|
||||
cursor: pointer;
|
||||
color: var(--color-muted);
|
||||
|
||||
&:hover { color: var(--color-primary); }
|
||||
&:focus-visible {
|
||||
outline: 1.5px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Three-column layout */
|
||||
.layout {
|
||||
display: flex;
|
||||
@@ -288,14 +320,7 @@ main.detail {
|
||||
body { display: block; }
|
||||
|
||||
header { flex-direction: column; align-items: flex-start; }
|
||||
.header-right {
|
||||
margin-left: 0;
|
||||
display: block;
|
||||
|
||||
> * {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.header-right { margin-left: 0; }
|
||||
|
||||
.layout { flex-direction: column; flex: none; }
|
||||
|
||||
|
||||
+8
-1
@@ -10,12 +10,19 @@
|
||||
{% block meta %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<script>try{var t=localStorage.getItem('ooru-theme');if(t)document.body.dataset.theme=t}catch(e){}</script>
|
||||
<header>
|
||||
<h1><a href="{{ get_url(path='/') }}">ooru.space</a></h1>
|
||||
{% block subtitle %}{% endblock %}
|
||||
<nav class="header-right">
|
||||
<a href="https://gitea.samagata.org/ooru.space/ooru.space">Source</a>
|
||||
<span>Add or correct something? <a href="https://docs.google.com/forms/d/1I6JjCiQ4_wVXgY1Y9EbpmJht4T6D714QIEDnAAR-nOk">Send suggestions</a></span>
|
||||
<a href="https://docs.google.com/forms/d/1I6JjCiQ4_wVXgY1Y9EbpmJht4T6D714QIEDnAAR-nOk">Send suggestions</a>
|
||||
<button type="button" id="theme-toggle" class="theme-toggle" title="Toggle dark mode" aria-label="Toggle dark mode">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="4.5"/>
|
||||
<path d="M12 1.5v3M12 19.5v3M4.22 4.22l2.12 2.12M17.66 17.66l2.12 2.12M1.5 12h3M19.5 12h3M4.22 19.78l2.12-2.12M17.66 6.34l2.12-2.12"/>
|
||||
</svg>
|
||||
</button>
|
||||
</nav>
|
||||
</header>
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
Reference in new issue
Block a user