🎈 Sync 2026-07-01 08:46:33

This commit is contained in:
github-actions[bot] committed 2026-07-01 08:46:33 +08:00
1 parent e5346defb9
commit 2e9ba9e02c
26 files changed
+311 -141

No files matched your search

+38 -5
View File
@@ -87,16 +87,48 @@ 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.), `_utilities.css`, and `_patches.css`.
- **`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.
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).
**Adding new styles:**
- New UI component → create `components/_<name>.css` and add an `@import` line to `main.css`. Each file is its own organizational unit — no `@layer` wrappers needed (any that remain are stripped by PostCSS).
- Compatibility fix for a third-party LuCI app/page → add a narrow, selector-scoped rule to `_patches.css` under a comment naming the app (e.g. `/* luci-app-openclash */`).
- Compatibility fix for a third-party LuCI app/page → add a new file under `media/patches/` (see below).
All rules use `@apply` with Tailwind utilities and CSS Nesting — no raw CSS properties.
### On-Demand Third-Party Patches
Some third-party LuCI apps ship markup that doesn't adapt to the theme and needs a narrow compatibility override. Instead of bundling every such patch into `main.css` (which would ship them to **every** page), each patch is a standalone CSS file loaded **only on the page it targets**.
**How it works:**
1. **One file per page, named by `data-page`.** Each patch lives at `media/patches/<page>.css`, where `<page>` is the value of `<body data-page="…">` for the target page — i.e. the request path segments joined by `-` (e.g. `admin-services-openclash-config`). `header.ut` computes the same string at render time from `ctx.request_path`, falling back to `ctx.path` when `request_path` is empty (`join('-', length(ctx.request_path) ? ctx.request_path : ctx.path)`) so default landings reached without an explicit path still resolve their patch.
2. **Build splits, not bundles.** `vite.config.ts` adds every `media/patches/*.css` as its own Rollup entry, so each compiles to `htdocs/luci-static/aurora/patches/<page>.css`. They are no longer part of `main.css`.
3. **`@reference`, not `@import`.** Every patch file starts with `@reference "../main.css";`. This loads the theme context (tokens, custom utilities like `bg-surface`, the `dark:` variant) so `@apply` resolves — **without** re-emitting `main.css` into the patch output. Using `@import` here would inline all of `main.css` into every patch (hundreds of KB); `@reference` keeps each patch to just its own rules.
4. **`header.ut` links exactly one patch.** A static allow-list `PATCH_PAGES` in `header.ut` lists which pages have a patch file. When the current page is in the list, header emits a single `<link>` to its patch file, right after `main.css` (so patches can override base styles). Pages not in the list get nothing — no extra request, no 404. A static list is used (rather than probing the filesystem) to avoid a hard `fs` dependency in the template and to avoid 404s on the ~95% of pages with no patch.
**Adding a patch:**
1. Open the target page in the browser and read `document.body.dataset.page` — that exact string is your filename.
2. Create `media/patches/<that-string>.css`:
```css
/* PATCH: <page> (luci-app-foo) */
@reference "../main.css";
[data-page="<page>"] {
/* narrow, selector-scoped overrides using @apply + CSS Nesting */
}
```
3. Run `pnpm build` (or just `pnpm gen:patch-pages`). The `PATCH_PAGES` allow-list in `header.ut` is **auto-generated** from the `patches/` directory by `scripts/gen-patch-pages.js` — no manual editing. It rewrites the region between the `//#patch-pages-start` / `//#patch-pages-end` markers; don't hand-edit inside them.
4. Verify `htdocs/luci-static/aurora/patches/<page>.css` is small (just your rules, not a copy of `main.css`).
> Removing a patch is symmetric: delete the file and rebuild — it drops out of `PATCH_PAGES` automatically.
> **Naming notes:** match the page exactly — granularity is per page, not per app. An app with several pages (e.g. openclash `…-config` and `…-settings`) gets one file per page. The filename has no `_` prefix (unlike the `_`-prefixed partials, which are `@import`-only fragments); patch files are real build entries that ship to `htdocs/`.
### Design Tokens
`src/media/_tokens.css` is **generated** — its header says "DO NOT EDIT". The source of truth is `.dev/tokens/`:
@@ -247,8 +279,8 @@ luci-theme-aurora/
│ │ │ ├── _elements.css # Base element styles (headings, links, …)
│ │ │ ├── _layout.css # Page layout/structure
│ │ │ ├── _utilities.css # Custom utility classes
│ │ │ ├── _patches.css # Third-party LuCI app/page overrides
│ │ │ └── components/ # One partial per UI component
│ │ │ ├── components/ # One partial per UI component
│ │ │ └── patches/ # Per-page third-party patches (on-demand, one file per data-page)
│ │ └── resource/ # JavaScript resources
│ │ └── menu-aurora.js # Menu logic
│ ├── tokens/ # Design token source (-> src/media/_tokens.css)
@@ -278,7 +310,8 @@ luci-theme-aurora/
│ │ ├── fonts/ # Built font files
│ │ ├── images/ # Built images + PWA icons
│ │ ├── main.css # Compiled admin UI CSS
│ │ └── login.css # Compiled login page CSS
│ │ ├── login.css # Compiled login page CSS
│ │ └── patches/ # Compiled per-page patches (linked on demand by header.ut)
│ └── resources/ # Built JavaScript modules
│ └── menu-aurora.js # Minified menu logic
├── root/etc/uci-defaults/ # OpenWrt system integration
+2 -1
View File
@@ -4,9 +4,10 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "npm run clean && npm run gen:tokens && vite build",
"build": "npm run clean && npm run gen:tokens && npm run gen:patch-pages && vite build",
"clean": "node scripts/clean.js",
"gen:tokens": "node scripts/gen-tokens.js",
"gen:patch-pages": "node scripts/gen-patch-pages.js",
"test": "node --test tests/*.test.js"
},
"devDependencies": {
@@ -0,0 +1,47 @@
import { readdirSync } from "node:fs";
import { readFile, writeFile } from "node:fs/promises";
import { resolve } from "node:path";
// Regenerates the PATCH_PAGES allow-list inside header.ut from the set of
// per-page patch files in src/media/patches/. Keeps the on-demand patch loader
// in sync with the patches that actually exist, with no manual bookkeeping.
// Source: src/media/patches/<page>.css -> header.ut //#patch-pages markers
const PATCHES_DIR = resolve(import.meta.dirname, "../src/media/patches");
const HEADER_UT = resolve(
import.meta.dirname,
"../../ucode/template/themes/aurora/header.ut",
);
const MARKER = /\t\/\/#patch-pages-start\n[\s\S]*?\t\/\/#patch-pages-end/;
const pages = readdirSync(PATCHES_DIR)
.filter((f) => f.endsWith(".css"))
.map((f) => f.slice(0, -4))
.sort();
const block =
"\t//#patch-pages-start\n" +
"\tconst PATCH_PAGES = [\n" +
pages.map((p) => `\t\t'${p}',`).join("\n") +
(pages.length ? "\n" : "") +
"\t];\n" +
"\t//#patch-pages-end";
const src = await readFile(HEADER_UT, "utf-8");
if (!MARKER.test(src)) {
console.error(
"gen-patch-pages: //#patch-pages-start.../#patch-pages-end markers not found in header.ut",
);
process.exit(1);
}
const next = src.replace(MARKER, block);
if (next !== src) {
await writeFile(HEADER_UT, next, "utf-8");
console.log(`gen-patch-pages: wrote ${pages.length} page(s) to header.ut`);
} else {
console.log("gen-patch-pages: header.ut already up to date");
}
@@ -1,125 +0,0 @@
/*
PLUGIN/PAGE PATCHES
- Purpose: Compatibility overrides for third-party LuCI apps/pages that don't adapt to the theme.
- Scope: Keep selectors narrow and limited to the specific app/page being patched.
- Note: May need to be revisited when the corresponding plugin updates its markup/styles.
*/
/* admin-network-network */
[data-name="bridge-vlan"] > div {
@apply max-md:overflow-visible!;
}
/* luci-app-filemanager */
#file-manager-container {
@apply overflow-auto;
#status-bar {
@apply bg-surface border-0;
}
}
/* luci-app-openclash */
.diag-style {
@apply max-md:grid max-md:grid-cols-1 max-md:gap-4;
& > div {
@apply flex flex-col items-center gap-2 max-md:w-full! max-md:gap-1;
}
}
[data-page="admin-services-openclash-config"] {
.sub_div {
img {
@apply h-5 w-5;
}
}
}
[data-page="admin-services-openclash-settings"] {
.cbi-value-field {
& .cbi-input-select {
@apply max-md:w-full!;
}
}
}
/* luci-app-statistics */
[data-page="admin-statistics-graphs"] [data-plugin] img {
@apply dark:hue-rotate-150 dark:invert;
}
/* luci-app-modemdata */
[data-page="admin-modem-modemdata-modempreview"] {
& .ifacebox-body {
& .ifacebadge {
@apply w-auto!;
}
}
}
/* luci-mod-dashboard */
.Dashboard {
& > .section-content {
& .dashboard-bg {
&.tr {
@apply rounded-none;
}
@apply bg-surface-overlay not-[.tr]:border-hairline not-[.tr]:hover:border-hairline max-md:bg-surface-overlay not-[.tr]:max-md:border-hairline not-[.tr]:border not-[.tr]:shadow-lg not-[.tr]:hover:shadow-xl not-[.tr]:max-md:border;
/* Dark mode: override custom.css hardcoded light-mode colors on all text nodes */
& span {
@apply dark:text-text!;
}
& td {
@apply dark:text-text!;
}
& th {
@apply dark:text-text!;
}
img[src*=".svg"] {
@apply dark:invert;
}
/* Compact spacing for assoclist cells (carry .td/.th classes) */
& .td {
@apply py-2 max-md:py-1;
}
& .th {
@apply py-2;
}
/* Inline info tables (e.g. DNS): strip card decoration, restore table layout */
& .table:not(.assoclist) {
@apply border-0 rounded-none shadow-none bg-transparent mb-0;
& tr {
@apply hover:bg-transparent max-md:table-row max-md:border-0 max-md:px-0 max-md:py-0;
}
& td {
@apply border-0 px-0 py-0.5 max-md:table-cell;
&:first-child {
@apply pr-2 whitespace-nowrap opacity-70;
}
}
}
}
.title {
h3 {
@apply text-text border-0 pb-4 max-md:mx-0 max-md:pb-2;
}
}
.router-status-wifi {
.wifi-info {
.devices-info {
.tr {
.td {
@apply nth-3:relative nth-3:top-0 nth-3:w-full nth-3:px-0 max-md:flex-[1_1_50%];
}
}
}
}
}
}
}
@@ -40,6 +40,5 @@
@import "./components/_tab.css";
@import "./_utilities.css";
@import "./_patches.css";
@import "./_reduced-motion.css";
@import "./_reduced-transparency.css";
@@ -0,0 +1,74 @@
/*
PATCH: admin-dashboard (luci-mod-dashboard)
Loaded on-demand by header.ut only when data-page matches this filename.
@reference makes @apply / theme utilities resolvable without re-emitting main.css.
*/
@reference "../main.css";
[data-page="admin-dashboard"] {
.Dashboard {
& > .section-content {
& .dashboard-bg {
&.tr {
@apply rounded-none;
}
@apply bg-surface-overlay not-[.tr]:border-hairline not-[.tr]:hover:border-hairline max-md:bg-surface-overlay not-[.tr]:max-md:border-hairline not-[.tr]:border not-[.tr]:shadow-lg not-[.tr]:hover:shadow-xl not-[.tr]:max-md:border;
/* Dark mode: override custom.css hardcoded light-mode colors on all text nodes */
& span {
@apply dark:text-text!;
}
& td {
@apply dark:text-text!;
}
& th {
@apply dark:text-text!;
}
img[src*=".svg"] {
@apply dark:invert;
}
/* Compact spacing for assoclist cells (carry .td/.th classes) */
& .td {
@apply py-2 max-md:py-1;
}
& .th {
@apply py-2;
}
/* Inline info tables (e.g. DNS): strip card decoration, restore table layout */
& .table:not(.assoclist) {
@apply mb-0 rounded-none border-0 bg-transparent shadow-none;
& tr {
@apply hover:bg-transparent max-md:table-row max-md:border-0 max-md:px-0 max-md:py-0;
}
& td {
@apply border-0 px-0 py-0.5 max-md:table-cell;
&:first-child {
@apply pr-2 whitespace-nowrap opacity-70;
}
}
}
}
.title {
h3 {
@apply text-text border-0 pb-4 max-md:mx-0 max-md:pb-2;
}
}
.router-status-wifi {
.wifi-info {
.devices-info {
.tr {
.td {
@apply nth-3:relative nth-3:top-0 nth-3:w-full nth-3:px-0 max-md:flex-[1_1_50%];
}
}
}
}
}
}
}
}
@@ -0,0 +1,14 @@
/*
PATCH: admin-modem-modemdata-modempreview (luci-app-modemdata)
Loaded on-demand by header.ut only when data-page matches this filename.
@reference makes @apply / theme utilities resolvable without re-emitting main.css.
*/
@reference "../main.css";
[data-page="admin-modem-modemdata-modempreview"] {
& .ifacebox-body {
& .ifacebadge {
@apply w-auto!;
}
}
}
@@ -0,0 +1,10 @@
/*
PATCH: admin-network-network (luci-app-network — bridge VLAN)
Loaded on-demand by header.ut only when data-page matches this filename.
@reference makes @apply / theme utilities resolvable without re-emitting main.css.
*/
@reference "../main.css";
[data-name="bridge-vlan"] > div {
@apply max-md:overflow-visible!;
}
@@ -0,0 +1,14 @@
/*
PATCH: admin-services-openclash-config (luci-app-openclash)
Loaded on-demand by header.ut only when data-page matches this filename.
@reference makes @apply / theme utilities resolvable without re-emitting main.css.
*/
@reference "../main.css";
[data-page="admin-services-openclash-config"] {
.sub_div {
img {
@apply h-5 w-5;
}
}
}
@@ -0,0 +1,20 @@
/*
PATCH: admin-services-openclash-settings (luci-app-openclash)
Loaded on-demand by header.ut only when data-page matches this filename.
@reference makes @apply / theme utilities resolvable without re-emitting main.css.
*/
@reference "../main.css";
[data-page="admin-services-openclash-settings"] {
.cbi-value-field {
& .cbi-input-select {
@apply max-md:w-full!;
}
}
.diag-style {
@apply max-md:grid max-md:grid-cols-1 max-md:gap-4;
& > div {
@apply flex flex-col items-center gap-2 max-md:w-full! max-md:gap-1;
}
}
}
@@ -0,0 +1,10 @@
/*
PATCH: admin-statistics-graphs (luci-app-statistics)
Loaded on-demand by header.ut only when data-page matches this filename.
@reference makes @apply / theme utilities resolvable without re-emitting main.css.
*/
@reference "../main.css";
[data-page="admin-statistics-graphs"] [data-plugin] img {
@apply dark:hue-rotate-150 dark:invert;
}
@@ -0,0 +1,15 @@
/*
PATCH: admin-system-filemanager (luci-app-filemanager)
Loaded on-demand by header.ut only when data-page matches this filename.
@reference makes @apply / theme utilities resolvable without re-emitting main.css.
*/
@reference "../main.css";
[data-page="admin-system-filemanager"] {
#file-manager-container {
@apply overflow-auto;
#status-bar {
@apply bg-surface border-0;
}
}
}
+26 -1
View File
@@ -6,7 +6,7 @@
import tailwindcss from "@tailwindcss/vite";
import browserslist from "browserslist";
import { exec } from "child_process";
import { watch as fsWatch } from "fs";
import { watch as fsWatch, readdirSync } from "fs";
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
import { browserslistToTargets } from "lightningcss";
import { basename, dirname, join, relative, resolve } from "path";
@@ -96,12 +96,26 @@ interface ResourceConfig {
js: RouteConfig;
}
// On-demand third-party patches: serve each 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
// edits don't trigger HMR. Mirrors the build entries derived from the same dir.
function patchCssRoutes(): Record<string, string> {
const dir = resolve(CURRENT_DIR, "src/media/patches");
return Object.fromEntries(
readdirSync(dir)
.filter((f) => f.endsWith(".css"))
.map((f) => [`/luci-static/aurora/patches/${f}`, `/src/media/patches/${f}`]),
);
}
function createLocalServePlugin(): Plugin {
const resourceConfig: ResourceConfig = {
css: {
routes: {
"/luci-static/aurora/main.css": "/src/media/main.css",
"/luci-static/aurora/login.css": "/src/media/login.css",
...patchCssRoutes(),
},
shouldRewrite: true,
hmrMessage: "CSS file changed",
@@ -415,6 +429,17 @@ export default defineConfig(({ mode }) => {
input: {
main: resolve(CURRENT_DIR, "src/media/main.css"),
login: resolve(CURRENT_DIR, "src/media/login.css"),
// On-demand third-party patches: one entry per page, output to
// aurora/patches/<page>.css (the `patches/` key prefix lands them there
// via assetFileNames below). header.ut links the matching one per page.
...Object.fromEntries(
readdirSync(resolve(CURRENT_DIR, "src/media/patches"))
.filter((f) => f.endsWith(".css"))
.map((f) => [
`patches/${f.slice(0, -4)}`,
resolve(CURRENT_DIR, "src/media/patches", f),
]),
),
},
output: {
assetFileNames: "aurora/[name].[ext]",
+1 -1
View File
@@ -26,7 +26,7 @@ No linter CLI. Formatting uses Prettier with format-on-save (`.vscode/settings.j
- `.dev/public/aurora/` → `htdocs/luci-static/aurora/` (copied as-is)
- `ucode/template/themes/aurora/*.ut` — server-side templates, not processed by Vite
**CSS**: two Tailwind CSS v4 entry points in `.dev/src/media/` — `main.css` (admin UI; a pure import manifest over `_tokens.css`, `_base.css`, `_elements.css`, `_layout.css`, `components/*.css`, `_utilities.css`, `_patches.css`) and `login.css` (standalone login page; imports `_base.css`). All styling MUST use TailwindCSS v4 utility classes via `@apply` — no raw CSS properties (e.g. write `@apply text-sm font-semibold;` not `font-size: 14px; font-weight: 600;`). Use [CSS Nesting syntax](https://drafts.csswg.org/css-nesting/) for selectors. Theme colors defined as OKLCH custom properties in `_tokens.css` and mapped via `@theme inline`. `@layer` at-rules stripped by PostCSS plugin for OpenWrt compatibility. See `.dev/docs/DEVELOPMENT.md` for the full CSS file layout.
**CSS**: two Tailwind CSS v4 entry points in `.dev/src/media/` — `main.css` (admin UI; a pure import manifest over `_tokens.css`, `_base.css`, `_elements.css`, `_layout.css`, `components/*.css`, `_utilities.css`) and `login.css` (standalone login page; imports `_base.css`). Third-party compatibility patches are not bundled into `main.css`; each lives in `media/patches/<data-page>.css`, builds to its own `htdocs/luci-static/aurora/patches/<data-page>.css`, and is linked on demand by `header.ut` (gated on a `PATCH_PAGES` allow-list) — see "On-Demand Third-Party Patches" in `.dev/docs/DEVELOPMENT.md`. All styling MUST use TailwindCSS v4 utility classes via `@apply` — no raw CSS properties (e.g. write `@apply text-sm font-semibold;` not `font-size: 14px; font-weight: 600;`). Use [CSS Nesting syntax](https://drafts.csswg.org/css-nesting/) for selectors. Theme colors defined as OKLCH custom properties in `_tokens.css` and mapped via `@theme inline`. `@layer` at-rules stripped by PostCSS plugin for OpenWrt compatibility. See `.dev/docs/DEVELOPMENT.md` for the full CSS file layout.
**Design tokens**: `_tokens.css` is GENERATED — do not hand-edit (see its header comment). Source of truth is `.dev/tokens/`: `defaults.js` holds the 10 editable input colors per mode (`bg`, `surface`, `text`, `brand`, `on_brand`, `link`, `info`, `warning`, `success`, `danger`); `spec.js` declares how every other token derives from those via `mix`/`shade`/`set`/`alpha`/`const` operators plus `FIXED` literals (shadows); `engine.js`/`resolve.js` do the OKLCH math and flatten everything to plain `oklch(...)` values. After changing inputs or derivations, run `pnpm gen:tokens` to rewrite `_tokens.css`, and `pnpm test` to check the color-math and token invariants.
+2 -2
View File
@@ -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.0.3
PKG_RELEASE:=36
PKG_VERSION:=1.0.4
PKG_RELEASE:=37
PKG_LICENSE:=Apache-2.0
LUCI_MINIFY_CSS:=
@@ -1,2 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--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-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}}: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)}*,: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}:wheLine truncated
@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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--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-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}}: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)}*,: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}:wheLine truncated
@@ -1,2 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--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-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--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}}: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;--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)}*,: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-feLine truncated
@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-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--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-leading:initial;--tw-tracking:initial;--tw-ease:initial;--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--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}}: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;--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)}*,: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-feLine truncated
@@ -0,0 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@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-border-style:solid;--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}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg.tr{border-radius:0}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg{background-color:var(--surface-overlay)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline);--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)}@media (hover:hover){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg:not(.tr):hover{border-color:var(--hairline);--tw-shadow:var(--app-shadow-lg);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg{background-color:var(--surface-overlay)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg:not(.tr){border-style:var(--tw-border-style);border-width:1px;border-color:var(--hairline)}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg span:where([data-darkmode=true],[data-darkmode=true] *),[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg td:where([data-darkmode=true],[data-darkmode=true] *),[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg th:where([data-darkmode=true],[data-darkmode=true] *){color:var(--text)!important}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg img[src*=\.svg]:where([data-darkmode=true],[data-darkmode=true] *){--tw-invert:invert(100%);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*2)}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .td{padding-block:calc(var(--spacing)*1)}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .th{padding-block:calc(var(--spacing)*2)}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist){margin-bottom:calc(var(--spacing)*0);border-style:var(--tw-border-style);--tw-shadow:0 0 #0000;box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);background-color:#0000;border-width:0;border-radius:0}@media (hover:hover){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr:hover{background-color:#0000}}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) tr{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*0);border-width:0;display:table-row}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{border-style:var(--tw-border-style);padding-inline:calc(var(--spacing)*0);padding-block:calc(var(--spacing)*.5);border-width:0}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td{display:table-cell}}[data-page=admin-dashboard] .Dashboard>.section-content .dashboard-bg .table:not(.assoclist) td:first-child{padding-right:calc(var(--spacing)*2);white-space:nowrap;opacity:.7}[data-page=admin-dashboard] .Dashboard>.section-content .title h3{border-style:var(--tw-border-style);padding-bottom:calc(var(--spacing)*4);color:var(--text);border-width:0}@media not all and (min-width:48rem){[data-page=admin-dashboard] .Dashboard>.section-content .title h3{margin-inline:calc(var(--spacing)*0);padding-bottom:calc(var(--spacing)*2)}}[data-page=admin-dashboard] .Dashboard>.section-content .router-status-wifi .wifi-info .devices-info .tr .td:nth-child(3){top:calc(var(--spacing)*0);width:100%;padding-inline:cLine truncated
@@ -0,0 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
[data-page=admin-modem-modemdata-modempreview] .ifacebox-body .ifacebadge{width:auto!important}
@@ -0,0 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@media not all and (min-width:48rem){[data-name=bridge-vlan]>div{overflow:visible!important}}
@@ -0,0 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
[data-page=admin-services-openclash-config] .sub_div img{height:calc(var(--spacing)*5);width:calc(var(--spacing)*5)}
@@ -0,0 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@media not all and (min-width:48rem){[data-page=admin-services-openclash-settings] .cbi-value-field .cbi-input-select{width:100%!important}[data-page=admin-services-openclash-settings] .diag-style{gap:calc(var(--spacing)*4);grid-template-columns:repeat(1,minmax(0,1fr));display:grid}}[data-page=admin-services-openclash-settings] .diag-style>div{align-items:center;gap:calc(var(--spacing)*2);flex-direction:column;display:flex}@media not all and (min-width:48rem){[data-page=admin-services-openclash-settings] .diag-style>div{gap:calc(var(--spacing)*1);width:100%!important}}
@@ -0,0 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@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-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}}[data-page=admin-statistics-graphs] [data-plugin] img:where([data-darkmode=true],[data-darkmode=true] *){--tw-hue-rotate:hue-rotate(150deg);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,);--tw-invert:invert(100%)}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
@@ -0,0 +1,2 @@
/*! tailwindcss v4.1.18 | MIT License | https://tailwindcss.com */
@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-border-style:solid}}[data-page=admin-system-filemanager] #file-manager-container{overflow:auto}[data-page=admin-system-filemanager] #file-manager-container #status-bar{border-style:var(--tw-border-style);background-color:var(--surface);border-width:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}
@@ -13,9 +13,25 @@
import { getuid, getspnam } from 'luci.core';
import { cursor } from 'uci';
const boardinfo = ubus.call('system', 'board');
const boardinfo = ubus.call('system', 'board') ?? {};
const uci = cursor();
const tokens = uci.get_all('aurora', 'theme') || {};
// On-demand third-party patches — see DEVELOPMENT.md. AUTO-GENERATED by
// `pnpm gen:patch-pages`; do not edit between the markers.
//#patch-pages-start
const PATCH_PAGES = [
'admin-dashboard',
'admin-modem-modemdata-modempreview',
'admin-network-network',
'admin-services-openclash-config',
'admin-services-openclash-settings',
'admin-statistics-graphs',
'admin-system-filemanager',
];
//#patch-pages-end
const page = join('-', length(ctx?.request_path) ? ctx.request_path : (ctx?.path || []));
const has_patch = page in PATCH_PAGES;
const nav_type = tokens.nav_type || 'mega-menu';
const toolbar_enabled = tokens.toolbar_enabled ? tokens.toolbar_enabled == '1' : true;
const icon_cache_version = tokens.icon_cache_version || '0';
@@ -138,6 +154,9 @@
<link rel="stylesheet" href="{{ media }}/login.css">
{% else %}
<link rel="stylesheet" href="{{ media }}/main.css">
{% if (has_patch): %}
<link rel="stylesheet" href="{{ media }}/patches/{{ page }}.css">
{% endif %}
{% endif %}
<link rel="stylesheet" href="{{ media }}/fonts/aurora-font.css?v={{ icon_cache_version }}">
<link rel="icon" href="{{ media }}/images/{{ logo_svg }}?v={{ icon_cache_version }}" sizes="any" type="image/svg+xml">
@@ -182,7 +201,7 @@
{% endif %}
</head>
<body class="lang_{{ dispatcher.lang }} {{ entityencode(striptags(node?.title ?? ''), true) }}" data-page="{{ entityencode(join('-', ctx.request_path), true) }}" data-nav-type="{{ nav_type }}">
<body class="lang_{{ dispatcher.lang }} {{ entityencode(striptags(node?.title ?? ''), true) }}" data-page="{{ entityencode(page, true) }}" data-nav-type="{{ nav_type }}">
{% if (!blank_page): %}
<header>
<div class="header-content">
@@ -9,7 +9,7 @@
{%
import { cursor } from 'uci';
const boardinfo = ubus.call('system', 'board');
const boardinfo = ubus.call('system', 'board') ?? {};
const themeTokens = cursor().get_all('aurora', 'theme') || {};
const loginLogoSvg = themeTokens.logo_svg || 'logo.svg';
const loginIconVer = themeTokens.icon_cache_version || '0';