🤞 Sync 2026-08-12 09:27:55

This commit is contained in:
github-actions[bot]
2026-08-12 09:27:55 +08:00
parent 030d9d82a4
commit d5f9df4a19
5 changed files with 73 additions and 24 deletions
+1 -1
View File
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
FOOTSTRAP_VERSION?=
ifneq ($(FOOTSTRAP_VERSION),)
PKG_VERSION:=$(FOOTSTRAP_VERSION)
PKG_RELEASE:=12
PKG_RELEASE:=13
endif
LUCI_TITLE:=Footstrap Theme
@@ -412,9 +412,13 @@ function navigate(pathname, push, kbd) {
* decided by how the user got there.
*
* So decline, exactly as the poisoned-document bail above does: speed is traded for correctness,
* never the other way. It costs ONE full load per such page — after it the <link> is in the
* document, this test passes, and every later visit is a swap again (fs-sheets.js owns the sheet
* from then on and re-lights it per page).
* never the other way. The cost is one full load per ENTRY INTO A DOCUMENT that lacks the sheet,
* not one per page ever: head.ut emits the link for the DISPATCHED node only, so each full load
* starts a document carrying exactly one such sheet and discards what the previous one had
* gathered. Within that document the page is a swap from then on (fs-sheets.js owns the sheet and
* re-lights it per page) — but two `css`-bearing pages alternating are a full load every time, in
* both directions. That is the trade taken knowingly: an unstyled page is worse than a reload, and
* no in-tree node sets `css` today.
*
* Injecting the <link> here instead would work and is deliberately not done: it would put the theme
* in charge of fetching and ordering a foreign stylesheet, which is the job fs-sheets.js exists to
@@ -325,14 +325,29 @@ function outlivesPage(el) {
* this module's because the answer is: a link inside #view is about to be deleted with the rest of
* the view, and counting it would hand the router a sheet the next dom.content() throws away
* `luci-app-nlbwmon` returns E('link', , L.resource('view/nlbw.css')) from render(), so that shape
* is real. Compared by PATH and by suffix: head.ut prints `{{ resource }}/{{ dispatched.css }}?v=…`,
* so neither the resource base nor the cache key is anyone's to assume. */
* is real.
*
* WHOLE PATH, NOT A SUFFIX. head.ut prints `{{ resource }}/{{ dispatched.css }}?v=…`, and the base in
* that line is the SAME value the runtime holds: header.ut hands `resource` to `new LuCI({…})`, and
* `L.resource()` joins it back exactly, so the server's href is reconstructable rather than guessable
* only the cache key has to come off. A suffix match is what a guess costs: anchored at nothing but
* a `/`, `custom.css` matches any sheet ending in that filename, and two in-tree apps append exactly
* that to <head> at module eval `luci-app-adblock` and `luci-app-banip` both add
* `L.resource('view/<app>/custom.css')`, outside #view, so outlivesPage() keeps them and this module
* disables rather than removes them: they stay for the life of the document. A third-party node
* declaring `"css": "custom.css"` would then read as already-carried the moment the user had passed
* through Adblock Feeds, and the router would swap into a page whose stylesheet was never linked
* the one outcome the guard exists to prevent.
*
* Equality also keeps the failure safe: L.path() drops a part that leaves its charset, so a malformed
* `css` yields the bare base and matches no href at all a full load, which is the correct answer for
* a value nobody can serve. */
function documentCarries(path) {
const want = '/' + String(path).replace(/^\/+/, '');
const want = L.resource(String(path));
for (const link of document.querySelectorAll('link[rel~="stylesheet"][href]')) {
if (!outlivesPage(link))
continue;
if ((link.getAttribute('href') || '').split('?')[0].endsWith(want))
if ((link.getAttribute('href') || '').split('?')[0] === want)
return true;
}
return false;
+13 -5
View File
@@ -469,14 +469,22 @@
--background-color-medium: color-mix(in srgb, var(--fs-panel) 55%, var(--fs-panel2));
--background-color-low: var(--fs-panel2);
/* --text-color-low cannot move: --fs-dim IS the muted text colour and already
* sits ON the AA floor (4.56:1 on --fs-panel2 in dark), so a fainter step would
* be an illegible grey. It stays equal to -medium the ramp is still wide, -high
* being --fs-text and a duplicate beats a value nobody can read. */
/* -low used to be a DUPLICATE of -medium, both --fs-dim, on the grounds that a fainter
* step would be illegible: --fs-dim sits on the AA floor in dark (4.56:1 on --fs-panel2)
* and --fs-faint is the fainter one. That held while the two were a hair apart. Light's
* secondary tier has since moved to 11.8:1 (see 03-palettes.css), so the duplicate stopped
* being a rounding decision and started flattening the export: `spread(high, low)` fell to
* 0.082 in the default palette and 0.094 in hicontrast, under the 0.10 the ramp promises
* an app asking for a gradation would have got one colour twice.
*
* So -low is --fs-faint, the theme's own third ink, and the illegibility that argument
* feared is what the gate below measures rather than assumes: every level is held to AA as
* text on all three surfaces AND as a fill under its --on-* ink, in both modes, across the
* whole palette x tint matrix. --fs-faint clears it everywhere. */
--text-color-highest: var(--fs-text);
--text-color-high: var(--fs-text);
--text-color-medium: var(--fs-dim);
--text-color-low: var(--fs-dim);
--text-color-low: var(--fs-faint);
/* borders carry no text, so here -low MAY fade toward the surface: a hairline
* is supposed to disappear into it. */
+33 -11
View File
@@ -257,9 +257,15 @@
--fs-panel2-base: #f6f8fa;
--fs-border-base: #d0d7de;
--fs-text: #1f2328;
--fs-dim: #656d76;
--fs-faint: #68727e;
--fs-select-chevron: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%23656d76" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
/* The SECONDARY tier carries LuCI's page descriptions and every field title, and at the
* GitHub-light greys it started from (#656d76/#68727e, 5.2:1 and 4.9:1 on the card) that is
* help text a reader has to lean into reported on the upstream PR by someone reading the
* light theme on a real router. LuCI's pages are technical and their descriptions are the
* part you read once and act on, so this tier is now 11.8:1 and 10.0:1: still a tier below
* the 15.8:1 body ink, which is what keeps a label a label, but no longer a whisper. */
--fs-dim: #33383d;
--fs-faint: #3d4348;
--fs-select-chevron: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%2333383d" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
--fs-accent-base: #0969da;
/* a tint OF --fs-accent, so mixed FROM it. It used to restate the accent's RGB as a
* literal, which goes stale silently the moment the accent is recoloured (or the
@@ -315,9 +321,13 @@
--fs-panel2-base: #f6f8fb;
--fs-border-base: #e4e9f1;
--fs-text: #101725;
--fs-dim: #5b6675;
--fs-faint: #69727f;
--fs-select-chevron: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%235b6675" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
/* and this palette's secondary tier moves with it, because the promise in the comment above
* is RELATIVE: 5.8:1 and 4.9:1 were above the default when the default was 5.2:1 and 4.9:1,
* and below it the moment that changed. 13.5:1 and 12.0:1 against the default's 11.8 and
* 10.0. */
--fs-dim: #272f3d;
--fs-faint: #2e3745;
--fs-select-chevron: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%23272f3d" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
--fs-accent-base: #0b6fbd;
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 10%, transparent);
--fs-track: #eaeef4;
@@ -388,10 +398,17 @@
--fs-panel-base: #ffffff;
--fs-panel2-base: #f5f5f5;
--fs-border-base: #cccccc;
--fs-text: #404040;
--fs-dim: #6a6a6a;
--fs-faint: #6e6e6e;
--fs-select-chevron: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%236a6a6a" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
/* THE INK IS THE ONE PLACE THIS COLOURWAY LEAVES BOOTSTRAP'S OWN VALUES BEHIND, and it is
* the same decision the default palette made: bootstrap's #404040 body and #6a6a6a muted
* tier measure 10.4:1 and 5.0:1 on white, and the muted one carries LuCI's page
* descriptions and every field title help text you have to lean into. Matched to the
* default palette's light ramp instead, ratio for ratio: 15.7 / 11.7 / 10.0 against its
* 15.8 / 11.8 / 10.0. Neutral greys, so the colourway still reads as bootstrap's what is
* copied is its hue, not its readability. */
--fs-text: #232323;
--fs-dim: #383838;
--fs-faint: #424242;
--fs-select-chevron: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%23383838" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
/* --primary-color-medium; -high (#1976d2) measured 4.22:1 on --fs-panel2 */
--fs-accent-base: #1564c0;
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 10%, transparent);
@@ -417,8 +434,13 @@
--fs-panel2-base: #303030;
--fs-border-base: #444444;
--fs-text: #bfbfbf;
/* faint == dim here, and that is the deviation this palette allows itself: bootstrap's dark
* has no third ink, and the export tier's -low now follows --fs-faint (02-tokens.css), so
* the #969696 this used to carry landed at 4.46:1 on a tinted --fs-panel2 under AA for a
* name apps print text in. The step above it is bootstrap's own, so the fainter one gives
* way rather than the ramp. */
--fs-dim: #9a9a9a;
--fs-faint: #969696;
--fs-faint: #9a9a9a;
--fs-select-chevron: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="%239a9a9a" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
--fs-accent-base: #53a4c2;
--fs-accent-soft: color-mix(in srgb, var(--fs-accent) 15%, transparent);