🎄 Sync 2026-07-26 03:59:09
No files matched your search
@@ -45,7 +45,7 @@ label, fixing a typo, adjusting a translation string).
|
||||
- **S4** — Memory has a ceiling. → references/server.md
|
||||
- **L1** — Kill render-blocking requests. → references/loading.md
|
||||
- **L2** — Download once, cache forever. → references/loading.md
|
||||
- **L3** — Ship compressed. → references/loading.md
|
||||
- **L3** — Make identity-transfer assets compact. → references/loading.md
|
||||
- **R1** — Animate compositor properties only. → references/runtime.md
|
||||
- **R2** — JS must not force synchronous layout. → references/runtime.md
|
||||
- **R3** — Accessibility is functionality. → references/runtime.md
|
||||
|
||||
@@ -11,8 +11,12 @@ address, so they stay local).
|
||||
|
||||
| Metric | Budget | Track | Source |
|
||||
|---|---|---|---|
|
||||
| main.css (gzip-transferred) | ≤ 30 KB | size | measured 2026-07 (28 KB) |
|
||||
| Per-page cold transfer (all theme assets, gzip) | ≤ 60 KB | size | sum of current gzip sizes + headroom |
|
||||
| main.css (identity/raw) | ≤ 190 KB | size | production build, 2026-07 (183,820 B) |
|
||||
| login.css (identity/raw) | ≤ 12 KB | size | production build, 2026-07 (10,935 B, token-pruned) |
|
||||
| menu-aurora.js (identity/raw) | ≤ 20 KB | size | production build, 2026-07 (19,100 B) |
|
||||
| Default logo (identity/raw) | ≤ 16 KB | size | production build, 2026-07 (15,057 B) |
|
||||
| Core admin cold theme assets (identity/raw) | ≤ 250 KB | size | main CSS + menu JS + default font + logo, 2026-07 (241,557 B) |
|
||||
| Login cold theme assets, excluding configured background (identity/raw) | ≤ 55 KB | size | login CSS + default font + logo, 2026-07 (49,572 B) |
|
||||
| Blocking requests before first paint | ≤ 4 | count | current waterfall |
|
||||
| Repeat-visit asset requests | ≈ 0 | count | target state; package-built CSS/JS URLs are versioned, but long-lived cache headers still need live verification |
|
||||
| TTFB, login page (device) | proposed: ≤ 130 ms | latency | local device baseline, 2026-07 |
|
||||
@@ -25,16 +29,22 @@ Budget revisions require a new baseline entry under `../baselines/`.
|
||||
## Optimization ledger
|
||||
|
||||
### Landed
|
||||
(compositor animation rework; mega-menu idle pre-measurement; on-demand
|
||||
patches; `font-display: swap`)
|
||||
|
||||
- Compositor animation rework; mega-menu idle pre-measurement; on-demand
|
||||
patches; `font-display: swap` and inline `@font-face` CSS.
|
||||
- Tailwind `source(none)`, login-only Preflight removal, native scrollbar
|
||||
styling, and local fade animation; unused CSS plugins removed.
|
||||
- Shared SVG custom properties prevent repeated mask payloads.
|
||||
- Terser compression/mangling with LuCI loader directives preserved.
|
||||
- Default logo raster resized inside its compatibility SVG wrapper.
|
||||
- Login template reuses its board/UCI reads when including `header.ut`.
|
||||
- Package-root `.DS_Store` metadata removed and covered by a regression test.
|
||||
- login.css pruned to its reachable custom properties at build time (the
|
||||
shared token sheet is admin-sized; the login page consumes a fraction).
|
||||
|
||||
### Pending
|
||||
| Item | Principle | Estimated gain |
|
||||
|---|---|---|
|
||||
| Precompressed `.gz` assets | S1+L3 | ~260 KB → ~35 KB cold |
|
||||
| Terser `compress`+`mangle` in `vite.config.ts` | L3 | ~20 KB → ~10 KB |
|
||||
| Inline `@font-face` + preload woff2 | L1 | −1 blocking RTT |
|
||||
| SVGO `logo.svg` | L3 | 45 KB → est. < 20 KB |
|
||||
| Long-lived cache headers for versioned CSS/JS | L2 | after LuCI build-time `?v=$(PKG_VERSION)`, kills per-click 304s if headers permit disk/memory cache reuse |
|
||||
| `defer` head scripts | L1 | needs on-device timing verification |
|
||||
|
||||
@@ -50,6 +60,9 @@ patches; `font-display: swap`)
|
||||
`/luci-static/aurora/login.css?v=1.0.7`. Do not re-propose manual
|
||||
cache-versioning for these links unless inspecting the installed package
|
||||
or live HTML proves the rewrite did not happen.
|
||||
- **No gzip on the target uhttpd** — budgets and the bench harness use raw
|
||||
identity bytes. Do not add precompressed sidecars unless the deployed HTTP
|
||||
server is changed and live response headers prove negotiation works.
|
||||
|
||||
### Accepted exceptions
|
||||
|
||||
|
||||
@@ -54,21 +54,23 @@ served from disk/memory cache).
|
||||
|
||||
**Quantify.** Repeat-visit request count.
|
||||
|
||||
## L3 — Ship compressed
|
||||
## L3 — Make identity-transfer assets compact
|
||||
|
||||
**Why.** An uncompressed asset costs both wire time on the slow link (see
|
||||
The Wire) and CPU on the slow server (see server.md S1) — compression is the
|
||||
one change that helps both planes at once.
|
||||
**Why.** This deployment's stock `uhttpd` serves identity responses even when
|
||||
the browser advertises gzip. Raw output size is therefore both flash cost and
|
||||
wire cost; minification and dead-code removal must do the work that transfer
|
||||
compression normally would.
|
||||
|
||||
**Do / Don't.** Precompress at build time so `uhttpd` can serve `foo.gz`
|
||||
verbatim (cross-reference server.md S1). Run shipped SVGs through SVGO
|
||||
(example: aurora's `logo.svg`, which doubles as the favicon, ships at 45 KB —
|
||||
an unoptimized SVG is a common place for dead editor metadata to hide).
|
||||
Choose sane image formats (WebP/AVIF over unoptimized PNG/JPEG) for anything
|
||||
that isn't vector.
|
||||
**Do / Don't.** Disable broad Tailwind content scanning when all utilities are
|
||||
declared with `@apply`; omit Preflight from isolated entries that only need a
|
||||
small reset; remove unused plugins; minify and mangle JS while preserving
|
||||
LuCI's loader directives; define repeated SVG data URLs once as custom
|
||||
properties. Resize or re-encode raster assets at their real display needs and
|
||||
keep generated metadata out of package roots. Do not ship precompressed
|
||||
sidecars when the target server cannot negotiate them.
|
||||
|
||||
**Verify.** `bench.mjs`'s gzip-negotiated rows; `du` on the build output
|
||||
directory to catch anything that grew.
|
||||
**Verify.** `bench.mjs`'s identity rows, automated raw-size budgets, and `du`
|
||||
on the build output directory to catch anything that grew.
|
||||
|
||||
**Quantify.** Per-page transferred bytes (cold load); individual asset
|
||||
sizes.
|
||||
@@ -79,5 +81,5 @@ sizes.
|
||||
|---|---|---|
|
||||
| Blocking requests before first paint | DevTools Network (Slow 4G) | `aurora-budgets.md` |
|
||||
| Repeat-visit request count | DevTools Network | `aurora-budgets.md` |
|
||||
| Transferred bytes (cold, gzip) | `bench.mjs` | `aurora-budgets.md` |
|
||||
| Transferred bytes (cold, identity/raw) | `bench.mjs` | `aurora-budgets.md` |
|
||||
| Individual asset size | `du` / `bench.mjs` | `aurora-budgets.md` |
|
||||
@@ -23,15 +23,19 @@ or returns an HTTP error status mid-run, it aborts with a one-line message
|
||||
naming the target that failed — it does not silently skip it.
|
||||
|
||||
**What it measures.** `BENCH_RUNS` (default 10) samples per target via
|
||||
`curl`, reduced to a median, against 5 fixed targets:
|
||||
`curl`, reduced to a median, against 6 fixed targets. All static assets use
|
||||
identity transfer because the target `uhttpd` cannot serve gzip:
|
||||
|
||||
1. `GET /cgi-bin/luci` (the login page) — the honest unauthenticated target:
|
||||
it's the one page a bench script can hit without a session, and TTFB
|
||||
comparisons must compare like with like.
|
||||
2. `main.css`, identity (no `Accept-Encoding`)
|
||||
3. `main.css`, gzip-negotiated
|
||||
4. `login.css`, gzip-negotiated
|
||||
5. the theme's menu script, gzip-negotiated (example: aurora's `menu-aurora.js`)
|
||||
A 403 is accepted for this target only because some LuCI builds render the
|
||||
login form with that status; other error responses still abort the run.
|
||||
2. `main.css`, identity
|
||||
3. `login.css`, identity
|
||||
4. the theme's menu script, identity (example: aurora's `menu-aurora.js`)
|
||||
5. the default first-paint font, identity
|
||||
6. the default logo, identity
|
||||
|
||||
Per target it reports TTFB, total time, transferred bytes, and HTTP status,
|
||||
summarized as a Markdown table on stdout.
|
||||
|
||||
@@ -11,20 +11,18 @@ same 1–2 cores and 64–512 MB of RAM as the rest of OpenWrt. Nothing here is
|
||||
is already slow. Anything that can be computed once, at build time, should
|
||||
never run again inside a request handler.
|
||||
|
||||
**Do / Don't.** Precompress assets at build time and let `uhttpd` serve the
|
||||
precompressed file verbatim when the client sends `Accept-Encoding: gzip` —
|
||||
this makes the request-time cost *decrease*, since `uhttpd` has no dynamic
|
||||
compression of its own. Generate derived CSS/config/tokens at build time,
|
||||
never per-request (example: aurora's generated `_tokens.css`, built from
|
||||
`.dev/tokens/` and never touched at runtime). When a genuinely per-request
|
||||
computation is unavoidable, record the accepted exception with its rationale
|
||||
in the theme budget sheet (`aurora-budgets.md`) so a future session doesn't re-litigate it
|
||||
(example: aurora's per-request `lsdir()` patch discovery in `header.ut` — one
|
||||
cheap readdir, measured negligible and accepted as-is).
|
||||
**Do / Don't.** Generate and minify derived CSS, JS, config, tokens and image
|
||||
assets at build time, never per-request (example: aurora's generated
|
||||
`_tokens.css`). This deployment uses stock `uhttpd` without gzip support, so
|
||||
raw file size is the wire size: do not add `.gz` files that the server will
|
||||
never select. When a genuinely per-request computation is unavoidable,
|
||||
record the accepted exception with its rationale in the theme budget sheet
|
||||
(`aurora-budgets.md`) so a future session doesn't re-litigate it (example:
|
||||
aurora's per-request `lsdir()` patch discovery in `header.ut` — one cheap
|
||||
readdir, measured negligible and accepted as-is).
|
||||
|
||||
**Verify.** `top` on the device shows no request-time compression process;
|
||||
`curl -H 'Accept-Encoding: gzip' -w '%{size_download}'` against the asset
|
||||
shows the byte drop versus an uncompressed request.
|
||||
**Verify.** A production build is deterministic and already minified; the
|
||||
identity-transfer rows in `bench.mjs` match the files' raw byte sizes.
|
||||
|
||||
**Quantify.** Transferred bytes; package/flash size delta.
|
||||
|
||||
@@ -55,10 +53,10 @@ the branch against the base.
|
||||
slow link, and it costs it on every page view — this is not "just bandwidth,"
|
||||
it's compute on the same constrained cores serving the request.
|
||||
|
||||
**Do / Don't.** Set (and respect) a per-page transferred-bytes budget.
|
||||
Prefer gzip-negotiated transfer over raw bytes wherever the client supports
|
||||
it (example: aurora's `main.css` is 211,725 B raw but ~28 KB gzip — always
|
||||
measure and budget the negotiated size, not the raw one).
|
||||
**Do / Don't.** Set (and respect) a per-page transferred-bytes budget. On this
|
||||
stock `uhttpd` deployment, measure identity responses and optimize the raw
|
||||
files themselves: restrict Tailwind source detection, remove unused framework
|
||||
layers/plugins, minify JS, deduplicate inlined SVGs, and resize raster assets.
|
||||
|
||||
**Verify.** The `bench.mjs` bytes column, compared against the theme's own
|
||||
budget table.
|
||||
@@ -85,5 +83,5 @@ after the change.
|
||||
|---|---|---|
|
||||
| TTFB | `bench.mjs` | `aurora-budgets.md` |
|
||||
| ubus/uci/fs call count | diff review | `aurora-budgets.md` |
|
||||
| Transferred bytes (gzip) | `bench.mjs` | `aurora-budgets.md` |
|
||||
| Transferred bytes (identity/raw) | `bench.mjs` | `aurora-budgets.md` |
|
||||
| uhttpd VmRSS | `bench.mjs` device report | `aurora-budgets.md` |
|
||||
@@ -20,8 +20,12 @@ export function parseCurlMetrics(line) {
|
||||
};
|
||||
}
|
||||
|
||||
export function assertOkStatus(sample) {
|
||||
if (!Number.isInteger(sample.status) || sample.status < 200 || sample.status >= 400) {
|
||||
export function assertOkStatus(sample, allowedStatuses = []) {
|
||||
if (
|
||||
!Number.isInteger(sample.status) ||
|
||||
(!(sample.status >= 200 && sample.status < 400) &&
|
||||
!allowedStatuses.includes(sample.status))
|
||||
) {
|
||||
throw new Error(`HTTP ${sample.status}`);
|
||||
}
|
||||
return sample;
|
||||
|
||||
@@ -44,6 +44,11 @@ test("assertOkStatus: rejects 404 samples", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("assertOkStatus: permits an explicitly expected login-page 403", () => {
|
||||
const sample = parseCurlMetrics("0.010000\t0.020000\t5788\t403\n");
|
||||
assert.equal(assertOkStatus(sample, [403]), sample);
|
||||
});
|
||||
|
||||
test("summarize: medians of times, first sample's bytes/status, run count", () => {
|
||||
const s = summarize([
|
||||
{ ttfbMs: 50, totalMs: 200, bytes: 1000, status: 200 },
|
||||
|
||||
@@ -49,11 +49,19 @@ const CURL_FORMAT = "%{time_starttransfer}\\t%{time_total}\\t%{size_download}\\t
|
||||
// unauthenticated bench can measure, and TTFB comparisons must compare like
|
||||
// with like (spec: error handling / edge cases).
|
||||
const TARGETS = [
|
||||
{ label: "GET /cgi-bin/luci (login page)", path: "/cgi-bin/luci" },
|
||||
{
|
||||
label: "GET /cgi-bin/luci (login page)",
|
||||
path: "/cgi-bin/luci",
|
||||
allowedStatuses: [403],
|
||||
},
|
||||
{ label: "main.css (identity)", path: "/luci-static/aurora/main.css" },
|
||||
{ label: "main.css (gzip negotiated)", path: "/luci-static/aurora/main.css", gzip: true },
|
||||
{ label: "login.css (gzip negotiated)", path: "/luci-static/aurora/login.css", gzip: true },
|
||||
{ label: "menu-aurora.js (gzip negotiated)", path: "/luci-static/resources/menu-aurora.js", gzip: true },
|
||||
{ label: "login.css (identity)", path: "/luci-static/aurora/login.css" },
|
||||
{ label: "menu-aurora.js (identity)", path: "/luci-static/resources/menu-aurora.js" },
|
||||
{
|
||||
label: "Lato regular font (identity)",
|
||||
path: "/luci-static/aurora/fonts/lato-v24-latin-regular.woff2",
|
||||
},
|
||||
{ label: "logo.svg (identity)", path: "/luci-static/aurora/images/logo.svg" },
|
||||
];
|
||||
|
||||
async function measureOnce(target) {
|
||||
@@ -63,10 +71,12 @@ async function measureOnce(target) {
|
||||
"-w", CURL_FORMAT,
|
||||
"--max-time", "30",
|
||||
];
|
||||
if (target.gzip) args.push("-H", "Accept-Encoding: gzip");
|
||||
args.push(`${HOST}${target.path}`);
|
||||
const { stdout } = await execFileAsync("curl", args);
|
||||
return assertOkStatus(parseCurlMetrics(stdout));
|
||||
return assertOkStatus(
|
||||
parseCurlMetrics(stdout),
|
||||
target.allowedStatuses ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
async function measureTarget(target) {
|
||||
|
||||
@@ -103,8 +103,8 @@ This will be compiled to standard CSS that works in all browsers.
|
||||
|
||||
The theme has two independent Tailwind CSS v4 entry points, both sourced from `.dev/src/media/`:
|
||||
|
||||
- **`main.css`** — the LuCI admin UI. It contains no rules of its own; it's a pure import manifest that pulls in (in order) `_tokens.css` (OKLCH theme tokens, mapped via `@theme inline`), `_base.css`, `_elements.css`, `_layout.css`, every file in `components/` (one partial per UI component — buttons, cards, modals, tables, etc.), and `_utilities.css`.
|
||||
- **`login.css`** — the standalone login page (`sysauth.ut`). Self-contained: imports Tailwind and `_tokens.css` directly.
|
||||
- **`main.css`** — the LuCI admin UI. It is an import manifest that disables Tailwind's automatic source scan (`source(none)`) and pulls in (in order) `_tokens.css` (OKLCH theme tokens, mapped via `@theme inline`), shared `_icons.css`, `_base.css`, `_elements.css`, `_layout.css`, every file in `components/` (one partial per UI component — buttons, cards, modals, tables, etc.), and `_utilities.css`.
|
||||
- **`login.css`** — the standalone login page (`sysauth.ut`). Self-contained: imports Tailwind theme/utilities with `source(none)`, omits full Preflight in favor of a tiny local reset, and imports `_tokens.css` directly. At build time the `login-css-prune` plugin (`vite.config.ts`) strips every custom property the page's var() chains never reach, so the admin-sized token sheet ships login-sized.
|
||||
|
||||
Third-party compatibility patches are **not** bundled into `main.css` — they are split into per-page files under `media/patches/` and loaded on demand (see [On-Demand Third-Party Patches](#on-demand-third-party-patches) below).
|
||||
|
||||
|
||||
@@ -22,10 +22,8 @@
|
||||
"lightningcss": "^1.32.0",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-tailwindcss": "^0.7.0",
|
||||
"tailwind-scrollbar": "^4.0.2",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"terser": "^5.43.1",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"vite": "^7.1.1"
|
||||
},
|
||||
"packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be"
|
||||
|
||||
@@ -32,18 +32,12 @@ importers:
|
||||
prettier-plugin-tailwindcss:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.2(prettier@3.8.1)
|
||||
tailwind-scrollbar:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2(react@19.1.1)(tailwindcss@4.3.2)
|
||||
tailwindcss:
|
||||
specifier: ^4.1.11
|
||||
version: 4.3.2
|
||||
terser:
|
||||
specifier: ^5.43.1
|
||||
version: 5.46.0
|
||||
tw-animate-css:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
vite:
|
||||
specifier: ^7.1.1
|
||||
version: 7.3.1(@types/node@24.10.9)(jiti@2.7.0)(lightningcss@1.32.0)(terser@5.46.0)
|
||||
@@ -466,9 +460,6 @@ packages:
|
||||
'@types/node@24.10.9':
|
||||
resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==}
|
||||
|
||||
'@types/prismjs@1.26.5':
|
||||
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
|
||||
|
||||
acorn@8.15.0:
|
||||
resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
@@ -490,10 +481,6 @@ packages:
|
||||
caniuse-lite@1.0.30001799:
|
||||
resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
colorjs.io@0.6.1:
|
||||
resolution: {integrity: sha512-8lyR2wHzuIykCpqHKgluGsqQi5iDm3/a2IgP2GBZrasn2sBRkE4NOGsglZxWLs/jZQoNkmA/KM/8NV16rLUdBg==}
|
||||
|
||||
@@ -698,15 +685,6 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
prism-react-renderer@2.4.1:
|
||||
resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==}
|
||||
peerDependencies:
|
||||
react: '>=16.0.0'
|
||||
|
||||
react@19.1.1:
|
||||
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
rollup@4.57.1:
|
||||
resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
@@ -723,12 +701,6 @@ packages:
|
||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
tailwind-scrollbar@4.0.2:
|
||||
resolution: {integrity: sha512-wAQiIxAPqk0MNTPptVe/xoyWi27y+NRGnTwvn4PQnbvB9kp8QUBiGl/wsfoVBHnQxTmhXJSNt9NHTmcz9EivFA==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
peerDependencies:
|
||||
tailwindcss: 4.x
|
||||
|
||||
tailwindcss@4.3.2:
|
||||
resolution: {integrity: sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==}
|
||||
|
||||
@@ -745,9 +717,6 @@ packages:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tw-animate-css@1.4.0:
|
||||
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
@@ -1054,8 +1023,6 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/prismjs@1.26.5': {}
|
||||
|
||||
acorn@8.15.0: {}
|
||||
|
||||
baseline-browser-mapping@2.10.37: {}
|
||||
@@ -1072,8 +1039,6 @@ snapshots:
|
||||
|
||||
caniuse-lite@1.0.30001799: {}
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
colorjs.io@0.6.1: {}
|
||||
|
||||
commander@2.20.3: {}
|
||||
@@ -1202,14 +1167,6 @@ snapshots:
|
||||
|
||||
prettier@3.8.1: {}
|
||||
|
||||
prism-react-renderer@2.4.1(react@19.1.1):
|
||||
dependencies:
|
||||
'@types/prismjs': 1.26.5
|
||||
clsx: 2.1.1
|
||||
react: 19.1.1
|
||||
|
||||
react@19.1.1: {}
|
||||
|
||||
rollup@4.57.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
@@ -1250,13 +1207,6 @@ snapshots:
|
||||
|
||||
source-map@0.6.1: {}
|
||||
|
||||
tailwind-scrollbar@4.0.2(react@19.1.1)(tailwindcss@4.3.2):
|
||||
dependencies:
|
||||
prism-react-renderer: 2.4.1(react@19.1.1)
|
||||
tailwindcss: 4.3.2
|
||||
transitivePeerDependencies:
|
||||
- react
|
||||
|
||||
tailwindcss@4.3.2: {}
|
||||
|
||||
tapable@2.3.3: {}
|
||||
@@ -1273,8 +1223,6 @@ snapshots:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tw-animate-css@1.4.0: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
update-browserslist-db@1.2.3(browserslist@4.28.2):
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><image width="512" height="512" href="data:image/png;base64,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 truncated
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><image width="512" height="512" href="data:image/png;base64,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 truncated
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 15 KiB |
@@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1760890864667" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="18898" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M918.8 83.7c-43.5-30.3-104-19.5-134.6 24.1l-425.8 606-127-135.7c-36.4-38.9-98.1-41.1-137-4.8-39 36.2-41.1 97.7-4.7 136.6l206.5 220.7c4.7 5 9.8 9.4 15.1 13.1 0.7 0.5 1.4 1 2.2 1.5 43.5 30.3 104 19.5 134.6-24.1l494-703.2c30.7-43.5 20.1-103.9-23.3-134.2z" fill="#0f162b" p-id="18899"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path d="M918.8 83.7c-43.5-30.3-104-19.5-134.6 24.1l-425.8 606-127-135.7c-36.4-38.9-98.1-41.1-137-4.8-39 36.2-41.1 97.7-4.7 136.6l206.5 220.7c4.7 5 9.8 9.4 15.1 13.1.7.5 1.4 1 2.2 1.5 43.5 30.3 104 19.5 134.6-24.1l494-703.2c30.7-43.5 20.1-103.9-23.3-134.2z"/></svg>
|
||||
|
Before Width: | Height: | Size: 628 B After Width: | Height: | Size: 330 B |
@@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1758357645269" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7610" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 85.333333c235.648 0 426.666667 191.018667 426.666667 426.666667s-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512 276.352 85.333333 512 85.333333z m0 128a298.666667 298.666667 0 1 0 0 597.333334 298.666667 298.666667 0 0 0 0-597.333334z" fill="#000000" fill-opacity=".05" p-id="7611"></path><path d="M813.696 813.696c166.613333-166.613333 166.613333-436.778667 0-603.392-166.613333-166.613333-436.778667-166.613333-603.392 0A64 64 0 0 0 300.8 300.8a298.666667 298.666667 0 1 1 422.4 422.4 64 64 0 0 0 90.496 90.496z" fill="#000000" p-id="7612"></path></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><path opacity=".05" d="M512 85.333c235.648 0 426.667 191.019 426.667 426.667S747.648 938.667 512 938.667 85.333 747.648 85.333 512 276.352 85.333 512 85.333zm0 128a298.667 298.667 0 1 0 0 597.334 298.667 298.667 0 0 0 0-597.334z"/><path d="M813.696 813.696c166.613-166.613 166.613-436.779 0-603.392s-436.779-166.613-603.392 0a64 64 0 0 0 90.496 90.496 298.667 298.667 0 1 1 422.4 422.4 64 64 0 0 0 90.496 90.496z"/></svg>
|
||||
|
Before Width: | Height: | Size: 922 B After Width: | Height: | Size: 486 B |
@@ -1,9 +1,9 @@
|
||||
*:not(html) {
|
||||
@apply scrollbar-thumb-rounded-full scrollbar-thin scrollbar-track-transparent scrollbar-thumb-text-muted;
|
||||
@apply [scrollbar-color:var(--text-muted)_transparent] [scrollbar-width:thin];
|
||||
}
|
||||
|
||||
html {
|
||||
@apply scrollbar-thumb-rounded-full scrollbar-thin scrollbar-track-surface scrollbar-thumb-text-muted;
|
||||
@apply [scrollbar-color:var(--text-muted)_var(--surface)] [scrollbar-width:thin];
|
||||
|
||||
body {
|
||||
@apply text-text relative flex min-h-dvh flex-col text-sm leading-relaxed font-normal;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/* Shared encoded icon payloads. Referencing these variables from masks keeps
|
||||
Vite from inlining the same SVG once per selector and vendor declaration. */
|
||||
:root {
|
||||
@apply [--icon-activity:url('@assets/icons/activity.svg')];
|
||||
@apply [--icon-adjustments:url('@assets/icons/adjustments.svg')];
|
||||
@apply [--icon-apps:url('@assets/icons/apps.svg')];
|
||||
@apply [--icon-arrow-down:url('@assets/icons/arrow-down.svg')];
|
||||
@apply [--icon-arrow-down-dark:url('@assets/icons/arrow-down-dark.svg')];
|
||||
@apply [--icon-arrow-right:url('@assets/icons/arrow-right.svg')];
|
||||
@apply [--icon-box:url('@assets/icons/box.svg')];
|
||||
@apply [--icon-category:url('@assets/icons/category.svg')];
|
||||
@apply [--icon-chart-bar:url('@assets/icons/chart-bar.svg')];
|
||||
@apply [--icon-check:url('@assets/icons/check.svg')];
|
||||
@apply [--icon-cpu:url('@assets/icons/cpu.svg')];
|
||||
@apply [--icon-error:url('@assets/icons/error.svg')];
|
||||
@apply [--icon-gauge:url('@assets/icons/gauge.svg')];
|
||||
@apply [--icon-info:url('@assets/icons/info.svg')];
|
||||
@apply [--icon-package:url('@assets/icons/package.svg')];
|
||||
@apply [--icon-phone:url('@assets/icons/phone.svg')];
|
||||
@apply [--icon-radio:url('@assets/icons/radio.svg')];
|
||||
@apply [--icon-refresh:url('@assets/icons/refresh.svg')];
|
||||
@apply [--icon-refresh-off:url('@assets/icons/refresh-off.svg')];
|
||||
@apply [--icon-router:url('@assets/icons/router.svg')];
|
||||
@apply [--icon-search:url('@assets/icons/search.svg')];
|
||||
@apply [--icon-server:url('@assets/icons/server.svg')];
|
||||
@apply [--icon-settings:url('@assets/icons/settings.svg')];
|
||||
@apply [--icon-shield:url('@assets/icons/shield.svg')];
|
||||
@apply [--icon-spinning:url('@assets/icons/spinning.svg')];
|
||||
@apply [--icon-tag:url('@assets/icons/tag.svg')];
|
||||
@apply [--icon-world:url('@assets/icons/world.svg')];
|
||||
@apply [--icon-x:url('@assets/icons/x.svg')];
|
||||
}
|
||||
@@ -114,9 +114,12 @@ header {
|
||||
|
||||
/* First-level icon: mask + currentColor so it follows the theme.
|
||||
--menu-icon comes from the shared name → icon map in
|
||||
components/_nav.css; unmapped → default. */
|
||||
components/_nav.css; unmapped → the var() fallback. The default
|
||||
must live in the fallback, not as a --menu-icon declaration
|
||||
here: this nested chain compiles to (0,5,1) and would outrank
|
||||
every (0,2,0) [data-section] mapping. */
|
||||
&::before {
|
||||
@apply size-6 shrink-0 bg-current content-[''] [mask:var(--menu-icon,url('@assets/icons/category.svg'))_center/contain_no-repeat];
|
||||
@apply size-6 shrink-0 bg-current content-[''] [mask:var(--menu-icon,var(--icon-category))_center/contain_no-repeat];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,19 +168,19 @@ header {
|
||||
}
|
||||
|
||||
& .board-line-host::before {
|
||||
@apply [mask:url('@assets/icons/router.svg')_center/contain_no-repeat];
|
||||
@apply [mask:var(--icon-router)_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
& .board-line-model::before {
|
||||
@apply [mask:url('@assets/icons/cpu.svg')_center/contain_no-repeat];
|
||||
@apply [mask:var(--icon-cpu)_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
& .board-line-firmware::before {
|
||||
@apply [mask:url('@assets/icons/package.svg')_center/contain_no-repeat];
|
||||
@apply [mask:var(--icon-package)_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
& .board-line-kernel::before {
|
||||
@apply [mask:url('@assets/icons/tag.svg')_center/contain_no-repeat];
|
||||
@apply [mask:var(--icon-tag)_center/contain_no-repeat];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,21 +321,21 @@ header {
|
||||
@apply before:text-text size-5 cursor-pointer text-[0px] transition-transform duration-150 before:absolute before:size-5 before:bg-current hover:scale-105 active:scale-95;
|
||||
|
||||
&[data-indicator="media_error"] {
|
||||
@apply before:[mask:url('@assets/icons/error.svg')_center/cover_no-repeat];
|
||||
@apply before:[mask:var(--icon-error)_center/cover_no-repeat];
|
||||
}
|
||||
|
||||
&[data-indicator="poll-status"] {
|
||||
&[data-style="active"] {
|
||||
@apply before:[mask:url('@assets/icons/refresh.svg')_center/cover_no-repeat];
|
||||
@apply before:[mask:var(--icon-refresh)_center/cover_no-repeat];
|
||||
}
|
||||
|
||||
&[data-style="inactive"] {
|
||||
@apply before:[mask:url('@assets/icons/refresh-off.svg')_center/cover_no-repeat];
|
||||
@apply before:[mask:var(--icon-refresh-off)_center/cover_no-repeat];
|
||||
}
|
||||
}
|
||||
|
||||
&[data-indicator="uci-changes"] {
|
||||
@apply relative before:[mask:url('@assets/icons/settings.svg')_center/cover_no-repeat];
|
||||
@apply relative before:[mask:var(--icon-settings)_center/cover_no-repeat];
|
||||
|
||||
&[data-count]:not([data-count="0"]) {
|
||||
@apply after:bg-danger after:text-on-brand after:absolute after:-top-0.5 after:-right-0.5 after:z-10 after:flex after:min-h-3 after:min-w-3 after:items-center after:justify-center after:rounded-full after:px-0.5 after:text-[8px] after:leading-none after:font-bold after:shadow-sm after:content-[attr(data-count)];
|
||||
@@ -349,9 +352,9 @@ body[data-nav-type="sidebar"] {
|
||||
reads as the content area's toolbar, not a page-wide bar.
|
||||
A CSS grid (not flex-wrap) keeps the header row at its natural height —
|
||||
so collapsing the sidebar never stretches it — lets the sidebar column
|
||||
animate to zero, and lets #maincontent fluidly reclaim the freed space.
|
||||
collapse to zero, and lets #maincontent reclaim the freed space.
|
||||
overflow-x-clip keeps the sliding sidebar out of horizontal overflow. */
|
||||
@apply md:grid md:grid-cols-[17rem_minmax(0,1fr)] md:grid-rows-[auto_minmax(0,1fr)] md:overflow-x-clip md:transition-[grid-template-columns] md:duration-[250ms] md:ease-out;
|
||||
@apply md:grid md:grid-cols-[17rem_minmax(0,1fr)] md:grid-rows-[auto_minmax(0,1fr)] md:overflow-x-clip;
|
||||
|
||||
&.sidebar-collapsed {
|
||||
@apply md:grid-cols-[0_minmax(0,1fr)];
|
||||
@@ -396,8 +399,9 @@ body[data-nav-type="sidebar"] {
|
||||
@apply border-hairline sticky top-0 hidden h-dvh w-full overflow-hidden rounded-none border-r transition-[visibility] duration-[250ms] md:block;
|
||||
}
|
||||
|
||||
/* Visibility flips at the end of the slide so collapsed controls leave the
|
||||
tab order without clipping the animation. */
|
||||
/* The grid column snaps closed to avoid a full-page layout animation.
|
||||
Visibility removes collapsed controls from the tab order; on expansion,
|
||||
the inner panel still enters via compositor-only translate + opacity. */
|
||||
&.sidebar-collapsed .sidebar-panel {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
@@ -51,8 +51,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes aurora-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
@apply animate-in fade-in-0 fill-mode-backwards duration-[250ms];
|
||||
@apply animate-[aurora-fade-in_250ms_ease_both];
|
||||
}
|
||||
|
||||
.fade-out {
|
||||
@@ -61,7 +67,7 @@
|
||||
|
||||
.spinning {
|
||||
@apply relative inline-block pl-8!;
|
||||
@apply before:absolute before:top-1/2 before:left-[6px] before:size-5 before:-translate-y-1/2 before:animate-spin before:bg-current before:[mask:url('@assets/icons/spinning.svg')_center/cover_no-repeat];
|
||||
@apply before:absolute before:top-1/2 before:left-[6px] before:size-5 before:-translate-y-1/2 before:animate-spin before:bg-current before:[mask:var(--icon-spinning)_center/cover_no-repeat];
|
||||
#view > & {
|
||||
@apply first:mx-auto first:grid first:min-h-48 first:w-fit first:place-items-center first:max-md:min-h-[calc(100dvh-4rem)];
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
@apply relative size-5 cursor-pointer text-[0px] before:absolute before:size-5 before:bg-current before:transition-transform before:duration-150 before:ease-out hover:before:scale-110 active:before:scale-95;
|
||||
|
||||
&[data-style="active"] {
|
||||
@apply before:rotate-0 before:[mask:url('@assets/icons/arrow-right.svg')_center/cover_no-repeat];
|
||||
@apply before:rotate-0 before:[mask:var(--icon-arrow-right)_center/cover_no-repeat];
|
||||
}
|
||||
|
||||
&[data-style="inactive"] {
|
||||
@apply before:rotate-90 before:[mask:url('@assets/icons/arrow-right.svg')_center/cover_no-repeat];
|
||||
@apply before:rotate-90 before:[mask:var(--icon-arrow-right)_center/cover_no-repeat];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
@apply text-text-muted relative mt-1 pl-4 leading-none wrap-break-word;
|
||||
|
||||
&:not(:empty) {
|
||||
@apply before:bg-info before:absolute before:-left-0.5 before:inline-block before:size-4 before:[mask:url('@assets/icons/info.svg')_center/cover_no-repeat];
|
||||
@apply before:bg-info before:absolute before:-left-0.5 before:inline-block before:size-4 before:[mask:var(--icon-info)_center/cover_no-repeat];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,11 @@ input[type="checkbox"] {
|
||||
}
|
||||
|
||||
input[type="radio"] {
|
||||
@apply before:rounded-full after:[mask:url('@assets/icons/radio.svg')_center/cover_no-repeat];
|
||||
@apply before:rounded-full after:[mask:var(--icon-radio)_center/cover_no-repeat];
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
@apply before:rounded after:[mask:url('@assets/icons/check.svg')_center/cover_no-repeat];
|
||||
@apply before:rounded after:[mask:var(--icon-check)_center/cover_no-repeat];
|
||||
}
|
||||
|
||||
.cbi-checkbox {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
/* hover:no-underline: the global `a:hover { underline }` in _elements.css
|
||||
outranks a bare class (a:hover is 0,1,1), so nav rows must opt out at
|
||||
the same specificity tier. */
|
||||
@apply text-text hover:bg-hover-faint block rounded-xl px-3 py-1.5 no-underline transition-all duration-150 hover:no-underline;
|
||||
@apply text-text hover:bg-hover-faint block rounded-xl px-3 py-1.5 no-underline transition-colors duration-150 hover:no-underline;
|
||||
}
|
||||
|
||||
.navigation-direct {
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
.navigation-group-toggle {
|
||||
@apply after:size-3.5 after:shrink-0 after:bg-current after:opacity-55 after:transition-[transform,opacity] after:duration-[250ms] after:content-[''] after:[mask:url('@assets/icons/arrow-right.svg')_center/cover_no-repeat] hover:after:opacity-100;
|
||||
@apply after:size-3.5 after:shrink-0 after:bg-current after:opacity-55 after:transition-[transform,opacity] after:duration-[250ms] after:content-[''] after:[mask:var(--icon-arrow-right)_center/cover_no-repeat] hover:after:opacity-100;
|
||||
}
|
||||
|
||||
.nav-category-label {
|
||||
@@ -90,38 +90,38 @@
|
||||
The only maintenance point: add new first-level sections here; anything
|
||||
unmapped falls back to the default icon at the consumer. */
|
||||
.desktop-nav-title[data-section="status"] {
|
||||
@apply [--menu-icon:url('@assets/icons/activity.svg')];
|
||||
@apply [--menu-icon:var(--icon-activity)];
|
||||
}
|
||||
.desktop-nav-title[data-section="statistics"] {
|
||||
@apply [--menu-icon:url('@assets/icons/chart-bar.svg')];
|
||||
@apply [--menu-icon:var(--icon-chart-bar)];
|
||||
}
|
||||
.desktop-nav-title[data-section="nlbw"] {
|
||||
@apply [--menu-icon:url('@assets/icons/gauge.svg')];
|
||||
@apply [--menu-icon:var(--icon-gauge)];
|
||||
}
|
||||
.desktop-nav-title[data-section="system"] {
|
||||
@apply [--menu-icon:url('@assets/icons/settings.svg')];
|
||||
@apply [--menu-icon:var(--icon-settings)];
|
||||
}
|
||||
.desktop-nav-title[data-section="services"] {
|
||||
@apply [--menu-icon:url('@assets/icons/apps.svg')];
|
||||
@apply [--menu-icon:var(--icon-apps)];
|
||||
}
|
||||
.desktop-nav-title[data-section="nas"] {
|
||||
@apply [--menu-icon:url('@assets/icons/server.svg')];
|
||||
@apply [--menu-icon:var(--icon-server)];
|
||||
}
|
||||
.desktop-nav-title[data-section="control"] {
|
||||
@apply [--menu-icon:url('@assets/icons/adjustments.svg')];
|
||||
@apply [--menu-icon:var(--icon-adjustments)];
|
||||
}
|
||||
.desktop-nav-title[data-section="network"] {
|
||||
@apply [--menu-icon:url('@assets/icons/world.svg')];
|
||||
@apply [--menu-icon:var(--icon-world)];
|
||||
}
|
||||
.desktop-nav-title[data-section="vpn"] {
|
||||
@apply [--menu-icon:url('@assets/icons/shield.svg')];
|
||||
@apply [--menu-icon:var(--icon-shield)];
|
||||
}
|
||||
.desktop-nav-title[data-section="docker"] {
|
||||
@apply [--menu-icon:url('@assets/icons/box.svg')];
|
||||
@apply [--menu-icon:var(--icon-box)];
|
||||
}
|
||||
/* "asterisk" covers VoIP/PBX apps (e.g. luci-app-asterisk forks) — no
|
||||
official OpenWrt package registers a verified menu.d node under this
|
||||
name, so this is a best-effort key. */
|
||||
.desktop-nav-title[data-section="asterisk"] {
|
||||
@apply [--menu-icon:url('@assets/icons/phone.svg')];
|
||||
@apply [--menu-icon:var(--icon-phone)];
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
.mobile-menu-overlay {
|
||||
@apply max-md:bg-mega-menu-bg max-md:invisible max-md:fixed max-md:inset-0 max-md:z-60 max-md:opacity-0 max-md:backdrop-blur-lg max-md:backdrop-saturate-150 md:hidden;
|
||||
/* The mobile surface is opaque, so filtering pixels behind it has no visual
|
||||
effect and only adds a full-screen paint/compositing cost. */
|
||||
@apply max-md:bg-mega-menu-bg max-md:invisible max-md:fixed max-md:inset-0 max-md:z-60 max-md:opacity-0 md:hidden;
|
||||
|
||||
/* Transitions live on the OPEN state only: opening fades in, closing cuts
|
||||
instantly. Link taps close into a full-page navigation, and any close
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
search icon carries the same stroke weight as the poll/changes ones
|
||||
sitting next to it. */
|
||||
&::before {
|
||||
@apply size-5 bg-current content-[''] [mask:url('@assets/icons/search.svg')_center/contain_no-repeat];
|
||||
@apply size-5 bg-current content-[''] [mask:var(--icon-search)_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -59,7 +59,7 @@
|
||||
@apply max-md:border-hairline max-md:bg-surface max-md:border-b max-md:px-4 max-md:py-2.5;
|
||||
|
||||
&::before {
|
||||
@apply text-text-subtle size-5 shrink-0 bg-current content-[''] [mask:url('@assets/icons/search.svg')_center/contain_no-repeat];
|
||||
@apply text-text-subtle size-5 shrink-0 bg-current content-[''] [mask:var(--icon-search)_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
& .cmdk-input {
|
||||
@@ -72,7 +72,7 @@
|
||||
@apply text-text-subtle grid size-5 shrink-0 cursor-pointer place-items-center rounded-md border-0 bg-transparent p-0 shadow-none transition-colors duration-150;
|
||||
|
||||
&::before {
|
||||
@apply size-4 bg-current content-[''] [mask:url('@assets/icons/x.svg')_center/contain_no-repeat];
|
||||
@apply size-4 bg-current content-[''] [mask:var(--icon-x)_center/contain_no-repeat];
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
select {
|
||||
@apply text-text border-hairline bg-control-bg focus:border-brand focus:ring-focus-ring min-w-52.5 max-w-full appearance-none md:max-w-100 rounded-2xl border px-3 py-1.5 pr-10 text-sm font-normal shadow-sm transition-[border-color,box-shadow] duration-150 focus:ring-2 focus:outline-none;
|
||||
@apply bg-[url('@assets/icons/arrow-down.svg')] bg-size-[16px] bg-position-[right_0.75rem_center] bg-no-repeat dark:bg-[url('@assets/icons/arrow-down-dark.svg')];
|
||||
@apply bg-size-[16px] bg-position-[right_0.75rem_center] bg-no-repeat [background-image:var(--icon-arrow-down)] dark:[background-image:var(--icon-arrow-down-dark)];
|
||||
&[disabled] {
|
||||
@apply cursor-not-allowed opacity-(--app-opacity-disabled);
|
||||
}
|
||||
|
||||
@@ -4,18 +4,48 @@
|
||||
* Licensed to the public under the Apache License 2.0.
|
||||
*/
|
||||
|
||||
@import "tailwindcss";
|
||||
/* Login only needs Tailwind's theme variables and generated utilities. The
|
||||
full Preflight reset is intentionally omitted from this standalone entry. */
|
||||
@layer theme, base, components, utilities;
|
||||
@import "tailwindcss/theme.css" layer(theme);
|
||||
@import "tailwindcss/utilities.css" layer(utilities) source(none);
|
||||
@import "./_tokens.css";
|
||||
|
||||
@import "./_reduced-motion.css";
|
||||
@import "./_base.css";
|
||||
|
||||
/* See main.css: keep dev-only tooling out of Tailwind's source detection. */
|
||||
@source not "../../vite.config.ts";
|
||||
@source not "../../scripts/*.client.js";
|
||||
|
||||
@custom-variant dark (&:where([data-darkmode=true], [data-darkmode=true] *));
|
||||
|
||||
/* Minimal reset for this template, replacing the much larger Preflight. */
|
||||
@layer base {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
@apply box-border;
|
||||
}
|
||||
|
||||
body,
|
||||
hr,
|
||||
p,
|
||||
button,
|
||||
input {
|
||||
@apply m-0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
@apply [font:inherit];
|
||||
}
|
||||
|
||||
a {
|
||||
@apply no-underline;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
@apply hidden!;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes divider-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
@@ -40,7 +70,7 @@
|
||||
|
||||
/* Full image fades in on top once loaded (compositor-only opacity change) */
|
||||
.login-bg::after {
|
||||
@apply pointer-events-none absolute inset-0 bg-cover bg-center bg-no-repeat opacity-0 [will-change:opacity] content-[''] [transition:opacity_0.5s_ease] [background-image:var(--login-bg)];
|
||||
@apply pointer-events-none absolute inset-0 bg-cover bg-center bg-no-repeat opacity-0 content-[''] [transition:opacity_0.5s_ease] [background-image:var(--login-bg)];
|
||||
}
|
||||
|
||||
.login-bg.full-loaded::after {
|
||||
@@ -102,7 +132,7 @@
|
||||
/* Focus ring: opacity-only transition runs on compositor thread, not main thread —
|
||||
border-color changes require a repaint (~400 ms INP); opacity changes do not. */
|
||||
.input-wrap::after {
|
||||
@apply border-brand pointer-events-none absolute -inset-px rounded-[calc(var(--radius-2xl)+1px)] border-2 opacity-0 [will-change:opacity] content-[''] [transition:opacity_150ms_ease];
|
||||
@apply border-brand pointer-events-none absolute -inset-px rounded-[calc(var(--radius-2xl)+1px)] border-2 opacity-0 content-[''] [transition:opacity_150ms_ease];
|
||||
}
|
||||
|
||||
.input-wrap:focus-within::after {
|
||||
|
||||
@@ -3,24 +3,15 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
*/
|
||||
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Dev-only tooling holds prose and plain CSS whose bare words (static,
|
||||
transition, relative, …) Tailwind's automatic source detection would
|
||||
otherwise read as utility candidates and emit into this shipped stylesheet.
|
||||
Nothing here renders markup, so none of it belongs in the scan. */
|
||||
@source not "../../vite.config.ts";
|
||||
@source not "../../scripts/*.client.js";
|
||||
/* Every shipped utility is referenced through @apply in these CSS sources.
|
||||
Disabling automatic repository scanning prevents class-like words in docs,
|
||||
scripts and fixtures from leaking into the OpenWrt payload. */
|
||||
@import "tailwindcss" source(none);
|
||||
|
||||
@custom-variant dark (&:where([data-darkmode=true], [data-darkmode=true] *));
|
||||
|
||||
@plugin "tailwind-scrollbar" {
|
||||
nocompatible: true;
|
||||
}
|
||||
|
||||
@import "tw-animate-css";
|
||||
|
||||
@import "./_tokens.css";
|
||||
@import "./_icons.css";
|
||||
|
||||
@import "./_base.css";
|
||||
@import "./_elements.css";
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
const projectRoot = resolve(import.meta.dirname, "../..");
|
||||
const output = resolve(projectRoot, "htdocs/luci-static");
|
||||
|
||||
const asset = (path) => resolve(output, path);
|
||||
const bytes = (path) => statSync(asset(path)).size;
|
||||
|
||||
test("production assets stay within raw-transfer budgets", () => {
|
||||
const main = bytes("aurora/main.css");
|
||||
const login = bytes("aurora/login.css");
|
||||
const menu = bytes("resources/menu-aurora.js");
|
||||
const font = bytes("aurora/fonts/lato-v24-latin-regular.woff2");
|
||||
const logo = bytes("aurora/images/logo.svg");
|
||||
|
||||
assert.ok(main <= 190_000, "main.css exceeds 190 KB");
|
||||
assert.ok(login <= 12_000, "login.css exceeds 12 KB");
|
||||
assert.ok(menu <= 20_000, "menu-aurora.js exceeds 20 KB");
|
||||
assert.ok(logo <= 16_000, "logo.svg exceeds 16 KB");
|
||||
assert.ok(main + menu + font + logo <= 250_000, "admin assets exceed 250 KB");
|
||||
assert.ok(login + font + logo <= 55_000, "login assets exceed 55 KB");
|
||||
});
|
||||
|
||||
test("compressed LuCI JS preserves its loader directives", () => {
|
||||
const js = readFileSync(asset("resources/menu-aurora.js"), "utf8");
|
||||
assert.match(js, /["']require baseclass["'];["']require ui["'];/);
|
||||
|
||||
const module = new Function("baseclass", "ui", js)(
|
||||
{ extend: (value) => value },
|
||||
{},
|
||||
);
|
||||
assert.equal(typeof module.__init__, "function");
|
||||
});
|
||||
|
||||
test("compiled CSS does not duplicate SVG payloads for every mask property", () => {
|
||||
const css = readFileSync(asset("aurora/main.css"), "utf8");
|
||||
const dataUrls = css.match(/data:image\/svg\+xml,[^"]+/g) ?? [];
|
||||
const encodedBytes = dataUrls.reduce((sum, value) => sum + value.length, 0);
|
||||
|
||||
assert.equal(
|
||||
dataUrls.length,
|
||||
new Set(dataUrls).size,
|
||||
"duplicate SVG data URL",
|
||||
);
|
||||
assert.ok(encodedBytes <= 17_000, `SVG data URLs occupy ${encodedBytes} B`);
|
||||
});
|
||||
|
||||
test("pruned login.css keeps every consumed variable resolvable", () => {
|
||||
const css = readFileSync(asset("aurora/login.css"), "utf8");
|
||||
const declared = new Set(
|
||||
[...css.matchAll(/[{;](--[\w-]+):/g)].map((m) => m[1]),
|
||||
);
|
||||
const registered = new Set(
|
||||
[...css.matchAll(/@property\s+(--[\w-]+)/g)].map((m) => m[1]),
|
||||
);
|
||||
// header.ut injects these from UCI at render time — consumed here, never
|
||||
// declared here.
|
||||
const injected = new Set(["--login-bg", "--login-bg-lqip"]);
|
||||
|
||||
const unresolvable = [];
|
||||
for (const [, name, delim] of css.matchAll(/var\(\s*(--[\w-]+)\s*([,)])/g)) {
|
||||
const hasFallback = delim === ",";
|
||||
if (
|
||||
!declared.has(name) &&
|
||||
!registered.has(name) &&
|
||||
!injected.has(name) &&
|
||||
!hasFallback
|
||||
) {
|
||||
unresolvable.push(name);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(unresolvable, []);
|
||||
|
||||
// The prune must actually strip admin-only tokens from the shared sheet.
|
||||
for (const adminOnly of ["--mega-menu-bg", "--icon-", "--sidebar"]) {
|
||||
assert.ok(!css.includes(adminOnly), `${adminOnly} should be pruned`);
|
||||
}
|
||||
// And the login page's own consumed tokens must survive, light and dark.
|
||||
for (const kept of ["--surface:", "--brand:", "--control-bg:"]) {
|
||||
const count = css.split(kept).length - 1;
|
||||
assert.ok(count >= 2, `${kept} should be declared for both modes`);
|
||||
}
|
||||
});
|
||||
|
||||
test("package roots contain no macOS metadata", () => {
|
||||
const offenders = [];
|
||||
const visit = (dir) => {
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const path = join(dir, entry.name);
|
||||
if (entry.name === ".DS_Store") offenders.push(path);
|
||||
else if (entry.isDirectory()) visit(path);
|
||||
}
|
||||
};
|
||||
|
||||
visit(resolve(projectRoot, "htdocs"));
|
||||
visit(resolve(projectRoot, "ucode"));
|
||||
assert.deepEqual(offenders, []);
|
||||
});
|
||||
@@ -108,7 +108,7 @@ test("shared navigation styles own accordion animation without a guide rail", ()
|
||||
"after:transition-[transform,opacity]",
|
||||
"after:duration-[250ms]",
|
||||
]);
|
||||
assert.match(toggle, /arrow-right\.svg/);
|
||||
assert.match(toggle, /var\(--icon-arrow-right\)/);
|
||||
assertIncludesUtilities(region, [
|
||||
"grid",
|
||||
"grid-rows-[0fr]",
|
||||
@@ -218,7 +218,11 @@ test("mega-menu category masks use Tailwind arbitrary utilities", () => {
|
||||
|
||||
assert.match(
|
||||
icon,
|
||||
/@apply[^;]*\[mask:var\(--menu-icon,url\(["']@assets\/icons\/category\.svg["']\)\)_center\/contain_no-repeat\]/,
|
||||
/@apply[^;]*\[mask:var\(--menu-icon,var\(--icon-category\)\)_center\/contain_no-repeat\]/,
|
||||
);
|
||||
// The default lives in the var() fallback only. Declaring --menu-icon on
|
||||
// the title compiles into a (0,5,1) selector chain that outranks every
|
||||
// .desktop-nav-title[data-section=…] (0,2,0) mapping in _nav.css.
|
||||
assert.doesNotMatch(title, /--menu-icon:/);
|
||||
assert.doesNotMatch(layoutStyles, /^\s*mask\s*:/m);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import test from "node:test";
|
||||
|
||||
const projectRoot = resolve(import.meta.dirname, "../..");
|
||||
const source = (path) => readFileSync(resolve(projectRoot, path), "utf8");
|
||||
|
||||
test("navigation transitions avoid blanket and full-page layout animation", () => {
|
||||
const nav = source(".dev/src/media/components/_nav.css");
|
||||
const layout = source(".dev/src/media/_layout.css");
|
||||
|
||||
assert.doesNotMatch(nav, /\btransition-all\b/);
|
||||
assert.doesNotMatch(layout, /transition-\[grid-template-columns\]/);
|
||||
});
|
||||
|
||||
test("login effects do not reserve compositor layers permanently", () => {
|
||||
const login = source(".dev/src/media/login.css");
|
||||
assert.doesNotMatch(login, /\[will-change:opacity\]/);
|
||||
});
|
||||
|
||||
test("login template reuses server data when including the header", () => {
|
||||
const sysauth = source("ucode/template/themes/aurora/sysauth.ut");
|
||||
const header = source("ucode/template/themes/aurora/header.ut");
|
||||
|
||||
assert.match(
|
||||
sysauth,
|
||||
/include\('header',\s*\{[^}]*prefetched_boardinfo:\s*boardinfo[^}]*prefetched_tokens:\s*themeTokens[^}]*\}\)/s,
|
||||
);
|
||||
assert.match(header, /prefetched_boardinfo\s*\?\?/);
|
||||
assert.match(header, /prefetched_tokens\s*\?\?/);
|
||||
});
|
||||
@@ -114,7 +114,10 @@ test("mega-menu reveal is compositor-only and the frost never overlaps it", () =
|
||||
// The curtain carries the blur permanently (Apple's globalnav-curtain) and
|
||||
// fades it with opacity/visibility. Close must start that fade immediately;
|
||||
// delaying the curtain left blur visible after the sheet had collapsed.
|
||||
assert.ok(overlay.includes("max-md:backdrop-blur-lg"), "mobile overlay blur");
|
||||
assert.ok(
|
||||
!overlay.includes("max-md:backdrop-blur-lg"),
|
||||
"opaque mobile overlay must not blur hidden pixels",
|
||||
);
|
||||
const curtain = overlay
|
||||
.split("\n")
|
||||
.find((l) => l.includes("bg-mega-menu-scrim"));
|
||||
|
||||
@@ -7,9 +7,9 @@ import tailwindcss from "@tailwindcss/vite";
|
||||
import browserslist from "browserslist";
|
||||
import { exec } from "child_process";
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "fs";
|
||||
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
||||
import { mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
||||
import type { IncomingMessage, ServerResponse } from "http";
|
||||
import { browserslistToTargets } from "lightningcss";
|
||||
import { browserslistToTargets, transform as lightningcssTransform } from "lightningcss";
|
||||
import { basename, dirname, join, relative, resolve, sep } from "path";
|
||||
import { minify as terserMinify } from "terser";
|
||||
import { promisify } from "util";
|
||||
@@ -27,6 +27,10 @@ const CURRENT_DIR = process.cwd();
|
||||
const PROJECT_ROOT = resolve(CURRENT_DIR, "..");
|
||||
const BUILD_OUTPUT = resolve(PROJECT_ROOT, "htdocs/luci-static");
|
||||
|
||||
const LIGHTNINGCSS_TARGETS = browserslistToTargets(
|
||||
browserslist("last 4 versions, Firefox ESR, not dead"),
|
||||
);
|
||||
|
||||
function createLuciJsCompressPlugin(): Plugin {
|
||||
let outDir: string;
|
||||
|
||||
@@ -48,8 +52,10 @@ function createLuciJsCompressPlugin(): Plugin {
|
||||
const sourceCode = await readFile(join(srcDir, relPath), "utf-8");
|
||||
const compressed = await terserMinify(sourceCode, {
|
||||
parse: { bare_returns: true },
|
||||
compress: false,
|
||||
mangle: false,
|
||||
/* LuCI dependency declarations are string directives. Keep
|
||||
them while enabling normal compression and local mangling. */
|
||||
compress: { directives: false, passes: 2 },
|
||||
mangle: true,
|
||||
format: { comments: false, beautify: false },
|
||||
});
|
||||
const outputPath = join(outDir, "resources", normalized);
|
||||
@@ -66,6 +72,94 @@ function createLuciJsCompressPlugin(): Plugin {
|
||||
};
|
||||
}
|
||||
|
||||
async function removeMacOsMetadata(dir: string): Promise<void> {
|
||||
if (!existsSync(dir)) return;
|
||||
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
||||
const path = join(dir, entry.name);
|
||||
if (entry.isDirectory()) await removeMacOsMetadata(path);
|
||||
else if (entry.name === ".DS_Store") await rm(path, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
/* login.css imports the full shared token sheet but the login page consumes
|
||||
only a fraction of it. Prune every custom-property declaration (and --tw-*
|
||||
@property registration) that no var() reference can reach, following
|
||||
declaration-value chains (--a: var(--b) keeps --b alive). --login-bg and
|
||||
--login-bg-lqip stay consumed-without-declaration by design: header.ut
|
||||
injects them from UCI at render time, so pruning never touches consumers. */
|
||||
function createLoginCssPrunePlugin(): Plugin {
|
||||
return {
|
||||
name: "login-css-prune",
|
||||
apply: "build",
|
||||
enforce: "post",
|
||||
async closeBundle() {
|
||||
const path = resolve(BUILD_OUTPUT, "aurora/login.css");
|
||||
if (!existsSync(path)) return;
|
||||
const css = await readFile(path, "utf-8");
|
||||
|
||||
// Roots are vars consumed by normal declarations; custom-property
|
||||
// declarations only contribute edges to the reachability walk.
|
||||
const VALUE = `(?:"[^"]*"|'[^']*'|[^;{}"'])*`;
|
||||
const roots = new Set<string>();
|
||||
const edges = new Map<string, Set<string>>();
|
||||
for (const [, name, value] of css.matchAll(
|
||||
new RegExp(`(?<=[{;])(--[\\w-]+|[a-zA-Z-]+):(${VALUE})`, "g"),
|
||||
)) {
|
||||
const refs = [...value.matchAll(/var\(\s*(--[\w-]+)/g)].map((m) => m[1]);
|
||||
if (!name.startsWith("--")) refs.forEach((r) => roots.add(r));
|
||||
else {
|
||||
const deps = edges.get(name) ?? new Set<string>();
|
||||
refs.forEach((r) => deps.add(r));
|
||||
edges.set(name, deps);
|
||||
}
|
||||
}
|
||||
const keep = new Set(roots);
|
||||
const stack = [...roots];
|
||||
while (stack.length) {
|
||||
for (const dep of edges.get(stack.pop()!) ?? []) {
|
||||
if (!keep.has(dep)) {
|
||||
keep.add(dep);
|
||||
stack.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const pruned = css
|
||||
.replace(
|
||||
new RegExp(`(?<=[{;])(--[\\w-]+):${VALUE};?`, "g"),
|
||||
(decl, name) => (keep.has(name) ? decl : ""),
|
||||
)
|
||||
.replace(/@property\s+(--[\w-]+)\{[^{}]*\}/g, (rule, name) =>
|
||||
keep.has(name) ? rule : "",
|
||||
);
|
||||
|
||||
// Re-minify with the shared targets: validates the edited syntax and
|
||||
// drops any rule the pruning emptied out.
|
||||
const { code } = lightningcssTransform({
|
||||
filename: "login.css",
|
||||
code: Buffer.from(pruned),
|
||||
minify: true,
|
||||
targets: LIGHTNINGCSS_TARGETS,
|
||||
});
|
||||
await writeFile(path, code);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createPackageHygienePlugin(): Plugin {
|
||||
return {
|
||||
name: "package-hygiene",
|
||||
apply: "build",
|
||||
enforce: "post",
|
||||
async closeBundle() {
|
||||
await Promise.all([
|
||||
removeMacOsMetadata(resolve(PROJECT_ROOT, "htdocs")),
|
||||
removeMacOsMetadata(resolve(PROJECT_ROOT, "ucode")),
|
||||
]);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// On-demand third-party patches: serve src/media/patches/<page>.css at
|
||||
// /luci-static/aurora/patches/<page>.css in dev. Without this, header.ut's patch
|
||||
// <link> falls through to the OpenWrt proxy (404 / stale router asset) and patch
|
||||
@@ -789,12 +883,12 @@ export default defineConfig(({ mode }) => {
|
||||
createMockPlugin(),
|
||||
createUtSyncPlugin(OPENWRT_SSH_HOST),
|
||||
createLuciJsCompressPlugin(),
|
||||
createLoginCssPrunePlugin(),
|
||||
createPackageHygienePlugin(),
|
||||
],
|
||||
css: {
|
||||
lightningcss: {
|
||||
targets: browserslistToTargets(
|
||||
browserslist("last 4 versions, Firefox ESR, not dead"),
|
||||
),
|
||||
targets: LIGHTNINGCSS_TARGETS,
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
||||
@@ -8,8 +8,8 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=Aurora Theme (A modern browser theme built with Vite and Tailwind CSS)
|
||||
LUCI_DEPENDS:=+luci-base
|
||||
|
||||
PKG_VERSION:=1.1.2
|
||||
PKG_RELEASE:=55
|
||||
PKG_VERSION:=1.1.3
|
||||
PKG_RELEASE:=56
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
|
||||
LUCI_MINIFY_CSS:=
|
||||
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><image width="512" height="512" href="data:image/png;base64,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 truncated
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 512 512"><image width="512" height="512" href="data:image/png;base64,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 truncated
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-tracking:initial}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-sm:calc(var(--radius-base) * .25);--radius-md:calc(var(--radius-base) * .75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base) * 1.5);--radius-2xl:calc(var(--radius-base) * 2);--radius-3xl:calc(var(--radius-base) * 3);--radius-4xl:calc(var(--radius-base) * 4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--animate-spin:spin 1s linear infinite;--blur-lg:16px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base) * .5);--container-max-width:var(--container-max-width)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacinLine truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-content:"";--tw-shadow:0 0 #0000;--tw-inset-shadow:0 0 #0000;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-shadow:0 0 #0000;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--spacing:var(--spacing);--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-widest:.1em;--radius-2xl:calc(var(--radius-base) * 2);--animate-spin:spin 1s linear infinite}}@layer base{*,:before,:after{box-sizing:border-box}body,hr,p,button,input{margin:0}button,input{font:inherit}a{text-decoration-line:none}[hidden]{display:none!important}}@layer components,utilities;:root{--bg:#f7fafc;--surface:#fff;--text:#121a22;--brand:#0085b7;--on-brand:#fff;--danger:#8d1925;--text-muted:#5f666d;--text-subtle:#7f858b;--hairline:#121a2221;--brand-subtle:#ddecf5;--danger-surface:#ffe8e6;--control-bg:#fff;--app-shadow-md:0 4px 16px #00000014, 0 1px 3px #0000000a;--font-sans:"Lato", ui-sans-serif, system-ui, sans-serif;--spacing:.25rem;--radius-base:.5rem}@supports (color:lab(0% 0 0)){:root{--bg:lab(98.1553% -.760227 -1.25223);--surface:lab(100% 0 0);--text:lab(8.89727% -1.68239 -6.93074);--brand:lab(51.3503% -19.237 -40.2116);--on-brand:lab(100% 0 0);--danger:lab(31.1495% 47.9256 25.8661);--text-muted:lab(42.8254% -1.57365 -4.8763);--text-subtle:lab(55.3248% -1.4126 -4.053);--hairline:lab(8.89727% -1.68239 -6.93074/.13);--brand-subtle:lab(92.5794% -3.9458 -6.17858);--danger-surface:lab(93.849% 11.2841 5.29771);--control-bg:lab(100.011% -.760317 -1.25225);--app-shadow-md:0 4px 16px lab(0% 0 0/.08), 0 1px 3px lab(0% 0 0/.04)}}[data-darkmode=true]{--bg:#05070e;--surface:#141822;--text:#f9fafb;--brand:#00958e;--on-brand:#fff;--danger:#f17070;--text-muted:#909297;--text-subtle:#5d6067;--hairline:#f9fafb1a;--brand-subtle:#061a20;--danger-surface:#541f20;--control-bg:#0a0e17;--app-shadow-md:0 10px 28px #0000006b}@supports (color:lab(0% 0 0)){[data-darkmode=true]{--bg:lab(1.93894% .0855997 -3.22397);--surface:lab(8.24713% -.0405461 -7.31381);--text:lab(98.252% -.0618994 -.756907);--brand:lab(55.0978% -44.5567 -7.62978);--on-brand:lab(100% 0 0);--danger:lab(63.5705% 50.8105 25.3639);--text-muted:lab(60.5379% -.225931 -3.05105);--text-subtle:lab(40.6865% -.278354 -4.23647);--hairline:lab(98.252% -.0618994 -.756907/.1);--brand-subtle:lab(7.95734% -6.34954 -6.72844);--danger-surface:lab(20.3014% 25.4342 12.827);--control-bg:lab(3.9802% .0812709 -5.64623);--app-shadow-md:0 10px 28px lab(0% 0 0/.42)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-property:none!important;animation:none!important}.spinning:before{animation:var(--animate-spin)!important}}html{background-color:var(--bg);min-height:100dvh;font-family:var(--font-sans)}html body{background-color:var(--bg);min-height:100dvh}@keyframes divider-in{0%{opacity:0;transform:scaleX(.15)}}.login-screen{min-height:100dvh;padding-inline:calc(var(--spacing) * 5);padding-bottom:calc(var(--spacing) * 14);flex-direction:column;justify-content:center;align-items:center;display:flex;position:relative}.login-shell{width:100%;max-width:var(--container-md)}.login-bg{pointer-events:none;background-image:var(--login-bg-lqip,var(--login-bg));background-position:50%;background-repeat:no-repeat;background-size:cover;position:fixed;inset:0;transform:translateZ(0)}.login-bg:after{pointer-events:none;background-image:var(--login-bg);opacity:0;--tw-content:"";content:var(--tw-content);background-position:50%;background-repeat:no-repeat;background-size:cover;transition:opacity .5s;position:absolute;inset:0}.login-bg.full-loaded:after{opacity:1}.login-card{gap:calc(var(--spacing) * 7);border-radius:calc(var(--radius-base) * 4);background-color:var(--surface);padding:calc(var(--spacing) * 14);--tw-shadow:var(--app-shadow-md);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);flex-direction:column;display:flex}@media not all and (width>=48rem){.login-card{border-radius:calc(var(--radius-base) * 3);padding:calc(var(--spacing) * 9)}}.login-brand{align-items:center;gap:calc(var(--spacing) * 3.5);display:flex}.login-divider{border-style:var(--tw-border-style);--tw-gradient-position:to right;borderLine truncated
|
||||
@@ -1,2 +1,2 @@
|
||||
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0;--tw-scrollbar-thumb:#0000;--tw-scrollbar-track:#0000;--tw-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--radius-sm:calc(var(--radius-base) * .25);--radius-md:calc(var(--radius-base) * .75);--radius-lg:var(--radius-base);--radius-xl:calc(var(--radius-base) * 1.5);--radius-2xl:calc(var(--radius-base) * 2);--radius-3xl:calc(var(--radius-base) * 3);--radius-4xl:calc(var(--radius-base) * 4);--shadow-sm:var(--app-shadow-sm);--shadow-md:var(--app-shadow-md);--shadow-lg:var(--app-shadow-md);--shadow-xl:var(--app-shadow-lg);--shadow-2xl:var(--app-shadow-lg);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--radius:calc(var(--radius-base) * .5);--container-max-width:var(--container-max-width)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mLine truncated
|
||||
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-border-style:solid;--tw-duration:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-content:"";--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-space-y-reverse:0;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-border-spacing-x:0;--tw-border-spacing-y:0}}}@layer theme{:root,:host{--font-sans:var(--font-sans);--font-mono:var(--font-mono);--spacing:var(--spacing);--container-xs:20rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--leading-tight:1.25;--leading-snug:1.375;--leading-normal:1.5;--leading-relaxed:1.625;--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--animate-spin:spin 1s linear infinite;--blur-md:12px;--blur-lg:16px;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--container-max-width:var(--container-max-width)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-butLine truncated
|
||||
@@ -1 +1 @@
|
||||
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(tree=>this.render(tree));this.initNavigationControls();this.initUciIndicator()},initUciIndicator(){const original=ui.changes?.setIndicator;if(!original)return;ui.changes.setIndicator=function(n){original.call(this,n);document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",n||0)}},initNavigationControls(){const overlay=document.querySelector("#mobile-menu-overlay");const navigationToggle=document.querySelector("#navigation-toggle");if(!navigationToggle||!overlay)return;const mobileList=overlay.querySelector("#mobile-nav-list");const desktop=window.matchMedia("(min-width: 768px)");const SIDEBAR_COLLAPSED_KEY="aurora.sidebarCollapsed";const isDesktopSidebar=()=>desktop.matches&&document.body.dataset.navType==="sidebar";const updateToggleState=expanded=>{const desktopSidebar=isDesktopSidebar();navigationToggle.classList.toggle("is-expanded",expanded&&!desktop.matches);navigationToggle.setAttribute("aria-expanded",expanded?"true":"false");navigationToggle.setAttribute("aria-controls",desktopSidebar?"sidebar-panel":"mobile-menu-overlay");const label=expanded&&!desktopSidebar?_("Close"):_("Navigation");navigationToggle.setAttribute("title",label);navigationToggle.setAttribute("aria-label",label)};const closeMobileNavigation=()=>{overlay.classList.remove("mobile-menu-open");document.body.classList.remove("mobile-navigation-open");document.body.style.overflow="";this.resetNavigationGroups(mobileList)};const getNavigationExpanded=()=>{if(isDesktopSidebar()){return!document.body.classList.contains("sidebar-collapsed")}return!desktop.matches&&overlay.classList.contains("mobile-menu-open")};const setNavigationExpanded=expanded=>{if(isDesktopSidebar()){closeMobileNavigation();const collapsed=!expanded;document.body.classList.toggle("sidebar-collapsed",collapsed);localStorage.setItem(SIDEBAR_COLLAPSED_KEY,collapsed);updateToggleState(expanded);return}if(desktop.matches){closeMobileNavigation();updateToggleState(false);return}overlay.classList.toggle("mobile-menu-open",expanded);document.body.classList.toggle("mobile-navigation-open",expanded);document.body.style.overflow=expanded?"hidden":"";if(expanded)this.expandActiveNavigationGroup(mobileList);else this.resetNavigationGroups(mobileList);updateToggleState(expanded)};const toggleNavigation=()=>setNavigationExpanded(!getNavigationExpanded());const syncNavigationState=()=>{closeMobileNavigation();if(isDesktopSidebar()){const collapsed=localStorage.getItem(SIDEBAR_COLLAPSED_KEY)==="true";document.body.classList.toggle("sidebar-collapsed",collapsed);updateToggleState(!collapsed);return}updateToggleState(false)};navigationToggle.addEventListener("click",e=>{e.stopPropagation();toggleNavigation()});overlay.addEventListener("click",e=>{if(e.target===overlay)setNavigationExpanded(false)});document.addEventListener("keydown",e=>{if(e.key==="Escape"&&overlay.classList.contains("mobile-menu-open")){setNavigationExpanded(false)}});desktop.addEventListener("change",syncNavigationState);syncNavigationState();document.addEventListener("click",e=>{const destination=e.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");if(destination&&overlay.contains(destination)){setNavigationExpanded(false)}})},renderMobileMenu(items){const list=document.querySelector("#mobile-nav-list");const footerAction=document.querySelector("#mobile-nav-footer-action");if(list)list.innerHTML="";if(footerAction)footerAction.innerHTML="";if(!list)return;items.forEach(item=>{if(item.isLogout){if(footerAction){footerAction.appendChild(E("a",{class:"mobile-nav-logout",href:item.href},[item.title]))}return}list.appendChild(this.renderNavigationItem(item,"mobile"))});this.bindNavigationAccordion(list)},render(tree){this.renderModeMenu(tree);if(L.env.dispatchpath.length>=3){let node=tree;let url="";for(let i=0;i<3&&node;i++){const segment=L.env.dispatchpath[i];node=node.children?.[segment];url+=(url?"/":"")+segment}if(node)this.renderTabMenu(node,url)}},renderTabMenu(tree,url,level=0){const container=document.querySelector("#tabmenu");const ul=E("ul",{class:"tabs"});const children=ui.menu.getChildren(tree);let activeNode=null;children.forEach(child=>{const isActive=L.env.dispatchpath[3+level]===child.name;ul.appendChild(E("li",{class:`tabmenu-item-${child.name}${isActive?" active":""}`},[E("a",{href:L.url(url,child.name)},[_(child.title)])]));if(isActive)activeNode=child});if(!ul.children.length)return E([]);container.appendChild(ul);container.style.display="";if(activeNode){this.renderTabMenu(activeNode,`${url}/${activeNode.name}`,level+1)}return ul},renderMainMenu(tree,url,level=0,navigationItems=null){const ul=level?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu");const children=ui.menu.getChildren(tree);if(level>1)return E([]);if(level===0){const navType=document.body?.dataset?.navType||"mega-menu";if(navType==="sidebar"){this.renderSidebLine truncated
|
||||
"use strict";"require baseclass";"require ui";return baseclass.extend({__init__(){ui.menu.load().then(e=>this.render(e)),this.initNavigationControls(),this.initUciIndicator()},initUciIndicator(){const e=ui.changes?.setIndicator;e&&(ui.changes.setIndicator=function(t){e.call(this,t),document.querySelector('[data-indicator="uci-changes"]')?.setAttribute("data-count",t||0)})},initNavigationControls(){const e=document.querySelector("#mobile-menu-overlay"),t=document.querySelector("#navigation-toggle");if(!t||!e)return;const i=e.querySelector("#mobile-nav-list"),n=window.matchMedia("(min-width: 768px)"),a="aurora.sidebarCollapsed",s=()=>n.matches&&"sidebar"===document.body.dataset.navType,o=e=>{const i=s();t.classList.toggle("is-expanded",e&&!n.matches),t.setAttribute("aria-expanded",e?"true":"false"),t.setAttribute("aria-controls",i?"sidebar-panel":"mobile-menu-overlay");const a=e&&!i?_("Close"):_("Navigation");t.setAttribute("title",a),t.setAttribute("aria-label",a)},l=()=>{e.classList.remove("mobile-menu-open"),document.body.classList.remove("mobile-navigation-open"),document.body.style.overflow="",this.resetNavigationGroups(i)},r=t=>{if(s()){l();const e=!t;return document.body.classList.toggle("sidebar-collapsed",e),localStorage.setItem(a,e),void o(t)}if(n.matches)return l(),void o(!1);e.classList.toggle("mobile-menu-open",t),document.body.classList.toggle("mobile-navigation-open",t),document.body.style.overflow=t?"hidden":"",t?this.expandActiveNavigationGroup(i):this.resetNavigationGroups(i),o(t)},c=()=>{if(l(),s()){const e="true"===localStorage.getItem(a);return document.body.classList.toggle("sidebar-collapsed",e),void o(!e)}o(!1)};t.addEventListener("click",t=>{t.stopPropagation(),r(!(s()?!document.body.classList.contains("sidebar-collapsed"):!n.matches&&e.classList.contains("mobile-menu-open")))}),e.addEventListener("click",t=>{t.target===e&&r(!1)}),document.addEventListener("keydown",t=>{"Escape"===t.key&&e.classList.contains("mobile-menu-open")&&r(!1)}),n.addEventListener("change",c),c(),document.addEventListener("click",t=>{const i=t.target?.closest?.(".navigation-direct, .navigation-sublink, .mobile-nav-logout");i&&e.contains(i)&&r(!1)})},renderMobileMenu(e){const t=document.querySelector("#mobile-nav-list"),i=document.querySelector("#mobile-nav-footer-action");t&&(t.innerHTML=""),i&&(i.innerHTML=""),t&&(e.forEach(e=>{e.isLogout?i&&i.appendChild(E("a",{class:"mobile-nav-logout",href:e.href},[e.title])):t.appendChild(this.renderNavigationItem(e,"mobile"))}),this.bindNavigationAccordion(t))},render(e){if(this.renderModeMenu(e),L.env.dispatchpath.length>=3){let t=e,i="";for(let e=0;e<3&&t;e++){const n=L.env.dispatchpath[e];t=t.children?.[n],i+=(i?"/":"")+n}t&&this.renderTabMenu(t,i)}},renderTabMenu(e,t,i=0){const n=document.querySelector("#tabmenu"),a=E("ul",{class:"tabs"}),s=ui.menu.getChildren(e);let o=null;return s.forEach(e=>{const n=L.env.dispatchpath[3+i]===e.name;a.appendChild(E("li",{class:`tabmenu-item-${e.name}${n?" active":""}`},[E("a",{href:L.url(t,e.name)},[_(e.title)])])),n&&(o=e)}),a.children.length?(n.appendChild(a),n.style.display="",o&&this.renderTabMenu(o,`${t}/${o.name}`,i+1),a):E([])},renderMainMenu(e,t,i=0,n=null){const a=i?E("ul",{class:"desktop-nav-list"}):document.querySelector("#topmenu"),s=ui.menu.getChildren(e);if(i>1)return E([]);if(0===i){const e=document.body?.dataset?.navType||"mega-menu";if("sidebar"===e)return this.renderSidebar(n||[]),a||E([]);if(!a||!s.length)return E([]);"mega-menu"===e?this.initMegaMenu(s,t,a):this.initDropdown(s,t,a)}else{if(!s.length)return E([]);s.forEach(i=>{const n=this.isActivePath(e.name,i.name),s={class:n?"is-active-page":"",href:L.url(t,i.name)};n&&(s["aria-current"]="page"),a.appendChild(E("li",{},[E("a",s,[_(i.title)])]))})}return a.style.display="",a},isActivePath:(e,t)=>L.env.dispatchpath[1]===e&&(null==t||L.env.dispatchpath[2]===t),buildNavigationModel(e,t){return e.filter(e=>e?.name).map(e=>{const i=ui.menu.getChildren(e).filter(e=>e?.name).map(i=>({name:i.name,title:_(i.title),href:L.url(t,e.name,i.name),isActivePage:this.isActivePath(e.name,i.name)})),n=i.length>0,a=this.isActivePath(e.name);return{name:e.name,title:_(e.title),href:L.url(t,e.name),hasChildren:n,isLogout:"logout"===e.name,isActiveGroup:n&&a,isActivePage:!n&&a,activePage:i.find(e=>e.isActivePage)||null,pages:i}})},navigationSubmenuId:(e,t)=>`${e}-submenu-${encodeURIComponent(String(t))}`,renderNavigationItem(e,t){const i="mobile"===t,n=i?"mobile-nav-item":"",a=i?"navigation-direct mobile-nav-link":"navigation-direct nav-link";if(!e.hasChildren){const t={class:`${a}${e.isActivePage?" is-active-page":""}`,href:e.href};return e.isActivePage&&(t["aria-current"]="page"),E("li",{class:n},[E("a",t,[e.title])])}const s=this.navigationSubmenuId(t,e.name),o=["navigation-group",i?"mobile-nav-item":"sidebar-group",e.isActiveGroup?"is-active-group":"",e.isActiveGroup?"is-expanded":""].filter(Boolean),l={class:i?"navigation-group-toggle mobile-nav-link mobile-nav-toggle":"navigatLine truncated
|
||||
@@ -14,10 +14,10 @@
|
||||
import { cursor } from 'uci';
|
||||
import { lsdir, readfile } from 'fs';
|
||||
|
||||
const boardinfo = ubus.call('system', 'board') ?? {};
|
||||
const boardinfo = prefetched_boardinfo ?? (ubus.call('system', 'board') ?? {});
|
||||
const hostname = striptags(boardinfo.hostname ?? '?');
|
||||
const uci = cursor();
|
||||
const tokens = uci.get_all('aurora', 'theme') || {};
|
||||
const tokens = prefetched_tokens ?? (uci.get_all('aurora', 'theme') || {});
|
||||
|
||||
const page_segs = length(ctx?.request_path) ? ctx.request_path : (ctx?.path || []);
|
||||
const page = join('-', page_segs);
|
||||
|
||||
@@ -14,7 +14,11 @@ const themeTokens = cursor().get_all('aurora', 'theme') || {};
|
||||
const loginLogoSvg = themeTokens.logo_svg || 'logo.svg';
|
||||
const loginIconVer = themeTokens.icon_cache_version || '0';
|
||||
-%}
|
||||
{% include('header', { blank_page: true }) %}
|
||||
{% include('header', {
|
||||
blank_page: true,
|
||||
prefetched_boardinfo: boardinfo,
|
||||
prefetched_tokens: themeTokens
|
||||
}) %}
|
||||
|
||||
{% if (themeTokens.struct_login_bg): %}
|
||||
<div class="login-bg" aria-hidden="true"></div>
|
||||
|
||||