🎈 Sync 2026-06-16 01:15:58

This commit is contained in:
github-actions[bot] committed 2026-06-16 01:15:58 +08:00
1 parent 26e9df1a69
commit bac0d35f6b
30 files changed
+295 -101

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-daede
PKG_VERSION:=1.12.0
PKG_RELEASE:=9
PKG_RELEASE:=10
PKG_MAINTAINER:=kenzok8
PKG_BUILD_DIR:=$(BUILD_DIR)/$(PKG_NAME)
@@ -139,9 +139,9 @@ return view.extend({
]);
};
// run a backgrounded script, stream its log, then toast the result.
// Scripts end their log with "done (rc=N)"; poll until that appears.
const runJob = function(script, arg, btn, logPath, label) {
// run a backgrounded script and stream its log into the log pane until it
// logs "done (rc=N)"; then refresh the badges. Result shows inline (no popup).
const runJob = function(script, arg, btn, logPath) {
const orig = btn.textContent;
btn.disabled = true;
btn.textContent = '...';
@@ -149,20 +149,13 @@ return view.extend({
const poll = function() {
return L.resolveDefault(fs.read_direct(logPath, 'text'), '').then(function(c) {
if (c) { logPane.textContent = c; logPane.classList.add('show'); }
const m = c.match(/done \(rc=(\d+)\)/);
if (m) {
const ok = m[1] === '0';
ui.addNotification(null, E('p', label + ' · ' + (ok ? _('done') : _('failed'))), ok ? null : 'danger');
refresh();
return;
}
if (/done \(rc=\d+\)/.test(c)) { refresh(); return; }
if (tries++ > 90) return;
return new Promise(function(r) { setTimeout(r, 2000); }).then(poll);
});
};
return fs.exec('/usr/share/luci-app-daede/' + script, [arg]).then(function(res) {
if (res.code !== 0) { ui.addNotification(null, E('p', label + ' · ' + _('failed to start')), 'danger'); return; }
return poll();
if (res.code === 0) return poll();
}).catch(function() {}).finally(function() {
btn.disabled = false;
btn.textContent = orig;
@@ -171,16 +164,12 @@ return view.extend({
// === Data updates (GeoIP / GeoSite) ===
const updateGeo = function(kind, btn) {
return runJob('update-geo.sh', kind, btn,
'/tmp/luci-app-daede.' + kind + '.log', kind === 'geoip' ? 'GeoIP' : 'GeoSite');
return runJob('update-geo.sh', kind, btn, '/tmp/luci-app-daede.' + kind + '.log');
};
// === Package updates (dae|daed / luci-app-daede) ===
const upgradePkg = function(pkg, btn) {
if (!confirm(_('Upgrade %s now? This may restart the active backend.').format(pkg)))
return;
return runJob('update-pkg.sh', pkg, btn,
'/tmp/luci-app-daede.pkg-' + pkg + '.log', pkg);
return runJob('update-pkg.sh', pkg, btn, '/tmp/luci-app-daede.pkg-' + pkg + '.log');
};
const refresh = function() {
@@ -10,8 +10,9 @@ fi
: > "$LOCK"
(
# -n: skip if a package op holds the shared apk lock (no need to refresh then)
if command -v apk >/dev/null 2>&1; then
apk update
flock -n /tmp/luci-app-daede.apk.lock apk update
elif command -v opkg >/dev/null 2>&1; then
opkg update
fi
@@ -38,15 +38,22 @@ fi
echo "$(date '+%F %T') begin upgrade: $PKG"
if command -v apk >/dev/null 2>&1; then
echo "--- apk update ---"
apk update 2>&1
echo "--- apk add $PKG ---"
# Target only $PKG (not `apk upgrade`, which re-solves the whole world).
apk add "$PKG" 2>&1
# Don't trust apk's exit code: apk-tools 3 returns non-zero whenever ANY
# unrelated installed package has an unavailable .apk in the configured
# feeds, even when $PKG itself upgraded fine. Judge success by state —
# $PKG must be installed and have no pending upgrade left.
# shared apk lock with the bg index refresh (avoid "Unable to lock database")
(
flock 9
apk update 2>&1
# pin exact latest version + --force-broken-world so unrelated broken
# packages can't block this upgrade (cf. clashoo component_update)
ver=$(apk list "$PKG" 2>/dev/null | awk -v p="$PKG" '$1 ~ "^" p "-[0-9]" { v=$1; sub("^" p "-", "", v); print v }' | sort -V | tail -1)
if [ -n "$ver" ]; then
echo "--- apk add $PKG=$ver ---"
apk add "$PKG=$ver" --force-broken-world 2>&1
else
echo "--- apk add $PKG ---"
apk add "$PKG" --force-broken-world 2>&1
fi
) 9>/tmp/luci-app-daede.apk.lock
# apk's exit code is unreliable (broken-world noise); judge by state instead
if ! apk list --installed 2>/dev/null | grep -q "^${PKG}-"; then
echo "result: $PKG is not installed"
rc=1
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12h4l3 8l4 -16l3 8h4" /></svg>

After

Width:  |  Height:  |  Size: 224 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 10a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M6 4v4" /><path d="M6 12v8" /><path d="M10 16a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M12 4v10" /><path d="M12 18v2" /><path d="M16 7a2 2 0 1 0 4 0a2 2 0 0 0 -4 0" /><path d="M18 4v1" /><path d="M18 9v11" /></svg>

After

Width:  |  Height:  |  Size: 452 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 5a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" /><path d="M4 15a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" /><path d="M14 15a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v4a1 1 0 0 1 -1 1h-4a1 1 0 0 1 -1 -1l0 -4" /><path d="M14 7l6 0" /><path d="M17 4l0 6" /></svg>

After

Width:  |  Height:  |  Size: 502 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 4h6v6h-6l0 -6" /><path d="M14 4h6v6h-6l0 -6" /><path d="M4 14h6v6h-6l0 -6" /><path d="M14 17a3 3 0 1 0 6 0a3 3 0 1 0 -6 0" /></svg>

After

Width:  |  Height:  |  Size: 325 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 6a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1l0 -12" /><path d="M9 9h6v6h-6l0 -6" /><path d="M3 10h2" /><path d="M3 14h2" /><path d="M10 3v2" /><path d="M14 3v2" /><path d="M21 10h-2" /><path d="M21 14h-2" /><path d="M14 21v-2" /><path d="M10 21v-2" /></svg>

After

Width:  |  Height:  |  Size: 478 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3l8 4.5l0 9l-8 4.5l-8 -4.5l0 -9l8 -4.5" /><path d="M12 12l8 -4.5" /><path d="M12 12l0 9" /><path d="M12 12l-8 -4.5" /><path d="M16 5.25l-8 4.5" /></svg>

After

Width:  |  Height:  |  Size: 347 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 15a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v4a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2l0 -4" /><path d="M17 17l0 .01" /><path d="M13 17l0 .01" /><path d="M15 13l0 -2" /><path d="M11.75 8.75a4 4 0 0 1 6.5 0" /><path d="M8.5 6.5a8 8 0 0 1 13 0" /></svg>

After

Width:  |  Height:  |  Size: 430 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3v-2" /><path d="M3 15a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v2a3 3 0 0 1 -3 3h-12a3 3 0 0 1 -3 -3l0 -2" /><path d="M7 8l0 .01" /><path d="M7 16l0 .01" /><path d="M11 8h6" /><path d="M11 16h6" /></svg>

After

Width:  |  Height:  |  Size: 457 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065" /><path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0" /></svg>

After

Width:  |  Height:  |  Size: 770 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6.5 7.5a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M3 6v5.172a2 2 0 0 0 .586 1.414l7.71 7.71a2.41 2.41 0 0 0 3.408 0l5.592 -5.592a2.41 2.41 0 0 0 0 -3.408l-7.71 -7.71a2 2 0 0 0 -1.414 -.586h-5.172a3 3 0 0 0 -3 3" /></svg>

After

Width:  |  Height:  |  Size: 411 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 18 0a9 9 0 0 0 -18 0" /><path d="M3.6 9h16.8" /><path d="M3.6 15h16.8" /><path d="M11.5 3a17 17 0 0 0 0 18" /><path d="M12.5 3a17 17 0 0 1 0 18" /></svg>

After

Width:  |  Height:  |  Size: 360 B

+102 -54
View File
@@ -1,5 +1,5 @@
header {
@apply bg-bg sticky top-0 z-60 mb-2;
@apply bg-bg sticky top-0 z-40 mb-2;
& .header-content {
@apply relative flex h-14 items-center justify-between px-6 py-3 max-md:px-4 max-md:py-2;
@@ -16,84 +16,130 @@ header {
/* Category panels live inside the container, not the nav items: the
transformed #topmenu would otherwise become the containing block of
a position:fixed panel and shrink the canvas to the menu's width.
Fixed coordinates shared by every category, so switching panels
never shifts anything: link columns start at 50% - 13rem (just
under the centered nav triggers), the anchor column sits at a
fixed slot left of them. The entering panel floats up 6px —
transform + opacity only. */
Each panel is the same centered three-column grid (max-width fixed),
so switching categories is a pure cross-fade with no width shift.
The entering panel floats up 6px — transform + opacity only. */
/* The server-rendered board is only a template that menu-aurora.js
clones into each panel's right column — the original stays hidden. */
& > .desktop-menu-board {
@apply hidden;
}
& .desktop-nav {
@apply pointer-events-none absolute inset-x-0 top-14 min-h-60 translate-y-1.5 pt-7 pb-8 pl-[calc(50%-13rem)] opacity-0 transition-[opacity,transform] duration-[250ms] ease-out;
/* Centered three-column canvas: anchor / links / device board.
Fixed max-width keeps every category the same width, so switching
is a pure cross-fade. Below lg the side tracks drop and only the
links column remains (tablet → just the submenu). The panel scrolls
internally when its content exceeds the viewport below the header. */
@apply pointer-events-none absolute inset-x-0 top-14 mx-auto grid max-h-[calc(100dvh-3.5rem)] max-w-[72rem] translate-y-1.5 grid-cols-[12rem_minmax(0,1fr)_13rem] gap-x-8 overflow-y-auto overscroll-contain px-10 pt-7 pb-8 opacity-0 transition-[opacity,transform] duration-[250ms] ease-out max-lg:max-w-[44rem] max-lg:grid-cols-1 max-lg:px-8;
&.active {
@apply pointer-events-auto translate-y-0 opacity-100;
}
& .desktop-nav-anchor {
@apply absolute top-7 left-[calc(50%-30rem)] w-56 max-lg:hidden;
@apply self-start max-lg:hidden;
& .desktop-nav-title {
@apply text-text text-2xl font-semibold tracking-tight;
@apply text-text flex items-center gap-2.5 text-2xl font-semibold tracking-tight;
/* First-level icon: mask + currentColor so it follows the theme.
--menu-icon is set per node name below; unmapped → default. */
&::before {
@apply size-6 shrink-0 bg-current content-[''] [mask:var(--menu-icon,url('@assets/icons/category.svg'))_center/contain_no-repeat];
}
}
/* name → icon map (the only maintenance point). Keyed on the LuCI
node name, not the translated title. Add new first-level
sections here; anything unmapped keeps the default icon. */
& .desktop-nav-title[data-section="status"] {
@apply [--menu-icon:url('@assets/icons/activity.svg')];
}
& .desktop-nav-title[data-section="system"] {
@apply [--menu-icon:url('@assets/icons/settings.svg')];
}
& .desktop-nav-title[data-section="services"] {
@apply [--menu-icon:url('@assets/icons/apps.svg')];
}
& .desktop-nav-title[data-section="nas"] {
@apply [--menu-icon:url('@assets/icons/server-2.svg')];
}
& .desktop-nav-title[data-section="control"] {
@apply [--menu-icon:url('@assets/icons/adjustments.svg')];
}
& .desktop-nav-title[data-section="network"] {
@apply [--menu-icon:url('@assets/icons/world.svg')];
}
}
& .desktop-nav-list {
/* Vertical fill: columns of equal height, no orphan rows. Row
count is set per submenu via --menu-rows in menu-aurora.js.
w-max is load-bearing: without it the block-level list fills
the viewport and justify-content:normal stretches the auto
column tracks across all of it. */
@apply grid w-max grid-flow-col grid-rows-[repeat(var(--menu-rows,6),auto)] content-start gap-x-8 gap-y-1;
/* Vertical fill, content-width columns. Row count per submenu via
--menu-rows (menu-aurora.js); more items grow rows, not width,
so the list stays inside the middle track. */
@apply grid grid-flow-col grid-rows-[repeat(var(--menu-rows,6),auto)] content-start justify-start gap-x-8 gap-y-1;
& > li {
@apply m-0 list-none;
& > a {
@apply text-text block w-fit py-2.5 text-sm whitespace-nowrap no-underline hover:underline hover:decoration-2 hover:underline-offset-4;
@apply text-text hover:bg-hover-faint block w-fit rounded-lg px-3 py-2 text-sm whitespace-nowrap no-underline transition-colors duration-150;
&.is-active-page {
@apply bg-brand-subtle text-brand font-medium;
}
}
}
}
}
/* Constant part of the canvas, Apple-flyout style: the device
summary lives once at container level, pinned just below the
fixed category-title slot (header 3.5rem + panel pt 1.75rem +
title line 2rem + 1.25rem gap = top-34). Its position never
depends on an individual panel's height, and switching categories
never re-fades it. */
& .desktop-menu-board {
@apply absolute top-34 left-[calc(50%-30rem)] flex w-56 flex-col gap-2 max-lg:hidden;
/* Right column: device summary, cloned per panel. self-start so it
sits at the top regardless of how tall the links column grows. */
& .desktop-menu-board {
@apply border-hairline flex flex-col gap-2 self-start border-l pl-7 max-lg:hidden;
& .board-line {
@apply text-text-muted flex max-w-full items-center gap-2.5 text-xs;
&::before {
@apply size-4 shrink-0 bg-current content-[''];
& .board-label {
@apply text-text-subtle mb-0.5 text-[11px] font-medium tracking-wide uppercase;
}
& .board-text {
@apply min-w-0 truncate;
& .board-line {
/* items-start keeps the icon aligned to the FIRST text line when a
long value wraps. Icon height (size-4) == text leading (leading-4)
so a single line still reads as vertically centered. */
@apply text-text-muted flex max-w-full items-start gap-2.5 text-xs;
&::before {
@apply size-4 shrink-0 bg-current content-[''];
}
& .board-text {
@apply min-w-0 leading-4 [overflow-wrap:anywhere];
}
}
}
& .board-line-host::before {
@apply [mask:url('@assets/icons/server.svg')_center/contain_no-repeat];
}
& .board-line-host::before {
@apply [mask:url('@assets/icons/router.svg')_center/contain_no-repeat];
}
& .board-line-model::before {
@apply [mask:url('@assets/icons/cpu-chip.svg')_center/contain_no-repeat];
}
& .board-line-model::before {
@apply [mask:url('@assets/icons/cpu.svg')_center/contain_no-repeat];
}
& .board-line-firmware::before {
@apply [mask:url('@assets/icons/cube.svg')_center/contain_no-repeat];
}
& .board-line-firmware::before {
@apply [mask:url('@assets/icons/package.svg')_center/contain_no-repeat];
}
& .board-line-kernel::before {
@apply [mask:url('@assets/icons/cog.svg')_center/contain_no-repeat];
& .board-line-kernel::before {
@apply [mask:url('@assets/icons/tag.svg')_center/contain_no-repeat];
}
}
}
}
}
[data-nav-type="mega-menu"] &:has(.desktop-menu-container.active) {
@apply z-70;
}
.brand {
@apply hover:text-brand text-text inline-block shrink-0 text-xl font-semibold tracking-tight no-underline transition-[color,transform] duration-150 hover:-translate-y-0.5 max-md:flex-1 max-md:text-lg;
}
@@ -105,16 +151,12 @@ header {
@apply relative;
.menu {
@apply text-text block rounded-xl px-3.5 py-1.5 font-medium no-underline transition-colors duration-150;
@apply text-text hover:bg-hover-faint block rounded-xl px-3.5 py-1.5 font-medium no-underline transition-colors duration-150;
/* Unified pill for both dropdown modes — the open/active category is a
filled brand pill, matching the submenu links and the sidebar. */
&.menu-active {
@apply text-text;
[data-nav-type="mega-menu"] & {
@apply underline decoration-2 underline-offset-4;
}
[data-nav-type="boxed-dropdown"] & {
@apply bg-brand-subtle text-brand;
}
@apply bg-brand-subtle text-brand;
}
}
@@ -140,7 +182,13 @@ header {
@apply text-text block whitespace-nowrap no-underline;
[data-nav-type="boxed-dropdown"] & {
@apply hover:bg-hover-faint mx-2 rounded-xl px-4 py-2;
@apply hover:bg-hover-faint mx-2 rounded-xl px-4 py-2 transition-colors duration-150;
}
/* Same active pill as the mega submenu and the top-level
trigger — one unified vocabulary across both dropdown modes. */
&.is-active-page {
@apply bg-brand-subtle text-brand font-medium;
}
}
}
@@ -310,7 +358,7 @@ body[data-nav-type="sidebar"] {
@apply max-w-max-width mx-auto min-h-[calc(100vh-4rem)] w-23/24 px-4 max-md:w-full max-md:px-3;
#view {
@apply animate-in fade-in-0 slide-in-from-top-2 fill-mode-both mx-0 w-full bg-transparent p-0 shadow-none empty:hidden md:p-0;
@apply mx-0 w-full bg-transparent p-0 shadow-none empty:hidden md:p-0;
.cbi-title-section {
@apply mb-6 leading-relaxed max-md:mx-2 max-md:mb-3;
@@ -0,0 +1,25 @@
/**
* Reduced-transparency fallback.
*
* When the user asks for less transparency, drop the most expensive layer —
* backdrop-filter blur — everywhere, and turn the one surface that relies on
* "translucent + blur" to stay readable (the mega-menu backdrop) opaque, so
* removing the blur doesn't let the page content bleed through it.
*
* surface-overlay / surface are already opaque, so dropping their blur is
* harmless; scrim keeps its translucency on purpose (it's a dimming layer).
* --mega-menu-bg points at surface-overlay via var() so it tracks light/dark
* automatically (resolved at the point of use, not here).
*/
@media (prefers-reduced-transparency: reduce) {
*,
*::before,
*::after {
/* standard property only — lightningcss adds the -webkit- prefix per browserslist */
@apply [backdrop-filter:none]!;
}
:root {
--mega-menu-bg: var(--surface-overlay);
}
}
@@ -50,7 +50,7 @@
}
&.dropdown {
@apply border-hairline bg-surface-overlay absolute left-0 z-60 w-fit min-w-full overflow-y-auto rounded-lg border shadow-xl backdrop-blur-md backdrop-saturate-150;
@apply border-hairline bg-surface-overlay absolute left-0 z-50 w-fit min-w-full overflow-y-auto rounded-lg border shadow-xl backdrop-blur-md backdrop-saturate-150;
& > li {
@apply text-text-muted hover:bg-brand-subtle min-h-9 w-full cursor-pointer px-3 py-1.5 font-medium;
@@ -1,5 +1,5 @@
.alert-message {
@apply bg-surface-sunken text-text border-hairline sticky top-14 z-40 mb-4 rounded-4xl border p-6 max-md:mx-0 max-md:mb-3 max-md:rounded-3xl max-md:p-4;
@apply bg-surface-sunken text-text border-hairline sticky top-14 z-30 mb-4 rounded-4xl border p-6 max-md:mx-0 max-md:mb-3 max-md:rounded-3xl max-md:p-4;
&.modal {
@apply static top-auto;
@@ -70,7 +70,7 @@ body.mobile-navigation-open > header {
[data-nav-type="mega-menu"] {
& .desktop-menu-overlay {
@apply bg-scrim pointer-events-none fixed inset-0 z-50 opacity-0 transition-opacity duration-[250ms] max-md:hidden;
@apply bg-scrim pointer-events-none fixed inset-0 z-60 opacity-0 transition-opacity duration-[250ms] max-md:hidden;
&.active {
@apply pointer-events-auto opacity-100 backdrop-blur-lg backdrop-saturate-150;
@@ -42,3 +42,4 @@
@import "./_utilities.css";
@import "./_patches.css";
@import "./_reduced-motion.css";
@import "./_reduced-transparency.css";
@@ -255,11 +255,17 @@ return baseclass.extend({
if (!children.length) return E([]);
children.forEach((child) => {
ul.appendChild(
E("li", {}, [
E("a", { href: L.url(url, child.name) }, [_(child.title)]),
]),
);
// Mark the current page so it gets the active pill — same vocabulary
// as the top-level trigger (.menu-active). tree is the section node,
// so tree.name is its dispatch segment.
const isActive = this.isActivePath(tree.name, child.name);
const attributes = {
class: isActive ? "is-active-page" : "",
href: L.url(url, child.name),
};
if (isActive) attributes["aria-current"] = "page";
ul.appendChild(E("li", {}, [E("a", attributes, [_(child.title)])]));
});
}
@@ -515,17 +521,32 @@ return baseclass.extend({
const children = [list];
if (document.body?.dataset?.navType === "mega-menu") {
// Constant canvas: links fill top-to-bottom; capping at 4 columns
// keeps the canvas height stable until a submenu exceeds 24 items.
// Constant canvas: links fill top-to-bottom. Base column count is 4;
// more items grow the row count, not the width, so the list stays
// inside the three-column canvas middle track.
list.style.setProperty(
"--menu-rows",
Math.max(6, Math.ceil(submenu.length / 4)),
);
// data-section keys the first-level icon off the node name (stable,
// language-independent) — see _layout.css. Unmapped names fall back
// to the default icon via var(--menu-icon, …).
children.unshift(
E("div", { class: "desktop-nav-anchor" }, [
E("span", { class: "desktop-nav-title" }, [_(child.title)]),
E(
"span",
{ class: "desktop-nav-title", "data-section": child.name },
[_(child.title)],
),
]),
);
// Right column: clone the server-rendered device board into each
// panel so the grid can lay it out as the third track. The original
// stays hidden as the template (see _layout.css).
const board = document.querySelector(
".desktop-menu-container > .desktop-menu-board",
);
if (board) children.push(board.cloneNode(true));
}
nav = E("div", { class: "desktop-nav" }, children);
@@ -570,7 +591,7 @@ return baseclass.extend({
let maxPanel = 0;
header
.querySelectorAll(".desktop-nav")
.forEach((nav) => (maxPanel = Math.max(maxPanel, nav.scrollHeight)));
.forEach((nav) => (maxPanel = Math.max(maxPanel, nav.offsetHeight)));
canvasHeight = headerHeight + maxPanel;
}
container?.style.setProperty("--mega-menu-height", `${canvasHeight}px`);
@@ -385,6 +385,30 @@ test("renders an active group expanded with active page semantics", () => {
assert.equal(activeLink.getAttribute("aria-current"), "page");
});
test("renders the active desktop submenu link with current-page semantics", () => {
const menu = loadMenuModule({
dispatchpath: ["admin", "network", "wireless"],
});
const list = menu.renderMainMenu(
{
name: "network",
children: {
interfaces: { title: "Interfaces" },
wireless: { title: "Wireless" },
},
},
"admin/network",
1,
);
const [inactiveItem, activeItem] = list.children;
const inactiveLink = inactiveItem.children[0];
const activeLink = activeItem.children[0];
assert.equal(inactiveLink.hasAttribute("aria-current"), false);
assert.equal(activeLink.getAttribute("class"), "is-active-page");
assert.equal(activeLink.getAttribute("aria-current"), "page");
});
test("renders an inactive group collapsed and inert", () => {
const menu = loadMenuModule();
const item = menu.renderNavigationItem(
@@ -506,6 +530,13 @@ test("skips mega-menu initialization when the top menu is missing", () => {
assert.ok(result instanceof FakeElement);
});
test("measures mega-menu canvas from the viewport-bounded panel height", () => {
const initMegaMenu = getMethodSource("initMegaMenu");
assert.match(initMegaMenu, /nav\.offsetHeight/);
assert.doesNotMatch(initMegaMenu, /nav\.scrollHeight/);
});
test("skips boxed-dropdown initialization when the top menu is missing", () => {
const menu = loadMenuModule({
document: createFakeDocument({ navType: "boxed-dropdown" }),
@@ -157,3 +157,22 @@ test("mobile drawer styles only provide mobile navigation density", () => {
);
assert.doesNotMatch(drawer, /bg-brand-subtle/);
});
test("mega-menu panels scroll within the viewport", () => {
const megaMenu = getBlock(layoutStyles, '[data-nav-type="mega-menu"] &');
const panel = getBlock(megaMenu, "& .desktop-nav");
assert.ok(panel.includes("max-h-[calc(100dvh-3.5rem)]"));
assertIncludesUtilities(panel, ["overflow-y-auto", "overscroll-contain"]);
});
test("mega-menu category masks use Tailwind arbitrary utilities", () => {
const title = getBlock(layoutStyles, "& .desktop-nav-title");
const icon = getBlock(title, "&::before");
assert.match(
icon,
/@apply[^;]*\[mask:var\(--menu-icon,url\(["']@assets\/icons\/category\.svg["']\)\)_center\/contain_no-repeat\]/,
);
assert.doesNotMatch(layoutStyles, /^\s*mask\s*:/m);
});
@@ -70,6 +70,47 @@ test("maincontent cards use a hairline border, not heavy shadow", () => {
assert.ok(!rule.includes("shadow-lg"), `still shadow-lg: ${rule}`);
});
test("main view does not create an animation stacking context", () => {
const layout = read("../src/media/_layout.css");
const rule = layout.match(/#view\s*\{\s*@apply\s+([^;]+);/)?.[1] ?? "";
for (const utility of [
"animate-in",
"fade-in-0",
"slide-in-from-top-2",
"fill-mode-backwards",
"fill-mode-both",
]) {
assert.ok(!rule.includes(utility), `view still uses ${utility}: ${rule}`);
}
});
test("content dropdowns stay above the closed header and below the open mega-menu", () => {
const layer = (value) => ["z", value].join("-");
const layout = read("../src/media/_layout.css");
const dropdown = read("../src/media/components/_dropdown.css");
const message = read("../src/media/components/_message.css");
const overlay = read("../src/media/components/_overlay.css");
const headerRule = layout.match(/^header\s*\{\s*@apply\s+([^;]+);/m)?.[1] ?? "";
const activeHeaderRule =
layout.match(
/\[data-nav-type="mega-menu"\] &:has\(\.desktop-menu-container\.active\)\s*\{\s*@apply\s+([^;]+);/,
)?.[1] ?? "";
const dropdownRule = dropdown.match(/&\.dropdown\s*\{\s*@apply\s+([^;]+);/)?.[1] ?? "";
const messageRule = message.match(/\.alert-message\s*\{\s*@apply\s+([^;]+);/)?.[1] ?? "";
const overlayRule =
overlay.match(/& \.desktop-menu-overlay\s*\{\s*@apply\s+([^;]+);/)?.[1] ?? "";
assert.ok(messageRule.includes(layer(30)), `message layer changed: ${messageRule}`);
assert.ok(headerRule.includes(layer(40)), `closed header layer changed: ${headerRule}`);
assert.ok(dropdownRule.includes(layer(50)), `dropdown layer changed: ${dropdownRule}`);
assert.ok(overlayRule.includes(layer(60)), `menu overlay layer changed: ${overlayRule}`);
assert.ok(
activeHeaderRule.includes(layer(70)),
`open mega-menu layer changed: ${activeHeaderRule}`,
);
});
test("tables get a hairline frame + sunken header, body stays unclipped", () => {
const t = read("../src/media/components/_table.css");
const root = t.match(/table\.table,\s*\.table\s*\{\s*@apply\s+([^;]+);/)?.[1] ?? "";
+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:=0.12.10
PKG_RELEASE:=27
PKG_VERSION:=0.12.11
PKG_RELEASE:=28
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
@@ -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(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
@@ -213,7 +213,7 @@
trim(`${boardinfo.release?.distribution ?? ''} ${boardinfo.release?.version ?? ''}`);
-%}
<div class="desktop-menu-container">
<div class="desktop-menu-board" aria-hidden="true">
<div class="desktop-menu-board" aria-hidden="true"><span class="board-label">{{ _('Device') }}</span>
<span class="board-line board-line-host" title="{{ entityencode(striptags(boardinfo.hostname ?? '?'), true) }}"><span class="board-text">{{ striptags(boardinfo.hostname ?? '?') }}</span></span>
{% if (board_model): %}
<span class="board-line board-line-model" title="{{ entityencode(striptags(board_model), true) }}"><span class="board-text">{{ striptags(board_model) }}</span></span>