mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 15:15:13 +08:00
🍉 Sync 2026-08-30 11:16:17
This commit is contained in:
1 parent
85339c08e1
commit
84eac136c8
127 files changed
+3726
-86808
No files matched your search
@@ -17,7 +17,7 @@ LUCI_NAME:=luci-theme-footstrap
|
||||
FOOTSTRAP_VERSION?=
|
||||
ifneq ($(FOOTSTRAP_VERSION),)
|
||||
PKG_VERSION:=$(FOOTSTRAP_VERSION)
|
||||
PKG_RELEASE:=43
|
||||
PKG_RELEASE:=44
|
||||
endif
|
||||
|
||||
LUCI_TITLE:=Footstrap Theme
|
||||
@@ -30,7 +30,7 @@ LUCI_DEPENDS:=+luci-base
|
||||
LUCI_PKGARCH:=all
|
||||
# Else luci.mk defaults these to LuCI's own, and the package claims MAINTAINER "OpenWrt LuCI
|
||||
# community" / URL openwrt/luci.
|
||||
LUCI_MAINTAINER:=VizzleTF <vizzletf47@gmail.com>
|
||||
LUCI_MAINTAINER:=Ivan Kvashonkin <vizzlef@gmail.com>
|
||||
LUCI_URL:=https://github.com/VizzleTF/luci-theme-footstrap
|
||||
|
||||
# CSS: ship verbatim. luci.mk would run csstidy over it, which is old enough to mangle :has(),
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 KiB |
@@ -9,8 +9,8 @@
|
||||
"theme_color": "#f6f8fa",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/luci-static/footstrap/app-icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"src": "/luci-static/footstrap/app-icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}
|
||||
|
||||
@@ -140,6 +140,44 @@ function guardDarkStamp() {
|
||||
attributeFilter: ['data-darkmode', 'data-theme', 'data-bs-theme']
|
||||
});
|
||||
}
|
||||
/* ---- the browser's own chrome follows the page ----
|
||||
*
|
||||
* `<meta name="theme-color">` is what colours a mobile address bar, the Android task-switcher card
|
||||
* and an installed PWA's title bar. head.ut ships it at the default palette's page colour, which is
|
||||
* all a static template can know; from here it tracks the live one.
|
||||
*
|
||||
* Read from the BODY's computed background, not from `--fs-bg`: a custom property returns its token
|
||||
* stream, so a palette whose page colour is a color-mix() would put `color-mix(in srgb, …)` in the
|
||||
* attribute, and 21 of the axes reach the canvas through one mix or another.
|
||||
*
|
||||
* One observer rather than a call in each applier: the colour is a function of :root's attributes
|
||||
* and inline properties — mode, palette, tint, its strength — and every axis already writes exactly
|
||||
* those. Adding an axis therefore needs nothing here. Coalesced into a frame because the tint and
|
||||
* strength sliders write on every input event, and getComputedStyle forces style resolution. */
|
||||
function paintThemeColor() {
|
||||
const meta = document.querySelector('meta[name="theme-color"]');
|
||||
if (!meta || !document.body) return;
|
||||
const bg = getComputedStyle(document.body).backgroundColor;
|
||||
/* a transparent body means the sheet has not applied yet; keep the server's value */
|
||||
if (bg && bg !== 'rgba(0, 0, 0, 0)' && bg !== 'transparent') meta.setAttribute('content', bg);
|
||||
}
|
||||
|
||||
function watchThemeColor() {
|
||||
let queued = false;
|
||||
const paint = () => {
|
||||
queued = false;
|
||||
paintThemeColor();
|
||||
};
|
||||
const schedule = () => {
|
||||
if (queued) return;
|
||||
queued = true;
|
||||
window.requestAnimationFrame(paint);
|
||||
};
|
||||
paintThemeColor();
|
||||
new MutationObserver(schedule).observe(document.documentElement,
|
||||
{ attributes: true, attributeFilter: [ 'style', 'class', 'data-darkmode', 'data-palette', 'data-wallpaper' ] });
|
||||
}
|
||||
|
||||
/* "Auto" means follow the OS continuously, not only at page load. Only while the effective mode is
|
||||
* auto: an explicit browser choice, or an explicit router default with no browser override. */
|
||||
_mqDark.addEventListener('change', () => {
|
||||
@@ -316,7 +354,7 @@ return baseclass.extend({
|
||||
/* the two axis shapes, so the nineteen axes in fs-axes.js can be built from them */
|
||||
listAxis, enumAxis,
|
||||
|
||||
currentMode, applyMode, modeDefault, guardDarkStamp,
|
||||
currentMode, applyMode, modeDefault, guardDarkStamp, watchThemeColor,
|
||||
currentDensity, applyDensity, densityDefault,
|
||||
currentLayout, applyLayout, isTopLayout,
|
||||
currentAutoCollapse, applyAutoCollapse, autoCollapseDefault,
|
||||
|
||||
@@ -205,6 +205,7 @@ return baseclass.extend({
|
||||
* below swallows a menu failure. */
|
||||
sheets.watchViewSheets();
|
||||
prefs.guardDarkStamp(); /* same, for a third party stamping :root */
|
||||
prefs.watchThemeColor(); /* the mobile address bar, from the live page colour */
|
||||
|
||||
ui.menu.load().then((menu) => {
|
||||
tree.setTree(menu);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
@layer tokens {
|
||||
/* Static twins for every token whose value is a color-mix(), for a browser that has none.
|
||||
*
|
||||
* Last in the tokens layer on purpose: it has to outrank 02-tokens.css AND the per-palette
|
||||
* definitions in 03-palettes.css, and directory order is source order (build-css.sh).
|
||||
*
|
||||
* Why a whole file rather than a fallback declaration in front of each color-mix(): a custom
|
||||
* property keeps whatever token stream it is given, so the failure is deferred to the point of USE
|
||||
* — `background: var(--fs-good-soft)` becomes invalid at computed-value time and computes to
|
||||
* `unset`, which is `initial` for a non-inherited property. IACVT does not fall back to an earlier
|
||||
* declaration in the same block, so the two-declaration trick that works for `background:
|
||||
* color-mix(…)` written literally does nothing for a token. 38 tokens are affected; without this
|
||||
* block a browser below the floor paints borders in currentcolor and loses every soft surface.
|
||||
*
|
||||
* color-mix() lands in Chrome 111, Firefox 113 and Safari 16.2 (all 2022-2023). See docs/css.md
|
||||
* for the theme's browser floor and which feature sets it.
|
||||
*
|
||||
* The rules: a tint over transparent degrades to `transparent` — an absent surface never lowers
|
||||
* contrast, while a solid version of the same colour under its own text does. A hairline degrades
|
||||
* to the solid colour it is a fraction of. A focus ring degrades to the -solo ring, which is
|
||||
* already color-mix-free, because an invisible focus ring is an accessibility regression rather
|
||||
* than a cosmetic one. */
|
||||
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
|
||||
:root {
|
||||
/* tints: absent, not solid */
|
||||
--fs-accent-soft: transparent;
|
||||
--fs-good-soft: transparent;
|
||||
--fs-warn-soft: transparent;
|
||||
--fs-danger-soft: transparent;
|
||||
--fs-accent-fill: transparent;
|
||||
--fs-good-fill: transparent;
|
||||
--fs-warn-fill: transparent;
|
||||
--fs-danger-fill: transparent;
|
||||
|
||||
/* hairlines: the solid colour they are a fraction of */
|
||||
--fs-accent-line: var(--fs-accent);
|
||||
--fs-good-line: var(--fs-good);
|
||||
--fs-warn-line: var(--fs-warn);
|
||||
--fs-danger-line: var(--fs-danger);
|
||||
--fs-accent-line-hi: var(--fs-accent);
|
||||
--fs-good-line-hi: var(--fs-good);
|
||||
--fs-warn-line-hi: var(--fs-warn);
|
||||
--fs-danger-line-hi: var(--fs-danger);
|
||||
|
||||
/* the two rings, restated: -soft and -fill are transparent above, and a ring nobody can
|
||||
* see is worse than a heavy one. -solo is already color-mix-free and measured. */
|
||||
--fs-focus-ring: var(--fs-focus-ring-solo);
|
||||
--fs-focus-ring-invalid: 0 0 0 2px var(--fs-panel), 0 0 0 5px var(--fs-danger);
|
||||
|
||||
/* surfaces that are a near-opaque panel anyway */
|
||||
--fs-glass: var(--fs-panel);
|
||||
--fs-bar-bg: var(--fs-panel);
|
||||
--background-color-medium: var(--fs-panel2);
|
||||
|
||||
/* depth cues: dropped rather than approximated — each is a 1px highlight whose whole
|
||||
* purpose is subtlety, and an opaque one reads as a stray line. */
|
||||
--fs-emboss: none;
|
||||
--fs-text-emboss: none;
|
||||
--fs-shadow-bar: 0 1px 0 var(--fs-border);
|
||||
|
||||
/* the scrim over an uploaded photo: --fs-photo-dim is a percentage this cannot apply, so
|
||||
* take the fixed dialog scrim. Losing it entirely would leave login text over a photo. */
|
||||
--fs-photo-scrim: var(--fs-scrim);
|
||||
|
||||
/* export tier — third-party apps read these, so they must resolve to something.
|
||||
* -medium keeps the hue, -low degrades to the dim text colour it was mixed toward. */
|
||||
--border-color-high: var(--fs-border);
|
||||
--border-color-low: var(--fs-border);
|
||||
--primary-color-medium: var(--fs-accent);
|
||||
--primary-color-low: var(--fs-dim);
|
||||
--error-color-medium: var(--fs-danger);
|
||||
--error-color-low: var(--fs-dim);
|
||||
--success-color-medium: var(--fs-good);
|
||||
--success-color-low: var(--fs-dim);
|
||||
--warn-color-medium: var(--fs-warn);
|
||||
--warn-color-low: var(--fs-dim);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -519,9 +519,14 @@
|
||||
* mid-card (measured). Stretched, both cells take the flex line's height and the
|
||||
* separators meet; content still sits at the top (the cells are blocks). */
|
||||
/* A `<tfoot>` with no `.tr` inside it IS the row — see the footer block below for which view
|
||||
* writes which shape. Here rather than in a rule of its own: `css-dup` compares bodies of three
|
||||
* declarations or more, so a second copy of these two would sit unflagged until they diverged. */
|
||||
.table.fs-stacked .tr,
|
||||
* writes which shape.
|
||||
*
|
||||
* TWO rules, not one list: a selector list is not forgiving, so a browser without `:has()`
|
||||
* (Firefox before 121) throws away the WHOLE list, and `.table.fs-stacked .tr` — every card on
|
||||
* every stacked table — loses `display: flex` with it. Split, the `:has()` half degrades alone.
|
||||
* The duplicated body is under `css-dup`'s three-declaration floor, so it will not be flagged;
|
||||
* the two must change together. */
|
||||
.table.fs-stacked .tr { display: flex; flex-wrap: wrap; }
|
||||
.table.fs-stacked tfoot:not(:has(> .tr)),
|
||||
.table.fs-stacked .tfoot:not(:has(> .tr)) { display: flex; flex-wrap: wrap; }
|
||||
.table.fs-stacked .td {
|
||||
|
||||
@@ -148,10 +148,16 @@
|
||||
localStorage. See the sanitiser above for the unset encodings. #}
|
||||
<script>window.__fsSD={layout:"{{ _sd_lay }}",darkmode:"{{ _sd_dark }}",palette:"{{ _sd_pal }}",wallpaper:"{{ _sd_wall }}",tint:"{{ _sd_tint }}",accent:"{{ _sd_acc }}",good:"{{ _sd_good }}",warn:"{{ _sd_warn }}",danger:"{{ _sd_dang }}",card:"{{ _sd_card }}",control:"{{ _sd_ctrl }}",bar:"{{ _sd_bar }}",line:"{{ _sd_line }}",rounding:{{ _sd_round }},autocollapse:"{{ _sd_ac }}",login_bg:"{{ _sd_lbg }}",tint_strength:{{ _sd_tstr }},photo_dim:{{ _sd_pdim }},density:"{{ _sd_dens }}",pattern:"{{ _sd_pat }}",pattern_size:{{ _sd_psize }},pattern_strength:{{ _sd_pstr }},pattern_ink:"{{ _sd_pink }}"};</script>
|
||||
<script>
|
||||
/* Each block below pre-paints one Appearance axis before the first frame, duplicating a
|
||||
/* Each IIFE below pre-paints one Appearance axis before the first frame, duplicating a
|
||||
live applier in fs-prefs.js. They cannot share code — this runs before the module
|
||||
loader exists — so tools/axes.mjs derives the contract (keys, attributes, properties,
|
||||
ranges, default, order) from the JS and holds this template to it.
|
||||
|
||||
ONE element for all of them, and exactly two in this file: the data blob above carries
|
||||
interpolations and this one carries none. That is the split tools/lib/ut-scripts.mjs
|
||||
keys off — an interpolated body is not JS until the server renders it, so it is exempt
|
||||
from the lint and everything mergeable into it would go unchecked with it. Two is also
|
||||
what a Content-Security-Policy would need nonces for, should uhttpd ever send one.
|
||||
Precedence for every axis: localStorage ?? router default (window.__fsSD) ?? built-in.
|
||||
Dark mode: a saved preference, else the router default, else the OS. */
|
||||
(() => {
|
||||
@@ -189,8 +195,7 @@
|
||||
set(m.matches);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
|
||||
/* the remaining axes, in the precedence stated above. */
|
||||
(() => {
|
||||
/* Guarded per read, not once around the block: a browser that refuses storage throws
|
||||
@@ -320,8 +325,7 @@
|
||||
root.style.setProperty('--fs-photo-dim', pd + '%');
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
|
||||
/* the icon-rail preference, so a reload does not flash the full sidebar first. Not a
|
||||
router-wide default: a transient chrome collapse, not an appearance choice. */
|
||||
(() => {
|
||||
@@ -330,8 +334,7 @@
|
||||
document.querySelector(':root').setAttribute('data-rail', 'true');
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
|
||||
/* the client's saved layout overrides the server's stamp above, before paint. Only a
|
||||
known value is honoured: a corrupt one leaves the stamp standing rather than blanking
|
||||
the attribute every layout rule matches on. */
|
||||
@@ -345,6 +348,17 @@
|
||||
</script>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="darkreader-lock">
|
||||
{# The colour a mobile browser paints its own chrome with — address bar, task switcher, the
|
||||
installed app's title bar. Without it a dark page keeps a white bar above it.
|
||||
|
||||
The literal is the default palette's page colour and the SAME value manifest.json carries,
|
||||
for the same reason: neither file can be rendered per request. It is a starting value
|
||||
only — fs-prefs.js repaints this tag from the live body background, so it follows palette,
|
||||
mode and every tint axis. tests/theme-color.test.mjs holds the two literals equal.
|
||||
|
||||
One tag, no `media` pair: a media-qualified tag would win over this one for a viewer whose
|
||||
OS is dark but who chose light HERE, and the choice is the theme's to honour. #}
|
||||
<meta name="theme-color" content="#f6f8fa">
|
||||
{# The sheet link below is emitted only when the TOKEN says the sheet exists — set the option
|
||||
after the file, never before. A preload is not in the stylesheet, so dropping the
|
||||
@font-face rules alone left the pair behind and every page went on asking the router for
|
||||
@@ -388,9 +402,9 @@
|
||||
that extension, so the same bytes come back `application/octet-stream` there and with a
|
||||
JSON type only as `.json`. The map is compiled into uhttpd. #}
|
||||
<link rel="manifest" href="{{ media }}/manifest.json?v={{ pkgs_update_time }}">
|
||||
{# iOS reads this link and never the manifest's icons array, and it scales the 512 square the
|
||||
manifest also uses. One raster serves both — tools/build-icons.mjs. #}
|
||||
<link rel="apple-touch-icon" href="{{ media }}/app-icon-512.png">
|
||||
{# iOS reads this link and never the manifest's icons array, and it scales the 192 square the
|
||||
manifest also uses down to its own 180. One raster serves both — tools/build-icons.mjs. #}
|
||||
<link rel="apple-touch-icon" href="{{ media }}/app-icon-192.png">
|
||||
{% if (dispatched?.css): %}
|
||||
{# The `css` property of the dispatched menu.d node: an app naming a stylesheet the server
|
||||
should link for its page, instead of the view injecting one at module eval.
|
||||
|
||||
Reference in new issue
Block a user