From 93b7d3ef24b9b961f8404fb0101e4358438cb935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20H=C3=A4gele?= Date: Thu, 10 Sep 2026 15:09:47 +0200 Subject: [PATCH 1/6] feat(web-standalone): adopt the shared DMC product bar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The style guide gained the shared product bar after this app's house-style adoption landed, so it had none of it and still carried the links the bar is meant to replace. A .dmc-bar band between the tricolour stripe and the app navbar: DMC brand, app switcher, language picker, theme picker. The theme picker moves out of the navbar into it, and the "Dutch MeshCore" dropdown leaves entirely — every link in it is in the switcher, which is now the one place cross-subdomain links appear. Vendored from style-guide.html, with this app's canonical token names, until the shared file has somewhere to be served from. Two decisions, both recorded in #142: the language picker ships even though the app is English-only, because the band is meant to be the same control set everywhere rather than one app being a control short; and Mesh-Hunter and MC-Radar stay in the navbar, since they are not DMC subdomains and MC-Radar is this app's data source. Two things fell out of making the navbar sticky beneath the band: - The tricolour stripe has been 4px of nothing since #141. Its --stripe-h definition never made it into that commit: the script that added it hit a failed assertion further down and never wrote the file, and the retry only added the rule. Restored, and visible for the first time. - The map is sticky inside a column that `align-items: start` kept short, so it slid under the newly sticky navbar. The column stretches now, which is what gives the map room to stay put, and its offset comes from a measured --nav-h because the navbar wraps to more rows at narrow widths. Verified at 1280x820 and 375x812 in both themes: stacking (stripe 0-4, band 4-38, navbar sticks at 34, map clears it at 95), the switcher marking Triangulator as current, dropdowns closing each other and on outside click, and the mobile rules hiding the wordmark and chip labels while the sheet goes full-width in two columns. Closes #142 Co-Authored-By: Claude Opus 5 --- web-standalone/assets/logo/dmc-logo.jpg | Bin 0 -> 15364 bytes web-standalone/changelog.json | 6 + web-standalone/index.html | 424 +++++++++++++++++------- 3 files changed, 319 insertions(+), 111 deletions(-) create mode 100644 web-standalone/assets/logo/dmc-logo.jpg diff --git a/web-standalone/assets/logo/dmc-logo.jpg b/web-standalone/assets/logo/dmc-logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6d189c51ea5810fd45f6f3cb8389865775978e4c GIT binary patch literal 15364 zcmbWe1yodD|1W&#kdW?BI;BKFU_hi}2&GdH7&-*$lp4B2WayAqT99Vw?rsF68;Lvo z;`u-Cdhc5Iz3;5`owLu`dw*+peD0_3=YUTDCOSF>IvOSh1_l-uCN>T+F3zJzI243L z_{21nbhI>-)YSBh+${7@IT@&_SzoYm^6&}>2+*+ziwW_Ga`OxDAz@)*;XJ}2$HgV* zdqVw$@BjLD-vSU|BE=xZpddX3kO`1b2$1eOkg@>)2@UD5AMno)2^j?y4IKj$3;WSy z02v7d1sN3u4Gk3)kVDK-n`Y+($>*6HG6MvVF|Hv{@~*J(aqf> z@KaE5NN5-|E>)tdgN%xbf{O8A4-)c61fdY1 zqCMe7CzMpfFmWV$%IA+s{3*`Y@6-aRN}KCJhij5GCth3L!%kSdlCd?Ah2X zGyg~We{1r82!GkO%=ROM;u8PGn;8OmIr<<<^?yt9iqZhWLI1Bv`BuUW}@Y0 z2Ry{{B8pIb6`}p#&Oj0PSK$BSDQAf!{Uk3}08k(K$9ex9{g-FS^DT+_7%4rhtZ=o0< zBMR3GlNMrQs9m+hg!|!n;|Zr9Y-=~YwH9jbPORigq57vLvJy!c?$mz`ReQ$c%)a zZIF0DT8RF3@LQJVvrC_R{F!wO9LyVBWtxN%m3Q_MV7f1MoR)!~)Rz3g)O5PZ2B#t> z39WYhj``v!4)_L55Xx55);Q(dV<*RJ&7R!@6B#seEAh7>1q03Ti?w^OlDQFO2C2O(XX!>ajoJ{U>g`Wp1xe{a(x z_|XTK8v~Up)NuYrp8FQzy=A|xCxO<5&bpgsIFlyJnP2Ujd^O)=AQlk!{xwRG^w5sS zn}9apM|$^KzWnZKr{!-{V?WtufgkR>>N|3iLfG=Q*QssD^^fP+BOC_Z?}1(Qa5CpG zJ97US^bxJ2sC2cOW5mvAYMm2x8 zrm&sknCRHnJ2&l82I7csJzXRuA>>@K_kb7F=*cEI=G6t2?B(RQHYm?mbTM?c7JMa* z^Dm*=R5wZkQ7vqom+uy2jkfNHj|$*DUh&f(P!5ad@e+u+0>_p}#7AX{G334D;%7P- z-Ww$lrAKAn0}B30GkOGFC!oR}l-=1XC05=aTcAB>l`af&;;Wt{5;1kf4QC!nr+c6! zrrcQME|F?h`tj{)2h23Ot6GP|e_npbW?UK>k0@|k`Hi_oYbW2EaKJWPucC90HH2zJ5Z_l&rua&`|?Z8({*?425vDMNojQG^vSD-mLKuZtg#V+ z1U4}=`T|zYX&`=2VuaBV-2oq1wh-PrEGC$hn5H$YE#tvhiC2nhlc#Xt!5s9M_*FIW z_9~2Vk|@mp48{#gul{g)VoEyMP0(PNQ4UQK;*=BDElzjY_}Z^oM0k^FQLgaWcw1X| zsc3hsHLnX zG@kZGmAVkWnz(K)k42P7?wzr#KxXV|-VJLe=cmnt=&txBf#&EfB|;)<$4)BAHPKr( z=lDUiC)Y$0DW>zITgaa;HGZffTVy0Gw**G{B*21*gUMHuDX7zUJ1OX022d&z>JFev zn^CNfDS2?8Fw{UiRmo`l@ICBNMv1P*U0RsvYRAPK3*ano zF1}{~oj1@wIuF^lDU@=VZ9_#&`EA;nZ12|@P>{am^<&}dw;yXb=l#GJ(a#+>b{sX| zX2hB+#b2e{s9lY}j$f>%?3~W7O(IKA)Tf&gh%9)~CN{ z>7-`=$mgty7r#fceNKJ^%EgMI-rH@bu7nP>(FtO@n=?5(6>&bv>3uI zcdv@u%_^4ct#h{T@>7o1G1=0s&`gsiE$=H&&W zF>XY%0xo&ND6=u|VszVoj2`XE$GjXxSt7<;yM`*^e8@LXAd)b61Xb~dfW?Y(&ohuom}dX^pOk*-fD)>ww#A~vcOnIc1qbZ$#k`7{ZSVh+0g`SGzTF# zvVZ!CJj}yk0Oi$4C&eaIS|2O-+EMaa!(EK^@f`?Tg^mAe)I24GC@PTHK^QI&QlM)m z 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 GGye;}cX#3d literal 0 HcmV?d00001 diff --git a/web-standalone/changelog.json b/web-standalone/changelog.json index a7e65a0..f27854e 100644 --- a/web-standalone/changelog.json +++ b/web-standalone/changelog.json @@ -1,4 +1,10 @@ [ + { + "id": "2026-09-10-product-bar", + "date": "2026-09-10", + "title": "One bar to every DutchMeshCore app", + "body": "A thin band at the top now carries the DMC brand, a switcher to every other DutchMeshCore app, and the language and theme pickers — the same band you will see on the other sites." + }, { "id": "2026-09-10-plain-scroll-zooms", "date": "2026-09-10", diff --git a/web-standalone/index.html b/web-standalone/index.html index 71a29bf..d815c81 100644 --- a/web-standalone/index.html +++ b/web-standalone/index.html @@ -113,6 +113,12 @@ --chip-bg: rgba(255, 255, 255, 0.05); --scrim: rgba(3, 4, 6, 0.62); --raised: rgba(255, 255, 255, 0.10); + /* Shared product bar (STYLE-GUIDE §9, #142). */ + --bar-h: 34px; + --bar-bg: rgba(3, 4, 6, 0.74); + --glass-bg: rgba(18, 21, 28, 0.88); + /* Measured from the rendered navbar; the fallback is its one-row height. */ + --nav-h: 53px; /* Type. */ --font-sans: 'IBM Plex Sans', 'Segoe UI', system-ui, -apple-system, Roboto, sans-serif; @@ -124,6 +130,10 @@ --fs-lg: 16px; --fs-xl: 18px; + /* The tricolour band every DMC surface opens with (STYLE-GUIDE §9). + Theme-constant: it is the brand, not the palette. */ + --stripe-h: 4px; + /* Geometry: one base radius for controls, one for large surfaces. */ --radius: 8px; --radius-lg: 16px; @@ -170,6 +180,8 @@ --chip-bg: #eef0f8; --scrim: rgba(26, 28, 46, 0.35); --raised: rgba(20, 30, 60, 0.06); + --bar-bg: rgba(255, 255, 255, 0.82); + --glass-bg: rgba(255, 255, 255, 0.94); } * { @@ -228,11 +240,6 @@ down over the map strip, and at equal z-index the later element in the document wins — so the strip's buttons cut straight through the theme and What's new panels. */ - .topbar { - position: relative; - z-index: 2; - } - /* Headings and the brand run in the display face, uppercased, with a hard drop shadow that light mode drops (STYLE-GUIDE §3). */ h1, h2, h3, @@ -253,7 +260,10 @@ accent stripe, not a repaint) so it still reads as "this page, but tied to the wider community". */ .topbar { - position: relative; + /* Sticks beneath the family band, which owns top: 0 (#142). */ + position: sticky; + top: var(--bar-h); + z-index: 2; display: flex; align-items: center; gap: 16px; @@ -265,6 +275,188 @@ background: var(--surface-2); border: 1px solid var(--border); } + /* ── Shared product bar (STYLE-GUIDE §9, #142) ────────────────────── + One thin band above the app navbar, the same on every DMC subdomain, + carrying the family brand, the app switcher, and the language and theme + pickers. Vendored from style-guide.html until the shared file has + somewhere to be served from — that is still an open decision in the + guide. Token names are this app's canonical ones (--text-muted, + --surface-hover) where the reference uses the website's. */ + .dmc-bar { + position: sticky; + top: 0; + z-index: 30; + height: var(--bar-h); + background: var(--bar-bg); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + border-bottom: 1px solid var(--border); + } + .dmc-bar-inner { + width: min(1200px, calc(100% - 32px)); + margin: 0 auto; + height: 100%; + display: flex; + align-items: center; + gap: 8px; + } + .dmc-bar-brand { + display: flex; + align-items: center; + gap: 8px; + text-decoration: none; + margin-right: auto; + } + .dmc-bar-logo { + width: 20px; + height: 20px; + display: block; + object-fit: cover; + border-radius: 5px; + border: 1px solid var(--border-strong); + } + .dmc-bar-name { + font-family: var(--font-head); + text-transform: uppercase; + letter-spacing: 0.1em; + font-size: 0.68rem; + line-height: 1; + color: var(--heading); + text-shadow: var(--heading-shadow); + } + + /* The dropdown pattern the band's three controls share. */ + .dd { position: relative; } + .dmc-bar .dd-trig { + display: flex; + align-items: center; + gap: 5px; + cursor: pointer; + font-family: var(--font-mono); + font-size: 0.72rem; + line-height: 1; + color: var(--text-muted); + background: var(--chip-bg); + border: 1px solid var(--border); + border-radius: 6px; + padding: 4px 8px; + margin: 0; + } + .dmc-bar .dd-trig:hover { + color: var(--text); + border-color: var(--accent); + } + .dmc-bar .dd-trig svg { width: 11px; height: 11px; fill: currentColor; } + .dd-caret { font-size: 0.65rem; opacity: 0.7; } + .dd-menu { + position: absolute; + right: 0; + top: calc(100% + 6px); + display: none; + min-width: 168px; + padding: 6px; + background: var(--menu-bg); + border: 1px solid var(--border); + border-radius: 10px; + box-shadow: var(--surface-shadow); + z-index: 40; + } + .dd.open .dd-menu { display: block; } + .dmc-bar .dd-menu button { + display: flex; + align-items: center; + gap: 9px; + width: 100%; + text-align: left; + cursor: pointer; + border: 0; + background: none; + color: var(--text); + font-family: var(--font-sans); + font-size: 0.85rem; + padding: 8px 10px; + border-radius: var(--radius); + margin: 0; + } + .dmc-bar .dd-menu button:hover { background: var(--surface-hover); } + .dd-menu .dd-ico { width: 20px; text-align: center; } + .dd-menu .chk { margin-left: auto; color: var(--accent); visibility: hidden; } + .dd-menu button[aria-checked="true"] .chk { visibility: visible; } + + /* App switcher: a glass sheet of monogram tiles. */ + .dmc-apps .dd-menu { + width: 372px; + padding: 10px; + background: var(--glass-bg); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); + } + .dmc-apps-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 4px; + } + .dmc-app { + display: flex; + flex-direction: column; + align-items: center; + gap: 6px; + padding: 10px 6px 8px; + border-radius: var(--radius); + text-decoration: none; + color: var(--text); + text-align: center; + } + .dmc-app:hover { background: var(--surface-hover); } + .dmc-app[aria-current="page"] { + background: var(--surface-hover); + box-shadow: inset 0 0 0 1px var(--border); + } + .dmc-app-ico { + width: 34px; + height: 34px; + display: grid; + place-items: center; + overflow: hidden; + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--chip-bg); + font-family: var(--font-head); + font-size: 0.74rem; + letter-spacing: 0.04em; + color: var(--accent-light); + } + .dmc-app-ico img { width: 100%; height: 100%; object-fit: cover; display: block; } + .dmc-app-name { font-size: 0.76rem; font-weight: 600; line-height: 1.1; } + .dmc-app-host { font-family: var(--font-mono); font-size: 0.58rem; color: var(--text-faint); } + .dmc-apps-foot { + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--border); + display: flex; + justify-content: space-between; + gap: 10px; + font-size: 0.74rem; + } + .dmc-apps-foot a { color: var(--link); text-decoration: none; } + .dmc-apps-foot a:hover { text-decoration: underline; } + + @media (max-width: 768px) { + .dmc-bar-name { display: none; } + .dmc-bar .dd-trig .dd-txt { display: none; } + .dmc-apps .dd-menu { + position: fixed; + left: 8px; + right: 8px; + top: calc(var(--bar-h) + 6px); + width: auto; + background: var(--menu-bg); + backdrop-filter: none; + -webkit-backdrop-filter: none; + } + .dmc-apps-grid { grid-template-columns: repeat(2, 1fr); } + } + /* Every DMC surface opens with this band (STYLE-GUIDE §9): full width, in normal flow, above the navbar. It was a 4px vertical bar beside the topbar, which is the same idea in the wrong orientation. */ @@ -313,82 +505,6 @@ .topbar-nav a:hover { color: var(--text); } - .topbar-dropdown { - position: relative; - } - .topbar-dropdown summary { - cursor: pointer; - color: var(--text); - list-style: none; - } - .topbar-dropdown summary::-webkit-details-marker { - display: none; - } - .topbar-dropdown summary::after { - content: " \25BE"; - } - .topbar-dropdown-menu { - position: absolute; - top: calc(100% + 8px); - left: 0; - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px 16px; - padding: 12px 14px; - min-width: 260px; - border-radius: var(--radius); - background: var(--menu-bg); - border: 1px solid var(--border); - box-shadow: var(--surface-shadow); - z-index: 20; - } - .topbar-dropdown-menu a { - font-size: 0.82rem; - } - /* Theme picker: chip trigger, checkable menu (STYLE-GUIDE §9). */ - .theme-dd > summary { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 28px; - border-radius: 9px; - border: 1px solid var(--border); - background: var(--surface-hover); - font-size: 0.8rem; - line-height: 1; - } - .theme-dd > summary::after { content: none; } - .theme-dd > summary:hover { border-color: var(--accent); } - .theme-menu { - left: auto; - right: 0; - min-width: 150px; - grid-template-columns: 1fr; - gap: 2px; - padding: 6px; - } - .theme-menu button { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - width: 100%; - margin: 0; - padding: 7px 9px; - border: 0; - border-radius: var(--radius); - background: none; - color: var(--text-muted); - font: inherit; - font-size: 0.82rem; - cursor: pointer; - text-align: left; - } - .theme-menu button:hover { background: var(--surface-hover); color: var(--text); } - .theme-menu .chk { color: var(--accent); visibility: hidden; } - .theme-menu button[aria-checked="true"] { color: var(--text); } - .theme-menu button[aria-checked="true"] .chk { visibility: visible; } .topbar-meta { margin-left: auto; display: flex; @@ -445,8 +561,11 @@ display: grid; grid-template-columns: 300px 1fr; gap: 18px; - /* Don't stretch the map row to match the (tall) left column. */ - align-items: start; + /* The map column DOES stretch to the left column's height, which is what + gives the sticky map room to stay put for the whole scroll. The map + keeps its own viewport-sized height, so stretching the column does not + stretch the map (#142). */ + align-items: stretch; } .map-wrap { @@ -1433,10 +1552,12 @@ .map-wrap { padding: 10px; - /* Fixed, viewport-sized map that stays put while the left column scrolls. */ + /* Viewport-sized map that stays put while the left column scrolls, resting + just below the two sticky bars above it. --nav-h is measured, because + the navbar wraps to two rows at some widths (#142). */ position: sticky; - top: 84px; - height: calc(100vh - 108px); + top: calc(var(--bar-h) + var(--nav-h) + 10px); + height: calc(100vh - var(--bar-h) - var(--nav-h) - 34px); } #map3d { @@ -1906,36 +2027,72 @@ + +
+
+ + + Dutch MeshCore + + + +
+ + +
+
+ + +
+
+
DMC MeshCore Triangulator
-
- 🌙 - -
- -
-
- - - Dutch MeshCore - - - -
- - -
-
- - -
-
-
+
-
- DMC - MeshCore Triangulator -
+ + Dutch MeshCore + MeshCore Triangulator +
- + + +
+ + +
+
+ + +
+ +
- GitHub ↗
@@ -6792,7 +6753,7 @@

Unknown Node Estimate

// One behaviour for all three: a trigger toggles, opening one closes the // others, and an outside click or Escape closes whatever is open. function closeBandDropdowns(except) { - document.querySelectorAll(".dmc-bar .dd.open").forEach((dd) => { + document.querySelectorAll(".topbar .dd.open").forEach((dd) => { if (dd === except) return; dd.classList.remove("open"); const trigger = dd.querySelector(".dd-trig"); @@ -6800,7 +6761,7 @@

Unknown Node Estimate

}); } - document.querySelectorAll(".dmc-bar .dd").forEach((dd) => { + document.querySelectorAll(".topbar .dd").forEach((dd) => { const trigger = dd.querySelector(".dd-trig"); if (!trigger) return; trigger.addEventListener("click", (event) => { @@ -6813,7 +6774,7 @@

Unknown Node Estimate

}); document.addEventListener("click", (event) => { - if (event.target instanceof Element && event.target.closest(".dmc-bar .dd")) return; + if (event.target instanceof Element && event.target.closest(".topbar .dd")) return; closeBandDropdowns(); }); document.addEventListener("keydown", (event) => { @@ -6828,7 +6789,13 @@

Unknown Node Estimate

const light = choice === "light" || (choice === "system" && systemLight && systemLight.matches); if (light) document.documentElement.setAttribute("data-theme", "light"); else document.documentElement.removeAttribute("data-theme"); - if (themeIcon) themeIcon.textContent = light ? "☀️" : "🌙"; + // Sun and moon as paths, so they take currentColor and the hover state + // like every other control in the bar (#143). + if (themeIcon) { + themeIcon.innerHTML = light + ? '' + : ''; + } document.querySelectorAll("[data-theme-choice]").forEach((button) => { button.setAttribute("aria-checked", button.dataset.themeChoice === choice ? "true" : "false"); }); From 98fde5e2ada054468562ad4888352281520c7261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20H=C3=A4gele?= Date: Thu, 10 Sep 2026 16:28:15 +0200 Subject: [PATCH 3/6] fix(web-standalone): give the sticky bar its own backdrop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bar was painted with --surface-2, which is rgba(8,10,13,.80) — fine for a panel that sits in the flow, wrong for a sticky bar that content scrolls under. Buttons and text showed straight through it. It takes --navbar-bg with a 16px backdrop blur and the navbar shadow, which is what STYLE-GUIDE §9 specifies for exactly this and what I should have used when the bar became sticky. Co-Authored-By: Claude Opus 5 --- web-standalone/changelog.json | 6 ++++++ web-standalone/index.html | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/web-standalone/changelog.json b/web-standalone/changelog.json index 148dba6..17dd4ab 100644 --- a/web-standalone/changelog.json +++ b/web-standalone/changelog.json @@ -1,4 +1,10 @@ [ + { + "id": "2026-09-10-opaque-bar", + "date": "2026-09-10", + "title": "The top bar stops being see-through", + "body": "Content scrolling underneath the top bar showed straight through it. The bar has its own backdrop now." + }, { "id": "2026-09-10-product-bar", "date": "2026-09-10", diff --git a/web-standalone/index.html b/web-standalone/index.html index 9926585..3fb72bb 100644 --- a/web-standalone/index.html +++ b/web-standalone/index.html @@ -117,6 +117,10 @@ --bar-h: 34px; --bar-bg: rgba(3, 4, 6, 0.74); --glass-bg: rgba(18, 21, 28, 0.88); + /* The bar is sticky: content scrolls UNDER it, so it needs a fill of its + own plus a blur, not a translucent surface token (STYLE-GUIDE §9). */ + --navbar-bg: rgba(3, 4, 6, 0.88); + --navbar-shadow: 0 14px 35px rgba(0, 0, 0, 0.42); /* Measured from the rendered navbar; the fallback is its one-row height. */ --nav-h: 53px; @@ -182,6 +186,8 @@ --raised: rgba(20, 30, 60, 0.06); --bar-bg: rgba(255, 255, 255, 0.82); --glass-bg: rgba(255, 255, 255, 0.94); + --navbar-bg: rgba(255, 255, 255, 0.90); + --navbar-shadow: 0 10px 26px rgba(20, 30, 60, 0.10); } * { @@ -272,7 +278,10 @@ margin: 16px auto 0; padding: 10px 16px; border-radius: var(--radius); - background: var(--surface-2); + background: var(--navbar-bg); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + box-shadow: var(--navbar-shadow); border: 1px solid var(--border); } /* ── Shared product bar (STYLE-GUIDE §9, #142) ────────────────────── From 05dab01c8a6cafa00e344fe75e936c3ccb1471b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20H=C3=A4gele?= Date: Thu, 10 Sep 2026 17:21:40 +0200 Subject: [PATCH 4/6] feat(web-standalone): one button system, flat surfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured on one screen with a cluster locked and an estimate present: 28 visible buttons drawn in 14 looks at 9 heights, 37 bordered elements in 8 border styles, 4 filled accent buttons at once. Almost no two buttons on the page were the same object, so nothing receded and the screen read as a pile. Three roles now, and nothing else: primary (filled accent, one per panel, the action that completes the step), secondary (outline, no fill), quiet (soft fill, no border — map controls, chips, the bar's icons). The rule doing most of the work is that a border is for a container, not for a control; quiet controls take a fill and get their border back on hover and focus. Observer rows lose theirs too, since fourteen rows each drawing an edge was most of why that panel looked like a pile. Flat instead of glass. The guide's signature — a sheen gradient on every panel over a red/blue-lit ground, a 42px grid and scanlines over that, and a hard 0 3px 0 #000 on headings — is four decorative layers behind the thing you came to read, and the heading shadow reads as a game title. Surfaces go flat, the overlays to zero, and the display face is reserved for the brand: panel titles become sentence-case body type. The brand stays in the colours, the tricolour band and the type. buttons 28 -> 19 distinct looks 14 -> 7 heights 9 -> 2 (38px, 28px) bordered elements 37 -> 23 filled accent 4 -> 3 (one per panel, as intended) A deliberate divergence from STYLE-GUIDE §3 and §6, in the same spirit as #143. Closes #144 Co-Authored-By: Claude Opus 5 --- web-standalone/changelog.json | 6 ++ web-standalone/index.html | 149 +++++++++++++++++++++++++--------- 2 files changed, 115 insertions(+), 40 deletions(-) diff --git a/web-standalone/changelog.json b/web-standalone/changelog.json index 17dd4ab..af07ed4 100644 --- a/web-standalone/changelog.json +++ b/web-standalone/changelog.json @@ -1,4 +1,10 @@ [ + { + "id": "2026-09-10-calmer-interface", + "date": "2026-09-10", + "title": "A calmer, flatter interface", + "body": "Buttons came in fourteen different looks at nine heights; there are three kinds now, and one blue button per panel. Panels lost their sheen, grid and glow, so what is on them stands out instead." + }, { "id": "2026-09-10-opaque-bar", "date": "2026-09-10", diff --git a/web-standalone/index.html b/web-standalone/index.html index 3fb72bb..1ee56c7 100644 --- a/web-standalone/index.html +++ b/web-standalone/index.html @@ -91,19 +91,18 @@ --danger: var(--red); --danger-text: #ff8080; - /* Glass over glow. */ - --surface: - linear-gradient(135deg, rgba(255, 255, 255, 0.07), transparent 34%), - linear-gradient(180deg, rgba(22, 24, 29, 0.94), rgba(6, 7, 10, 0.95)); - --surface-2: rgba(8, 10, 13, 0.80); - --surface-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.035), 0 18px 40px rgba(0, 0, 0, 0.35); - --body-bg: - radial-gradient(circle at 18% 18%, rgba(223, 32, 32, 0.15), transparent 28%), - radial-gradient(circle at 82% 28%, rgba(25, 118, 232, 0.14), transparent 30%), - linear-gradient(180deg, #090b10 0%, #06070a 48%, #030304 100%); - --sheen-opacity: 0.15; - --grid-opacity: 0.28; - --scan-opacity: 0.11; + /* Flat surfaces (#144). The guide's signature is "glass over glow": a + sheen gradient on every panel over a red/blue-lit ground, with a 42px + grid and scanlines on top. That look belongs on a landing page; in a + dense tool it is four decorative layers behind the thing you came to + read. The brand stays in the colours, the tricolour and the type. */ + --surface: #12151b; + --surface-2: #0e1117; + --surface-shadow: none; + --body-bg: none; + --sheen-opacity: 0; + --grid-opacity: 0; + --scan-opacity: 0; /* App-level surfaces the guide does not name. Defined in both roots so no rule below has to know which theme is running. Values sit on the @@ -128,7 +127,9 @@ --font-sans: 'IBM Plex Sans', 'Segoe UI', system-ui, -apple-system, Roboto, sans-serif; --font-head: 'Aldrich', 'Segoe UI', system-ui, sans-serif; --font-mono: 'IBM Plex Mono', ui-monospace, Consolas, 'Fira Mono', monospace; - --heading-shadow: 0 3px 0 #000; + /* A hard 3px drop shadow reads as a game title, not as a section + heading in a tool (#144). */ + --heading-shadow: none; --fs-sm: 12px; --fs-md: 14px; --fs-lg: 16px; @@ -169,9 +170,8 @@ --danger-text: #c43a3a; --surface: #ffffff; - --surface-2: #eef0f8; - --surface-shadow: 0 10px 26px rgba(20, 30, 60, 0.08); - /* Flat ground, and the two overlays switched off by their tokens. */ + --surface-2: #f4f6fb; + --surface-shadow: none; --body-bg: none; --sheen-opacity: 0; --grid-opacity: 0; @@ -248,14 +248,27 @@ and What's new panels. */ /* Headings and the brand run in the display face, uppercased, with a hard drop shadow that light mode drops (STYLE-GUIDE §3). */ - h1, h2, h3, - .step-kicker, + /* The display face carries the brand, not every section heading (#144). + Uppercase Aldrich on each panel title made three shouted words per card; + panel titles are sentence-case body type, which is what a tool's chrome + looks like. */ .topbar-title { font-family: var(--font-head); text-transform: uppercase; - letter-spacing: 0.03em; + letter-spacing: 0.06em; color: var(--heading); - text-shadow: var(--heading-shadow); + } + h1, h2, h3 { + font-family: var(--font-sans); + font-weight: 600; + letter-spacing: -0.01em; + color: var(--heading); + } + /* The step number is a label, so it keeps the small-caps treatment. */ + .step-kicker { + font-family: var(--font-mono); + text-transform: uppercase; + letter-spacing: 0.12em; } code, kbd, samp, pre { font-family: var(--font-mono); @@ -664,7 +677,6 @@ display: none; } .controls-card .layer-chip { - padding: 6px 10px; font-size: 0.78rem; } /* The help and What's new buttons stay together at the right, and wrap @@ -681,8 +693,7 @@ display: inline-flex; align-items: center; justify-content: center; - width: 32px; - height: 32px; + width: 28px; padding: 0; line-height: 1; } @@ -803,9 +814,11 @@ } .step-panel .entry-tabs .entry-tab[aria-pressed="true"] { background: color-mix(in srgb, var(--accent) 18%, transparent); - color: var(--text); + color: var(--accent-light); font-weight: 600; - box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 35%, transparent); + /* The tint alone says which one is on; the inset ring was one more edge + in a screen that had 37 of them (#144). */ + box-shadow: none; } .step-panel .entry-tabs .entry-tab:hover { color: var(--text); @@ -1150,19 +1163,32 @@ /* Primary actions (the workflow buttons) carry the accent. */ .source-panel button, + /* ── Three button roles, and nothing else (#144) ──────────────────── + The app had 28 visible buttons drawn in 14 looks at 9 heights, with 37 + bordered elements. Panels, buttons and chips all shouted at the same + volume, so nothing receded and the screen read as a pile. + + primary filled accent, ONE per panel, completes the step + secondary outline, everything else you may do here + quiet soft fill, NO border — map controls, chips, icons + + The rule doing most of the work: a border is for a container, not for a + control. Quiet controls carry a fill instead and get their border back + on hover and focus, so nothing looks dead. */ .step-panel button { margin-top: 12px; + min-height: 38px; border: 0; border-radius: var(--radius); - padding: 10px 14px; + padding: 0 16px; font: inherit; font-weight: 600; - color: var(--bg); + color: #ffffff; background: var(--accent); cursor: pointer; } .source-panel button:hover, - .step-panel button:hover { filter: brightness(1.06); } + .step-panel button:hover { filter: brightness(1.08); } /* Utility actions (reset / view switches) are quiet ghost buttons. */ .utility-panel button, @@ -1194,21 +1220,59 @@ margin-top: 12px; } + /* Secondary: outline, no fill. */ .button-secondary, .step-panel button.button-secondary { - background: var(--chip-bg); + background: transparent; color: var(--text); - border: 1px solid var(--raised); + border: 1px solid var(--border); + font-weight: 500; } + .button-secondary:hover, .step-panel button.button-secondary:hover { - background: var(--raised); + background: var(--surface-hover); + border-color: var(--border-strong); + filter: none; } + /* Quiet: a fill and no border at all. */ .button-muted, - .step-panel button.button-muted { - background: var(--surface-hover); + .step-panel button.button-muted, + .controls-card .layer-chip, + .topbar .dd-trig, + .topbar-meta .is-icon { + min-height: 28px; + height: 28px; + padding: 0 9px; + border: 1px solid transparent; + border-radius: var(--radius); + background: var(--chip-bg); color: var(--text-muted); - border: 1px solid var(--border); + font-weight: 500; + cursor: pointer; + } + .button-muted:hover, + .step-panel button.button-muted:hover, + .controls-card .layer-chip:hover, + .topbar .dd-trig:hover, + .topbar-meta .is-icon:hover { + background: var(--raised); + border-color: var(--border); + color: var(--text); + filter: none; + } + /* A toggle that is on reads as active through a tint of the accent, which + does not compete with a real primary action. */ + .controls-card .layer-chip[aria-pressed="true"], + .button-muted[aria-pressed="true"] { + background: color-mix(in srgb, var(--accent) 18%, transparent); + color: var(--accent-light); + border-color: transparent; + } + .controls-card .layer-chip[aria-pressed="true"]:hover, + .button-muted[aria-pressed="true"]:hover { + border-color: color-mix(in srgb, var(--accent) 45%, transparent); + color: var(--accent-light); } /* Focus visibility + disabled states (accessibility polish). */ @@ -1388,8 +1452,13 @@ align-items: center; padding: 5px 8px; border-radius: var(--radius); + /* A row in a list is not a separate object, and fourteen of them each + drawing an edge is most of why the panel read as a pile (#144). */ background: var(--surface-hover); - border: 1px solid rgba(255, 255, 255, 0.07); + border: 1px solid transparent; + } + .observer-row:hover { + border-color: var(--border); } .cluster-status .cluster-headline { @@ -2106,7 +2175,7 @@

Clues + Regions

- +

@@ -2156,7 +2225,7 @@

Choose Cluster

- + @@ -2188,7 +2257,7 @@

Unknown Node Estimate

above the map because the whole column does. -->
- +
- +
From 805c342cb4b09e7af3aae9ebe9cea2dde29cd712 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20H=C3=A4gele?= Date: Fri, 11 Sep 2026 07:16:53 +0200 Subject: [PATCH 5/6] docs: bring this branch's changelog entries under the length cap Two of the entries added on this branch ran 194 and 155 characters, over the ~120-character cap AGENTS.md sets for the What's new panel. Both trimmed to one line. Co-Authored-By: Claude Opus 5 --- web-standalone/changelog.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web-standalone/changelog.json b/web-standalone/changelog.json index af07ed4..ab7f2fe 100644 --- a/web-standalone/changelog.json +++ b/web-standalone/changelog.json @@ -3,7 +3,7 @@ "id": "2026-09-10-calmer-interface", "date": "2026-09-10", "title": "A calmer, flatter interface", - "body": "Buttons came in fourteen different looks at nine heights; there are three kinds now, and one blue button per panel. Panels lost their sheen, grid and glow, so what is on them stands out instead." + "body": "Buttons came in fourteen looks; now there are three, with one blue button per panel and flat panels." }, { "id": "2026-09-10-opaque-bar", @@ -15,7 +15,7 @@ "id": "2026-09-10-product-bar", "date": "2026-09-10", "title": "Every DutchMeshCore app, one click away", - "body": "The top bar now carries a switcher to every other DutchMeshCore app, plus the language and theme pickers, all in one row rather than a second bar above it." + "body": "The top bar now has a switcher to every DutchMeshCore app, plus language and theme, in one row." }, { "id": "2026-09-10-plain-scroll-zooms", From 970eb77997b2f75493bf784640c187cd5e935317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20H=C3=A4gele?= Date: Fri, 11 Sep 2026 07:57:09 +0200 Subject: [PATCH 6/6] fix(web-standalone): fix the regressions the branch review found A static review of this branch confirmed 19 defects (each checked by three independent skeptics against the code); all are fixed here and re-measured in the running page. - The phone's What's new sheet was an empty 26px strip. backdrop-filter on .topbar made the bar the containing block for its position:fixed descendants, and the narrow sheet rule (0,1,0) lost top/left/right to the desktop .topbar-meta rule (0,2,0). The blur moves to .topbar::before, the sheet rule gains the ancestor, and the apps menu becomes a bottom sheet. 375px: 26px -> 487px tall; apps sheet 325px -> 359px wide. - .controls-card button (0,1,1) and its [aria-pressed] variant (0,2,1) outranked the quiet role, so the map strip kept the old bordered look. - Bar dropdowns: the trigger's stopPropagation kept What's new and the relief popover open; the English option had no handler; closing dropped keyboard focus to ; the app switcher claimed role="menu" with plain links. - Contrast: white on the brand blue was 4.38:1 (dark primary), light hover brightened to 4.09:1, and --accent-light text on tints was 2.48-3.08:1 in light mode. New per-theme --accent-fill and --accent-ink tokens; hover darkens. All measured >= 4.5:1 in both themes. - Smaller: scroll-margin-top under the sticky bar, #117 touch targets retargeted to where the icons moved, two dead <=768px gap overrides, the intro's unstyled Got it button, and the manual-banner pill buttons. Closes #145 Co-Authored-By: Claude Opus 5 --- web-standalone/changelog.json | 6 ++ web-standalone/index.html | 166 +++++++++++++++++++++++++--------- 2 files changed, 130 insertions(+), 42 deletions(-) diff --git a/web-standalone/changelog.json b/web-standalone/changelog.json index ab7f2fe..a21c658 100644 --- a/web-standalone/changelog.json +++ b/web-standalone/changelog.json @@ -1,4 +1,10 @@ [ + { + "id": "2026-09-11-review-fixes", + "date": "2026-09-11", + "title": "What's new works on a phone", + "body": "On a phone What's new now opens as a sheet instead of off-screen, and light-mode toggles pass contrast." + }, { "id": "2026-09-10-calmer-interface", "date": "2026-09-10", diff --git a/web-standalone/index.html b/web-standalone/index.html index 1ee56c7..7d66473 100644 --- a/web-standalone/index.html +++ b/web-standalone/index.html @@ -80,6 +80,12 @@ --accent: #1976e8; --accent-light: #74adff; --accent-strong: #93bfff; + /* Text on an accent tint (on-toggles, the selected tab, app monograms) + and the fill behind a primary button's white text, each picked per + theme for AA contrast (#145). --accent stays the brand blue for links + and tints. */ + --accent-ink: #74adff; + --accent-fill: #1671e0; --border: rgba(255, 255, 255, 0.14); --border-strong: rgba(255, 255, 255, 0.22); --surface-hover: rgba(255, 255, 255, 0.055); @@ -159,6 +165,8 @@ --accent: #3d6fdb; --accent-light: #5b86e5; --accent-strong: #3455b0; + --accent-ink: #3455b0; + --accent-fill: #3d6fdb; --border: #cdd2e8; --border-strong: #b5bce0; --surface-hover: #eef0f8; @@ -291,11 +299,24 @@ margin: 16px auto 0; padding: 10px 16px; border-radius: var(--radius); + box-shadow: var(--navbar-shadow); + border: 1px solid var(--border); + } + /* No background or backdrop-filter on the bar element itself (#145). A + backdrop-filter makes an element the containing block for its + position:fixed descendants, which pinned the phone's What's new sheet and + the apps sheet to the bar instead of the viewport: the sheet collapsed to + a 26px strip. The blurred fill lives on ::before, a separate box, so the + bar is no longer that containing block. */ + .topbar::before { + content: ""; + position: absolute; + inset: 0; + z-index: -1; + border-radius: inherit; background: var(--navbar-bg); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); - box-shadow: var(--navbar-shadow); - border: 1px solid var(--border); } /* ── Shared product bar (STYLE-GUIDE §9, #142) ────────────────────── One thin band above the app navbar, the same on every DMC subdomain, @@ -383,7 +404,7 @@ font-family: var(--font-head); font-size: 0.74rem; letter-spacing: 0.04em; - color: var(--accent-light); + color: var(--accent-ink); } .dmc-app-ico img { width: 100%; height: 100%; object-fit: cover; display: block; } .dmc-app-name { font-size: 0.76rem; font-weight: 600; line-height: 1.1; } @@ -410,13 +431,17 @@ 375px those three carets are the difference between the brand and the controls sharing a row and the bar growing a third one. */ .topbar .dd-caret { display: none; } - .topbar { gap: 8px; } - .topbar-meta { gap: 5px; } + /* A bottom sheet on a phone, the same pattern as What's new (#145). It + hung from calc(var(--nav-h) + 6px), which overlapped the bar by the + page's top margin whenever the page was not scrolled. */ .dmc-apps .dd-menu { position: fixed; + top: auto; left: 8px; right: 8px; - top: calc(var(--nav-h) + 6px); + bottom: 12px; + max-height: 70vh; + overflow-y: auto; width: auto; background: var(--menu-bg); backdrop-filter: none; @@ -542,6 +567,14 @@ right: 0; } + /* revealOnNarrow() scrolls these to the top of the viewport; with the bar + sticky that put their first line underneath it (#145). */ + .step-panel, + .map-col, + #terrain-status { + scroll-margin-top: calc(var(--nav-h) + 12px); + } + .shell { width: min(1200px, calc(100% - 32px)); margin: 16px auto 24px; @@ -580,7 +613,10 @@ gap: 8px; padding: 8px 10px; border-radius: var(--radius); - background: var(--chip-bg); + /* --surface, not --chip-bg: the quiet controls inside are filled with + --chip-bg, which on a --chip-bg card measured 1.00:1 in light mode + and left them as bare text (#145). */ + background: var(--surface); border: 1px solid var(--border); } .layer-chips-label { @@ -614,21 +650,16 @@ border: 1px solid var(--border-strong); box-shadow: var(--surface-shadow); } + /* Only the reset the three-role system does not cover. Padding, weight + and a pressed look used to live here as `.controls-card button` (0,1,1) + and `...[aria-pressed="true"]` (0,2,1), which outranked the quiet role + (0,1,0 / 0,2,0): Clear Map, Share and Resolve on map kept the old + bordered look (#145). Every button in the strip carries a role class + now, so the role rules decide. */ .controls-card button { margin: 0; - border-radius: var(--radius); - padding: 7px 12px; - font: inherit; - font-weight: 600; + font-family: inherit; font-size: 0.85rem; - cursor: pointer; - } - /* 3D View is a toggle, not a one-way "open" (#29): the same control - enters and exits the mode, so it needs a visible pressed state. */ - .controls-card button[aria-pressed="true"] { - background: color-mix(in srgb, var(--accent) 16%, transparent); - border-color: color-mix(in srgb, var(--accent) 55%, transparent); - color: var(--accent); } /* Share (#34): confirmation text and the manual-copy fallback both take a full row under the buttons, so appearing/disappearing doesn't @@ -814,7 +845,7 @@ } .step-panel .entry-tabs .entry-tab[aria-pressed="true"] { background: color-mix(in srgb, var(--accent) 18%, transparent); - color: var(--accent-light); + color: var(--accent-ink); font-weight: 600; /* The tint alone says which one is on; the inset ring was one more edge in a screen that had 37 of them (#144). */ @@ -1184,11 +1215,16 @@ font: inherit; font-weight: 600; color: #ffffff; - background: var(--accent); + /* White on the brand blue #1976e8 is 4.38:1, under AA for 16px text; + the fill is 4% darker (#145). */ + background: var(--accent-fill); cursor: pointer; } + /* Hover darkens (#145). Brightening dropped white text on the light fill + from 4.67:1 to 4.09:1; darkening raises it instead. */ .source-panel button:hover, - .step-panel button:hover { filter: brightness(1.08); } + .step-panel button:hover { filter: brightness(0.92); } + .step-panel .step-head:hover { filter: none; } /* Utility actions (reset / view switches) are quiet ghost buttons. */ .utility-panel button, @@ -1266,13 +1302,13 @@ .controls-card .layer-chip[aria-pressed="true"], .button-muted[aria-pressed="true"] { background: color-mix(in srgb, var(--accent) 18%, transparent); - color: var(--accent-light); + color: var(--accent-ink); border-color: transparent; } .controls-card .layer-chip[aria-pressed="true"]:hover, .button-muted[aria-pressed="true"]:hover { border-color: color-mix(in srgb, var(--accent) 45%, transparent); - color: var(--accent-light); + color: var(--accent-ink); } /* Focus visibility + disabled states (accessibility polish). */ @@ -1326,19 +1362,26 @@ .manual-banner[hidden] { display: none; } + /* Quiet role (#145): these reset map edits, they do not complete a step. + They were a 999px green-outlined pill at 23px, a fourth look. */ .manual-banner button { margin: 0; - padding: 3px 9px; - border: 1px solid color-mix(in srgb, var(--success) 60%, transparent); - border-radius: 999px; - background: transparent; + height: 28px; + padding: 0 9px; + border: 1px solid transparent; + border-radius: var(--radius); + background: var(--chip-bg); color: var(--text); font: inherit; - font-size: 0.74rem; - font-weight: 600; + font-size: 0.78rem; + font-weight: 500; cursor: pointer; white-space: nowrap; } + .manual-banner button:hover { + background: var(--raised); + border-color: var(--border); + } .manual-banner button[hidden] { display: none; } @@ -1855,16 +1898,23 @@ border-width: 2px; flex: none; } + /* The intro's one action is a primary (#145). With no background, border + or colour of its own, the browser's grey outset button showed through + on the first thing a new visitor clicks. */ .intro-foot button { flex: none; margin: 0; + min-height: 38px; + border: 0; border-radius: var(--radius); - padding: 7px 15px; + padding: 0 16px; font: inherit; font-weight: 600; - font-size: 0.8rem; + color: #ffffff; + background: var(--accent-fill); cursor: pointer; } + .intro-foot button:hover { filter: brightness(0.92); } .loading-card { width: min(260px, 80%); @@ -1971,8 +2021,11 @@ } /* On a narrow screen the What's new panel is a sheet at the bottom of the viewport instead of a dropdown under the bell, whose position - depends on how the button row wrapped (#112). */ - .whats-new-panel { + depends on how the button row wrapped (#112). Written with the + .topbar-meta ancestor so it outranks the desktop rule of the same + selector above; as a bare .whats-new-panel (0,1,0) it lost top, left + and right to it (#145). */ + .topbar-meta .whats-new-panel { position: fixed; top: auto; right: 12px; @@ -1982,13 +2035,25 @@ max-height: 60vh; } } + /* These two only win placed after the (max-width: 860px) block and the + unconditional .topbar-meta rule. Inside the earlier 768px block both were + silently overridden at equal specificity (#145). */ + @media (max-width: 768px) { + .topbar { gap: 8px; } + .topbar-meta { gap: 5px; } + } /* Touch targets (#117): 40 px for the icon buttons and the map's own controls, a thumb you can hit for the relief slider. Coarse pointer only, so the desktop layout keeps its density. */ @media (pointer: coarse) { - .controls-card button.is-icon { - width: 40px; + /* The icon buttons moved into the bar (#142), so the old + `.controls-card button.is-icon` matched nothing and the #117 touch + targets silently stopped applying (#145). */ + .topbar-meta .is-icon, + .topbar .dd-trig { + min-width: 40px; height: 40px; + min-height: 40px; } /* MapLibre's own sheet loads after this one, so the selector needs the extra ancestor to win. */ @@ -2087,10 +2152,12 @@
- -