mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-10 16:17:51 +08:00
1179 lines
29 KiB
CSS
1179 lines
29 KiB
CSS
/*
|
||
* luci-theme-shadcnui — main stylesheet
|
||
* Targets LuCI markup (.cbi-*, .table, .alert-message, etc.) and reskins it
|
||
* to match shadcn/ui visual language using CSS variables from tokens.css.
|
||
*/
|
||
|
||
@import url("./tokens.css");
|
||
|
||
/* ============================================================
|
||
* RESET / BASE
|
||
* ============================================================ */
|
||
*,
|
||
*::before,
|
||
*::after {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html {
|
||
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
|
||
"Helvetica Neue", Arial, "Noto Sans", sans-serif,
|
||
"Apple Color Emoji", "Segoe UI Emoji";
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
-webkit-font-smoothing: antialiased;
|
||
-moz-osx-font-smoothing: grayscale;
|
||
}
|
||
|
||
body {
|
||
margin: 0;
|
||
background: hsl(var(--background));
|
||
color: hsl(var(--foreground));
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
a {
|
||
color: hsl(var(--primary));
|
||
text-decoration: none;
|
||
transition: color 120ms ease;
|
||
}
|
||
a:hover { text-decoration: underline; }
|
||
|
||
code, pre, kbd, samp {
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||
"Liberation Mono", "Courier New", monospace;
|
||
font-size: 0.875em;
|
||
}
|
||
|
||
pre {
|
||
background: hsl(var(--muted));
|
||
color: hsl(var(--foreground));
|
||
padding: 0.75rem 1rem;
|
||
border-radius: var(--radius);
|
||
border: 1px solid hsl(var(--border));
|
||
overflow-x: auto;
|
||
}
|
||
|
||
hr {
|
||
border: 0;
|
||
border-top: 1px solid hsl(var(--border));
|
||
margin: 1rem 0;
|
||
}
|
||
|
||
/* ============================================================
|
||
* APP SHELL: sidebar + topbar + main
|
||
* ============================================================ */
|
||
#shadcn-app {
|
||
display: grid;
|
||
grid-template-columns: 260px 1fr;
|
||
grid-template-rows: 56px 1fr;
|
||
grid-template-areas:
|
||
"sidebar topbar"
|
||
"sidebar main";
|
||
min-height: 100vh;
|
||
}
|
||
|
||
#shadcn-sidebar {
|
||
grid-area: sidebar;
|
||
background: hsl(var(--sidebar-background, var(--card)));
|
||
color: hsl(var(--sidebar-foreground, var(--foreground)));
|
||
border-right: 1px solid hsl(var(--sidebar-border, var(--border)));
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: sticky;
|
||
top: 0;
|
||
height: 100vh;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#shadcn-topbar {
|
||
grid-area: topbar;
|
||
background: hsl(var(--background) / 0.85);
|
||
backdrop-filter: saturate(180%) blur(8px);
|
||
-webkit-backdrop-filter: saturate(180%) blur(8px);
|
||
border-bottom: 1px solid hsl(var(--border));
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.75rem;
|
||
padding: 0 1rem;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 30;
|
||
}
|
||
|
||
#shadcn-main {
|
||
grid-area: main;
|
||
padding: 1.5rem;
|
||
min-width: 0;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
#shadcn-app {
|
||
grid-template-columns: 1fr;
|
||
grid-template-areas:
|
||
"topbar"
|
||
"main";
|
||
}
|
||
#shadcn-sidebar {
|
||
position: fixed;
|
||
inset: 0 auto 0 0;
|
||
width: 280px;
|
||
z-index: 50;
|
||
transform: translateX(-100%);
|
||
transition: transform 200ms ease;
|
||
box-shadow: 0 8px 24px -8px hsl(0 0% 0% / 0.18);
|
||
}
|
||
#shadcn-app[data-sidebar="open"] #shadcn-sidebar {
|
||
transform: translateX(0);
|
||
}
|
||
#shadcn-app[data-sidebar="open"]::after {
|
||
content: "";
|
||
position: fixed;
|
||
inset: 0;
|
||
background: hsl(0 0% 0% / 0.4);
|
||
z-index: 40;
|
||
}
|
||
#shadcn-main { padding: 1rem; }
|
||
}
|
||
|
||
/* ============================================================
|
||
* SIDEBAR
|
||
* ============================================================ */
|
||
.sidebar-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.625rem;
|
||
padding: 0 1rem;
|
||
height: 56px;
|
||
border-bottom: 1px solid hsl(var(--sidebar-border, var(--border)));
|
||
font-weight: 600;
|
||
font-size: 0.95rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sidebar-header .logo {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: calc(var(--radius) - 2px);
|
||
background: hsl(var(--primary));
|
||
color: hsl(var(--primary-foreground));
|
||
display: grid;
|
||
place-items: center;
|
||
font-weight: 700;
|
||
font-size: 0.85rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sidebar-nav {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 0.75rem 0.5rem;
|
||
scrollbar-width: thin;
|
||
scrollbar-color: hsl(var(--border)) transparent;
|
||
}
|
||
|
||
.sidebar-nav::-webkit-scrollbar { width: 6px; }
|
||
.sidebar-nav::-webkit-scrollbar-thumb {
|
||
background: hsl(var(--border));
|
||
border-radius: 3px;
|
||
}
|
||
|
||
.sidebar-section {
|
||
margin-bottom: 0.125rem;
|
||
}
|
||
|
||
.sidebar-section-label {
|
||
font-size: 0.72rem;
|
||
font-weight: 600;
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
color: hsl(var(--muted-foreground));
|
||
padding: 0.5rem 0.75rem 0.25rem;
|
||
}
|
||
|
||
.sidebar-link,
|
||
.sidebar-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.625rem;
|
||
padding: 0.5rem 0.75rem;
|
||
margin: 1px 0;
|
||
border-radius: calc(var(--radius) - 2px);
|
||
font-size: 0.9375rem; /* ~15px, matches shadcn original */
|
||
color: hsl(var(--sidebar-foreground, var(--foreground)));
|
||
cursor: pointer;
|
||
user-select: none;
|
||
border: 0;
|
||
background: transparent;
|
||
width: 100%;
|
||
text-align: left;
|
||
transition: background-color 120ms ease, color 120ms ease;
|
||
}
|
||
|
||
.sidebar-link:hover,
|
||
.sidebar-toggle:hover {
|
||
background: hsl(var(--sidebar-accent, var(--accent)));
|
||
color: hsl(var(--sidebar-accent-foreground, var(--accent-foreground)));
|
||
text-decoration: none;
|
||
}
|
||
|
||
.sidebar-link.active {
|
||
background: hsl(var(--sidebar-accent, var(--accent)));
|
||
color: hsl(var(--sidebar-accent-foreground, var(--accent-foreground)));
|
||
font-weight: 500;
|
||
}
|
||
|
||
.sidebar-link .icon,
|
||
.sidebar-toggle .icon {
|
||
width: 17px;
|
||
height: 17px;
|
||
flex-shrink: 0;
|
||
opacity: 0.85;
|
||
stroke-linecap: round;
|
||
stroke-linejoin: round;
|
||
}
|
||
|
||
.sidebar-toggle .chevron {
|
||
margin-left: auto;
|
||
width: 15px;
|
||
height: 15px;
|
||
transition: transform 200ms ease;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
/* Closed: chevron points right (default polyline 9 18 15 12 9 6) */
|
||
/* Open: rotate to point down */
|
||
.sidebar-section[data-open="true"] > .sidebar-toggle .chevron {
|
||
transform: rotate(90deg);
|
||
}
|
||
|
||
/* Children wrapper with vertical guide line */
|
||
.sidebar-children {
|
||
display: none;
|
||
position: relative;
|
||
padding-left: 2.25rem; /* icon width (17px) + gap (10px) + indent */
|
||
margin-left: 0.875rem; /* align line under icon center */
|
||
}
|
||
|
||
.sidebar-children::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0.85rem; /* center under the icon */
|
||
top: 0.25rem;
|
||
bottom: 0.25rem;
|
||
width: 1px;
|
||
background: hsl(var(--border));
|
||
border-radius: 1px;
|
||
}
|
||
|
||
.sidebar-section[data-open="true"] > .sidebar-children {
|
||
display: block;
|
||
}
|
||
|
||
.sidebar-children .sidebar-link {
|
||
font-size: 0.875rem;
|
||
padding: 0.35rem 0.5rem;
|
||
}
|
||
|
||
.sidebar-footer {
|
||
padding: 0.5rem;
|
||
border-top: 1px solid hsl(var(--sidebar-border, var(--border)));
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 0.25rem;
|
||
}
|
||
|
||
.sidebar-footer .mode-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 0.25rem 0.6rem;
|
||
border-radius: 9999px;
|
||
font-size: 0.75rem;
|
||
color: hsl(var(--muted-foreground));
|
||
background: transparent;
|
||
border: 1px solid hsl(var(--border));
|
||
text-decoration: none;
|
||
transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
|
||
}
|
||
.sidebar-footer .mode-chip:hover {
|
||
background: hsl(var(--accent));
|
||
color: hsl(var(--accent-foreground));
|
||
text-decoration: none;
|
||
}
|
||
.sidebar-footer .mode-chip.active {
|
||
background: hsl(var(--primary));
|
||
color: hsl(var(--primary-foreground));
|
||
border-color: hsl(var(--primary));
|
||
}
|
||
|
||
/* Brand link in the sidebar header */
|
||
.sidebar-header .brand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.625rem;
|
||
color: inherit;
|
||
text-decoration: none;
|
||
width: 100%;
|
||
font-weight: 600;
|
||
font-size: 0.95rem;
|
||
}
|
||
.sidebar-header .brand:hover { text-decoration: none; }
|
||
|
||
/* Top-level sidebar links (no children) */
|
||
.sidebar-link.sidebar-link-top {
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Mobile overlay */
|
||
#shadcn-overlay {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: hsl(0 0% 0% / 0.4);
|
||
z-index: 40;
|
||
}
|
||
@media (max-width: 768px) {
|
||
#shadcn-app[data-sidebar="open"] #shadcn-overlay {
|
||
display: block;
|
||
}
|
||
}
|
||
|
||
/* ============================================================
|
||
* TOPBAR
|
||
* ============================================================ */
|
||
.topbar-toggle {
|
||
display: none; /* shown only on mobile via the media query below */
|
||
place-items: center;
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: calc(var(--radius) - 2px);
|
||
border: 1px solid hsl(var(--border));
|
||
background: hsl(var(--background));
|
||
color: hsl(var(--foreground));
|
||
cursor: pointer;
|
||
}
|
||
.topbar-toggle:hover { background: hsl(var(--accent)); }
|
||
|
||
@media (max-width: 768px) {
|
||
.topbar-toggle { display: inline-grid; }
|
||
}
|
||
|
||
.topbar-breadcrumb {
|
||
font-size: 0.875rem;
|
||
color: hsl(var(--muted-foreground));
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.4rem;
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.topbar-breadcrumb .crumb-sep { opacity: 0.5; }
|
||
.topbar-breadcrumb .crumb-current {
|
||
color: hsl(var(--foreground));
|
||
font-weight: 500;
|
||
}
|
||
|
||
.topbar-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
/* ============================================================
|
||
* BUTTONS (LuCI uses input.cbi-button + .btn)
|
||
* ============================================================ */
|
||
.btn,
|
||
input.cbi-button,
|
||
button.cbi-button,
|
||
.cbi-page-actions .cbi-button,
|
||
.cbi-page-actions input[type="submit"],
|
||
.cbi-page-actions input[type="button"],
|
||
.cbi-page-actions input[type="reset"] {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 0.45rem;
|
||
height: 36px;
|
||
padding: 0 0.875rem;
|
||
border-radius: calc(var(--radius) - 2px);
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
font-family: inherit;
|
||
border: 1px solid transparent;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
white-space: nowrap;
|
||
transition: background-color 120ms ease, border-color 120ms ease,
|
||
color 120ms ease, opacity 120ms ease,
|
||
transform 80ms ease, box-shadow 120ms ease;
|
||
|
||
background: hsl(var(--primary));
|
||
color: hsl(var(--primary-foreground));
|
||
}
|
||
.btn:hover,
|
||
input.cbi-button:hover,
|
||
button.cbi-button:hover {
|
||
opacity: 0.9;
|
||
}
|
||
|
||
/* Press animation — subtle downward shift to feel tactile */
|
||
.btn:active,
|
||
input.cbi-button:active,
|
||
button.cbi-button:active {
|
||
transform: translateY(1px);
|
||
}
|
||
|
||
/* Focus ring — thin inset shadow instead of a chunky outline */
|
||
.btn:focus,
|
||
input.cbi-button:focus,
|
||
button.cbi-button:focus,
|
||
.btn:focus-visible,
|
||
input.cbi-button:focus-visible,
|
||
button.cbi-button:focus-visible {
|
||
outline: none;
|
||
box-shadow: 0 0 0 2px hsl(var(--background)),
|
||
0 0 0 4px hsl(var(--ring) / 0.5);
|
||
}
|
||
/* Hide the ring for mouse clicks; only show for keyboard focus */
|
||
.btn:focus:not(:focus-visible),
|
||
input.cbi-button:focus:not(:focus-visible),
|
||
button.cbi-button:focus:not(:focus-visible) {
|
||
box-shadow: none;
|
||
}
|
||
|
||
.btn-secondary,
|
||
.cbi-button-neutral,
|
||
input.cbi-button-neutral {
|
||
background: hsl(var(--secondary));
|
||
color: hsl(var(--secondary-foreground));
|
||
}
|
||
|
||
/* Reset / Clear — destructive (red) */
|
||
.cbi-button-reset,
|
||
input.cbi-button-reset {
|
||
background: hsl(var(--destructive) / 0.1);
|
||
color: hsl(var(--destructive));
|
||
border: 1px solid hsl(var(--destructive) / 0.3);
|
||
}
|
||
.cbi-button-reset:hover,
|
||
input.cbi-button-reset:hover {
|
||
background: hsl(var(--destructive) / 0.18);
|
||
opacity: 1;
|
||
}
|
||
|
||
.btn-outline,
|
||
.cbi-button-edit,
|
||
input.cbi-button-edit {
|
||
background: hsl(var(--background));
|
||
color: hsl(var(--foreground));
|
||
border-color: hsl(var(--border));
|
||
}
|
||
.btn-outline:hover,
|
||
.cbi-button-edit:hover { background: hsl(var(--accent)); }
|
||
|
||
.btn-ghost {
|
||
background: transparent;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
.btn-ghost:hover { background: hsl(var(--accent)); }
|
||
|
||
.btn-destructive,
|
||
.cbi-button-remove,
|
||
.cbi-button-negative,
|
||
input.cbi-button-remove,
|
||
input.cbi-button-negative {
|
||
background: hsl(var(--destructive));
|
||
color: hsl(var(--destructive-foreground));
|
||
}
|
||
|
||
/* Save — green outline */
|
||
.cbi-button-save,
|
||
input.cbi-button-save {
|
||
background: hsl(142 71% 95%) !important;
|
||
color: hsl(142 71% 30%) !important;
|
||
border: 1px solid hsl(142 71% 60%) !important;
|
||
}
|
||
.cbi-button-save:hover,
|
||
input.cbi-button-save:hover {
|
||
background: hsl(142 71% 88%) !important;
|
||
opacity: 1 !important;
|
||
}
|
||
|
||
/* Reset / Clear — red outline */
|
||
.cbi-button-reset,
|
||
input.cbi-button-reset {
|
||
background: hsl(var(--destructive) / 0.06) !important;
|
||
color: hsl(var(--destructive)) !important;
|
||
border: 1px solid hsl(var(--destructive) / 0.4) !important;
|
||
}
|
||
.cbi-button-reset:hover,
|
||
input.cbi-button-reset:hover {
|
||
background: hsl(var(--destructive) / 0.12) !important;
|
||
opacity: 1 !important;
|
||
}
|
||
|
||
/* Add / positive */
|
||
.cbi-button-add,
|
||
.cbi-button-positive,
|
||
input.cbi-button-add {
|
||
background: hsl(var(--primary));
|
||
color: hsl(var(--primary-foreground));
|
||
}
|
||
|
||
/* Apply dropdown — CSS only, argon-style */
|
||
.cbi-page-actions .cbi-dropdown {
|
||
position: relative !important;
|
||
display: inline-flex !important;
|
||
align-items: center !important;
|
||
height: 36px !important;
|
||
padding: 0 !important;
|
||
background: hsl(var(--background)) !important;
|
||
color: hsl(var(--foreground)) !important;
|
||
border: 1px solid hsl(var(--border)) !important;
|
||
border-radius: calc(var(--radius) - 2px) !important;
|
||
font-size: 0.875rem !important;
|
||
font-weight: 500 !important;
|
||
cursor: pointer !important;
|
||
overflow: visible !important;
|
||
box-shadow: none !important;
|
||
line-height: 36px !important;
|
||
min-width: 120px !important;
|
||
}
|
||
|
||
/* Main ul — show inline items */
|
||
.cbi-page-actions .cbi-dropdown > ul:not(.dropdown) {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
padding: 0 0.75rem !important;
|
||
margin: 0 !important;
|
||
list-style: none !important;
|
||
height: 100% !important;
|
||
}
|
||
|
||
.cbi-page-actions .cbi-dropdown > ul:not(.dropdown) > li {
|
||
display: none !important;
|
||
list-style: none !important;
|
||
}
|
||
|
||
/* Show only LuCI-selected item (has display attribute) */
|
||
.cbi-page-actions .cbi-dropdown > ul:not(.dropdown) > li[display] {
|
||
display: block !important;
|
||
white-space: nowrap !important;
|
||
}
|
||
|
||
/* Hide more (···) like argon */
|
||
.cbi-page-actions .cbi-dropdown > .more {
|
||
display: block !important;
|
||
width: 1px !important;
|
||
height: 100% !important;
|
||
overflow: hidden !important;
|
||
text-indent: 5000px !important;
|
||
}
|
||
|
||
/* Hide hidden input div */
|
||
.cbi-page-actions .cbi-dropdown > div {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Arrow */
|
||
.cbi-page-actions .cbi-dropdown > .open {
|
||
display: flex !important;
|
||
align-items: center !important;
|
||
justify-content: center !important;
|
||
width: 2rem !important;
|
||
height: 100% !important;
|
||
padding: 0 !important;
|
||
font-size: 0.75rem !important;
|
||
color: hsl(var(--muted-foreground)) !important;
|
||
cursor: pointer !important;
|
||
background-color: transparent !important;
|
||
mask-image: none !important;
|
||
-webkit-mask-image: none !important;
|
||
border-left: 1px solid hsl(var(--border)) !important;
|
||
flex-shrink: 0 !important;
|
||
}
|
||
|
||
/* Dropdown list */
|
||
.cbi-page-actions .cbi-dropdown > ul.dropdown {
|
||
position: absolute !important;
|
||
bottom: auto !important;
|
||
top: calc(100% + 4px) !important;
|
||
left: 0 !important;
|
||
min-width: 180px !important;
|
||
background: hsl(var(--card)) !important;
|
||
border: 1px solid hsl(var(--border)) !important;
|
||
border-radius: calc(var(--radius) - 2px) !important;
|
||
box-shadow: 0 4px 16px hsl(0 0% 0% / 0.12) !important;
|
||
list-style: none !important;
|
||
padding: 0.25rem !important;
|
||
margin: 0 !important;
|
||
z-index: 200 !important;
|
||
}
|
||
|
||
.cbi-page-actions .cbi-dropdown > ul.dropdown > li {
|
||
display: block !important;
|
||
padding: 0.4rem 0.65rem !important;
|
||
font-size: 0.875rem !important;
|
||
border-radius: calc(var(--radius) - 4px) !important;
|
||
cursor: pointer !important;
|
||
white-space: nowrap !important;
|
||
color: hsl(var(--foreground)) !important;
|
||
border-bottom: 0 !important;
|
||
list-style: none !important;
|
||
line-height: 1.4 !important;
|
||
}
|
||
|
||
.cbi-page-actions .cbi-dropdown > ul.dropdown > li:hover {
|
||
background: hsl(var(--accent)) !important;
|
||
color: hsl(var(--accent-foreground)) !important;
|
||
}
|
||
|
||
/* Last item (Применить без проверки) — destructive style with separator */
|
||
.cbi-page-actions .cbi-dropdown > ul.dropdown > li:last-child {
|
||
color: hsl(var(--destructive)) !important;
|
||
margin-top: 0.25rem !important;
|
||
padding-top: 0.4rem !important;
|
||
border-top: 1px solid hsl(var(--border)) !important;
|
||
}
|
||
|
||
.cbi-page-actions .cbi-dropdown > ul.dropdown > li:last-child:hover {
|
||
background: hsl(var(--destructive) / 0.08) !important;
|
||
color: hsl(var(--destructive)) !important;
|
||
}
|
||
|
||
.btn-sm { height: 32px; padding: 0 0.65rem; font-size: 0.8125rem; }
|
||
.btn-lg { height: 40px; padding: 0 1.125rem; font-size: 0.9375rem; }
|
||
.btn-icon { width: 36px; padding: 0; }
|
||
|
||
/* ============================================================
|
||
* INPUTS
|
||
* ============================================================ */
|
||
input[type="text"],
|
||
input[type="password"],
|
||
input[type="email"],
|
||
input[type="number"],
|
||
input[type="search"],
|
||
input[type="url"],
|
||
input[type="tel"],
|
||
input:not([type]),
|
||
textarea,
|
||
select {
|
||
display: block;
|
||
width: 100%;
|
||
height: 36px;
|
||
padding: 0 0.75rem;
|
||
font-size: 0.875rem;
|
||
font-family: inherit;
|
||
background: hsl(var(--background));
|
||
color: hsl(var(--foreground));
|
||
border: 1px solid hsl(var(--input));
|
||
border-radius: calc(var(--radius) - 2px);
|
||
transition: border-color 120ms ease, box-shadow 120ms ease;
|
||
}
|
||
|
||
textarea {
|
||
height: auto;
|
||
min-height: 80px;
|
||
padding: 0.5rem 0.75rem;
|
||
resize: vertical;
|
||
}
|
||
|
||
input:focus,
|
||
textarea:focus,
|
||
select:focus {
|
||
outline: none;
|
||
border-color: hsl(var(--ring));
|
||
box-shadow: 0 0 0 3px hsl(var(--ring) / 0.18);
|
||
}
|
||
|
||
input[type="checkbox"],
|
||
input[type="radio"] {
|
||
width: 16px;
|
||
height: 16px;
|
||
accent-color: hsl(var(--primary));
|
||
}
|
||
|
||
label {
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
|
||
/* LuCI form rows */
|
||
.cbi-section {
|
||
background: hsl(var(--card));
|
||
color: hsl(var(--card-foreground));
|
||
border: 1px solid hsl(var(--border));
|
||
border-radius: var(--radius);
|
||
padding: 1.25rem 1.5rem;
|
||
margin-bottom: 1.25rem;
|
||
box-shadow: 0 1px 2px 0 hsl(0 0% 0% / 0.04);
|
||
}
|
||
|
||
.cbi-section h3,
|
||
.cbi-section legend,
|
||
.cbi-section .cbi-section-descr {
|
||
margin: 0 0 0.25rem;
|
||
}
|
||
.cbi-section legend,
|
||
.cbi-section h3 {
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
}
|
||
.cbi-section-descr {
|
||
color: hsl(var(--muted-foreground));
|
||
font-size: 0.875rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.cbi-value {
|
||
display: grid;
|
||
grid-template-columns: 220px 1fr;
|
||
gap: 1rem;
|
||
align-items: start;
|
||
padding: 0.625rem 0;
|
||
border-bottom: 1px solid hsl(var(--border));
|
||
}
|
||
.cbi-value:last-child { border-bottom: 0; }
|
||
|
||
.cbi-value-title {
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
padding-top: 0.5rem;
|
||
}
|
||
|
||
.cbi-value-description {
|
||
grid-column: 2;
|
||
font-size: 0.8125rem;
|
||
color: hsl(var(--muted-foreground));
|
||
margin-top: 0.25rem;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.cbi-value { grid-template-columns: 1fr; gap: 0.4rem; }
|
||
.cbi-value-description { grid-column: auto; }
|
||
}
|
||
|
||
/* ============================================================
|
||
* TABLES
|
||
* ============================================================ */
|
||
.table,
|
||
table.cbi-section-table {
|
||
width: 100%;
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
background: hsl(var(--card));
|
||
border: 1px solid hsl(var(--border));
|
||
border-radius: var(--radius);
|
||
overflow: hidden;
|
||
font-size: 0.875rem;
|
||
}
|
||
|
||
.table .tr,
|
||
.cbi-section-table tr {
|
||
border-bottom: 1px solid hsl(var(--border));
|
||
}
|
||
.table .tr:last-child,
|
||
.cbi-section-table tr:last-child { border-bottom: 0; }
|
||
|
||
.table .th,
|
||
.cbi-section-table th {
|
||
background: hsl(var(--muted));
|
||
color: hsl(var(--muted-foreground));
|
||
font-weight: 500;
|
||
text-align: left;
|
||
padding: 0.625rem 0.875rem;
|
||
font-size: 0.8125rem;
|
||
letter-spacing: 0.01em;
|
||
}
|
||
|
||
.table .td,
|
||
.cbi-section-table td {
|
||
padding: 0.625rem 0.875rem;
|
||
vertical-align: middle;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
|
||
.table .tr:hover .td,
|
||
.cbi-section-table tr:hover td {
|
||
background: hsl(var(--accent) / 0.5);
|
||
}
|
||
|
||
/* ============================================================
|
||
* ALERTS
|
||
* ============================================================ */
|
||
.alert-message {
|
||
display: flex;
|
||
gap: 0.625rem;
|
||
padding: 0.875rem 1rem;
|
||
border: 1px solid hsl(var(--border));
|
||
border-radius: var(--radius);
|
||
background: hsl(var(--card));
|
||
color: hsl(var(--card-foreground));
|
||
font-size: 0.875rem;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.alert-message.warning,
|
||
.alert-message.notice {
|
||
border-color: hsl(var(--warning) / 0.4);
|
||
background: hsl(var(--warning) / 0.08);
|
||
}
|
||
|
||
.alert-message.error,
|
||
.alert-message.danger {
|
||
border-color: hsl(var(--destructive) / 0.4);
|
||
background: hsl(var(--destructive) / 0.08);
|
||
color: hsl(var(--destructive));
|
||
}
|
||
|
||
.alert-message.info {
|
||
border-color: hsl(var(--info) / 0.4);
|
||
background: hsl(var(--info) / 0.08);
|
||
}
|
||
|
||
.alert-message.success {
|
||
border-color: hsl(var(--success) / 0.4);
|
||
background: hsl(var(--success) / 0.08);
|
||
}
|
||
|
||
/* ============================================================
|
||
* TABS (LuCI tab strip)
|
||
* ============================================================ */
|
||
.cbi-tabmenu {
|
||
display: flex;
|
||
gap: 0.25rem;
|
||
border-bottom: 1px solid hsl(var(--border));
|
||
margin-bottom: 1rem;
|
||
padding: 0;
|
||
list-style: none;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
/* CBI tab content panels — hidden by default, shown when active */
|
||
[data-tab-title] {
|
||
display: none !important;
|
||
}
|
||
|
||
[data-tab-title][data-tab-active="true"] {
|
||
display: block !important;
|
||
}
|
||
|
||
.cbi-tabmenu li {
|
||
list-style: none;
|
||
}
|
||
|
||
.cbi-tabmenu li a {
|
||
display: inline-block;
|
||
padding: 0.5rem 0.875rem;
|
||
font-size: 0.875rem;
|
||
color: hsl(var(--muted-foreground));
|
||
border-bottom: 2px solid transparent;
|
||
margin-bottom: -1px;
|
||
transition: color 120ms ease, border-color 120ms ease;
|
||
}
|
||
.cbi-tabmenu li a:hover {
|
||
color: hsl(var(--foreground));
|
||
text-decoration: none;
|
||
}
|
||
.cbi-tabmenu li.cbi-tab a,
|
||
.cbi-tabmenu li.active a {
|
||
color: hsl(var(--foreground));
|
||
border-bottom-color: hsl(var(--primary));
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* ============================================================
|
||
* BADGES
|
||
* ============================================================ */
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 2px 0.5rem;
|
||
border-radius: 9999px;
|
||
font-size: 0.75rem;
|
||
font-weight: 500;
|
||
background: hsl(var(--secondary));
|
||
color: hsl(var(--secondary-foreground));
|
||
}
|
||
.badge-primary { background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); }
|
||
.badge-success { background: hsl(var(--success) / 0.15); color: hsl(var(--success)); }
|
||
.badge-warning { background: hsl(var(--warning) / 0.15); color: hsl(var(--warning)); }
|
||
.badge-destructive { background: hsl(var(--destructive) / 0.15); color: hsl(var(--destructive)); }
|
||
|
||
/* ============================================================
|
||
* LOGIN / SYSAUTH PAGE
|
||
* Our overridden /usr/share/ucode/luci/template/sysauth.ut renders:
|
||
* <body class="lang_xx sysauth">
|
||
* <main id="login-root">
|
||
* <div class="login-bg" style="background-image:url(...)"></div> (optional)
|
||
* <div class="sysauth-card">
|
||
* <div class="sysauth-brand">…</div>
|
||
* <form>
|
||
* <div class="alert-message error">…</div> (only on failure)
|
||
* <div class="cbi-value"><label/><input/></div> (× 2)
|
||
* <div class="cbi-page-actions"><input submit/></div>
|
||
* </form>
|
||
* </div>
|
||
* </main>
|
||
* </body>
|
||
* ============================================================ */
|
||
body.sysauth {
|
||
margin: 0;
|
||
min-height: 100vh;
|
||
background: hsl(var(--background));
|
||
color: hsl(var(--foreground));
|
||
}
|
||
|
||
#login-root {
|
||
position: relative;
|
||
min-height: 100vh;
|
||
display: grid;
|
||
place-items: center;
|
||
padding: 2rem 1rem;
|
||
}
|
||
|
||
.login-bg {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: -1;
|
||
background-size: cover;
|
||
background-position: center;
|
||
background-repeat: no-repeat;
|
||
}
|
||
.login-bg::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background: hsl(var(--background) / 0.7);
|
||
backdrop-filter: blur(4px);
|
||
-webkit-backdrop-filter: blur(4px);
|
||
}
|
||
|
||
.sysauth-card {
|
||
width: 100%;
|
||
max-width: 400px;
|
||
background: hsl(var(--card));
|
||
color: hsl(var(--card-foreground));
|
||
border: 1px solid hsl(var(--border));
|
||
border-radius: var(--radius);
|
||
padding: 1.75rem;
|
||
box-shadow: 0 1px 3px 0 hsl(0 0% 0% / 0.1),
|
||
0 1px 2px -1px hsl(0 0% 0% / 0.1);
|
||
}
|
||
|
||
.sysauth-card .sysauth-brand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.875rem;
|
||
margin-bottom: 1.5rem;
|
||
}
|
||
|
||
.sysauth-card .sysauth-brand .logo {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: calc(var(--radius) - 2px);
|
||
background: hsl(var(--primary));
|
||
color: hsl(var(--primary-foreground));
|
||
display: grid;
|
||
place-items: center;
|
||
font-weight: 700;
|
||
font-size: 0.9rem;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.sysauth-card h2 {
|
||
margin: 0;
|
||
font-size: 1.125rem;
|
||
font-weight: 600;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.sysauth-card .muted {
|
||
color: hsl(var(--muted-foreground));
|
||
font-size: 0.8125rem;
|
||
margin: 0.125rem 0 0;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.sysauth-card form { margin: 0; }
|
||
|
||
.sysauth-card .cbi-value {
|
||
display: grid;
|
||
grid-template-columns: 1fr;
|
||
gap: 0.375rem;
|
||
padding: 0;
|
||
border: 0;
|
||
margin-bottom: 0.875rem;
|
||
}
|
||
.sysauth-card .cbi-value:last-of-type { margin-bottom: 0; }
|
||
|
||
.sysauth-card .cbi-value label {
|
||
font-size: 0.8125rem;
|
||
font-weight: 500;
|
||
color: hsl(var(--foreground));
|
||
}
|
||
|
||
.sysauth-card .cbi-value input { width: 100%; }
|
||
|
||
.sysauth-card .cbi-page-actions {
|
||
margin-top: 1.5rem;
|
||
padding-top: 0;
|
||
border-top: 0;
|
||
justify-content: stretch;
|
||
}
|
||
|
||
.sysauth-card .cbi-page-actions input[type="submit"],
|
||
.sysauth-card .cbi-page-actions button {
|
||
width: 100%;
|
||
height: 38px;
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* ============================================================
|
||
* MISC LuCI-isms
|
||
* ============================================================ */
|
||
.cbi-page-actions {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
justify-content: flex-end;
|
||
margin-top: 1.25rem;
|
||
padding-top: 1.25rem;
|
||
border-top: 1px solid hsl(var(--border));
|
||
}
|
||
|
||
/* ============================================================
|
||
* Progress bar
|
||
* LuCI ships <div class="cbi-progressbar" title="…"><div style="width:N%"></div></div>
|
||
* The `title` attribute carries a human-readable label (e.g. "Used: 264 / 4096 MB (6%)")
|
||
* and the inner div carries the fill percentage as inline width.
|
||
* Style mirrors shadcn's <Progress /> component: small label above, thin track below.
|
||
* ============================================================ */
|
||
/* Wrapper — no overflow:hidden here so ::before is not clipped */
|
||
.cbi-progressbar {
|
||
position: relative;
|
||
height: 8px;
|
||
margin-top: 2em; /* leave room for the title pseudo-element */
|
||
margin-bottom: 0.75em;
|
||
min-width: 120px;
|
||
width: 100%;
|
||
}
|
||
|
||
/* The actual track — overflow:hidden lives here so the fill is clipped properly */
|
||
.cbi-progressbar > div {
|
||
background: hsl(var(--primary));
|
||
height: 100%;
|
||
border-radius: 9999px;
|
||
transition: width 250ms ease-out;
|
||
}
|
||
|
||
/* Track background rendered via ::after so we keep overflow:hidden off the wrapper */
|
||
.cbi-progressbar::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background: hsl(var(--muted));
|
||
border-radius: 9999px;
|
||
z-index: -1;
|
||
}
|
||
|
||
/* Title text: positioned above the track using attr(title) */
|
||
.cbi-progressbar::before {
|
||
content: attr(title);
|
||
position: absolute;
|
||
top: -1.75em;
|
||
left: 0;
|
||
right: 0;
|
||
font-size: 0.8125rem;
|
||
color: hsl(var(--foreground));
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* Inside cbi-section table cells, progress bars share the row with a label
|
||
on the left, so they should not push themselves down with extra top margin. */
|
||
td > .cbi-progressbar,
|
||
.td > .cbi-progressbar,
|
||
.cbi-value-field > .cbi-progressbar {
|
||
margin-top: 2em;
|
||
}
|
||
|
||
|
||
/* ============================================================
|
||
* SPINNER (.spinning)
|
||
* The element contains a text label like "Loading view…"; we want a small
|
||
* spinning ring on the left and the text right next to it (mirrors how the
|
||
* stock bootstrap theme renders it).
|
||
* ============================================================ */
|
||
.spinning {
|
||
position: relative;
|
||
padding-left: 26px;
|
||
display: inline-block;
|
||
line-height: 20px;
|
||
color: hsl(var(--muted-foreground));
|
||
}
|
||
|
||
.spinning::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 0;
|
||
width: 16px;
|
||
height: 16px;
|
||
margin-top: -8px;
|
||
border: 2px solid hsl(var(--border));
|
||
border-top-color: hsl(var(--primary));
|
||
border-radius: 50%;
|
||
animation: shadcnui-spin 0.7s linear infinite;
|
||
}
|
||
|
||
/* When LuCI shows a full-page loader (`<div id="view"><div class="spinning">…`)
|
||
it centers it via `display: table; margin: 15vh auto`. Honor that by giving
|
||
the wrapper some breathing room. */
|
||
#view > .spinning:first-child {
|
||
display: block;
|
||
margin: 15vh auto 0;
|
||
text-align: left;
|
||
font-size: 0.9375rem;
|
||
}
|
||
|
||
@keyframes shadcnui-spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
/* H1/H2 headings of pages */
|
||
h1, h2 {
|
||
font-weight: 600;
|
||
line-height: 1.25;
|
||
margin: 0 0 0.5rem;
|
||
}
|
||
h1 { font-size: 1.5rem; }
|
||
h2 { font-size: 1.25rem; }
|
||
|
||
.page-title {
|
||
font-size: 1.5rem;
|
||
font-weight: 600;
|
||
margin: 0 0 0.25rem;
|
||
}
|
||
.page-description {
|
||
color: hsl(var(--muted-foreground));
|
||
font-size: 0.9375rem;
|
||
margin: 0 0 1.25rem;
|
||
}
|
||
|
||
/* Scrollbars (Webkit) for whole page */
|
||
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb {
|
||
background: hsl(var(--border));
|
||
border-radius: 5px;
|
||
}
|
||
::-webkit-scrollbar-thumb:hover { background: hsl(var(--muted-foreground) / 0.5); }
|