Give the website the app's Material 3 look
docker / test (8.5) (push) Successful in 3m29s
linter / quality (push) Successful in 1m21s
tests / ci (8.5) (push) Successful in 3m27s
docker / build-and-push (push) Successful in 7m38s
docker / release (push) Skipped

The site's tokens now carry the app's --md-sys-* names, with shape,
elevation, motion, state, type and spacing values copied from Livewire
Material 2.1 and the colours from the indigo profile, including its
high-contrast light and dark values for prefers-contrast: more. Cards
are outlined, the gallery uses a connected button group instead of
segmented buttons, buttons use M3 Expressive sizes, state layers and
pressed shapes, and layouts follow the M3 window classes with logical
properties. A Light/Dark/System toggle in the nav sets data-theme
before the first paint, and no text is dimmed with opacity.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Andreas Reinhold / reini
2026-09-16 22:58:24 +02:00
co-authored by Claude Opus 5
parent 1bcb868897
commit 7ffc1c1ead
5 changed files with 1185 additions and 658 deletions
+105 -2
View File
@@ -7,6 +7,83 @@
<meta name="description" content="How the SealShare website handles visitor data: server logs at the host and Plausible analytics, nothing else.">
<meta name="theme-color" content="#faf4ff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#10062d" media="(prefers-color-scheme: dark)">
<script>
// Light, Dark or System, decided before the first paint: the visitor's choice in the nav, kept in
// localStorage, else the system's. css/theme.css keys on <html data-theme>.
(function () {
var root = document.documentElement;
var key = 'sealshare-website-theme';
var choices = ['light', 'dark', 'system'];
var surfaces = { light: '#faf4ff', dark: '#10062d' };
var system = window.matchMedia('(prefers-color-scheme: dark)');
var choice = 'system';
try {
var stored = localStorage.getItem(key);
if (choices.indexOf(stored) !== -1) {
choice = stored;
}
} catch (error) {
// Storage is blocked: follow the system.
}
// A <picture>'s dark source (data-theme-dark) follows the site's theme instead of the system's.
var sources = function () {
document.querySelectorAll('source[data-theme-dark]').forEach(function (source) {
source.media = root.dataset.theme === 'dark' ? 'all' : 'not all';
});
};
var apply = function () {
var theme = choice === 'system' ? (system.matches ? 'dark' : 'light') : choice;
root.dataset.themeChoice = choice;
root.dataset.theme = theme;
document.querySelectorAll('meta[name="theme-color"]').forEach(function (meta) {
meta.removeAttribute('media');
meta.setAttribute('content', surfaces[theme]);
});
sources();
document.dispatchEvent(new CustomEvent('sealshare:theme', { detail: { choice: choice, theme: theme } }));
};
window.sealshareTheme = {
sources: sources,
get choice() {
return choice;
},
get theme() {
return root.dataset.theme;
},
set: function (next) {
if (choices.indexOf(next) === -1) {
return;
}
choice = next;
try {
localStorage.setItem(key, next);
} catch (error) {
// Storage is blocked: the page still switches, it just will not remember.
}
apply();
},
};
system.addEventListener('change', function () {
if (choice === 'system') {
apply();
}
});
apply();
})();
</script>
<!-- Privacy-friendly analytics by Plausible -->
<script async src="https://plausible.io/js/pa-zWB4R2-rrBeLfwms3uIBT.js"></script>
<script>
@@ -19,18 +96,21 @@
<link rel="stylesheet" href="css/theme.css">
</head>
<body id="top">
<a class="skip-link" href="#main">Skip to content</a>
<a class="skip-link btn btn--filled" href="#main">Skip to content</a>
<svg xmlns="http://www.w3.org/2000/svg" class="sprite" aria-hidden="true">
<symbol id="i-menu" viewBox="0 -960 960 960"><path d="M160-240q-17 0-28.5-11.5T120-280q0-17 11.5-28.5T160-320h640q17 0 28.5 11.5T840-280q0 17-11.5 28.5T800-240H160Zm0-200q-17 0-28.5-11.5T120-480q0-17 11.5-28.5T160-520h640q17 0 28.5 11.5T840-480q0 17-11.5 28.5T800-440H160Zm0-200q-17 0-28.5-11.5T120-680q0-17 11.5-28.5T160-720h640q17 0 28.5 11.5T840-680q0 17-11.5 28.5T800-640H160Z"/></symbol>
<symbol id="i-close" viewBox="0 -960 960 960"><path d="M480-424 284-228q-11 11-28 11t-28-11q-11-11-11-28t11-28l196-196-196-196q-11-11-11-28t11-28q11-11 28-11t28 11l196 196 196-196q11-11 28-11t28 11q11 11 11 28t-11 28L536-480l196 196q11 11 11 28t-11 28q-11 11-28 11t-28-11L480-424Z"/></symbol>
<symbol id="i-open-in-new" viewBox="0 -960 960 960"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h240q17 0 28.5 11.5T480-800q0 17-11.5 28.5T440-760H200v560h560v-240q0-17 11.5-28.5T800-480q17 0 28.5 11.5T840-440v240q0 33-23.5 56.5T760-120H200Zm560-584L416-360q-11 11-28 11t-28-11q-11-11-11-28t11-28l344-344H600q-17 0-28.5-11.5T560-800q0-17 11.5-28.5T600-840h200q17 0 28.5 11.5T840-800v200q0 17-11.5 28.5T800-560q-17 0-28.5-11.5T760-600v-104Z"/></symbol>
<symbol id="i-light-mode" viewBox="0 -960 960 960"><path d="M480-360q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35Zm0 80q-83 0-141.5-58.5T280-480q0-83 58.5-141.5T480-680q83 0 141.5 58.5T680-480q0 83-58.5 141.5T480-280ZM80-440q-17 0-28.5-11.5T40-480q0-17 11.5-28.5T80-520h80q17 0 28.5 11.5T200-480q0 17-11.5 28.5T160-440H80Zm720 0q-17 0-28.5-11.5T760-480q0-17 11.5-28.5T800-520h80q17 0 28.5 11.5T920-480q0 17-11.5 28.5T880-440h-80ZM480-760q-17 0-28.5-11.5T440-800v-80q0-17 11.5-28.5T480-920q17 0 28.5 11.5T520-880v80q0 17-11.5 28.5T480-760Zm0 720q-17 0-28.5-11.5T440-80v-80q0-17 11.5-28.5T480-200q17 0 28.5 11.5T520-160v80q0 17-11.5 28.5T480-40ZM226-678l-43-42q-12-11-11.5-28t11.5-29q12-12 29-12t28 12l42 43q11 12 11 28t-11 28q-11 12-27.5 11.5T226-678Zm494 495-42-43q-11-12-11-28.5t11-27.5q11-12 27.5-11.5T734-282l43 42q12 11 11.5 28T777-183q-12 12-29 12t-28-12Zm-42-495q-12-11-11.5-27.5T678-734l42-43q11-12 28-11.5t29 11.5q12 12 12 29t-12 28l-43 42q-12 11-28 11t-28-11ZM183-183q-12-12-12-29t12-28l43-42q12-11 28.5-11t27.5 11q12 11 11.5 27.5T282-226l-42 43q-11 12-28 11.5T183-183Zm297-297Z"/></symbol>
<symbol id="i-dark-mode" viewBox="0 -960 960 960"><path d="M480-120q-151 0-255.5-104.5T120-480q0-138 90-239.5T440-838q13-2 23 3.5t16 14.5q6 9 6.5 21t-7.5 23q-17 26-25.5 55t-8.5 61q0 90 63 153t153 63q31 0 61.5-9t54.5-25q11-7 22.5-6.5T819-479q10 5 15.5 15t3.5 24q-14 138-117.5 229T480-120Zm0-80q88 0 158-48.5T740-375q-20 5-40 8t-40 3q-123 0-209.5-86.5T364-660q0-20 3-40t8-40q-78 32-126.5 102T200-480q0 116 82 198t198 82Zm-10-270Z"/></symbol>
<symbol id="i-brightness-auto" viewBox="0 -960 960 960"><path d="M408-412h146l25 73q3 8 10.5 13.5T606-320q15 0 23.5-12.5T633-359L519-661q-3-9-11-14t-17-5h-22q-9 0-17 5t-11 14L327-360q-5 14 3.5 27t24.5 13q10 0 17.5-5.5T383-340l25-72Zm18-52 52-150h4l52 150H426Zm-80 304H240q-33 0-56.5-23.5T160-240v-106l-77-78q-11-12-17-26.5T60-480q0-15 6-29.5T83-536l77-78v-106q0-33 23.5-56.5T240-800h106l78-77q12-11 26.5-17t29.5-6q15 0 29.5 6t26.5 17l78 77h106q33 0 56.5 23.5T800-720v106l77 78q11 12 17 26.5t6 29.5q0 15-6 29.5T877-424l-77 78v106q0 33-23.5 56.5T720-160H614l-78 77q-12 11-26.5 17T480-60q-15 0-29.5-6T424-83l-78-77Zm34-80 100 100 100-100h140v-140l100-100-100-100v-140H580L480-820 380-720H240v140L140-480l100 100v140h140Zm100-240Z"/></symbol>
</svg>
<header class="nav">
<div class="container nav__bar">
<a class="brand" href="index.html"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2Z" stroke="currentColor" stroke-width="1.5"/><path d="M14 2v5a1 1 0 0 0 1 1h5" stroke="currentColor" stroke-width="1.5"/><path d="M12 17v-6m0 0-2.5 2.5M12 11l2.5 2.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg><span>SealShare</span></a>
<button class="nav__toggle" id="navToggle" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Menu">
<button class="icon-btn nav__toggle" id="navToggle" type="button" aria-expanded="false" aria-controls="navLinks" aria-label="Menu">
<svg class="icon icon-menu" aria-hidden="true"><use href="#i-menu"/></svg>
<svg class="icon icon-close" aria-hidden="true"><use href="#i-close"/></svg>
</button>
@@ -45,6 +125,11 @@
<li><a class="btn btn--filled" href="https://gitea.nonameweb.ch/noNameWEB/SealShare"><svg class="icon" aria-hidden="true"><use href="#i-open-in-new"/></svg>Gitea</a></li>
</ul>
</nav>
<button class="icon-btn theme-toggle" id="themeToggle" type="button" aria-label="Theme: System">
<svg class="icon icon-light-mode" aria-hidden="true"><use href="#i-light-mode"/></svg>
<svg class="icon icon-dark-mode" aria-hidden="true"><use href="#i-dark-mode"/></svg>
<svg class="icon icon-brightness-auto" aria-hidden="true"><use href="#i-brightness-auto"/></svg>
</button>
</div>
</header>
@@ -118,6 +203,24 @@
</footer>
<script>
// The top app bar takes surface-container once the page scrolls under it.
const nav = document.querySelector('.nav');
const markScrolled = () => nav.toggleAttribute('data-scrolled', window.scrollY > 0);
window.addEventListener('scroll', markScrolled, { passive: true });
markScrolled();
// Theme: Light, then Dark, then System (the <head> script applies and remembers it).
const themeToggle = document.getElementById('themeToggle');
const themeNames = { light: 'Light', dark: 'Dark', system: 'System' };
const nextTheme = { light: 'dark', dark: 'system', system: 'light' };
const nameThemeToggle = () => themeToggle.setAttribute('aria-label', 'Theme: ' + themeNames[sealshareTheme.choice]);
themeToggle.addEventListener('click', () => sealshareTheme.set(nextTheme[sealshareTheme.choice]));
document.addEventListener('sealshare:theme', nameThemeToggle);
nameThemeToggle();
// Phone menu.
const navToggle = document.getElementById('navToggle');
const navLinks = document.getElementById('navLinks');