mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-27 18:41:15 +08:00
489 lines
16 KiB
CSS
489 lines
16 KiB
CSS
header {
|
|
@apply bg-bg sticky top-0 z-40 mb-2 transition-colors 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;
|
|
}
|
|
|
|
[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.
|
|
|
|
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. */
|
|
& .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;
|
|
|
|
&.active,
|
|
&.closing {
|
|
@apply visible;
|
|
}
|
|
|
|
&.active {
|
|
@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.
|
|
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 {
|
|
/* 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-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;
|
|
|
|
&.active {
|
|
@apply pointer-events-auto translate-y-0 opacity-100;
|
|
}
|
|
|
|
& .desktop-nav-anchor {
|
|
@apply self-start max-lg:hidden;
|
|
|
|
& .desktop-nav-title {
|
|
@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, 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 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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-label {
|
|
@apply text-text-subtle mb-0.5 text-[11px] font-medium tracking-wide uppercase;
|
|
}
|
|
|
|
& .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/router.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/package.svg')_center/contain_no-repeat];
|
|
}
|
|
|
|
& .board-line-kernel::before {
|
|
@apply [mask:url('@assets/icons/tag.svg')_center/contain_no-repeat];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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). */
|
|
[data-nav-type="mega-menu"]
|
|
&:has(.desktop-menu-container:is(.active, .closing)) {
|
|
@apply bg-mega-menu-bg 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;
|
|
}
|
|
|
|
.nav {
|
|
@apply absolute left-1/2 z-60 m-0 flex -translate-x-1/2 transform list-none items-center gap-1 p-0 max-md:hidden;
|
|
|
|
& > li {
|
|
@apply relative;
|
|
|
|
.menu {
|
|
@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 bg-brand-subtle text-brand;
|
|
}
|
|
}
|
|
|
|
/* Dropdown panels only — mega-menu panels are reparented into
|
|
.desktop-menu-container (see above). */
|
|
& .desktop-nav {
|
|
@apply pointer-events-none left-0 z-40 px-0 pt-0 opacity-0 transition-opacity duration-150;
|
|
[data-nav-type="dropdown"] & {
|
|
@apply absolute top-full mt-2 min-w-48;
|
|
}
|
|
&.active {
|
|
@apply pointer-events-auto opacity-100;
|
|
}
|
|
|
|
& .desktop-nav-list {
|
|
[data-nav-type="dropdown"] & {
|
|
@apply border-hairline bg-surface-overlay flex flex-col gap-y-1 rounded-3xl border py-2 shadow-lg backdrop-blur-md backdrop-saturate-150;
|
|
}
|
|
& > li {
|
|
@apply m-0 list-none;
|
|
|
|
& > a {
|
|
@apply text-text block whitespace-nowrap no-underline;
|
|
|
|
[data-nav-type="dropdown"] & {
|
|
@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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.navigation-controls {
|
|
@apply mr-3 flex shrink-0 items-center md:hidden;
|
|
|
|
[data-nav-type="sidebar"] & {
|
|
@apply md:flex;
|
|
}
|
|
|
|
& .navigation-toggle {
|
|
@apply cursor-pointer rounded-none border-0 bg-transparent p-0 shadow-none transition-transform duration-150 hover:scale-105 active:scale-95;
|
|
|
|
& svg {
|
|
@apply text-text size-5;
|
|
}
|
|
|
|
& .navigation-toggle-line {
|
|
transform-box: fill-box;
|
|
@apply origin-center transition-[transform,opacity] duration-150 ease-in-out;
|
|
}
|
|
|
|
&.is-expanded {
|
|
& .navigation-toggle-line-top {
|
|
transform: translateY(5px) rotate(45deg);
|
|
}
|
|
|
|
& .navigation-toggle-line-middle {
|
|
transform: scaleX(0.35);
|
|
@apply opacity-0;
|
|
}
|
|
|
|
& .navigation-toggle-line-bottom {
|
|
transform: translate(3.75px, -5px) rotate(-45deg) scaleX(2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#indicators {
|
|
@apply flex shrink-0 flex-row-reverse gap-5 md:gap-8;
|
|
|
|
& span[data-indicator] {
|
|
@apply before:text-text size-5 cursor-pointer text-[0px] before:absolute before:size-5 before:bg-current;
|
|
|
|
&[data-indicator="media_error"] {
|
|
@apply before:[mask:url('@assets/icons/error.svg')_center/cover_no-repeat];
|
|
}
|
|
|
|
&[data-indicator="poll-status"] {
|
|
&[data-style="active"] {
|
|
@apply before:[mask:url('@assets/icons/refresh.svg')_center/cover_no-repeat];
|
|
}
|
|
|
|
&[data-style="inactive"] {
|
|
@apply before:[mask:url('@assets/icons/refresh-off.svg')_center/cover_no-repeat];
|
|
}
|
|
}
|
|
|
|
&[data-indicator="uci-changes"] {
|
|
@apply relative before:[mask:url('@assets/icons/setting.svg')_center/cover_no-repeat];
|
|
|
|
&[data-count]:not([data-count="0"]) {
|
|
@apply after:bg-danger after:text-on-brand after:absolute after:-top-0.5 after:-right-0.5 after:z-10 after:flex after:min-h-3 after:min-w-3 after:items-center after:justify-center after:rounded-full after:px-0.5 after:text-[8px] after:leading-none after:font-bold after:shadow-sm after:content-[attr(data-count)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
body[data-nav-type="sidebar"] {
|
|
/* Desktop header + sidebar form one flush, L-shaped application frame: a
|
|
full-width header on top, a sidebar pinned to the left edge below it.
|
|
A CSS grid (not flex-wrap) keeps the header row at its natural height —
|
|
so collapsing the sidebar never stretches it — lets the sidebar column
|
|
animate to zero, and lets #maincontent fluidly reclaim the freed space.
|
|
overflow-x-clip keeps the sliding sidebar out of horizontal overflow. */
|
|
@apply md:grid md:grid-cols-[17rem_minmax(0,1fr)] md:grid-rows-[auto_minmax(0,1fr)] md:overflow-x-clip md:transition-[grid-template-columns] md:duration-[250ms] md:ease-out;
|
|
|
|
&.sidebar-collapsed {
|
|
@apply md:grid-cols-[0_minmax(0,1fr)];
|
|
}
|
|
|
|
& > header {
|
|
@apply md:border-hairline md:col-span-full md:row-start-1 md:mb-0 md:border-b;
|
|
}
|
|
|
|
& > .sidebar-panel {
|
|
@apply md:col-start-1 md:row-start-2;
|
|
}
|
|
|
|
& > #maincontent {
|
|
@apply md:col-start-2 md:row-start-2 md:min-w-0 md:pt-2;
|
|
}
|
|
|
|
& .header-crumb {
|
|
@apply text-text-muted my-0 mr-auto ml-6 hidden min-w-0 list-none items-center gap-2 overflow-hidden p-0 text-sm md:flex;
|
|
|
|
& li {
|
|
@apply m-0 list-none whitespace-nowrap;
|
|
}
|
|
|
|
& .crumb-sep {
|
|
@apply opacity-50;
|
|
}
|
|
|
|
& .current {
|
|
@apply text-text min-w-0 truncate font-medium;
|
|
}
|
|
}
|
|
|
|
& .sidebar-panel {
|
|
@apply border-hairline sticky top-14 hidden h-[calc(100vh-3.5rem)] w-full overflow-hidden rounded-none border-r transition-[visibility] duration-[250ms] md:block;
|
|
}
|
|
|
|
/* Visibility flips at the end of the slide so collapsed controls leave the
|
|
tab order without clipping the animation. */
|
|
&.sidebar-collapsed .sidebar-panel {
|
|
@apply invisible;
|
|
}
|
|
|
|
& .sidebar-panel-inner {
|
|
@apply bg-bg flex h-full w-68 flex-col overflow-hidden transition-[translate,opacity] duration-[250ms];
|
|
}
|
|
|
|
&.sidebar-collapsed .sidebar-panel-inner {
|
|
@apply -translate-x-full opacity-0;
|
|
}
|
|
|
|
& .sidebar-list {
|
|
@apply m-0 flex-1 list-none space-y-0.5 overflow-y-auto p-3;
|
|
|
|
& li {
|
|
@apply m-0 list-none;
|
|
}
|
|
}
|
|
|
|
& .sidebar-list .navigation-direct {
|
|
@apply truncate text-lg;
|
|
}
|
|
|
|
& .sidebar-submenu {
|
|
@apply m-0 min-h-0 list-none space-y-0.5 overflow-hidden p-0 pl-4;
|
|
}
|
|
|
|
& .sidebar-submenu .navigation-sublink {
|
|
@apply px-3 py-1.5 text-sm;
|
|
}
|
|
|
|
& .sidebar-footer {
|
|
@apply border-hairline shrink-0 border-t p-3;
|
|
|
|
/* Logout: muted at rest; on hover the base .nav-link pill background
|
|
plus error-colored text is the intended compound state (matches v1). */
|
|
& .nav-link {
|
|
@apply text-text-muted hover:text-danger;
|
|
}
|
|
}
|
|
}
|
|
|
|
#maincontent {
|
|
@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 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;
|
|
}
|
|
|
|
.controls {
|
|
@apply m-2 flex flex-wrap items-center gap-3 max-md:m-1 max-md:gap-2;
|
|
|
|
& label {
|
|
& input[type="radio"],
|
|
& input[type="checkbox"] {
|
|
@apply mr-1 align-middle;
|
|
}
|
|
}
|
|
}
|
|
|
|
#content_syslog {
|
|
@apply m-2 max-md:m-1;
|
|
|
|
& > div {
|
|
@apply flex flex-wrap items-baseline gap-x-2 max-md:gap-x-1.5;
|
|
& > .cbi-input-select,
|
|
& > .cbi-input-text {
|
|
@apply max-md:min-w-45;
|
|
}
|
|
}
|
|
}
|
|
|
|
& div[style] {
|
|
& > svg {
|
|
@apply dark:!bg-surface;
|
|
|
|
& line[style] {
|
|
@apply dark:!stroke-text;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.cbi-map-descr,
|
|
.cbi-section-descr {
|
|
@apply text-text-muted mb-6 text-sm leading-relaxed max-md:mx-2 max-md:mb-3 max-md:text-sm;
|
|
}
|
|
|
|
.cbi-page-actions {
|
|
@apply border-hairline mt-6 flex flex-wrap items-center justify-end gap-3 border-t pt-4 max-md:mx-2 max-md:mt-4 max-md:gap-2 max-md:pt-3;
|
|
}
|
|
|
|
.zone-forwards {
|
|
@apply flex items-start gap-3 leading-relaxed;
|
|
|
|
& > span {
|
|
@apply text-text-muted mt-1.5;
|
|
}
|
|
|
|
.zone-dest {
|
|
@apply flex flex-col items-start gap-2;
|
|
}
|
|
}
|
|
|
|
#syslog,
|
|
#log_textarea {
|
|
@apply w-full font-mono text-xs leading-snug;
|
|
}
|
|
|
|
#syslog {
|
|
@apply bg-surface-sunken text-text rounded-3xl border p-6 shadow-lg max-md:p-3;
|
|
}
|
|
}
|
|
|
|
footer {
|
|
@apply text-text-muted mt-2 flex min-h-16 flex-wrap items-center justify-between text-xs;
|
|
|
|
& a {
|
|
@apply text-brand;
|
|
}
|
|
|
|
& span {
|
|
@apply inline-block text-center;
|
|
}
|
|
|
|
& .breadcrumb {
|
|
@apply bg-surface-overlay hover:bg-surface flex items-center gap-1 rounded-full px-3 py-1.5 text-xs shadow-sm backdrop-blur-md backdrop-saturate-150 transition-colors duration-150;
|
|
|
|
& li {
|
|
@apply list-none;
|
|
|
|
&.active a {
|
|
@apply text-text;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.control-group {
|
|
@apply mt-1;
|
|
}
|
|
|
|
.control-group,
|
|
.cbi-page-actions > div,
|
|
.cbi-section-actions > div {
|
|
@apply flex flex-row flex-nowrap items-center gap-2 max-md:flex-wrap max-md:gap-1;
|
|
}
|