Add automatic dark theme and persistent theme toggle to NASA hub
Deploy nasa (www.nasa.family) / deploy (push) Successful in 15s
Deploy nasa (www.nasa.family) / deploy (push) Successful in 15s
This commit is contained in:
+38
-2
@@ -1,11 +1,25 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#e9edf2"><meta name="description" content="The NASA family home server. Photos, files, entertainment and the tools that keep it all running."><title>NASA Family — At home, connected.</title>
|
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="theme-color" content="#e9edf2"><meta name="description" content="The NASA family home server. Photos, files, entertainment and the tools that keep it all running."><title>NASA Family — At home, connected.</title>
|
||||||
|
<script>
|
||||||
|
(() => {
|
||||||
|
let preference = 'auto';
|
||||||
|
try { preference = localStorage.getItem('nasa-theme') || 'auto'; } catch (_) {}
|
||||||
|
if (preference === 'light' || preference === 'dark') document.documentElement.dataset.theme = preference;
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<style>
|
<style>
|
||||||
:root{color-scheme:light;--bg:#e9edf2;--ink:#26364c;--muted:#627087;--shadow:#cbd2dd;--light:#fff;--radius:28px}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;-webkit-font-smoothing:antialiased}main{max-width:1260px;margin:auto;padding:36px 38px 24px}header{display:flex;justify-content:space-between;align-items:center;margin-bottom:34px;gap:16px}.brand{display:flex;align-items:center;gap:12px;font-weight:750;letter-spacing:.13em;font-size:13px}.brand-mark{width:38px;height:38px;display:grid;place-items:center;border-radius:12px;background:var(--bg);box-shadow:5px 5px 12px var(--shadow),-5px -5px 12px var(--light);font-size:18px;letter-spacing:0}.header-label{font-size:12px;color:var(--muted);letter-spacing:.12em;text-transform:uppercase}.bento{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:24px}.welcome,.service{border-radius:var(--radius);box-shadow:9px 9px 23px var(--shadow),-9px -9px 23px var(--light);background:var(--bg)}.welcome{grid-column:span 2;grid-row:span 2;padding:38px;display:flex;flex-direction:column;justify-content:space-between;min-height:500px;position:relative;overflow:hidden}.eyebrow{font-size:11px;font-weight:750;letter-spacing:.18em;text-transform:uppercase;color:var(--muted)}h1{font-size:clamp(42px,5vw,64px);line-height:1.06;letter-spacing:-.055em;margin:24px 0 20px;font-weight:720;position:relative}h1 span{color:#6a7f9f}.intro{font-size:16px;line-height:1.7;max-width:310px;color:var(--muted);margin:0}.home-art{position:absolute;right:32px;bottom:98px;font-size:104px;line-height:1;color:#7d91ae;opacity:.12;transform:rotate(-12deg);user-select:none}.welcome-bottom{display:flex;align-items:center;gap:10px;margin-top:42px;font-size:12px;color:var(--muted)}.home-dot{width:8px;height:8px;background:#7a91b2;border-radius:50%}.service{padding:25px;min-height:238px;text-decoration:none;color:inherit;display:flex;flex-direction:column;transition:transform .2s,box-shadow .2s;position:relative}.service:hover{transform:translateY(-4px);box-shadow:13px 13px 27px var(--shadow),-10px -10px 24px var(--light)}.service:active{transform:translateY(0);box-shadow:inset 5px 5px 12px var(--shadow),inset -5px -5px 12px var(--light)}a:focus-visible,button:focus-visible{outline:3px solid #52749d;outline-offset:6px}.card-top{display:flex;align-items:center;justify-content:space-between}.icon-well{width:64px;height:64px;border-radius:20px;display:grid;place-items:center;box-shadow:inset 4px 4px 9px #ccd4df,inset -4px -4px 9px #fff;background:#e2e9f3}.rose .icon-well{background:#efe4e8}.lavender .icon-well{background:#e6e3f2}.mint .icon-well{background:#e0ece8}.peach .icon-well{background:#efe7dc}.icon-well img{width:42px;height:42px;object-fit:contain}.launch{color:var(--muted);font-size:23px}.card-copy{margin-top:20px}.app-name{font-size:11px;text-transform:uppercase;letter-spacing:.13em;font-weight:700;color:var(--muted)}h2{font-size:19px;line-height:1.25;letter-spacing:-.025em;margin:7px 0 8px;font-weight:700}.service p{font-size:13px;line-height:1.55;color:var(--muted);margin:0}.address{font-size:11px;margin-top:auto;padding-top:20px;letter-spacing:.02em;color:var(--muted)}.wide{grid-column:span 2;min-height:238px}.wide .card-top{position:absolute;right:27px;top:27px;gap:24px}.wide .icon-well{width:76px;height:76px;border-radius:24px}.wide .icon-well img{width:52px;height:52px}.wide .card-copy{margin-top:20px;max-width:65%}.wide h2{font-size:27px;margin-top:13px;max-width:260px}.section{grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;padding:14px 2px 0;gap:16px}.section h2{font-size:13px;letter-spacing:.12em;text-transform:uppercase;margin:0}.section span{font-size:12px;color:var(--muted)}footer{display:flex;justify-content:space-between;gap:16px;padding:35px 2px 10px;font-size:11px;color:var(--muted)}footer a{color:inherit;text-underline-offset:3px}.filterbar{display:flex;flex-wrap:wrap;gap:10px;margin:30px 0}.filterbar button{border:0;padding:10px 18px;border-radius:100px;background:var(--bg);color:var(--muted);font-family:inherit;font-weight:600;font-size:12px;cursor:pointer}.filterbar button[aria-pressed="true"]{color:var(--ink);box-shadow:inset 4px 4px 8px var(--shadow),inset -4px -4px 8px var(--light)}[hidden]{display:none!important}.filtered .welcome{grid-row:auto;min-height:238px}.filtered .welcome h1{font-size:40px}.filtered .welcome .intro,.filtered .welcome .welcome-bottom,.filtered .home-art{display:none}
|
:root{color-scheme:light;--bg:#e9edf2;--ink:#26364c;--muted:#627087;--shadow:#cbd2dd;--light:#fff;--radius:28px;--focus:#52749d;--accent-text:#6a7f9f;--art:#7d91ae;--dot:#7a91b2;--well-shadow:#ccd4df;--well-light:#fff;--well:#e2e9f3;--rose:#efe4e8;--lavender:#e6e3f2;--mint:#e0ece8;--peach:#efe7dc;}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;-webkit-font-smoothing:antialiased}main{max-width:1260px;margin:auto;padding:36px 38px 24px}header{display:flex;justify-content:space-between;align-items:center;margin-bottom:34px;gap:16px}.brand{display:flex;align-items:center;gap:12px;font-weight:750;letter-spacing:.13em;font-size:13px}.brand-mark{width:38px;height:38px;display:grid;place-items:center;border-radius:12px;background:var(--bg);box-shadow:5px 5px 12px var(--shadow),-5px -5px 12px var(--light);font-size:18px;letter-spacing:0}.header-label{font-size:12px;color:var(--muted);letter-spacing:.12em;text-transform:uppercase}.bento{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:24px}.welcome,.service{border-radius:var(--radius);box-shadow:9px 9px 23px var(--shadow),-9px -9px 23px var(--light);background:var(--bg)}.welcome{grid-column:span 2;grid-row:span 2;padding:38px;display:flex;flex-direction:column;justify-content:space-between;min-height:500px;position:relative;overflow:hidden}.eyebrow{font-size:11px;font-weight:750;letter-spacing:.18em;text-transform:uppercase;color:var(--muted)}h1{font-size:clamp(42px,5vw,64px);line-height:1.06;letter-spacing:-.055em;margin:24px 0 20px;font-weight:720;position:relative}h1 span{color:var(--accent-text)}.intro{font-size:16px;line-height:1.7;max-width:310px;color:var(--muted);margin:0}.home-art{position:absolute;right:32px;bottom:98px;font-size:104px;line-height:1;color:var(--art);opacity:.12;transform:rotate(-12deg);user-select:none}.welcome-bottom{display:flex;align-items:center;gap:10px;margin-top:42px;font-size:12px;color:var(--muted)}.home-dot{width:8px;height:8px;background:var(--dot);border-radius:50%}.service{padding:25px;min-height:238px;text-decoration:none;color:inherit;display:flex;flex-direction:column;transition:transform .2s,box-shadow .2s;position:relative}.service:hover{transform:translateY(-4px);box-shadow:13px 13px 27px var(--shadow),-10px -10px 24px var(--light)}.service:active{transform:translateY(0);box-shadow:inset 5px 5px 12px var(--shadow),inset -5px -5px 12px var(--light)}a:focus-visible,button:focus-visible{outline:3px solid var(--focus);outline-offset:6px}.card-top{display:flex;align-items:center;justify-content:space-between}.icon-well{width:64px;height:64px;border-radius:20px;display:grid;place-items:center;box-shadow:inset 4px 4px 9px var(--well-shadow),inset -4px -4px 9px var(--well-light);background:var(--well)}.rose .icon-well{background:var(--rose)}.lavender .icon-well{background:var(--lavender)}.mint .icon-well{background:var(--mint)}.peach .icon-well{background:var(--peach)}.icon-well img{width:42px;height:42px;object-fit:contain}.launch{color:var(--muted);font-size:23px}.card-copy{margin-top:20px}.app-name{font-size:11px;text-transform:uppercase;letter-spacing:.13em;font-weight:700;color:var(--muted)}h2{font-size:19px;line-height:1.25;letter-spacing:-.025em;margin:7px 0 8px;font-weight:700}.service p{font-size:13px;line-height:1.55;color:var(--muted);margin:0}.address{font-size:11px;margin-top:auto;padding-top:20px;letter-spacing:.02em;color:var(--muted)}.wide{grid-column:span 2;min-height:238px}.wide .card-top{position:absolute;right:27px;top:27px;gap:24px}.wide .icon-well{width:76px;height:76px;border-radius:24px}.wide .icon-well img{width:52px;height:52px}.wide .card-copy{margin-top:20px;max-width:65%}.wide h2{font-size:27px;margin-top:13px;max-width:260px}.section{grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;padding:14px 2px 0;gap:16px}.section h2{font-size:13px;letter-spacing:.12em;text-transform:uppercase;margin:0}.section span{font-size:12px;color:var(--muted)}footer{display:flex;justify-content:space-between;gap:16px;padding:35px 2px 10px;font-size:11px;color:var(--muted)}footer a{color:inherit;text-underline-offset:3px}.filterbar{display:flex;flex-wrap:wrap;gap:10px;margin:30px 0}.filterbar button{border:0;padding:10px 18px;border-radius:100px;background:var(--bg);color:var(--muted);font-family:inherit;font-weight:600;font-size:12px;cursor:pointer}.filterbar button[aria-pressed="true"]{color:var(--ink);box-shadow:inset 4px 4px 8px var(--shadow),inset -4px -4px 8px var(--light)}[hidden]{display:none!important}.filtered .welcome{grid-row:auto;min-height:238px}.filtered .welcome h1{font-size:40px}.filtered .welcome .intro,.filtered .welcome .welcome-bottom,.filtered .home-art{display:none}
|
||||||
@media(min-width:1000px){.family-section{display:none}}@media(max-width:999px){.bento{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px}.welcome{grid-row:auto;min-height:360px}.welcome h1{font-size:56px}.wide{grid-column:span 1}.wide .card-top{position:static;gap:0}.wide .icon-well{width:64px;height:64px;border-radius:20px}.wide .icon-well img{width:42px;height:42px}.wide .card-copy{max-width:100%}.wide h2{font-size:22px}.service{min-height:255px}}
|
@media(min-width:1000px){.family-section{display:none}}@media(max-width:999px){.bento{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px}.welcome{grid-row:auto;min-height:360px}.welcome h1{font-size:56px}.wide{grid-column:span 1}.wide .card-top{position:static;gap:0}.wide .icon-well{width:64px;height:64px;border-radius:20px}.wide .icon-well img{width:42px;height:42px}.wide .card-copy{max-width:100%}.wide h2{font-size:22px}.service{min-height:255px}}
|
||||||
@media(max-width:540px){main{padding:25px 20px 18px}header{margin-bottom:26px}.header-label{font-size:10px}.bento{gap:18px;grid-template-columns:minmax(0,1fr)}.welcome{grid-column:auto;min-height:360px;padding:30px}h1,.welcome h1{font-size:52px}.service,.wide{grid-column:auto;min-height:220px;padding:24px}.card-top{position:absolute!important;right:24px;top:24px;gap:18px!important}.card-copy,.wide .card-copy{max-width:65%;margin-top:14px}.service h2,.wide h2{font-size:23px}.icon-well,.wide .icon-well{width:58px;height:58px}.icon-well img,.wide .icon-well img{width:38px;height:38px}.section{align-items:flex-start;flex-direction:column;gap:5px}.filterbar{gap:4px}.filterbar button{padding:10px 12px}footer{flex-direction:column;gap:8px}.address{overflow-wrap:anywhere}}@media(prefers-reduced-motion:reduce){.service{transition:none}.service:hover{transform:none}}
|
@media(max-width:540px){main{padding:25px 20px 18px}header{margin-bottom:26px}.header-label{font-size:10px}.bento{gap:18px;grid-template-columns:minmax(0,1fr)}.welcome{grid-column:auto;min-height:360px;padding:30px}h1,.welcome h1{font-size:52px}.service,.wide{grid-column:auto;min-height:220px;padding:24px}.card-top{position:absolute!important;right:24px;top:24px;gap:18px!important}.card-copy,.wide .card-copy{max-width:65%;margin-top:14px}.service h2,.wide h2{font-size:23px}.icon-well,.wide .icon-well{width:58px;height:58px}.icon-well img,.wide .icon-well img{width:38px;height:38px}.section{align-items:flex-start;flex-direction:column;gap:5px}.filterbar{gap:4px}.filterbar button{padding:10px 12px}footer{flex-direction:column;gap:8px}.address{overflow-wrap:anywhere}}@media(prefers-reduced-motion:reduce){.service{transition:none}.service:hover{transform:none}}
|
||||||
|
|
||||||
|
:root[data-theme="dark"]{color-scheme:dark;--bg:#202936;--ink:#edf2f8;--muted:#b2bfd1;--shadow:#151c26;--light:#2b3646;--focus:#a8caff;--accent-text:#a9bfdf;--art:#a9bfdf;--dot:#a9bfdf;--well-shadow:#8794a7;--well-light:#d2dbe7;--well:#bbc9dc;--rose:#d4bfc8;--lavender:#c7c0db;--mint:#b8d1c8;--peach:#d8cbb7;}
|
||||||
|
@media(prefers-color-scheme:dark){:root:not([data-theme="light"]){color-scheme:dark;--bg:#202936;--ink:#edf2f8;--muted:#b2bfd1;--shadow:#151c26;--light:#2b3646;--focus:#a8caff;--accent-text:#a9bfdf;--art:#a9bfdf;--dot:#a9bfdf;--well-shadow:#8794a7;--well-light:#d2dbe7;--well:#bbc9dc;--rose:#d4bfc8;--lavender:#c7c0db;--mint:#b8d1c8;--peach:#d8cbb7;}}
|
||||||
|
.header-tools{display:flex;align-items:center;justify-content:flex-end;gap:18px}
|
||||||
|
.theme-toggle{flex-shrink:0;min-height:44px;border:0;border-radius:14px;padding:10px 14px;background:var(--bg);color:var(--ink);font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;box-shadow:4px 4px 10px var(--shadow),-4px -4px 10px var(--light)}
|
||||||
|
.theme-toggle:active{box-shadow:inset 3px 3px 7px var(--shadow),inset -3px -3px 7px var(--light)}
|
||||||
|
@media(max-width:700px){.header-label{display:none}.header-tools{gap:0}}
|
||||||
</style></head><body><main>
|
</style></head><body><main>
|
||||||
<header><div class="brand"><span class="brand-mark" aria-hidden="true">⌂</span>NASA FAMILY</div><span class="header-label">Our little corner of the internet</span></header>
|
<header><div class="brand"><span class="brand-mark" aria-hidden="true">⌂</span>NASA FAMILY</div><div class="header-tools"><span class="header-label">Our little corner of the internet</span><button class="theme-toggle" id="theme-toggle" type="button" aria-label="Theme: automatic. Switch to light theme.">Theme: Auto</button></div></header>
|
||||||
<nav class="filterbar" aria-label="Filter services"><button type="button" data-filter="all" aria-pressed="true">Everything</button><button type="button" data-filter="family" aria-pressed="false">Everyday</button><button type="button" data-filter="media" aria-pressed="false">Media tools</button><button type="button" data-filter="admin" aria-pressed="false">Server tools</button></nav>
|
<nav class="filterbar" aria-label="Filter services"><button type="button" data-filter="all" aria-pressed="true">Everything</button><button type="button" data-filter="family" aria-pressed="false">Everyday</button><button type="button" data-filter="media" aria-pressed="false">Media tools</button><button type="button" data-filter="admin" aria-pressed="false">Server tools</button></nav>
|
||||||
<div class="bento" id="services"><section class="welcome"><div><span class="eyebrow">At home, connected.</span><h1>One home.<br>A world of<br><span>possibilities.</span></h1><p class="intro">Our photos, files, favorite shows and everyday essentials. All with a place to call home.</p></div><span class="home-art" aria-hidden="true">⌂</span><div class="welcome-bottom"><span class="home-dot" aria-hidden="true"></span>Welcome to the family server.</div></section>
|
<div class="bento" id="services"><section class="welcome"><div><span class="eyebrow">At home, connected.</span><h1>One home.<br>A world of<br><span>possibilities.</span></h1><p class="intro">Our photos, files, favorite shows and everyday essentials. All with a place to call home.</p></div><span class="home-art" aria-hidden="true">⌂</span><div class="welcome-bottom"><span class="home-dot" aria-hidden="true"></span>Welcome to the family server.</div></section>
|
||||||
<a class="service wide rose" data-group="family" href="https://photos.nasa.family" aria-label="Open Immich at photos.nasa.family">
|
<a class="service wide rose" data-group="family" href="https://photos.nasa.family" aria-label="Open Immich at photos.nasa.family">
|
||||||
@@ -57,4 +71,26 @@
|
|||||||
</div><footer><span>NASA FAMILY · Your digital home.</span><span>Services may require sign-in. <a href="https://dashboard.nasa.family">Open dashboard ↗</a></span></footer>
|
</div><footer><span>NASA FAMILY · Your digital home.</span><span>Services may require sign-in. <a href="https://dashboard.nasa.family">Open dashboard ↗</a></span></footer>
|
||||||
<script>
|
<script>
|
||||||
const grid=document.getElementById('services');const buttons=[...document.querySelectorAll('[data-filter]')];const cards=[...document.querySelectorAll('.service')];buttons.forEach(button=>button.addEventListener('click',()=>{const filter=button.dataset.filter;buttons.forEach(b=>b.setAttribute('aria-pressed',String(b===button)));cards.forEach(card=>card.hidden=filter!=='all'&&card.dataset.group!==filter);document.querySelectorAll('.section').forEach(section=>section.hidden=filter!=='all'&§ion.dataset.group!==filter);grid.classList.toggle('filtered',filter!=='all');}));
|
const grid=document.getElementById('services');const buttons=[...document.querySelectorAll('[data-filter]')];const cards=[...document.querySelectorAll('.service')];buttons.forEach(button=>button.addEventListener('click',()=>{const filter=button.dataset.filter;buttons.forEach(b=>b.setAttribute('aria-pressed',String(b===button)));cards.forEach(card=>card.hidden=filter!=='all'&&card.dataset.group!==filter);document.querySelectorAll('.section').forEach(section=>section.hidden=filter!=='all'&§ion.dataset.group!==filter);grid.classList.toggle('filtered',filter!=='all');}));
|
||||||
|
|
||||||
|
const themeButton = document.getElementById('theme-toggle');
|
||||||
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
const themeModes = ['auto', 'light', 'dark'];
|
||||||
|
let themePreference = document.documentElement.dataset.theme || 'auto';
|
||||||
|
function updateTheme() {
|
||||||
|
if (themePreference === 'auto') delete document.documentElement.dataset.theme;
|
||||||
|
else document.documentElement.dataset.theme = themePreference;
|
||||||
|
const isDark = themePreference === 'dark' || (themePreference === 'auto' && systemTheme.matches);
|
||||||
|
document.querySelector('meta[name="theme-color"]').content = isDark ? '#202936' : '#e9edf2';
|
||||||
|
const label = themePreference === 'auto' ? 'Auto' : themePreference === 'light' ? 'Light' : 'Dark';
|
||||||
|
const next = themeModes[(themeModes.indexOf(themePreference) + 1) % themeModes.length];
|
||||||
|
themeButton.textContent = 'Theme: ' + label;
|
||||||
|
themeButton.setAttribute('aria-label', 'Theme: ' + label + '. Switch to ' + next + ' theme.');
|
||||||
|
}
|
||||||
|
themeButton.addEventListener('click', () => {
|
||||||
|
themePreference = themeModes[(themeModes.indexOf(themePreference) + 1) % themeModes.length];
|
||||||
|
try { localStorage.setItem('nasa-theme', themePreference); } catch (_) {}
|
||||||
|
updateTheme();
|
||||||
|
});
|
||||||
|
systemTheme.addEventListener('change', updateTheme);
|
||||||
|
updateTheme();
|
||||||
</script></main></body></html>
|
</script></main></body></html>
|
||||||
Reference in New Issue
Block a user