mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-27 01:42:52 +08:00
🍓 Sync 2026-07-02 20:41:59
This commit is contained in:
1 parent
50fbe1704c
commit
14537a500f
21 files changed
+474
-114
No files matched your search
+5
-1
@@ -6,7 +6,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=daed
|
||||
PKG_VERSION:=2026.06.14
|
||||
PKG_RELEASE:=16
|
||||
PKG_RELEASE:=17
|
||||
|
||||
PKG_SOURCE:=daed-src-2026.06.14-4e215048068a.tar.gz
|
||||
PKG_SOURCE_URL:=https://github.com/kenzok8/openwrt-daede/releases/download/daed-src
|
||||
@@ -132,6 +132,10 @@ define Package/daed/install
|
||||
$(call GoPackage/Package/Install/Bin,$(PKG_INSTALL_DIR))
|
||||
$(INSTALL_DIR) $(1)/usr/bin
|
||||
$(INSTALL_BIN) $(PKG_INSTALL_DIR)/usr/bin/dae-wing $(1)/usr/bin/daed
|
||||
$(INSTALL_BIN) $(CURDIR)/files/daed-guard $(1)/usr/bin/daed-guard
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/daed
|
||||
$(INSTALL_DATA) $(CURDIR)/files/daed-cleanup.sh $(1)/usr/share/daed/cleanup.sh
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) $(CURDIR)/files/daed.config $(1)/etc/config/daed
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
daed_cleanup_runtime() {
|
||||
local pid
|
||||
|
||||
for pid in $(ip netns pids daens 2>/dev/null); do
|
||||
kill "$pid" 2>/dev/null
|
||||
done
|
||||
|
||||
if [ -n "$(ip netns pids daens 2>/dev/null)" ]; then
|
||||
sleep 1
|
||||
for pid in $(ip netns pids daens 2>/dev/null); do
|
||||
kill -9 "$pid" 2>/dev/null
|
||||
done
|
||||
fi
|
||||
|
||||
if ! ip netns del daens 2>/dev/null; then
|
||||
umount /run/netns/daens 2>/dev/null
|
||||
rm -f /run/netns/daens
|
||||
fi
|
||||
|
||||
ip link del dae0 2>/dev/null || true
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /usr/share/daed/cleanup.sh
|
||||
|
||||
daed_cleanup_runtime
|
||||
exec /usr/bin/daed "$@"
|
||||
+4
-14
@@ -5,9 +5,11 @@ USE_PROCD=1
|
||||
START=99
|
||||
|
||||
CONF="daed"
|
||||
PROG="/usr/bin/daed"
|
||||
PROG="/usr/bin/daed-guard"
|
||||
LOG="/var/log/daed/daed.log"
|
||||
|
||||
. /usr/share/daed/cleanup.sh
|
||||
|
||||
start_service() {
|
||||
config_load "$CONF"
|
||||
|
||||
@@ -40,19 +42,7 @@ start_service() {
|
||||
|
||||
stop_service() {
|
||||
rm -f "$LOG"
|
||||
# kill lingering procs in daens: TERM first, then KILL for stubborn ones
|
||||
for pid in $(ip netns pids daens 2>/dev/null); do kill "$pid" 2>/dev/null; done
|
||||
if [ -n "$(ip netns pids daens 2>/dev/null)" ]; then
|
||||
sleep 1
|
||||
for pid in $(ip netns pids daens 2>/dev/null); do kill -9 "$pid" 2>/dev/null; done
|
||||
fi
|
||||
# cleanup daens netns; ip-full needed for netns, busybox fallback via umount
|
||||
if ! ip netns del daens 2>/dev/null; then
|
||||
umount /run/netns/daens 2>/dev/null
|
||||
rm -f /run/netns/daens
|
||||
fi
|
||||
# clean orphaned dae0
|
||||
ip link del dae0 2>/dev/null || true
|
||||
daed_cleanup_runtime
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=dnsproxy
|
||||
PKG_VERSION:=0.82.0
|
||||
PKG_RELEASE:=11
|
||||
PKG_VERSION:=0.82.1
|
||||
PKG_RELEASE:=12
|
||||
|
||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
||||
PKG_SOURCE_URL:=https://codeload.github.com/AdguardTeam/dnsproxy/tar.gz/v$(PKG_VERSION)?
|
||||
|
||||
@@ -1,28 +1,39 @@
|
||||
header {
|
||||
@apply bg-bg sticky top-0 z-40 mb-2 transition-colors duration-[300ms] ease-[cubic-bezier(0.4,0,0.6,1)];
|
||||
@apply bg-bg sticky top-0 z-40 mb-2 transition-colors duration-(--mega-menu-duration,300ms) ease-[cubic-bezier(0.4,0,0.6,1)];
|
||||
|
||||
& .header-content {
|
||||
@apply relative flex h-14 items-center justify-between px-6 py-3 max-md:px-4 max-md:py-2;
|
||||
@apply relative z-10 flex h-14 items-center justify-between px-6 py-3 max-md:px-4 max-md:py-2;
|
||||
}
|
||||
|
||||
[data-nav-type="mega-menu"] & {
|
||||
/* Apple's layer split: the panel that animates GEOMETRY must not carry a
|
||||
backdrop-filter, or every height frame re-rasterises the blur (the old
|
||||
clip-path flicker). So this is a fully-opaque SOLID surface that retracts
|
||||
via height — the drawer close — while the frost lives entirely on the
|
||||
separate .desktop-menu-overlay curtain (opacity/visibility only). Height
|
||||
is driven by --mega-menu-height (set on open, dropped on close in
|
||||
menu-aurora.js); overflow-hidden clips the absolutely-positioned content
|
||||
as the canvas grows and collapses.
|
||||
/* Compositor-only curtain (supersedes the height transition, which paid
|
||||
main-thread layout + repaint on every frame and janked low-end devices):
|
||||
the container is a STATIC clip frame rooted at the bar, sized by
|
||||
--mega-menu-height (the tallest panel, measured once in menu-aurora.js
|
||||
and kept across open/close), and the reveal is a pair of opposite
|
||||
translateY transitions — .desktop-menu-sheet (the opaque panel surface,
|
||||
overflow-hidden) slides down from behind the bar while
|
||||
.desktop-menu-canvas inside it counter-slides by the same distance, so
|
||||
the content nets to a fixed position and the sheet's bottom edge wipes
|
||||
it in, visually identical to the old drawer but animated entirely on
|
||||
the compositor thread.
|
||||
|
||||
Opacity is NOT animated — it's gated by `visibility` (like the curtain).
|
||||
Fading the panel in left it half-transparent for the whole open, so the
|
||||
header brand/icons showed THROUGH it until opacity settled (the flash).
|
||||
Opaque-from-the-first-frame means the growing height wipes solidly down
|
||||
over the header instead. The closing state keeps the panel visible and
|
||||
above the curtain while height animates down, without re-enabling input. */
|
||||
Apple's layer split still applies: the surface that animates must not
|
||||
carry a backdrop-filter, or every frame re-rasterises the blur. The
|
||||
sheet is fully opaque; the frost lives on the separate
|
||||
.desktop-menu-overlay curtain.
|
||||
|
||||
Opacity is not animated on open — visibility gates the panel so the
|
||||
descending sheet is opaque from the first visible frame. Closing is the
|
||||
opposite: the whole menu surface fades out immediately while the sheet
|
||||
retracts, so the frost, scrim and menu colour do not disappear in
|
||||
separate stages. */
|
||||
/* Apple-style clip frame: the opened surface starts at the top of the
|
||||
header, not below it. The header content floats above this layer. The
|
||||
header-height band is part of the moving sheet, so open and close share
|
||||
the same page-top origin. */
|
||||
& .desktop-menu-container {
|
||||
@apply border-hairline bg-mega-menu-bg pointer-events-none invisible absolute inset-x-0 top-0 z-30 h-(--mega-menu-height,0) w-full overflow-hidden border-b shadow-xl transition-[height,visibility] duration-[300ms] ease-[cubic-bezier(0.4,0,0.6,1)] [will-change:height] motion-reduce:transition-none max-md:hidden;
|
||||
@apply pointer-events-none invisible absolute inset-x-0 top-0 z-0 h-[calc(var(--mega-menu-height,0px)+3.5rem+3rem)] w-full overflow-clip max-md:hidden;
|
||||
|
||||
&.active,
|
||||
&.closing {
|
||||
@@ -33,12 +44,46 @@ header {
|
||||
@apply pointer-events-auto;
|
||||
}
|
||||
|
||||
/* 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.
|
||||
&.closing {
|
||||
@apply opacity-0 transition-opacity duration-[220ms] ease-[cubic-bezier(0.4,0,0.6,1)] motion-reduce:transition-none;
|
||||
}
|
||||
|
||||
/* The sheet is the whole opened surface, including the header-height
|
||||
band. Its closed bottom edge is page top (not the header bottom), so
|
||||
the close stops at the same origin that the reveal starts from. The
|
||||
canvas counter-translates by the same travel distance so the menu
|
||||
content remains visually fixed.
|
||||
|
||||
Duration is distance-adaptive via --mega-menu-duration, set on the
|
||||
header by menu-aurora.js from apple.com's flyout pacing (2px/ms over
|
||||
the visible travel, clamped to 240–480ms). */
|
||||
& .desktop-menu-sheet {
|
||||
@apply bg-mega-menu-bg absolute inset-x-0 top-0 h-[calc(var(--mega-menu-height,0px)+3.5rem)] -translate-y-full overflow-hidden transition-[translate] duration-(--mega-menu-duration,300ms) ease-[cubic-bezier(0.4,0,0.6,1)] motion-reduce:transition-none;
|
||||
@apply after:border-hairline after:pointer-events-none after:absolute after:inset-0 after:border-b after:shadow-xl after:transition-opacity after:duration-(--mega-menu-duration,300ms) after:content-[''] motion-reduce:after:transition-none;
|
||||
}
|
||||
|
||||
/* The counter-transform must mirror the sheet's endpoints and timing
|
||||
exactly or the content drifts during the wipe — keep both in
|
||||
lockstep. */
|
||||
& .desktop-menu-canvas {
|
||||
@apply absolute inset-0 translate-y-full transition-[translate] duration-(--mega-menu-duration,300ms) ease-[cubic-bezier(0.4,0,0.6,1)] motion-reduce:transition-none;
|
||||
}
|
||||
|
||||
&.active .desktop-menu-sheet,
|
||||
&.active .desktop-menu-canvas {
|
||||
@apply translate-y-0;
|
||||
}
|
||||
|
||||
&.closing .desktop-menu-sheet {
|
||||
@apply after:opacity-0;
|
||||
}
|
||||
|
||||
/* Category panels live inside .desktop-menu-canvas, 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.
|
||||
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 entering panel floats up 6px — translate + 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 {
|
||||
@@ -51,7 +96,11 @@ header {
|
||||
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-13 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;
|
||||
/* Tailwind v4 translate-* utilities emit the native `translate`
|
||||
property, so that is what must be transitioned — transitioning
|
||||
`transform` here left the 6px entrance float snapping instead of
|
||||
animating (only the opacity ever faded). */
|
||||
@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-13 pb-8 opacity-0 transition-[opacity,translate] 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;
|
||||
@@ -156,12 +205,11 @@ header {
|
||||
}
|
||||
}
|
||||
|
||||
/* When a category is open the bar lifts to the panel's opened colour so the
|
||||
header and the mega-menu read as one continuous surface — no tonal seam
|
||||
where the bar (bg) meets the panel (mega-menu-bg). */
|
||||
/* The bar must sit above the z-60 curtain for the whole open AND the
|
||||
whole retract, or the scrim would dim the closing panel. */
|
||||
[data-nav-type="mega-menu"]
|
||||
&:has(.desktop-menu-container:is(.active, .closing)) {
|
||||
@apply bg-mega-menu-bg z-70;
|
||||
@apply z-70;
|
||||
}
|
||||
|
||||
.brand {
|
||||
|
||||
@@ -69,15 +69,26 @@ body.mobile-navigation-open > header {
|
||||
}
|
||||
|
||||
[data-nav-type="mega-menu"] {
|
||||
/* Apple's curtain technique: backdrop-filter is DECLARED here permanently and
|
||||
never toggled. Gating the blur on `.active` snapped the page from blurred
|
||||
to sharp the instant the menu closed, while the dim was still fading — the
|
||||
flash. Instead opacity + visibility carry it: closing keeps the layer
|
||||
visible (visibility flips only at the end of the transition) so the blur
|
||||
fades out together with the dim; once hidden, visibility:hidden fully
|
||||
disables the filter so an invisible full-screen blur costs nothing. */
|
||||
/* Apple's curtain: the frost is DECLARED here permanently and never
|
||||
toggled — it fades in and out WITH the dim, so the page blurs
|
||||
progressively as the sheet descends (apple.com's globalnav curtain runs
|
||||
its blur exactly like this while the flyout animates). Gating the blur
|
||||
on a post-transition "settled" state was tried and rejected: the frost
|
||||
popped in only after the menu landed, visibly off-design.
|
||||
|
||||
The cost is per-frame blur re-compositing for the 300ms of fade — but it
|
||||
is now the ONLY per-frame work: the reveal itself runs entirely on the
|
||||
compositor (see _layout.css). What janked low-end devices was the old
|
||||
overlap of main-thread height layout + live blur, not the blur alone.
|
||||
If the fade still janks a device, reduce the radius or rely on
|
||||
_reduced-transparency.css, which strips the filter outright.
|
||||
|
||||
Closing starts immediately and uses the same 220ms exit as the menu
|
||||
surface. Delaying the opacity transition left the page frosted after the
|
||||
sheet had already collapsed into the header, which read as a flash. Once
|
||||
hidden, visibility:hidden makes the invisible filter cost nothing. */
|
||||
& .desktop-menu-overlay {
|
||||
@apply bg-mega-menu-scrim pointer-events-none invisible fixed inset-0 z-60 opacity-0 backdrop-blur-lg backdrop-saturate-150 transition-[opacity,visibility] delay-[80ms] duration-[300ms] ease-[cubic-bezier(0.4,0,0.6,1)] max-md:hidden;
|
||||
@apply bg-mega-menu-scrim pointer-events-none invisible fixed inset-0 z-60 opacity-0 backdrop-blur-lg backdrop-saturate-150 transition-[opacity,visibility] duration-[220ms] ease-[cubic-bezier(0.4,0,0.6,1)] max-md:hidden;
|
||||
|
||||
&.active {
|
||||
@apply pointer-events-auto visible opacity-100;
|
||||
|
||||
@@ -570,6 +570,7 @@ return baseclass.extend({
|
||||
|
||||
initMegaMenu(children, url, ul) {
|
||||
const container = document.querySelector(".desktop-menu-container");
|
||||
const canvas = container?.querySelector(".desktop-menu-canvas");
|
||||
const overlay = document.querySelector(".desktop-menu-overlay");
|
||||
const header = document.querySelector("header");
|
||||
|
||||
@@ -579,28 +580,43 @@ return baseclass.extend({
|
||||
let hideTimer = null;
|
||||
|
||||
// Constant canvas: every category opens at the same height — the
|
||||
// tallest submenu wins. Measured lazily on first open (after fonts
|
||||
// settle) and cached, so switching categories is a pure cross-fade
|
||||
// with zero layout work. A resize invalidates the cache.
|
||||
// tallest submenu wins. --mega-menu-height is that panel height alone
|
||||
// (the visible travel; the bar is not part of the wipe distance).
|
||||
// Pre-measured at idle (after fonts settle) so the first hover pays no
|
||||
// synchronous reflow, and cached, so switching categories is a pure
|
||||
// cross-fade with zero layout work. The height stays set across
|
||||
// open/close — it is the sheet's translate reference, not the animated
|
||||
// property. A resize invalidates the cache.
|
||||
let canvasHeight = 0;
|
||||
let revealDuration = 300;
|
||||
|
||||
const applyCanvasHeight = () => {
|
||||
if (!canvasHeight) {
|
||||
const headerHeight =
|
||||
header.querySelector(".header-content")?.offsetHeight || 56;
|
||||
let maxPanel = 0;
|
||||
header
|
||||
.querySelectorAll(".desktop-nav")
|
||||
.forEach((nav) => (maxPanel = Math.max(maxPanel, nav.offsetHeight)));
|
||||
canvasHeight = headerHeight + maxPanel;
|
||||
.forEach(
|
||||
(nav) => (canvasHeight = Math.max(canvasHeight, nav.offsetHeight)),
|
||||
);
|
||||
// apple.com's flyout pacing: a constant 2px/ms over the visible
|
||||
// travel, clamped to 240–480ms. Short panels open snappily, tall
|
||||
// ones don't blink.
|
||||
revealDuration = Math.min(
|
||||
480,
|
||||
Math.max(240, Math.round(canvasHeight / 2)),
|
||||
);
|
||||
}
|
||||
container?.style.setProperty("--mega-menu-height", `${canvasHeight}px`);
|
||||
// Both vars live on the header, not the container: the container
|
||||
// inherits the height, and the header's own transition-colors reads
|
||||
// the duration so the bar colour fades in lockstep with the wipe.
|
||||
header.style.setProperty("--mega-menu-height", `${canvasHeight}px`);
|
||||
header.style.setProperty("--mega-menu-duration", `${revealDuration}ms`);
|
||||
};
|
||||
|
||||
// Re-measure on resize even while closed — leaving the cache cold would
|
||||
// push the reflow back onto the next hover's open path.
|
||||
let resizeTimer = null;
|
||||
window.addEventListener("resize", () => {
|
||||
canvasHeight = 0;
|
||||
if (!container?.classList.contains("active")) return;
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(applyCanvasHeight, 150);
|
||||
});
|
||||
@@ -651,12 +667,12 @@ return baseclass.extend({
|
||||
);
|
||||
if (!hasSubmenu) return;
|
||||
|
||||
// Reparent the panel into the canvas container: the transformed
|
||||
// #topmenu would otherwise become the containing block of the
|
||||
// absolutely-positioned panel and shrink the canvas to the menu's
|
||||
// width. Inside the container the panel is also clipped by the same
|
||||
// clip-path reveal as the rest of the canvas.
|
||||
if (container) container.appendChild(nav);
|
||||
// Reparent the panel into the counter-transformed canvas: the
|
||||
// transformed #topmenu would otherwise become the containing block of
|
||||
// the absolutely-positioned panel and shrink the canvas to the menu's
|
||||
// width. Inside the canvas the panel rides the sheet's compositor
|
||||
// reveal (and is clipped by the sheet's overflow) for free.
|
||||
if (canvas) canvas.appendChild(nav);
|
||||
|
||||
li.addEventListener("mouseenter", () => {
|
||||
if (hideTimer) {
|
||||
@@ -683,6 +699,7 @@ return baseclass.extend({
|
||||
|
||||
if (container) {
|
||||
applyCanvasHeight();
|
||||
container.dataset.closeToken = "";
|
||||
container.classList.add("active");
|
||||
container.classList.remove("closing");
|
||||
overlay.classList.add("active");
|
||||
@@ -699,6 +716,16 @@ return baseclass.extend({
|
||||
});
|
||||
});
|
||||
|
||||
// Pre-measure the canvas off the interaction path: fonts change panel
|
||||
// heights, so wait for them, then measure when the main thread is idle.
|
||||
// A hover that beats this still falls back to the lazy measure above.
|
||||
const idle = window.requestIdleCallback || ((fn) => setTimeout(fn, 1));
|
||||
(document.fonts?.ready || Promise.resolve()).then(() =>
|
||||
idle(() => {
|
||||
if (!canvasHeight) applyCanvasHeight();
|
||||
}),
|
||||
);
|
||||
|
||||
const hideMenu = () => {
|
||||
if (showTimer) {
|
||||
clearTimeout(showTimer);
|
||||
@@ -777,41 +804,61 @@ return baseclass.extend({
|
||||
)
|
||||
return;
|
||||
|
||||
// Retract the curtain: removing `.active` sends the sheet back to
|
||||
// translateY(-100%) and the canvas to its counter-position — the drawer
|
||||
// close, run entirely on the compositor. --mega-menu-height stays put;
|
||||
// it is the translate reference, not the animated property. `.closing`
|
||||
// keeps the container in the tree only while its opacity fades out and
|
||||
// the sheet returns to the closed translate position.
|
||||
container.classList.add("closing");
|
||||
container.classList.remove("active");
|
||||
|
||||
// Retract the canvas: dropping --mega-menu-height lets the container's
|
||||
// height fall to its 0 fallback, and because the container transitions
|
||||
// height (see _layout.css) it animates the collapse — the drawer close —
|
||||
// instead of the height vanishing in one frame after the fade. overflow
|
||||
// -hidden clips the content as it goes; the end state is already h-0 so no
|
||||
// post-transition reset is needed.
|
||||
container.style.removeProperty("--mega-menu-height");
|
||||
const sheet = container.querySelector(".desktop-menu-sheet");
|
||||
const closeToken = `${Date.now()}-${Math.random()}`;
|
||||
container.dataset.closeToken = closeToken;
|
||||
|
||||
let closeFallback = null;
|
||||
const finishClosing = (event) => {
|
||||
if (event?.target && event.target !== container) return;
|
||||
if (event?.propertyName && event.propertyName !== "height") return;
|
||||
|
||||
if (event?.target && event.target !== sheet) return;
|
||||
if (container.dataset.closeToken !== closeToken) {
|
||||
if (closeFallback !== null) {
|
||||
clearTimeout(closeFallback);
|
||||
closeFallback = null;
|
||||
}
|
||||
sheet?.removeEventListener("transitionend", finishClosing);
|
||||
return;
|
||||
}
|
||||
if (closeFallback !== null) {
|
||||
clearTimeout(closeFallback);
|
||||
closeFallback = null;
|
||||
}
|
||||
|
||||
container.removeEventListener("transitionend", finishClosing);
|
||||
sheet?.removeEventListener("transitionend", finishClosing);
|
||||
|
||||
if (!container.classList.contains("active")) {
|
||||
container.classList.remove("closing");
|
||||
delete container.dataset.closeToken;
|
||||
}
|
||||
};
|
||||
|
||||
if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) {
|
||||
if (
|
||||
!sheet ||
|
||||
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
||||
) {
|
||||
finishClosing();
|
||||
return;
|
||||
}
|
||||
|
||||
container.addEventListener("transitionend", finishClosing);
|
||||
closeFallback = setTimeout(finishClosing, 350);
|
||||
sheet.addEventListener("transitionend", finishClosing);
|
||||
// The retract duration is distance-adaptive (see applyCanvasHeight);
|
||||
// read it back so the safety net always outlasts the real transition.
|
||||
const duration =
|
||||
parseInt(
|
||||
document
|
||||
.querySelector("header")
|
||||
?.style.getPropertyValue("--mega-menu-duration"),
|
||||
) || 300;
|
||||
closeFallback = setTimeout(finishClosing, duration + 50);
|
||||
},
|
||||
|
||||
renderModeMenu(tree) {
|
||||
|
||||
@@ -71,6 +71,7 @@ class FakeElement {
|
||||
this.style = {
|
||||
display: "",
|
||||
properties: new Map(),
|
||||
getPropertyValue: (name) => this.style.properties.get(name) ?? "",
|
||||
removeProperty: (name) => this.style.properties.delete(name),
|
||||
setProperty: (name, value) => this.style.properties.set(name, value),
|
||||
};
|
||||
@@ -228,6 +229,7 @@ const loadMenuModule = ({
|
||||
getChildren = getMenuChildren,
|
||||
requestpath = [],
|
||||
translate = (value) => value,
|
||||
window = {},
|
||||
} = {}) => {
|
||||
const baseclass = {
|
||||
extend(module) {
|
||||
@@ -250,7 +252,7 @@ const loadMenuModule = ({
|
||||
"window",
|
||||
"localStorage",
|
||||
source,
|
||||
)(baseclass, ui, E, L, translate, document, {}, {});
|
||||
)(baseclass, ui, E, L, translate, document, window, {});
|
||||
};
|
||||
|
||||
const textContent = (element) =>
|
||||
@@ -768,15 +770,20 @@ test("renders an active group expanded when an open mobile list was initially em
|
||||
assert.equal(region.hasAttribute("inert"), false);
|
||||
});
|
||||
|
||||
test("keeps mega-menu stacking state while the close height transition runs", () => {
|
||||
const container = new FakeElement("div", {
|
||||
class: "desktop-menu-container active",
|
||||
});
|
||||
test("starts the mega-menu overlay and surface exit together", () => {
|
||||
const sheet = new FakeElement("div", { class: "desktop-menu-sheet" });
|
||||
const container = new FakeElement(
|
||||
"div",
|
||||
{ class: "desktop-menu-container active" },
|
||||
[sheet],
|
||||
);
|
||||
const overlay = new FakeElement("div", {
|
||||
class: "desktop-menu-overlay active",
|
||||
});
|
||||
const header = new FakeElement("header");
|
||||
const document = createFakeDocument({
|
||||
elements: {
|
||||
header,
|
||||
".desktop-menu-container": container,
|
||||
".desktop-menu-overlay": overlay,
|
||||
},
|
||||
@@ -784,19 +791,142 @@ test("keeps mega-menu stacking state while the close height transition runs", ()
|
||||
const menu = loadMenuModule({ document });
|
||||
|
||||
container.style.setProperty("--mega-menu-height", "320px");
|
||||
header.style.setProperty("--mega-menu-duration", "1ms");
|
||||
|
||||
menu.hideDesktopNav();
|
||||
|
||||
// Removing .active starts the scrim/blur fade immediately. The container
|
||||
// remains in .closing so its own opacity can fade while the sheet retracts.
|
||||
assert.equal(overlay.classList.contains("active"), false);
|
||||
assert.equal(container.classList.contains("active"), false);
|
||||
assert.equal(container.classList.contains("closing"), true);
|
||||
assert.equal(container.style.properties.has("--mega-menu-height"), false);
|
||||
// The canvas height is the sheet's translate reference, not the animated
|
||||
// property — it must survive the close for the retract to have a distance.
|
||||
assert.equal(container.style.properties.get("--mega-menu-height"), "320px");
|
||||
|
||||
container.dispatchEvent({
|
||||
sheet.dispatchEvent({
|
||||
type: "transitionend",
|
||||
target: container,
|
||||
propertyName: "height",
|
||||
target: sheet,
|
||||
propertyName: "translate",
|
||||
});
|
||||
|
||||
assert.equal(container.classList.contains("closing"), false);
|
||||
});
|
||||
|
||||
test("ignores stale mega-menu close timers after reopen and second close", async () => {
|
||||
const sheet = new FakeElement("div", { class: "desktop-menu-sheet" });
|
||||
const container = new FakeElement(
|
||||
"div",
|
||||
{ class: "desktop-menu-container active" },
|
||||
[sheet],
|
||||
);
|
||||
const overlay = new FakeElement("div", {
|
||||
class: "desktop-menu-overlay active",
|
||||
});
|
||||
const header = new FakeElement("header");
|
||||
const document = createFakeDocument({
|
||||
elements: {
|
||||
header,
|
||||
".desktop-menu-container": container,
|
||||
".desktop-menu-overlay": overlay,
|
||||
},
|
||||
});
|
||||
const menu = loadMenuModule({ document });
|
||||
|
||||
header.style.setProperty("--mega-menu-duration", "300ms");
|
||||
|
||||
menu.hideDesktopNav();
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 120));
|
||||
|
||||
container.classList.add("active");
|
||||
container.classList.remove("closing");
|
||||
overlay.classList.add("active");
|
||||
|
||||
menu.hideDesktopNav();
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 260));
|
||||
|
||||
assert.equal(container.classList.contains("closing"), true);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 120));
|
||||
|
||||
assert.equal(container.classList.contains("closing"), false);
|
||||
});
|
||||
|
||||
test("the hover path opens, switches, closes and reopens the mega-menu", async () => {
|
||||
// The first-open dwell is 100ms; category switches on an open menu fire
|
||||
// with no dwell but still through a timer.
|
||||
const openDwell = () => new Promise((resolve) => setTimeout(resolve, 120));
|
||||
|
||||
const canvas = new FakeElement("div", { class: "desktop-menu-canvas" });
|
||||
const sheet = new FakeElement("div", { class: "desktop-menu-sheet" }, [
|
||||
canvas,
|
||||
]);
|
||||
const container = new FakeElement(
|
||||
"div",
|
||||
{ class: "desktop-menu-container" },
|
||||
[sheet],
|
||||
);
|
||||
const overlay = new FakeElement("div", { class: "desktop-menu-overlay" });
|
||||
const header = new FakeElement("header", {}, [
|
||||
new FakeElement("div", { class: "header-content" }),
|
||||
container,
|
||||
]);
|
||||
const document = createFakeDocument({
|
||||
elements: {
|
||||
header,
|
||||
".desktop-menu-container": container,
|
||||
".desktop-menu-overlay": overlay,
|
||||
},
|
||||
});
|
||||
const ul = E("ul", { id: "topmenu" });
|
||||
// deactivateDesktopNavExcept sweeps document-wide; give the fake document
|
||||
// a real tree to sweep or stale `.active` panels trip the wasActive guard.
|
||||
const root = new FakeElement("html", {}, [header, ul]);
|
||||
document.querySelectorAll = (selector) => root.querySelectorAll(selector);
|
||||
const menu = loadMenuModule({
|
||||
document,
|
||||
window: { addEventListener() {} },
|
||||
});
|
||||
|
||||
menu.initMegaMenu(
|
||||
[
|
||||
{ name: "network", title: "Network", children: { wifi: {} } },
|
||||
{ name: "system", title: "System", children: { admin: {} } },
|
||||
],
|
||||
"admin",
|
||||
ul,
|
||||
);
|
||||
|
||||
const [firstItem, secondItem] = ul.children;
|
||||
const navFor = (item) => item.querySelector(".desktop-nav");
|
||||
|
||||
// First open: hover dwell activates the container, the overlay and the
|
||||
// hovered category's panel together.
|
||||
firstItem.dispatchEvent({ type: "mouseenter" });
|
||||
await openDwell();
|
||||
assert.equal(container.classList.contains("active"), true);
|
||||
assert.equal(overlay.classList.contains("active"), true);
|
||||
assert.equal(navFor(firstItem).classList.contains("active"), true);
|
||||
|
||||
// Switching categories on the open menu swaps the active panel without
|
||||
// dropping the container out of its open state.
|
||||
secondItem.dispatchEvent({ type: "mouseenter" });
|
||||
await openDwell();
|
||||
assert.equal(container.classList.contains("active"), true);
|
||||
assert.equal(navFor(firstItem).classList.contains("active"), false);
|
||||
assert.equal(navFor(secondItem).classList.contains("active"), true);
|
||||
|
||||
// Close, then reopen BEFORE the retract finishes: the interrupted close
|
||||
// must not leave a stale `.active` panel that trips the wasActive guard
|
||||
// and blocks the container from re-activating.
|
||||
menu.hideDesktopNav();
|
||||
assert.equal(container.classList.contains("active"), false);
|
||||
assert.equal(container.classList.contains("closing"), true);
|
||||
firstItem.dispatchEvent({ type: "mouseenter" });
|
||||
await openDwell();
|
||||
assert.equal(container.classList.contains("active"), true);
|
||||
assert.equal(container.classList.contains("closing"), false);
|
||||
assert.equal(navFor(firstItem).classList.contains("active"), true);
|
||||
});
|
||||
@@ -166,22 +166,50 @@ test("mega-menu panels scroll within the viewport", () => {
|
||||
assertIncludesUtilities(panel, ["overflow-y-auto", "overscroll-contain"]);
|
||||
});
|
||||
|
||||
test("mega-menu closing state keeps the panel above the curtain", () => {
|
||||
test("mega-menu reveal and retract share the page-top origin", () => {
|
||||
const megaMenu = getBlock(layoutStyles, '[data-nav-type="mega-menu"] &');
|
||||
const headerContent = getBlock(layoutStyles, "& .header-content");
|
||||
const container = getBlock(megaMenu, "& .desktop-menu-container");
|
||||
const sheet = getBlock(container, "& .desktop-menu-sheet");
|
||||
const canvas = getBlock(container, "& .desktop-menu-canvas");
|
||||
const panel = getBlock(megaMenu, "& .desktop-nav");
|
||||
const headerLift = layoutStyles.match(
|
||||
/When a category is open[\s\S]*?(\[data-nav-type="mega-menu"\][\s\S]*?)\n\s*\.brand/,
|
||||
/The bar must sit above[\s\S]*?(\[data-nav-type="mega-menu"\][\s\S]*?)\n\s*\.brand/,
|
||||
)?.[1];
|
||||
|
||||
assertIncludesUtilities(headerContent, ["z-10"]);
|
||||
assertIncludesUtilities(container, ["top-0", "z-0"]);
|
||||
assert.doesNotMatch(
|
||||
container,
|
||||
/@apply bg-mega-menu-bg pointer-events-none absolute inset-x-0 top-0 h-14/,
|
||||
);
|
||||
assert.match(
|
||||
container,
|
||||
/&\.active,\s*&\.closing\s*\{[\s\S]*@apply[^;]*\bvisible\b/,
|
||||
);
|
||||
assert.match(
|
||||
container,
|
||||
/&\.closing\s*\{[\s\S]*@apply[^;]*\bopacity-0\b[^;]*\btransition-opacity\b[^;]*\bduration-\[220ms\]/,
|
||||
);
|
||||
assert.match(
|
||||
container,
|
||||
/&\.active\s*\{[\s\S]*@apply[^;]*pointer-events-auto/,
|
||||
);
|
||||
assert.match(headerLift ?? "", /desktop-menu-container[\s\S]*closing/);
|
||||
assertIncludesUtilities(sheet, ["top-0", "-translate-y-full"]);
|
||||
assert.match(
|
||||
sheet,
|
||||
/h-\[calc\(var\(--mega-menu-height,0px\)\+3\.5rem\)\]/,
|
||||
);
|
||||
assertIncludesUtilities(canvas, ["translate-y-full"]);
|
||||
assertIncludesUtilities(panel, ["top-14"]);
|
||||
// z-70 must span the retract too (or the curtain dims the closing panel).
|
||||
assert.match(
|
||||
headerLift ?? "",
|
||||
/desktop-menu-container:is\(\.active, \.closing\)\)\s*\{\s*@apply[^;]*z-70/,
|
||||
);
|
||||
// The bar no longer performs its own background transition. The flyout sheet
|
||||
// owns the opened surface and retracts fully to page top before hiding.
|
||||
assert.doesNotMatch(headerLift ?? "", /bg-mega-menu-bg/);
|
||||
});
|
||||
|
||||
test("mega-menu category masks use Tailwind arbitrary utilities", () => {
|
||||
|
||||
@@ -49,25 +49,84 @@ test("dark layering: page < card, sunken between page and card", () => {
|
||||
assert.ok(sunk >= bg && sunk < surf, `sunken not between page and card: ${d.surface_sunken}`);
|
||||
});
|
||||
|
||||
test("mega-menu frost lives on the curtain, not the height-animating panel", () => {
|
||||
test("mega-menu reveal is compositor-only and the frost never overlaps it", () => {
|
||||
const layout = read("../src/media/_layout.css");
|
||||
const overlay = read("../src/media/components/_overlay.css");
|
||||
// The panel animates geometry (height) for the drawer open/close, so it must
|
||||
// carry NO backdrop-filter — blurring a resizing box re-rasterises every
|
||||
// frame (the old clip-path flicker). The frost moved entirely to the curtain.
|
||||
const panel = layout.split("\n").find((l) => l.includes("bg-mega-menu-bg"));
|
||||
assert.ok(!panel?.includes("backdrop-blur"), `panel must not blur: ${panel}`);
|
||||
// The sheet is the moving surface, so it must carry NO backdrop-filter —
|
||||
// blurring an animating box re-rasterises every frame (the old clip-path
|
||||
// flicker) — and must animate translate, not height: a height transition
|
||||
// pays main-thread layout + repaint per frame (the low-end hover jank).
|
||||
const sheet =
|
||||
layout.match(/& \.desktop-menu-sheet\s*\{([\s\S]*?)\n\s*\}/)?.[1] ?? "";
|
||||
assert.ok(!sheet?.includes("backdrop-blur"), `sheet must not blur: ${sheet}`);
|
||||
assert.ok(
|
||||
panel?.includes("transition-[height,visibility]"),
|
||||
`panel animates height: ${panel}`,
|
||||
!layout.includes(
|
||||
"@apply bg-mega-menu-bg pointer-events-none absolute inset-x-0 top-0 h-14",
|
||||
),
|
||||
"header band must not be static",
|
||||
);
|
||||
// Apple-style geometry: the sheet is the whole opened surface, including the
|
||||
// header-height band. Its closed bottom edge is page top, matching the
|
||||
// reveal origin.
|
||||
assert.ok(
|
||||
sheet?.includes("transition-[translate]") &&
|
||||
sheet?.includes("top-0") &&
|
||||
sheet?.includes("h-[calc(var(--mega-menu-height,0px)+3.5rem)]") &&
|
||||
sheet?.includes("-translate-y-full"),
|
||||
`sheet must move from and to page top: ${sheet}`,
|
||||
);
|
||||
const containerRule =
|
||||
layout.match(/& \.desktop-menu-container\s*\{\s*@apply\s+([^;]+);/)?.[1] ??
|
||||
"";
|
||||
assert.ok(
|
||||
!containerRule.includes("transition") &&
|
||||
!containerRule.includes("will-change"),
|
||||
`container is a static frame, it must not animate: ${containerRule}`,
|
||||
);
|
||||
// A translated sheet moves inside a static clip frame rooted at the page top.
|
||||
// The container adds shadow slack below the travel but does not move itself.
|
||||
assert.ok(
|
||||
containerRule.includes("top-0") &&
|
||||
containerRule.includes("h-[calc(var(--mega-menu-height,0px)+3.5rem+3rem)]") &&
|
||||
containerRule.includes("overflow-clip"),
|
||||
`container must clip the page-top retract: ${containerRule}`,
|
||||
);
|
||||
assert.match(
|
||||
layout,
|
||||
/&\.closing\s*\{[\s\S]*?@apply[^;]*opacity-0[^;]*transition-opacity[^;]*duration-\[220ms\]/,
|
||||
);
|
||||
// The counter-transformed canvas must mirror the sheet's endpoints and
|
||||
// timing exactly or the content drifts during the wipe; both take the
|
||||
// distance-adaptive duration from the same variable.
|
||||
const canvasRule =
|
||||
layout.match(/& \.desktop-menu-canvas\s*\{([\s\S]*?)\n\s*\}/)?.[1] ?? "";
|
||||
for (const token of [
|
||||
"transition-[translate]",
|
||||
"duration-(--mega-menu-duration,300ms)",
|
||||
"translate-y-full",
|
||||
]) {
|
||||
assert.ok(canvasRule.includes(token), `canvas missing ${token}: ${canvasRule}`);
|
||||
}
|
||||
assert.ok(
|
||||
sheet?.includes("duration-(--mega-menu-duration,300ms)"),
|
||||
`sheet must share the adaptive duration: ${sheet}`,
|
||||
);
|
||||
// The curtain carries the blur permanently (Apple's globalnav-curtain) and
|
||||
// fades it with opacity/visibility, never snapping it on/off via .active.
|
||||
// fades it with opacity/visibility. Close must start that fade immediately;
|
||||
// delaying the curtain left blur visible after the sheet had collapsed.
|
||||
assert.ok(overlay.includes("max-md:backdrop-blur-lg"), "mobile overlay blur");
|
||||
const curtain = overlay
|
||||
.split("\n")
|
||||
.find((l) => l.includes("bg-mega-menu-scrim"));
|
||||
assert.ok(curtain?.includes("backdrop-blur-lg"), `desktop curtain blur: ${curtain}`);
|
||||
assert.ok(
|
||||
curtain?.includes("backdrop-blur-lg"),
|
||||
`desktop curtain blur: ${curtain}`,
|
||||
);
|
||||
assert.ok(
|
||||
curtain?.includes("duration-[220ms]") && !curtain.includes("delay-"),
|
||||
`desktop curtain exit must be immediate: ${curtain}`,
|
||||
);
|
||||
assert.ok(!overlay.includes(".settled"), "settle-gated frost must stay gone");
|
||||
});
|
||||
|
||||
test("maincontent cards use a hairline border, not heavy shadow", () => {
|
||||
|
||||
@@ -215,8 +215,10 @@ function buildSshArgs(cfg: ScpConfig): string {
|
||||
return args.join(" ");
|
||||
}
|
||||
|
||||
// Stream the file over ssh stdin instead of scp: OpenSSH 9+ scp defaults to
|
||||
// the SFTP protocol, which Dropbear on OpenWrt does not ship a server for.
|
||||
function buildScpCommand(localPath: string, remotePath: string, cfg: ScpConfig): string {
|
||||
return `scp ${buildSshArgs(cfg)} "${localPath}" "${cfg.host}:${remotePath}"`;
|
||||
return `ssh ${buildSshArgs(cfg)} "${cfg.host}" "cat > '${remotePath}'" < "${localPath}"`;
|
||||
}
|
||||
|
||||
function parseHost(sshHost: string): string {
|
||||
|
||||
@@ -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.4
|
||||
PKG_RELEASE:=37
|
||||
PKG_VERSION:=1.0.5
|
||||
PKG_RELEASE:=38
|
||||
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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--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(selecLine 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-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--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-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
|
||||
@@ -232,6 +232,14 @@
|
||||
trim(`${boardinfo.release?.distribution ?? ''} ${boardinfo.release?.version ?? ''}`);
|
||||
-%}
|
||||
<div class="desktop-menu-container">
|
||||
{#
|
||||
Curtain-reveal layer pair: .desktop-menu-sheet is the opaque
|
||||
surface that slides down (translateY -100% → 0) and clips its
|
||||
content; .desktop-menu-canvas counter-translates (100% → 0) so
|
||||
the panels menu-aurora.js reparents into it appear static while
|
||||
the sheet edge wipes over them — see _layout.css.
|
||||
#}
|
||||
<div class="desktop-menu-sheet"><div class="desktop-menu-canvas"></div></div>
|
||||
<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): %}
|
||||
|
||||
+2
-2
@@ -10,12 +10,12 @@ include $(INCLUDE_DIR)/kernel.mk
|
||||
|
||||
PKG_NAME:=natflow
|
||||
PKG_VERSION:=20260531
|
||||
PKG_RELEASE:=28
|
||||
PKG_RELEASE:=29
|
||||
|
||||
PKG_SOURCE:=$(PKG_VERSION).tar.xz
|
||||
PKG_SOURCE_URL:=https://github.com/ptpt52/natflow.git
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_VERSION:=e3c6601723677e8c97199f68b8075257ec8d64e8
|
||||
PKG_SOURCE_VERSION:=3700ee2266c74690f1f43fc547bdefb0b8d6090f
|
||||
PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION)
|
||||
PKG_MAINTAINER:=Chen Minqiang <ptpt52@gmail.com>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
Regular → Executable
+1
-1
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=pcie_mhi
|
||||
PKG_VERSION:=1.3.8
|
||||
PKG_RELEASE:=25
|
||||
PKG_RELEASE:=26
|
||||
|
||||
include $(INCLUDE_DIR)/kernel.mk
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=watchcat
|
||||
PKG_VERSION:=1
|
||||
PKG_RELEASE:=3
|
||||
PKG_RELEASE:=4
|
||||
|
||||
PKG_MAINTAINER:=Roger D <rogerdammit@gmail.com>
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
@@ -169,6 +169,8 @@ watchcat_monitor_network() {
|
||||
done
|
||||
|
||||
[ "$((time_now - time_lastcheck_withinternet))" -ge "$failure_period" ] && {
|
||||
recovery_started="$time_now"
|
||||
|
||||
if [ "$script" != "" ]; then
|
||||
watchcat_run_script "$script" "$iface"
|
||||
else
|
||||
@@ -188,8 +190,10 @@ watchcat_monitor_network() {
|
||||
time_now="$(cat /proc/uptime)"
|
||||
time_now="${time_now%%.*}"
|
||||
time_lastcheck="$time_now"
|
||||
time_lastcheck_withinternet="$time_now"
|
||||
else
|
||||
time_lastcheck_withinternet="$recovery_started"
|
||||
fi
|
||||
time_lastcheck_withinternet="$time_now"
|
||||
}
|
||||
|
||||
done
|
||||
|
||||
Reference in new issue
Block a user