Files

6865 lines
218 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@import "./fonts.css" layer(base);
@import "tailwindcss";
@import "tw-animate-css";
@import "./utilities.css";
@plugin 'tailwind-scrollbar';
@plugin '@tailwindcss/typography';
@plugin '@tailwindcss/forms';
@source '../../storage/framework/views/*.php';
/* A custom theme can give the sidebar the other scheme than the page
(data-sidebar-scheme on <html>). dark: utilities follow the sidebar there. */
@custom-variant dark (&:where(.dark, .dark *, [data-sidebar-scheme="dark"] :is(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]), [data-sidebar-scheme="dark"] :is(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) *):not(:where([data-sidebar-scheme="light"] :is(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]), [data-sidebar-scheme="light"] :is(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) *)));
@theme {
--font-sans: 'Geist Sans', Inter, sans-serif;
--font-mono: 'Geist Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
--font-geist-sans: 'Geist Sans', Inter, sans-serif;
--font-logs: 'Geist Mono', 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
--color-base: oklch(17.3% 0 0);
--color-warning: oklch(88.13% 0.1507 91.7);
--color-warning-50: oklch(98.73% 0.0262 102.21);
--color-warning-100: oklch(97.29% 0.0693 103.19);
--color-warning-200: oklch(94.51% 0.1243 101.54);
--color-warning-300: oklch(90.52% 0.1657 98.11);
--color-warning-400: oklch(88.13% 0.1507 91.7);
--color-warning-500: oklch(86.06% 0.1731 91.94);
--color-warning-600: oklch(68.06% 0.1423 75.83);
--color-warning-700: oklch(55.38% 0.1207 66.44);
--color-warning-800: oklch(47.62% 0.1034 61.91);
--color-warning-900: oklch(42.1% 0.0897 57.71);
--color-success: oklch(72.27% 0.192 149.58);
--color-error: oklch(57.71% 0.2152 27.33);
--color-coollabs-50: oklch(96.33% 0.0206 301.15);
--color-coollabs: oklch(49.65% 0.2709 289.33);
--color-coollabs-100: oklch(52.34% 0.287 289.16);
--color-coollabs-200: oklch(43.76% 0.2369 289.82);
--color-coollabs-300: oklch(38.04% 0.2031 290.47);
/* coolLabs brand purple. Primary actions, focus, and active navigation
use these in both themes. Warning yellow is not an accent. */
--color-brand: #6b16ed;
--color-brand-hover: #7317ff;
--color-brand-press: #5a12c7;
--color-brand-depth: #4a0fa3;
--color-brand-light: #a391ff;
--color-coolgray-100: oklch(20.9% 0 0);
--color-coolgray-200: oklch(24.35% 0 0);
--color-coolgray-300: oklch(26.03% 0 0);
--color-coolgray-400: oklch(27.68% 0 0);
--color-coolgray-500: oklch(31.71% 0 0);
/* Graphite design language (ported from ref/frontend). Layered neutral
surfaces + translucent hairlines. See DESIGN.md. */
/* Content canvas: the darkest shell layer (sRGB ~10). Hex, not oklch:
oklch lightness compresses to near-black below ~15%, so oklch(7.5%)
renders as sRGB 1 with no visible step. */
--color-app: oklch(14.48% 0 0);
/* Sidebar + topbar chrome (sRGB ~20): a clear step lighter than the content
canvas so the shell chrome separates from the content area. */
--color-panel: oklch(19.13% 0 0);
/* Pure neutral (r=g=b). Were cool-tinted (#161618/#1c1c1e/#26262a, b>r),
which clashed with the neutral panel ladder. */
--color-surface: oklch(20.02% 0 0);
--color-raised: oklch(22.64% 0 0);
--color-selected: oklch(26.86% 0 0);
--color-fg: oklch(96.12% 0 0);
--color-fg-dim: oklch(76.99% 0 0);
/* Tertiary text. Raised from #6e6e74 (3.78:1 on base, failed WCAG AA)
to #7e7e84 so 13-14px text clears 4.5:1 on the dark surface ladder. */
--color-fg-faint: oklch(59.31% 0 0);
--color-accent: oklch(49.65% 0.2709 289.33);
--color-accent-foreground: oklch(100.0% 0 0);
--color-hairline: oklch(100.0% 0 0 / 0.08);
--color-nav-text: oklch(43.86% 0 0);
--color-nav-muted: oklch(51.03% 0 0);
--color-nav-active: oklch(20.46% 0 0);
--color-log: oklch(15.91% 0 0);
--shadow-modal: 0 24px 64px rgba(0, 0, 0, 0.55), 0 4px 16px rgba(0, 0, 0, 0.4);
/* One restrained lift shared by every dropdown/menu/listbox panel and the
command palette. Lighter and consistent, replacing the old scattered
0.45 / 0.35 / shadow-lg / shadow-sm mix. Modals keep --shadow-modal. */
--shadow-dropdown: 0 4px 12px rgb(0 0 0 / 0.12), 0 2px 4px rgb(0 0 0 / 0.08);
/* Shared motion curves (stronger than the built-in CSS easings). Use these
for consistency: --ease-out for UI enter/press, --ease-drawer for
drawers/sheets, --ease-in-out for on-screen movement. */
--ease-out: cubic-bezier(0.23, 1, 0.32, 1);
--ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
}
/* Standard buttons have a compact physical edge. Hover lifts the face by 1px;
pressing nudges it down while the depth disappears. */
.button:not(:disabled) {
--button-depth-color: rgb(0 0 0 / 0.22);
--button-depth: 0 2px 0 var(--button-depth-color);
--button-depth-hover: 0 3px 0 var(--button-depth-color);
box-shadow: var(--button-depth);
}
.button.button-highlighted:not(:disabled),
.button[isHighlighted]:not(:disabled) {
--button-depth-color: var(--color-coollabs-300);
}
.dark .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled) {
--button-depth-color: color-mix(in srgb, var(--color-brand) 35%, transparent);
background-color: color-mix(in srgb, var(--color-brand) 14%, transparent);
border-color: color-mix(in srgb, var(--color-brand-light) 24%, transparent);
color: oklch(from var(--color-brand-light) 93.5% 0.03 h);
}
.dark .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled):hover {
background-color: color-mix(in srgb, var(--color-brand) 18%, transparent);
border-color: color-mix(in srgb, var(--color-brand-light) 34%, transparent);
}
.dark .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled) svg {
color: var(--color-brand-light);
opacity: 1;
}
.dark .button.button-highlighted:not(:disabled),
.dark .button[isHighlighted]:not(:disabled) {
background: var(--color-brand);
border-color: transparent;
color: var(--color-brand-foreground);
--button-depth-color: var(--color-brand-depth);
}
.dark .button.button-highlighted:not(:disabled):hover,
.dark .button[isHighlighted]:not(:disabled):hover {
background: var(--color-brand-hover);
}
.button.button-error:not(:disabled) {
--button-depth-color: var(--color-red-300);
}
.dark .button.button-error:not(:disabled) {
--button-depth-color: var(--color-red-800);
}
/* Keep the shared focus indicator when the depth shadow owns box-shadow. */
.button:not(:disabled):focus-visible {
box-shadow: var(--button-depth), 0 0 0 1px var(--color-accent);
}
.button:not(:disabled):hover {
transform: translateY(-1px);
box-shadow: var(--button-depth-hover);
}
.button:not(:disabled):hover:focus-visible {
box-shadow: var(--button-depth-hover), 0 0 0 1px var(--color-accent);
}
/* Destructive buttons keep a focus indicator in their own red instead of the accent. */
.button.button-error:not(:disabled):focus-visible {
box-shadow: var(--button-depth), 0 0 0 1px var(--color-red-500);
}
.button.button-error:not(:disabled):hover:focus-visible {
box-shadow: var(--button-depth-hover), 0 0 0 1px var(--color-red-500);
}
/* Highlighted buttons share the accent hue, so a flush ring reads as a stray
border. Offset the indicator with an outline to leave a clear gap. */
.button.button-highlighted:not(:disabled):focus-visible,
.button[isHighlighted]:not(:disabled):focus-visible {
box-shadow: var(--button-depth);
outline: 1px solid var(--color-accent);
outline-offset: 2px;
}
.button.button-highlighted:not(:disabled):hover:focus-visible,
.button[isHighlighted]:not(:disabled):hover:focus-visible {
box-shadow: var(--button-depth-hover);
}
/* Other button-like controls retain the lighter scale feedback. */
.button,
.icon-button,
.app-tab,
.split-action-main,
.split-action-caret {
transition-property: color, background-color, border-color, box-shadow, transform;
transition-timing-function: var(--ease-out);
transition-duration: 120ms;
}
.icon-button:not(:disabled):active,
.app-tab:active {
transform: scale(0.97);
}
.button,
.split-action-main,
.split-action-caret {
transition-duration: 120ms, 120ms, 120ms, 80ms, 80ms;
}
.button:not(:disabled):active,
.split-action-main:not(:disabled):active,
.split-action-caret:not(:disabled):active {
transform: translateY(2px);
box-shadow: none;
}
/* Appearance → Style → Flat (data-nav-items="flat") removes the raised edge
everywhere, not only in the sidebar: the depth color goes transparent (the
!important beats the theme overrides of the variable) and the press uses
the scale feedback of .icon-button instead of the lift and sink. */
html[data-nav-items="flat"] :is(.button, .split-action-main, .split-action-caret, .toast-icon) {
--button-depth-color: transparent !important;
}
html[data-nav-items="flat"] :is(.button, .split-action-main, .split-action-caret):not(:disabled):hover {
transform: none;
}
html[data-nav-items="flat"] :is(.button, .split-action-main, .split-action-caret):not(:disabled):active {
transform: scale(0.97);
}
/* Focus border/ring on any form control should ease, not snap. The accent
focus indicator is a border-color and/or box-shadow (ring) change, which the
utilities' `transition-colors` did not fully animate. Target the elements
themselves (covers bespoke search fields too) plus the div-based controls. */
input,
textarea,
select,
.listbox-trigger,
.chip-input {
transition-property: color, background-color, border-color, box-shadow;
transition-timing-function: var(--ease-out);
transition-duration: 120ms;
}
/*
Traffic-analytics chart tokens (light defaults; dark overrides below).
One source of truth shared by ApexCharts donuts, the geo choropleth SVG,
and proportional bars — JS reads them at runtime via getComputedStyle.
Palette validated with the dataviz skill; contrast ratios are recorded in
the PR description. See resources/views/livewire/traffic/_geo.blade.php.
*/
:root {
/* Categorical HTTP status palette (labelled 2xx/3xx/4xx/5xx in every legend). */
--chart-status-2xx: #15803d;
--chart-status-3xx: #2563eb;
--chart-status-4xx: #d97706;
--chart-status-5xx: #dc2626;
/* KPI sparkline accent for Bandwidth (violet — distinct from the status hues). */
--chart-spark-bandwidth: #7c3aed;
/* Sequential 5-step geo ramp (low -> high traffic) + neutral empty. */
--chart-geo-1: #3b82f6;
--chart-geo-2: #2563eb;
--chart-geo-3: #1d4ed8;
--chart-geo-4: #1e40af;
--chart-geo-5: #172554;
--chart-geo-empty: #e5e7eb;
--chart-geo-stroke: #ffffff;
}
.dark {
--chart-status-2xx: #22c55e;
--chart-status-3xx: #3b82f6;
--chart-status-4xx: #f59e0b;
--chart-status-5xx: #ef4444;
--chart-spark-bandwidth: #a78bfa;
--chart-geo-1: #2563eb;
--chart-geo-2: #3b82f6;
--chart-geo-3: #60a5fa;
--chart-geo-4: #93c5fd;
--chart-geo-5: #bfdbfe;
--chart-geo-empty: #262626;
--chart-geo-stroke: #101010;
}
/*
The default border color has changed to `currentcolor` in Tailwind CSS v4,
so we've added these compatibility styles to make sure everything still
looks the same as it did with Tailwind CSS v3.
If we ever want to remove these styles, we need to add an explicit border
color utility to any element that depends on these defaults.
*/
@layer components {
.terminal-mobile-key {
@apply min-h-8 shrink-0 rounded-full border bg-transparent px-3 py-1 text-sm font-medium text-neutral-300 active:text-white;
border-color: color-mix(in srgb, var(--terminal-scrollbar, #fff) 24%, transparent);
}
.terminal-key-row {
border: 1px solid color-mix(in srgb, var(--terminal-scrollbar, #fff) 22%, transparent);
background: transparent;
}
/* Active state is a solid fill only (no accent rail / border). */
.menu-item-active .menu-item-icon,
.menu-subitem-active .menu-item-icon {
opacity: 1;
}
/* Kill any legacy accent wash or rail; fill is solid via menu-item-active utility. */
.menu-item-active,
.menu-subitem-active,
.dark .menu-item-active,
.dark .menu-subitem-active {
background-image: none;
}
.menu-item-active::before,
.menu-subitem-active::before {
content: none;
display: none;
}
/* vertical connector line for a nested nav group */
.nav-children {
position: relative;
margin-left: 1.1rem;
padding-left: 0.25rem;
border-left: 1px solid var(--color-hairline);
}
}
@layer base {
html {
color-scheme: light;
}
html.dark {
color-scheme: dark;
}
*,
::after,
::before,
::backdrop,
::file-selector-button {
border-color: var(--color-coolgray-200, currentcolor);
}
}
@keyframes lds-heart {
0% {
transform: scale(1);
}
5% {
transform: scale(1.2);
}
39% {
transform: scale(0.85);
}
45% {
transform: scale(1);
}
60% {
transform: scale(0.95);
}
100% {
transform: scale(0.9);
}
}
@keyframes coolbox-border-track {
0% {
background-position: 0 0, 100% 0, 100% 100%, 0 100%;
background-size: 35% 2px, 0 0, 0 0, 0 0;
}
33% {
background-position: 100% 0, 100% 0, 100% 100%, 0 100%;
background-size: 35% 2px, 0 0, 0 0, 0 0;
}
34% {
background-position: 100% 0, 100% 0, 100% 100%, 0 100%;
background-size: 0 0, 2px 35%, 0 0, 0 0;
}
50% {
background-position: 100% 0, 100% 100%, 100% 100%, 0 100%;
background-size: 0 0, 2px 35%, 0 0, 0 0;
}
51% {
background-position: 0 0, 100% 0, 100% 100%, 0 100%;
background-size: 0 0, 0 0, 35% 2px, 0 0;
}
84% {
background-position: 0 0, 100% 0, 0 100%, 0 100%;
background-size: 0 0, 0 0, 35% 2px, 0 0;
}
85% {
background-position: 0 0, 100% 0, 100% 100%, 0 100%;
background-size: 0 0, 0 0, 0 0, 2px 35%;
}
100% {
background-position: 0 0, 100% 0, 100% 100%, 0 0;
background-size: 0 0, 0 0, 0 0, 2px 35%;
}
}
@layer components {
.coolbox-loading {
@apply overflow-hidden border-transparent;
isolation: isolate;
}
.coolbox-loading::before {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
--coolbox-loading-color: var(--color-coollabs);
background:
linear-gradient(var(--coolbox-loading-color), var(--coolbox-loading-color)),
linear-gradient(var(--coolbox-loading-color), var(--coolbox-loading-color)),
linear-gradient(var(--coolbox-loading-color), var(--coolbox-loading-color)),
linear-gradient(var(--coolbox-loading-color), var(--coolbox-loading-color));
background-repeat: no-repeat;
animation: coolbox-border-track 2400ms linear infinite;
pointer-events: none;
z-index: 0;
}
.coolbox-loading::after {
content: "";
position: absolute;
inset: 2px;
border-radius: calc(0.25rem - 2px);
background: white;
pointer-events: none;
z-index: 1;
}
.dark .coolbox-loading::before {
--coolbox-loading-color: var(--color-brand-light);
}
.dark .coolbox-loading::after {
background: var(--color-coolgray-100);
}
.coolbox-loading > * {
position: relative;
z-index: 2;
}
@media (max-width: 767px) {
:root input,
:root textarea,
:root select {
font-size: 16px !important;
}
}
}
/*
* Base styles
*/
html,
body {
@apply w-full min-h-full bg-neutral-50 dark:bg-app dark:text-fg-dim;
}
body {
/* overflow-x: clip (not hidden) — hidden creates a scroll containment that
breaks position:sticky on settings sidebars and other in-page pins. */
min-height: 100dvh;
@apply text-sm font-sans antialiased scrollbar overflow-x-clip;
}
.coolify-monaco-editor {
@apply min-w-0 w-full;
overflow: hidden;
border-radius: 10px;
border: 1px solid var(--coollabs-line, #e5e5e5);
}
.coolify-monaco-editor .monaco-editor,
.coolify-monaco-editor .overflow-guard {
max-width: 100%;
}
option {
@apply dark:text-white dark:bg-coolgray-100;
}
.button.button-error {
@apply text-red-800 dark:text-red-300 bg-red-50 dark:bg-red-900/30 border-red-300 dark:border-red-800;
}
.button.button-error:disabled {
@apply text-red-800 dark:text-red-300 bg-red-50 dark:bg-red-900/30 border-red-300 dark:border-red-800;
}
.button-error:not(:disabled) {
@apply hover:bg-red-100 hover:text-red-900 dark:hover:bg-red-700 dark:hover:text-white;
}
button[isHighlighted]:not(:disabled) {
@apply button-highlighted;
}
h1 {
@apply text-[24px] leading-7 font-semibold tracking-tight dark:text-white;
}
h2 {
@apply text-xl font-bold dark:text-white;
}
h3 {
@apply text-lg font-bold dark:text-white;
}
h4 {
@apply text-base font-bold dark:text-white;
}
a {
@apply hover:text-black dark:hover:text-white;
}
button:focus-visible,
a:focus-visible {
@apply outline-none ring-2 ring-coollabs dark:ring-1 dark:ring-brand-light ring-offset-2 dark:ring-offset-coolgray-100;
}
label {
@apply dark:text-neutral-400;
}
table {
@apply min-w-full divide-y dark:divide-coolgray-200 divide-neutral-300;
}
thead {
@apply uppercase;
}
tbody {
@apply divide-y dark:divide-coolgray-200 divide-neutral-300;
}
tr {
@apply text-black dark:text-neutral-400 dark:hover:bg-coolgray-300 hover:bg-neutral-100;
}
tr th {
@apply px-3 py-3.5 text-left text-neutral-500 dark:text-fg-faint;
}
tr th:first-child {
@apply py-3.5 pr-3 pl-4 sm:pl-6;
}
tr td {
@apply px-3 py-4 whitespace-nowrap;
}
tr td:first-child {
@apply pr-3 pl-4 font-bold sm:pl-6;
}
/*
* Utility classes
*/
/* Reserve space for the visibility toggle. Use a class (not [type=password]) so padding
remains when Alpine reveals the value as type=text, and so settings-workspace input
padding-right overrides cannot collapse the field into the eye icon. */
.input.input-with-password-toggle,
.input[type="password"] {
padding-right: 2.5rem;
}
/* Same durable clearance for copy-affordance inputs (utility pr-* loses to settings CSS). */
.input.input-with-copy-button {
padding-right: 2.5rem;
}
/* Number fields use the brand −/+ stepper (components/forms/input) instead of the native spinner. */
.input.input-with-stepper {
padding-right: 4.25rem;
appearance: textfield;
-moz-appearance: textfield;
}
.input.input-with-stepper::-webkit-outer-spin-button,
.input.input-with-stepper::-webkit-inner-spin-button {
margin: 0;
-webkit-appearance: none;
}
.number-stepper {
@apply absolute inset-y-px right-px flex;
}
.number-stepper button {
@apply flex w-8 cursor-pointer items-center justify-center border-l border-neutral-200 text-neutral-500 transition-colors last:rounded-r-[5px] hover:bg-neutral-100 hover:text-black active:bg-neutral-200 disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent dark:border-white/[0.08] dark:text-fg-dim dark:hover:bg-white/[0.06] dark:hover:text-white dark:active:bg-white/[0.1];
}
.number-stepper button svg {
@apply transition-transform duration-150 ease-out;
}
.number-stepper button:active:not(:disabled) svg {
@apply scale-75;
}
/* Room for both the peek eye and the copy button on maskable read-only fields. */
.input.input-with-copy-and-peek {
padding-right: 4.25rem;
}
.lds-heart {
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);
}
/* Loading feedback uses the brand-light accent in dark mode. */
.dark .animate-spin {
color: var(--color-brand-light) !important;
}
.dark #nprogress .bar {
background: var(--color-brand-light) !important;
}
.dark #nprogress .peg {
box-shadow:
0 0 10px var(--color-brand-light),
0 0 5px var(--color-brand-light) !important;
}
.dark #nprogress .spinner-icon {
border-top-color: var(--color-brand-light) !important;
border-left-color: var(--color-brand-light) !important;
}
html[data-theme="custom"] .loading-indicator,
html[data-theme="custom"] .animate-spin {
color: var(--theme-bright-color) !important;
}
/* Opt out of the brand spinner when the surrounding surface is a selected/neutral control
or a highlighted button, whose accent surface would camouflage a brand-colored spinner. */
.dark .animate-spin.spinner-current,
html[data-theme="custom"] .animate-spin.spinner-current,
html[data-theme="custom"] .button-highlighted .animate-spin,
html[data-theme="custom"] button[isHighlighted] .animate-spin {
color: inherit !important;
}
html[data-theme="custom"] #nprogress .bar {
background: var(--theme-bright-color) !important;
}
html[data-theme="custom"] #nprogress .peg {
box-shadow:
0 0 10px var(--theme-bright-color),
0 0 5px var(--theme-bright-color) !important;
}
html[data-theme="custom"] #nprogress .spinner-icon {
border-top-color: var(--theme-bright-color) !important;
border-left-color: var(--theme-bright-color) !important;
}
.log-highlight {
background-color: rgba(234, 179, 8, 0.4);
border-radius: 2px;
box-decoration-break: clone;
-webkit-box-decoration-break: clone;
}
.dark .log-highlight {
background-color: rgba(234, 179, 8, 0.3);
}
/* Wave-style application console: transparent viewport and an overlay
scrollbar that stays out of the terminal content until it is used. */
#terminal.terminal-host,
#terminal.terminal-host .xterm {
width: 100%;
height: 100%;
max-height: 100%;
}
#terminal.terminal-host .xterm {
padding: 0;
}
#terminal.terminal-host .xterm-viewport {
overflow-y: auto !important;
}
#terminal[data-terminal-style="application"] .xterm-viewport {
background-color: transparent !important;
scrollbar-width: thin;
scrollbar-color: rgba(255, 255, 255, 0.15) transparent;
}
#terminal[data-terminal-style="application"] .xterm-viewport::-webkit-scrollbar {
width: 4px;
}
#terminal[data-terminal-style="application"] .xterm-viewport::-webkit-scrollbar-thumb {
border-radius: 4px;
background: rgba(255, 255, 255, 0.15);
}
#terminal[data-terminal-style="application"] .xterm-viewport::-webkit-scrollbar-track {
background: transparent;
}
.application-console-shell,
.terminal-fullscreen-shell {
position: relative;
isolation: isolate;
color: #f2f2f2;
background-color: #121214;
}
.application-console-shell {
box-shadow:
inset 0 0 0 1px var(--console-theme-border, rgb(255 255 255 / 0.08)),
0 18px 50px rgb(0 0 0 / 0.18);
}
/* Fullscreen is portaled to body — keep position:fixed from utilities. */
.terminal-fullscreen-shell {
position: fixed;
}
.application-console-shell::before,
.terminal-fullscreen-shell::before {
position: absolute;
z-index: 0;
inset: 0;
border-radius: inherit;
background: var(--console-theme-background);
opacity: var(--console-theme-opacity);
content: "";
pointer-events: none;
transition:
background 220ms ease,
opacity 220ms ease;
}
.application-console-shell[data-console-theme="system"],
.terminal-fullscreen-shell[data-console-theme="system"] {
--console-theme-background: oklch(100.0% 0 0);
--console-theme-border: oklch(87.11% 0.0055 286.29);
--console-theme-opacity: 1;
}
/* Session toolbar (selected-target trigger + label) sits over the white
system console in light mode. Without this its text-white/* stayed white on
white and the selected server was invisible (issue #11532). */
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-session-toolbar[class*="text-white"],
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-session-toolbar [class*="text-white"],
html:not(.dark) .terminal-fullscreen-shell[data-console-theme="system"] .terminal-session-toolbar[class*="text-white"],
html:not(.dark) .terminal-fullscreen-shell[data-console-theme="system"] .terminal-session-toolbar [class*="text-white"] {
color: #52525b !important;
}
html.dark .application-console-shell[data-console-theme="system"],
html.dark .terminal-fullscreen-shell[data-console-theme="system"] {
--console-theme-background: oklch(18.31% 0.004 285.99);
--console-theme-border: rgb(255 255 255 / 0.08);
}
html[data-theme="custom"] .application-console-shell[data-console-theme="system"],
html[data-theme="custom"] .terminal-fullscreen-shell[data-console-theme="system"] {
--console-theme-background: var(--color-log);
--console-theme-border: var(--coollabs-line);
}
/* Docked System console only: the fill behind the terminal panel is the
elevated (header) surface, so the panel's rounded-corner notch reads as the
header colour — the card's figure/ground. The panel paints its own base
surface over this fill, so only the notch shows it. The fullscreen shell has
no panel, so it is intentionally excluded and keeps its plain terminal fill. */
.application-console-shell[data-console-theme="system"] {
--console-theme-background: var(--coollabs-elevated);
}
html.dark .application-console-shell[data-console-theme="system"] {
--console-theme-background: var(--coollabs-elevated);
}
/* Dark mode: the docked System console uses the dark card colors (darker outer
header, --color-card body, faint white ring), so it matches the cards around it. */
html.dark .application-console-shell[data-console-theme="system"] {
--coollabs-elevated: color-mix(in srgb, var(--color-card) 66%, black);
--coollabs-base: var(--color-card);
--coollabs-hairline: rgb(255 255 255 / 8%);
}
.console-theme-selector {
scrollbar-color: rgb(161 161 170) transparent;
scrollbar-width: thin;
}
.console-theme-selector::-webkit-scrollbar {
width: 8px;
}
.console-theme-selector::-webkit-scrollbar-thumb {
border: 2px solid transparent;
border-radius: 9999px;
background: rgb(161 161 170);
background-clip: padding-box;
}
.console-theme-selector::-webkit-scrollbar-track {
background: transparent;
}
html.dark .console-theme-selector {
scrollbar-color: rgb(255 255 255 / 0.28) transparent;
}
html.dark .console-theme-selector::-webkit-scrollbar-thumb {
background: rgb(255 255 255 / 0.28);
background-clip: padding-box;
}
.application-console-shell[data-console-theme="shadows-midnight"],
.terminal-fullscreen-shell[data-console-theme="shadows-midnight"] {
--console-theme-background: linear-gradient(135deg, #2a3b4c, rgb(42 59 76 / 0.4));
--console-theme-opacity: 0.9;
}
.application-console-shell[data-console-theme="shadows-golden-hour"],
.terminal-fullscreen-shell[data-console-theme="shadows-golden-hour"] {
--console-theme-background: linear-gradient(135deg, #d58a42, rgb(213 138 66 / 0.4));
--console-theme-opacity: 0.4;
}
.application-console-shell[data-console-theme="shadows-cosmic-purple"],
.terminal-fullscreen-shell[data-console-theme="shadows-cosmic-purple"] {
--console-theme-background: linear-gradient(135deg, #5d3e66, rgb(93 62 102 / 0.4));
--console-theme-opacity: 0.8;
}
.application-console-shell[data-console-theme="shadows-neon-glow"],
.terminal-fullscreen-shell[data-console-theme="shadows-neon-glow"] {
--console-theme-background: linear-gradient(135deg, #f300a6, rgb(243 0 166 / 0.3));
--console-theme-opacity: 0.45;
}
.application-console-shell[data-console-theme="shadows-icy-mist"],
.terminal-fullscreen-shell[data-console-theme="shadows-icy-mist"] {
--console-theme-background: linear-gradient(135deg, #d0d8e2, rgb(208 216 226 / 0.2));
--console-theme-opacity: 0.5;
}
.application-console-shell[data-console-theme="shadows-tropical-storm"],
.terminal-fullscreen-shell[data-console-theme="shadows-tropical-storm"] {
--console-theme-background: linear-gradient(135deg, #00b894, #1fa771, #2ecc71, #27ae60);
--console-theme-opacity: 0.3;
}
.application-console-shell[data-console-theme="shadows-golden-nebula"],
.terminal-fullscreen-shell[data-console-theme="shadows-golden-nebula"] {
--console-theme-background: linear-gradient(135deg, #ffd700, #ff6347, #d4a20e, #ffcc00, #1f3d6f);
--console-theme-opacity: 0.44;
}
.application-console-shell[data-console-theme="shadows-cosmic-lagoon"],
.terminal-fullscreen-shell[data-console-theme="shadows-cosmic-lagoon"] {
--console-theme-background: linear-gradient(135deg, #1d2b64, #2f4f96, #00b5b8, #9c27b0, #8e24aa);
--console-theme-opacity: 0.59;
}
.application-console-shell[data-console-theme="shadows-neon-nebula"],
.terminal-fullscreen-shell[data-console-theme="shadows-neon-nebula"] {
--console-theme-background: linear-gradient(135deg, #00d9d9, #ff55aa, #1e1e2f, #2f3b57, #ff99ff);
--console-theme-opacity: 0.6;
}
.application-console-shell[data-console-theme="shadows-transparent"],
.terminal-fullscreen-shell[data-console-theme="shadows-transparent"] {
--console-theme-background: rgb(0 0 0 / 0.7);
--console-theme-opacity: 0.7;
}
/*
* Terminal page: lock to the viewport so the shell fills remaining height
* without document scroll. Offsets match layouts/app.blade.php chrome:
* mobile sticky top bar (~3.5rem) + main py-6 (3rem)
* desktop fixed top bar accounted in main lg:pt-[calc(3rem+1.75rem)] + lg:pb-10
*/
.terminal-page {
display: flex;
flex-direction: column;
width: 100%;
min-width: 0;
min-height: 0;
overflow: hidden;
height: calc(100dvh - 6.75rem);
max-height: calc(100dvh - 6.75rem);
}
@media (min-width: 1024px) {
.terminal-page {
height: calc(100dvh - 7.25rem);
max-height: calc(100dvh - 7.25rem);
}
}
.terminal-page-header {
margin-bottom: 1rem;
}
.terminal-page-console {
display: flex;
min-height: 0;
flex: 1 1 0%;
flex-direction: column;
overflow: hidden;
/* Breathing room so the console's box-shadow ring + lift are not clipped by
this overflow-hidden, viewport-height container (the card gets this from
the scrollable page around it). */
padding: 6px;
}
.terminal-page-console > .application-console-shell {
min-height: 0;
flex: 1 1 0%;
}
/* Fullscreen: freeze the page; only xterm scrollback may move. */
html.terminal-is-fullscreen,
html.terminal-is-fullscreen body {
overflow: hidden !important;
overscroll-behavior: none !important;
touch-action: none;
height: 100% !important;
max-height: 100% !important;
}
html.terminal-is-fullscreen body {
/* position:fixed is also applied inline for scroll-lock */
width: 100% !important;
}
html.terminal-is-fullscreen main {
overflow: hidden !important;
overscroll-behavior: none !important;
}
/* Fullscreen terminal must win against layout chrome (header z-50, sidebar z-40). */
html.terminal-is-fullscreen .terminal-fullscreen-shell,
body.terminal-is-fullscreen .terminal-fullscreen-shell {
position: fixed !important;
inset: 0 !important;
z-index: 100000 !important;
width: 100vw !important;
height: auto !important;
max-width: none !important;
margin: 0 !important;
overflow: hidden !important;
overscroll-behavior: none !important;
touch-action: none;
}
/* Re-enable pan/scroll only inside the xterm buffer viewport. */
html.terminal-is-fullscreen .terminal-fullscreen-shell .xterm-viewport,
body.terminal-is-fullscreen .terminal-fullscreen-shell .xterm-viewport {
overscroll-behavior: contain !important;
touch-action: pan-y;
}
html.terminal-is-fullscreen .terminal-fullscreen-shell .terminal-fullscreen-btn,
html.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-toolbar],
body.terminal-is-fullscreen .terminal-fullscreen-shell .terminal-fullscreen-btn,
body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-toolbar] {
touch-action: manipulation;
}
/* Session header band: an attached card-style header on top of the console,
mirroring the page card header (elevated surface + hairline divider). The
System theme uses the neutral card surface in both modes; colorful themes
fall back to a translucent dark band so the white controls stay legible. */
.terminal-session-toolbar {
position: relative;
z-index: 2;
flex-shrink: 0;
min-height: 3rem;
padding: 0.5rem 0.75rem 0.5rem 1rem;
/* No border-radius: the shell is overflow-hidden + rounded and clips the
header's top corners, so rounding here only risks a sub-pixel seam. */
border-bottom: 1px solid var(--terminal-header-border, rgb(255 255 255 / 0.1));
background: var(--terminal-header-bg, rgb(0 0 0 / 0.28));
-webkit-backdrop-filter: blur(12px);
backdrop-filter: blur(12px);
}
/* System header has no bottom border: the body panel's ring is the divider, and
its rounded top corners tuck under the header — the same merge the
target-picker card uses (elevated header meeting a base body panel). */
.application-console-shell[data-console-theme="system"] .terminal-session-toolbar {
--terminal-header-bg: var(--coollabs-elevated);
--terminal-header-border: transparent;
/* Shell no longer clips (overflow: visible), so the header rounds its own
top corners — matching the card header. */
border-radius: 8px 8px 0 0;
/* Drop the z-index:2 stacking context: it painted the header OVER the body
panel, hiding the panel's ring (the divider). The header only had z-2 to
beat ::before, but its dropdowns sit in their own .relative wrappers, so
z:auto is safe and lets the body panel's ring paint over the header —
exactly how the card body overlaps the card header. */
z-index: auto;
-webkit-backdrop-filter: none;
backdrop-filter: none;
}
/* System console body: the terminal panel, a full rounded box-shadow ring like
the card body. Its base surface is the terminal fill; the elevated surface
behind it (::before) shows only through the rounded-corner notch, reading as
the header colour — the card's figure/ground. */
.application-console-shell[data-console-theme="system"] .terminal-session-panel {
background: var(--coollabs-base);
/* The body panel is a full rounded ring (like the card body). position:
relative puts it (and its ring) above ::before AND, because it comes
after the now-z:auto header in the DOM, above the header — so the ring's
top edge is the divider and its rounded top corners connect down into the
side ring, which coincides with the shell's ring (one hairline, no
doubling). A box-shadow ring rounds and connects cleanly; a border-top
cannot (square corners, or a disconnected transparent edge). */
position: relative;
border-radius: 8px;
box-shadow: 0 0 0 1px var(--coollabs-hairline);
}
/* Connected System console frame = the target-picker card recipe: a hairline
box-shadow ring + a restrained lift. overflow stays visible so the ring is
not clipped (the .terminal-page-console padding gives it room inside the
viewport-locked, overflow-hidden page). Colorful themes keep the themed,
overflow-hidden shell. */
.application-console-shell[data-console-theme="system"] {
overflow: visible;
box-shadow:
0 0 0 1px var(--coollabs-hairline),
0 1px 2px 0 rgb(0 0 0 / 0.05);
}
.terminal-loading-label {
font-size: 0.875rem;
font-weight: 500;
color: rgb(255 255 255 / 0.75);
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-loading-label {
color: #52525b;
}
.terminal-target-picker {
color: rgb(255 255 255 / 0.75);
background: oklch(18.31% 0.004 285.99);
border-color: rgb(255 255 255 / 0.1);
}
/* Pre-session target list: a normal page card, not the themed console canvas. */
.terminal-target-card .application-settings-section-body {
overflow: hidden;
}
/* The header inset is tuned for 32px buttons. A full-width filter needs the
same gutter on both sides once the actions row wraps below the title.
Matches the base header selector's specificity so the shorthand cannot win. */
@media (max-width: 640px) {
.application-settings-section.terminal-target-card > :is(header, .application-settings-section-header) {
padding-right: 1rem;
}
}
.terminal-target-card-list {
max-height: min(70vh, 34rem);
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: var(--coollabs-fill) transparent;
}
.terminal-target-card-list::-webkit-scrollbar {
width: 8px;
}
.terminal-target-card-list::-webkit-scrollbar-track {
background: transparent;
}
.terminal-target-card-list::-webkit-scrollbar-thumb {
border: 2px solid transparent;
border-radius: 9999px;
background-color: var(--coollabs-fill);
background-clip: padding-box;
}
/* Group headers stay readable while scrolling long container lists. */
.terminal-target-group-label {
display: flex;
position: sticky;
z-index: 1;
top: 0;
align-items: center;
gap: 0.375rem;
padding: 0.5rem 0.5rem 0.375rem;
background: var(--coollabs-base);
font-size: 0.6875rem;
font-weight: 500;
letter-spacing: 0.01em;
color: var(--coollabs-subtle);
}
.terminal-target-group-count {
border-radius: 9999px;
background: var(--coollabs-fill);
padding: 0 0.375rem;
font-size: 0.625rem;
line-height: 1rem;
font-variant-numeric: tabular-nums;
}
/* The server column only earns its space once the row is wide enough. */
.terminal-target-item-server {
display: none;
max-width: 14rem;
flex-shrink: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.6875rem;
line-height: 1rem;
color: var(--coollabs-subtle);
}
@media (min-width: 640px) {
.terminal-target-item-server {
display: block;
}
}
.terminal-session-panel {
border: 0;
border-radius: 0.75rem;
background: transparent;
box-shadow: none;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-session-panel {
background: var(--coollabs-base);
}
/* Console (attach) command box. Colours come from the active terminal theme's
foreground (--console-fg), so the input is readable on every console theme. */
.terminal-console-input {
border-color: color-mix(in srgb, var(--console-fg, #fff) 10%, transparent);
}
.console-command-row {
box-shadow: inset 0 0 0 1px transparent;
transition:
background-color 150ms ease,
box-shadow 150ms ease;
}
.console-command-row:hover {
background-color: color-mix(in srgb, var(--console-fg, #fff) 3%, transparent);
}
.console-command-row:focus-within {
background-color: color-mix(in srgb, var(--console-fg, #fff) 6%, transparent);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--console-fg, #fff) 22%, transparent);
}
.console-command-field {
color: var(--console-fg, #fff) !important;
caret-color: var(--console-fg, #fff);
}
.console-command-field::placeholder {
color: color-mix(in srgb, var(--console-fg, #fff) 40%, transparent);
}
.console-command-row:focus-within .console-command-field::placeholder {
color: color-mix(in srgb, var(--console-fg, #fff) 28%, transparent);
}
.console-command-prompt {
color: color-mix(in srgb, var(--console-fg, #fff) 40%, transparent);
transition: color 200ms ease;
}
.console-command-row:focus-within .console-command-prompt {
color: var(--console-fg, #fff);
}
.console-command-prompt[data-sent] {
color: #fcd452 !important;
}
.console-command-pending {
color: color-mix(in srgb, var(--console-fg, #fff) 60%, transparent);
}
.console-command-send {
color: color-mix(in srgb, var(--console-fg, #fff) 60%, transparent);
outline-color: color-mix(in srgb, var(--console-fg, #fff) 60%, transparent);
}
.console-command-send:hover {
color: var(--console-fg, #fff);
background-color: color-mix(in srgb, var(--console-fg, #fff) 8%, transparent);
}
.terminal-target-list {
scrollbar-width: thin;
scrollbar-color: var(--terminal-scrollbar, rgb(255 255 255 / 0.3)) transparent;
}
.terminal-target-list::-webkit-scrollbar {
width: 8px;
}
.terminal-target-list::-webkit-scrollbar-thumb {
border: 2px solid transparent;
border-radius: 9999px;
background: var(--terminal-scrollbar, rgb(255 255 255 / 0.3));
background-clip: padding-box;
}
.terminal-target-list::-webkit-scrollbar-track {
background: transparent;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-target-picker {
color: #52525b;
background: #fff;
border-color: #d4d4d8;
box-shadow: var(--shadow-dropdown);
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-target-picker [class*="text-white"] {
color: #52525b !important;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-target-picker input {
color: #27272a !important;
background: #f4f4f5 !important;
border-color: #d4d4d8 !important;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-target-picker input::placeholder {
color: #71717a !important;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-target-picker button:hover {
color: #18181b !important;
background: #f4f4f5;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-theme-trigger {
color: #52525b;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-session-target-trigger {
color: #52525b;
}
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-theme-trigger:hover,
html:not(.dark) .application-console-shell[data-console-theme="system"] .terminal-session-target-trigger:hover {
color: #18181b;
background: rgb(0 0 0 / 0.06);
}
.application-console-block {
position: relative;
z-index: 1;
isolation: isolate;
}
.terminal-fullscreen-btn {
display: flex;
width: 1.75rem;
height: 1.75rem;
align-items: center;
justify-content: center;
border-radius: 0.375rem;
border: 0;
background: transparent;
color: var(--terminal-scrollbar, rgb(255 255 255 / 0.65));
transition:
background-color 150ms ease,
color 150ms ease,
opacity 150ms ease;
}
.terminal-fullscreen-btn:hover {
background: color-mix(in srgb, var(--terminal-scrollbar, #fff) 18%, transparent);
color: var(--terminal-scrollbar, #fff);
}
/*
* Coollabs layer-card settings surfaces.
* Anatomy: root shell = elevated bg + hairline ring; the header is a compact
* strip on the shell; the body is a nested rounded panel with base bg + fill
* ring. Depth comes from the neutral oklch surface ladder below.
* Deliberately unlayered plain CSS: the global element rules above (h3, h4,
* section, label) and the .input/.button utilities would otherwise override
* utility classes placed in the card markup.
*/
:root {
/* Page canvas is a faint off-white so white/elevated cards lift off it
(shadcn pattern), instead of card and page reading as one flat color. */
--coollabs-canvas: oklch(97% 0 0);
--coollabs-elevated: oklch(98% 0 0);
--coollabs-recessed: oklch(96% 0 0);
--coollabs-base: oklch(100.0% 0 0);
--coollabs-fill: oklch(92.2% 0 0);
--coollabs-line: oklch(14.5% 0 0 / 0.1);
/* Card/table edge ring. Crisped from 93.5% (1.17:1 on canvas, invisible)
to 85.5% (1.51:1) so surfaces read as bordered cards, not flat fills. */
--coollabs-hairline: oklch(85.5% 0 0);
/* Row and section separators inside cards. Softer than the card edge.
Translucent, so it stays visible on any card or theme surface. */
--coollabs-divider: oklch(0% 0 0 / 0.04);
/* Muted labels/titles. Darkened from 55.6% (failed WCAG AA on elevated
4.46, recessed 4.24, fill 3.76) to 50% so muted text clears 4.5:1 on
every light surface above white. Dark stays 70.8% (already passes). */
--coollabs-subtle: oklch(50% 0 0);
/* Border of brand fills and the text on them. A custom theme overrides both. */
--color-brand-ring: #8a4dff;
--color-brand-foreground: #fff;
}
.dark {
/* Purple is the accent in dark mode too. Warning yellow stays on
--color-warning for real warnings only (update available, attention). */
--color-accent: var(--color-brand);
/* Cards sit one step above the #0a0a0a canvas. The top bar uses the canvas,
not this color. The sidebar uses --color-sidebar. */
--color-card: #121212;
--color-sidebar: #120e1a;
/* Dark surface ladder in hex (sRGB), not oklch: oklch lightness compresses
to near-black below ~15%, so oklch(15%) rendered as sRGB 11 and cards
could not lift off the content canvas (sRGB 10). These give clear,
visible steps: content 10 -> elevated 22 -> base 28 -> recessed 34.
Pure neutral (r=g=b), matching the neutral borders/text and the light
ladder, so every panel shares one temperature (no blue cast). */
--color-app: oklch(14.48% 0 0);
--color-panel: oklch(19.13% 0 0);
--coollabs-canvas: var(--color-app);
--coollabs-elevated: oklch(20.02% 0 0);
--coollabs-recessed: oklch(25.2% 0 0);
--coollabs-base: oklch(22.64% 0 0);
--coollabs-fill: oklch(29.31% 0 0);
--color-surface: oklch(20.02% 0 0);
--color-raised: oklch(22.64% 0 0);
--color-selected: oklch(26.86% 0 0);
--color-log: oklch(15.91% 0 0);
--coollabs-line: oklch(36% 0 0);
/* Kept about 1.6:1 on the canvas so dark cards
and tables show a visible edge instead of blending into the canvas. */
--coollabs-hairline: oklch(36% 0 0);
--coollabs-divider: oklch(100% 0 0 / 0.045);
--coollabs-subtle: oklch(70.8% 0 0);
/* Neutral (was #a8a8b0 / #7a7a84, both cool-tinted). Sidebar text now shares
the same neutral temperature as the content text, so the sidebar no longer
reads cooler than the panels/cards. */
--color-nav-text: oklch(73.8% 0 0);
/* Raised from 58.97% (4.47:1 on panel, just under AA) to 61% (4.86:1) so the
11px sidebar section labels clear 4.5:1. */
--color-nav-muted: oklch(61% 0 0);
--color-nav-active: oklch(96.12% 0 0);
/* Tertiary text. Base token (59.31%) only clears 4.5:1 on app/panel; it
failed on surface/raised/selected (4.46/4.20/3.73). Raised to 64% so the
313 dark:text-fg-faint usages clear AA across the whole surface ladder,
while staying clearly dimmer than fg-dim (76.99%). */
--color-fg-faint: oklch(65% 0 0);
/* Error text. The base error red (57.71%) failed on every dark surface
(4.10/3.75/3.53). Brightened for dark mode so text-error clears AA on the
card ladder while staying unmistakably red. */
--color-error: oklch(66% 0.2 27.33);
}
/* Light-mode semantic text.
warning / success / error double as the bright brand accent and as solid
fills, which only appear under `dark:` — so in light mode these tokens are
used almost entirely as text, translucent tints (bg-warning/10) and borders.
The bright values (yellow 88%, green 72%) and the base red (57.71%) fail AA
as text on white and the light surface ladder (warning 1.43, success 2.28,
error 4.43 on canvas). Scoped to `:not(.dark)` so dark keeps the brand
colors and the custom theme keeps its own overrides. Every value clears
4.5:1 on white, canvas, elevated, recessed and fill, and is in sRGB gamut. */
html:not(.dark) {
--color-warning: oklch(50% 0.105 80);
--color-success: oklch(48% 0.135 149.58);
--color-error: oklch(52% 0.2 27.33);
}
/* Custom theme. applyCustomTheme() in base.blade.php sets four colors and the
text color that is readable on each. The page and the sidebar each derive
one surface ladder from three variables:
--t-bg the background color
--t-text the text color on it
--t-light 1 for a light background, 0 for a dark one
Light schemes put white cards on a slightly darker canvas. Dark schemes lift
cards toward the text color. */
html[data-theme="custom"] {
--t-bg: var(--theme-bg);
--t-text: var(--theme-text);
--t-light: var(--theme-light);
--theme-base-color: oklch(49.65% 0.2709 289.33);
--theme-bright-color: color-mix(in srgb, var(--theme-base-color) 85%, oklch(100% 0 0));
--theme-scrollbar-thumb: color-mix(in srgb, var(--theme-bright-color) 70%, var(--theme-accent-foreground));
/* Accent text/icons sit directly on dark surfaces. Force the lightness into a
readable band (and tame chroma) so any picked hue keeps enough contrast. */
--theme-fg-on-surface: oklch(from var(--theme-base-color) clamp(0.72, l, 0.86) min(c, 0.13) h);
--color-accent: var(--theme-bright-color);
--color-coollabs: var(--theme-bright-color);
--color-coollabs-100: color-mix(in oklab, var(--theme-bright-color) 88%, oklch(100% 0 0));
--color-coollabs-200: color-mix(in oklab, var(--theme-bright-color) 88%, oklch(0% 0 0));
--color-coollabs-300: color-mix(in oklab, var(--theme-bright-color) 72%, oklch(0% 0 0));
/* Legacy dark-theme accent utilities use warning as the brand color. */
--color-warning: var(--theme-bright-color);
/* Brand fills, tints, and accent text follow the picked color. Text on a
brand fill uses the computed foreground, so light colors stay readable. */
--color-brand: var(--theme-bright-color);
--color-brand-hover: color-mix(in oklab, var(--theme-bright-color) 88%, oklch(100% 0 0));
--color-brand-press: color-mix(in oklab, var(--theme-bright-color) 85%, oklch(0% 0 0));
--color-brand-depth: color-mix(in oklab, var(--theme-bright-color) 65%, oklch(0% 0 0));
--color-brand-ring: color-mix(in oklab, var(--theme-bright-color) 75%, oklch(100% 0 0));
--color-brand-foreground: var(--theme-accent-foreground);
--color-brand-light: var(--theme-fg-on-surface);
--color-accent-foreground: var(--theme-accent-foreground);
--color-sidebar: var(--theme-sidebar);
}
html[data-theme="custom"]:not(.dark) {
--theme-scrollbar-thumb: color-mix(in srgb, var(--theme-base-color) 45%, oklch(60% 0 0));
/* Accent text/icons on light surfaces: force lightness into a dark band. */
--theme-fg-on-surface: oklch(from var(--theme-base-color) clamp(0.30, l, 0.48) min(c, 0.16) h);
}
html[data-theme="custom"] :is(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) {
--t-bg: var(--theme-sidebar);
--t-text: var(--theme-sidebar-text);
--t-light: var(--theme-sidebar-light);
}
html[data-theme="custom"],
html[data-theme="custom"] :is(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) {
--color-app: color-mix(in oklab, var(--t-text) calc(var(--t-light) * 3%), var(--t-bg));
--color-panel: color-mix(in oklab, var(--t-text) calc((1 - var(--t-light)) * 3%), var(--t-bg));
--color-surface: color-mix(in oklab, var(--t-text) calc((1 - var(--t-light)) * 4%), var(--t-bg));
--color-raised: color-mix(in oklab, var(--t-text) calc(4% + (1 - var(--t-light)) * 2%), var(--t-bg));
--color-selected: color-mix(in oklab, var(--t-text) calc(8% + (1 - var(--t-light)) * 2%), var(--t-bg));
--color-card: var(--color-surface);
--color-coolgray-100: var(--color-surface);
--color-coolgray-200: var(--color-raised);
--color-coolgray-300: var(--color-selected);
--color-coolgray-400: color-mix(in oklab, var(--t-text) calc(9% + (1 - var(--t-light)) * 3%), var(--t-bg));
--color-coolgray-500: color-mix(in oklab, var(--t-text) calc(12% + (1 - var(--t-light)) * 3%), var(--t-bg));
--coollabs-canvas: var(--color-app);
--coollabs-elevated: var(--color-surface);
--coollabs-recessed: var(--color-raised);
--coollabs-base: color-mix(in oklab, var(--t-text) calc((1 - var(--t-light)) * 5%), var(--t-bg));
--color-content-surface: var(--coollabs-base);
--coollabs-fill: color-mix(in oklab, var(--t-text) calc(8% + (1 - var(--t-light)) * 4%), var(--t-bg));
--coollabs-line: color-mix(in oklab, var(--t-text) calc(16% + (1 - var(--t-light)) * 6%), var(--t-bg));
--coollabs-hairline: color-mix(in oklab, var(--t-text) calc(11% + (1 - var(--t-light)) * 3%), var(--t-bg));
--coollabs-divider: color-mix(in oklab, var(--t-text) calc(5% + (1 - var(--t-light)) * 1%), transparent);
--color-hairline: color-mix(in oklab, var(--t-text) 8%, transparent);
--color-fg: var(--t-text);
--color-fg-dim: color-mix(in oklab, var(--t-text) 74%, var(--t-bg));
--color-fg-faint: color-mix(in oklab, var(--t-text) 60%, var(--t-bg));
--coollabs-subtle: var(--color-fg-faint);
--theme-placeholder-color: color-mix(in oklab, var(--t-text) 50%, var(--t-bg));
--color-nav-text: color-mix(in oklab, var(--t-text) 74%, var(--t-bg));
--color-nav-muted: color-mix(in oklab, var(--t-text) 60%, var(--t-bg));
--color-nav-active: var(--t-text);
/* Logs sit a little deeper than the canvas. */
--color-log: color-mix(in oklab, oklch(0% 0 0) calc((1 - var(--t-light)) * 20%), var(--color-app));
--color-log-toolbar: var(--color-surface);
}
html[data-theme="custom"] .control-selected,
html[data-theme="custom"] .logs-viewer-btn-active {
color: var(--color-accent-foreground);
}
html[data-theme="custom"] * {
scrollbar-color: var(--theme-scrollbar-thumb) var(--color-panel);
}
html[data-theme="custom"] *::-webkit-scrollbar-thumb {
border-radius: 9999px;
background: var(--theme-scrollbar-thumb);
}
html[data-theme="custom"] *::-webkit-scrollbar-thumb:hover {
background: var(--theme-bright-color);
}
html[data-theme="custom"] *::-webkit-scrollbar-track {
background: var(--color-panel);
}
html[data-theme="custom"] .application-settings-navigation {
overflow-y: scroll;
scrollbar-gutter: stable;
scrollbar-width: thin;
}
html[data-theme="custom"] .application-settings-navigation::-webkit-scrollbar {
width: 8px;
}
/* Use the same surface recipe as application settings cards for legacy
translucent cards and tables across the application. */
html[data-theme="custom"] .data-table,
html[data-theme="custom"] .box,
html[data-theme="custom"] .coolbox,
html[data-theme="custom"] .resource-view,
html[data-theme="custom"] [class~="bg-surface"],
html[data-theme="custom"] [class~="dark:bg-surface"],
html[data-theme="custom"] [class*="dark:bg-white/[0.025]"] {
background-color: var(--color-content-surface) !important;
}
.dark [class~="dark:border-white/[0.07]"],
.dark [class~="dark:border-white/[0.08]"] {
border-color: var(--coollabs-line) !important;
}
.dark [class~="dark:border-white/[0.06]"] {
border-color: var(--coollabs-hairline) !important;
}
html[data-theme="custom"] input::placeholder,
html[data-theme="custom"] textarea::placeholder {
color: var(--theme-placeholder-color) !important;
/* The token is already a readable light tone; no extra opacity dampening,
which would drop it back below the contrast floor. */
opacity: 1;
}
/* Accent text/icons (links, badges, "update available" pill, check marks) use the
brand color as foreground on dark surfaces. Route them to the clamped, always
readable token; fills (bg-*) keep the brighter accent + its computed foreground. */
html[data-theme="custom"] :is(
[class~="text-coollabs"],
[class~="text-warning"],
[class~="text-accent"],
[class~="dark:text-coollabs"],
[class~="dark:text-warning"],
[class~="dark:text-brand-light"]
) {
color: var(--theme-fg-on-surface) !important;
}
html.dark[data-theme="custom"] [class~="dark:bg-brand"] {
color: var(--theme-accent-foreground) !important;
}
html[data-theme="custom"] [class~="hover:text-coollabs"]:hover,
html[data-theme="custom"] [class~="hover:text-warning"]:hover,
html[data-theme="custom"] [class~="dark:hover:text-warning"]:hover,
html[data-theme="custom"] [class~="dark:hover:text-brand-light"]:hover,
html[data-theme="custom"] [class~="dark:hover:text-coollabs"]:hover {
color: var(--theme-fg-on-surface) !important;
}
/* Light custom theme reaches raw neutral utilities too. Dark mode tints
everything through the pervasive dark:bg-* and dark:border-* utilities (which
resolve to --color-* vars the block overrides); light mode has no equivalent,
so components fall back to hardcoded neutrals. Remap the dominant light
surface/border utilities to the tinted custom vars so the color reaches the
whole UI, not just design-system classes. A light sidebar on a dark page
gets the same remap. */
html[data-theme="custom"]:not(.dark) :is([class~="bg-white"], [class~="bg-white/95"], [class~="bg-white/80"]),
html[data-sidebar-scheme="light"] :where(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) :is([class~="bg-white"], [class~="bg-white/95"], [class~="bg-white/80"]) {
background-color: var(--coollabs-elevated) !important;
}
html[data-theme="custom"]:not(.dark) [class~="bg-neutral-50"],
html[data-sidebar-scheme="light"] :where(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) [class~="bg-neutral-50"] {
background-color: var(--coollabs-canvas) !important;
}
html[data-theme="custom"]:not(.dark) :is([class~="bg-neutral-100"], [class~="bg-neutral-200"]),
html[data-sidebar-scheme="light"] :where(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) :is([class~="bg-neutral-100"], [class~="bg-neutral-200"]) {
background-color: var(--coollabs-fill) !important;
}
html[data-theme="custom"]:not(.dark) [class~="border-neutral-200"],
html[data-sidebar-scheme="light"] :where(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) [class~="border-neutral-200"] {
border-color: var(--coollabs-hairline) !important;
}
html[data-theme="custom"]:not(.dark) :is([class~="border-neutral-300"], [class~="border-neutral-100"]),
html[data-sidebar-scheme="light"] :where(.app-sidebar, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) :is([class~="border-neutral-300"], [class~="border-neutral-100"]) {
border-color: var(--coollabs-line) !important;
}
/* Row and section separators (one-sided borders and divide-y) use the softer
divider token, so lists read as one card instead of a stack of boxes. Card
edges (full borders) keep the hairline/line tokens. */
html:not(.dark) :is([class~="border-b"], [class~="border-t"], [class~="border-y"])[class~="border-neutral-200"],
html:not(.dark) [class~="divide-neutral-200"] > :not(:last-child),
.dark :is([class~="border-b"], [class~="border-t"], [class~="border-y"]):is([class~="dark:border-white/[0.06]"], [class~="dark:border-white/[0.07]"], [class~="dark:border-white/[0.08]"]),
.dark :is([class~="dark:divide-white/[0.06]"], [class~="dark:divide-white/[0.07]"], [class~="dark:divide-white/[0.08]"]) > :not(:last-child) {
border-color: var(--coollabs-divider) !important;
}
html[data-theme="custom"] input:read-only,
html[data-theme="custom"] textarea:read-only,
html[data-theme="custom"] input:disabled,
html[data-theme="custom"] textarea:disabled {
color: var(--color-fg-dim) !important;
}
/* Public authentication surfaces */
.auth-shell {
min-height: 100dvh;
display: flex;
background:
radial-gradient(circle at 50% 0%, color-mix(in oklab, var(--color-coollabs) 9%, transparent), transparent 34rem),
var(--coollabs-canvas);
color: #171717;
}
.dark .auth-shell {
color: var(--color-fg);
}
.auth-shell-content {
display: flex;
align-items: center;
justify-content: center;
min-height: 100dvh;
width: 100%;
padding: 2rem 1rem;
}
.auth-card {
width: min(100%, 27rem);
overflow: hidden;
border-radius: 10px;
background: var(--coollabs-elevated);
box-shadow: 0 0 0 1px var(--coollabs-hairline), 0 20px 60px rgb(0 0 0 / 0.14);
}
.auth-card-heading {
padding: 1.25rem 1.25rem 1rem;
}
.auth-card-heading h1 {
margin: 0;
font-size: 1.25rem;
line-height: 1.75rem;
font-weight: 600;
letter-spacing: -0.015em;
color: #171717;
}
.dark .auth-card-heading h1 {
color: var(--color-fg);
}
.auth-card-heading p {
margin-top: 0.25rem;
max-width: 38ch;
font-size: 0.8125rem;
line-height: 1.25rem;
color: var(--coollabs-subtle);
}
.auth-card-body {
border-radius: 10px;
padding: 1.25rem;
background: var(--coollabs-base);
box-shadow: 0 0 0 1px var(--coollabs-fill);
}
.auth-card-footer {
display: flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
min-height: 3rem;
padding: 0.75rem 1.25rem;
font-size: 0.8125rem;
color: var(--coollabs-subtle);
}
.auth-text-link {
font-size: 0.8125rem;
font-weight: 500;
color: #525252;
text-underline-offset: 3px;
transition: color 0.15s ease;
}
.dark .auth-text-link {
color: var(--color-fg-dim);
}
.auth-text-link:hover {
color: var(--color-accent);
text-decoration: underline;
}
.auth-divider {
display: flex;
align-items: center;
gap: 0.75rem;
color: var(--coollabs-subtle);
font-size: 0.75rem;
}
.auth-divider::before,
.auth-divider::after {
content: '';
height: 1px;
flex: 1;
background: var(--coollabs-fill);
}
.auth-guidance {
display: flex;
align-items: flex-start;
gap: 0.5rem;
border-radius: 8px;
padding: 0.625rem 0.75rem;
background: var(--coollabs-recessed);
color: var(--coollabs-subtle);
font-size: 0.75rem;
line-height: 1rem;
}
/* Public error surfaces (401/404/500/…). Match auth shell canvas + accent. */
.error-page-body {
width: 100vw;
height: 100dvh;
overflow: hidden;
}
.error-shell {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 2rem 1rem;
background:
radial-gradient(circle at 50% 0%, color-mix(in oklab, var(--color-coollabs) 9%, transparent), transparent 34rem),
var(--coollabs-canvas);
color: #171717;
}
.dark .error-shell {
color: var(--color-fg);
}
.error-shell-content {
width: min(100%, 32rem);
}
.error-code {
margin: 0;
font-family: var(--font-mono);
font-size: clamp(3.5rem, 12vw, 5.5rem);
font-weight: 600;
line-height: 1;
letter-spacing: -0.03em;
color: var(--color-coollabs);
}
.dark .error-code {
color: var(--color-brand-light);
}
.error-code-danger {
color: var(--color-error);
}
.dark .error-code-danger {
color: var(--color-error);
}
.error-title {
margin: 1rem 0 0;
font-size: 1.25rem;
line-height: 1.75rem;
font-weight: 600;
letter-spacing: -0.015em;
color: #171717;
}
.dark .error-title {
color: var(--color-fg);
}
.error-description {
margin: 0.375rem 0 0;
max-width: 42ch;
font-size: 0.875rem;
line-height: 1.375rem;
color: var(--coollabs-subtle);
}
.error-extra {
margin-top: 1.25rem;
}
.error-extra .error-message {
margin: 0;
padding: 0.75rem 0.875rem;
border-radius: 8px;
background: color-mix(in oklab, var(--color-error) 8%, transparent);
color: var(--color-error);
font-size: 0.8125rem;
line-height: 1.25rem;
word-break: break-word;
}
.error-extra details {
margin: 0;
font-size: 0.8125rem;
line-height: 1.25rem;
color: var(--coollabs-subtle);
}
.error-extra details summary {
cursor: pointer;
font-weight: 500;
color: #525252;
list-style: none;
}
.dark .error-extra details summary {
color: var(--color-fg-dim);
}
.error-extra details summary::-webkit-details-marker {
display: none;
}
.error-extra details summary:hover {
color: var(--color-accent);
}
.error-extra details ul,
.error-proxy-help ul {
margin: 0.5rem 0 0;
padding-left: 1.125rem;
display: flex;
flex-direction: column;
gap: 0.375rem;
}
.error-extra details code,
.error-proxy-help code {
font-family: var(--font-mono);
font-size: 0.75rem;
padding: 0.05rem 0.3rem;
border-radius: 4px;
background: var(--coollabs-recessed);
}
.error-actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem;
margin-top: 1.75rem;
}
/* Layer card root: soft outer box around a darker inset well (the body).
overflow stays visible so floating panels (listbox, tooltips) can escape
the card. */
.application-settings-section,
.application-settings-section-body,
.empty-state {
--card-bg: rgb(0 0 0 / 0.75%);
/* The page color under a card. The card tints are translucent, so
top-level cards paint them over this color (see below). */
--card-underlay: var(--color-neutral-50);
--card-border: rgb(0 0 0 / 9%);
--well-bg: #ffffff;
--well-border: rgb(0 0 0 / 8%);
--well-shadow: 0 1px 2px rgb(0 0 0 / 6%);
/* Light mode has no dim/faint fg tokens; these keep WCAG AA on the card. */
--card-fg: oklch(14.5% 0 0);
--card-fg-dim: oklch(40% 0 0);
--card-fg-faint: var(--coollabs-subtle);
}
/* Layout only for cards: wells set their own display (often a Tailwind grid). */
.application-settings-section,
.empty-state {
margin: 0;
display: flex;
width: 100%;
flex-direction: column;
border: 1px solid var(--card-border);
border-radius: 0.75rem;
padding: 0.375rem;
background: var(--card-bg);
box-shadow: 0 1px 2px rgb(0 0 0 / 5%);
/* keep anchored scrolls (scrollIntoView) clear of the fixed topbar and page padding */
scroll-margin-top: 7rem;
}
.dark .application-settings-section,
.dark .application-settings-section-body,
.dark .empty-state {
--card-bg: rgb(255 255 255 / 6%);
--card-underlay: var(--color-app);
--card-border: rgb(255 255 255 / 8%);
--well-bg: rgb(0 0 0 / 34%);
--well-border: rgb(255 255 255 / 7%);
--well-shadow: 0 1px 2px rgb(0 0 0 / 25%);
--card-fg: var(--color-fg);
--card-fg-dim: var(--color-fg-dim);
--card-fg-faint: var(--color-fg-faint);
}
html[data-theme="custom"]:not(.dark) :is(.application-settings-section, .application-settings-section-body, .empty-state) {
--card-underlay: var(--coollabs-canvas);
}
/* Top-level cards and wells sit on the page: paint the translucent tint over
the page color, so a page backdrop (the floating navigation dots) does not
show through. Nested ones keep the tint on top of their parent. */
:is(.application-settings-section, .empty-state):not(:is(.application-settings-section, .application-settings-section-body) *) {
background: linear-gradient(var(--card-bg), var(--card-bg)), var(--card-underlay);
}
.application-settings-section-body:not(.application-settings-section *) {
background: linear-gradient(var(--well-bg), var(--well-bg)), var(--card-underlay);
}
/* Dark mode: top-level cards are a near-canvas shade (#121212 under 34% black),
and their well is #121212, a step above it. The top bar matches the canvas
(see the brand shell below). */
.dark :is(.application-settings-section, .empty-state):not(:is(.application-settings-section, .application-settings-section-body) *) {
background: color-mix(in srgb, var(--color-card) 66%, black);
box-shadow: none;
}
.dark :is(.application-settings-section, .empty-state):not(:is(.application-settings-section, .application-settings-section-body) *) > :is(.application-settings-section-body, .empty-state-well) {
--well-bg: var(--color-card);
--well-shadow: none;
}
/* Dark mode: collection tiles, pagination bars, and tables match the cards.
A tile is #121212, like a card well. A table (a shell with a header row) has
#121212 rows and a header row in the darker card outer color. */
.dark [class~="rounded-xl"][class~="dark:bg-white/[0.05]"] {
background-color: var(--color-card) !important;
border-color: rgb(255 255 255 / 0.08) !important;
box-shadow: none;
}
.dark [class~="rounded-xl"][class~="dark:bg-white/[0.05]"] > [class~="border-b"]:first-child {
background: color-mix(in srgb, var(--color-card) 66%, black) !important;
}
/* Brief accent ring when a settings nav sub-item scrolls a section into view.
Use a real border on ::after (not animated multi-layer box-shadow) so the
ring is the same weight on every side — box-shadow rings look thicker on
the header edge next to the elevated strip. */
@keyframes application-settings-section-highlight {
0% {
opacity: 0;
}
15%,
60% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.application-settings-section.is-section-highlight {
position: relative;
}
.application-settings-section.is-section-highlight::after {
content: '';
position: absolute;
inset: 0;
z-index: 5;
border-radius: inherit;
border: 0.5px solid var(--color-accent);
pointer-events: none;
animation: application-settings-section-highlight 500ms ease-out forwards;
}
/* Modals reuse the layer-card shell. The card tint is translucent, so paint it
over the page canvas to keep the page behind the modal hidden. */
.application-settings-section.application-settings-form {
background: linear-gradient(var(--card-bg), var(--card-bg)), var(--coollabs-canvas);
}
/* Dark mode: the modal markup adds an inline hairline ring outside the card
border, which reads as a light double edge. Keep only the card border. */
.dark .application-settings-section.application-settings-form {
box-shadow: var(--shadow-modal) !important;
}
/* Modals size to content on large screens */
@media (min-width: 1024px) {
.application-settings-section.application-settings-form {
width: auto;
}
}
/* Layer card header: small title row on the outer box */
.application-settings-section > :is(header, .application-settings-section-header) {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.25rem 0.5rem 0.625rem;
font-weight: 500;
color: var(--card-fg-faint);
}
.application-settings-section > :is(header, .application-settings-section-header) :is(h2, h3) {
font-size: 13px;
font-weight: 600;
line-height: 1.25rem;
letter-spacing: 0;
color: var(--card-fg-dim);
}
.application-settings-section > :is(header, .application-settings-section-header) :is(h2, h3) .info-helper {
color: inherit;
}
.application-settings-section > .application-settings-section-header p {
margin-top: 0.125rem;
font-size: 11px;
line-height: 1rem;
color: var(--card-fg-faint);
}
.application-settings-section > :is(header, .application-settings-section-header) > div:nth-child(2) > a:not(.button) {
font-size: 11px;
font-weight: 500;
color: var(--card-fg-faint);
}
.application-settings-section > :is(header, .application-settings-section-header) > div:nth-child(2) > a:not(.button):hover {
color: var(--card-fg);
}
/* Layer card body: the inset well */
.application-settings-section-body {
position: relative;
border: 1px solid var(--well-border);
border-radius: 0.5rem;
background: var(--well-bg);
box-shadow: var(--well-shadow);
padding: 1rem;
}
/* The dark well is darker than the canvas, so controls inside it use a darker,
translucent fill and border (like haruvo) instead of the global opaque ones. */
.dark .application-settings-section-body {
--coollabs-recessed: rgb(255 255 255 / 3%);
--coollabs-line: rgb(255 255 255 / 8%);
}
/* A well without a card around it sits straight on the canvas. In dark mode it
matches a top-level card: #121212 content, and a table header row in the
darker card outer color. */
.dark .application-settings-section-body:not(.application-settings-section *) {
--well-bg: var(--color-card);
--well-border: var(--card-border);
--well-shadow: none;
}
.dark .application-settings-section-body:not(.application-settings-section *) .data-table-header {
background: color-mix(in srgb, var(--color-card) 66%, black);
}
/* Floating panels in the well must stay opaque: restore the global dark values. */
.dark .application-settings-section-body :is(.listbox-panel, .searchable-listbox-panel, [role="listbox"], [role="menu"]) {
--coollabs-recessed: oklch(25.2% 0 0);
--coollabs-line: oklch(36% 0 0);
}
.settings-mobile-header {
margin-bottom: 1.25rem;
}
.settings-mobile-title {
font-size: 1.5rem;
font-weight: 600;
line-height: 1.75rem;
letter-spacing: -0.025em;
color: #000000;
}
.dark .settings-mobile-title {
color: #ffffff;
}
.settings-mobile-description {
margin-top: 0.25rem;
font-size: 0.8125rem;
line-height: 1.25rem;
color: #737373;
}
.dark .settings-mobile-description {
color: var(--coollabs-subtle);
}
/* All settings sidebars share one alignment rule. Keep the sticky offset equal
to the desktop main-content top padding so the menu does not jump upward
when it changes from its natural position to sticky positioning. */
.server-settings-workspace > :not(.application-settings-navigation) {
min-width: 0;
}
/* Below xl the rail is a grid above the content; a canvas has no room for it. */
@media (max-width: 1279.98px) {
.app-main-full .application-settings-navigation {
display: none;
}
}
/* A resource menu that the main sidebar (and the mobile drawer) shows instead
(navbar #main-nav-context-{depth}). */
.application-settings-navigation.in-main-nav {
display: none;
}
.application-settings-navigation.in-main-nav ~ * {
grid-column: 1 / -1;
}
@media (min-width: 1280px) {
/* Fixed like the main sidebar (3rem → bottom); left tracks its width (--sidebar-w).
Fixed leaves the grid, so the content sibling is pinned to column 2 below. */
.application-settings-navigation {
position: fixed;
top: 3rem;
left: var(--sidebar-w, 14rem);
width: calc(210px + 2.5rem);
height: calc(100dvh - 3rem);
overflow-x: hidden;
overflow-y: auto;
overscroll-behavior: contain;
scrollbar-width: thin;
scrollbar-color: var(--coollabs-fill) transparent;
scrollbar-gutter: stable;
--rail-fill: var(--coollabs-elevated);
padding: 0.5rem;
background: var(--rail-fill);
border-right: 1px solid var(--coollabs-hairline);
transition: left 200ms ease;
}
.application-settings-navigation ~ * {
grid-column: 2;
}
html:not(.dark) .application-settings-navigation {
--rail-fill: var(--coollabs-base);
}
.application-settings-navigation::-webkit-scrollbar {
width: 6px;
}
.application-settings-navigation::-webkit-scrollbar-track {
background: transparent;
}
.application-settings-navigation::-webkit-scrollbar-thumb {
border-radius: 9999px;
background: var(--coollabs-fill);
}
.application-settings-navigation::-webkit-scrollbar-thumb:hover {
background: var(--coollabs-line);
}
/* Canvas pages with a rail (server canvas): the rail stays above the canvas,
and the canvas starts after the rail. */
.app-main-full .application-settings-navigation {
z-index: 20;
}
.app-main-full .application-settings-navigation:not(.in-main-nav) ~ [data-environment-canvas] {
margin-left: calc(210px + 2.5rem);
}
/* Embeds: keep the surface, drop the fixed positioning. */
.application-settings-navigation.is-flush {
position: static;
inset: auto;
width: auto;
height: auto;
overflow: visible;
transition: none;
}
}
/* Honor reduced-motion for the settings nav disclosure/accordion and the mobile
sidebar sheet: keep open/close functional but drop the height/slide/fade. */
@media (prefers-reduced-motion: reduce) {
.application-settings-navigation,
.application-settings-navigation *,
.mobile-sidebar-sheet * {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
}
}
/* Field labels */
.application-settings-workspace label,
.application-settings-form label {
margin-bottom: 0.375rem;
font-size: 13px;
font-weight: 500;
color: var(--coollabs-subtle);
}
/*
* Labels that sit in a fixed-height control row (with optional helper icon) must not
* add their own bottom margin — the row wrapper owns spacing so side-by-side inputs align.
*/
.application-settings-workspace .mb-0\!,
.application-settings-form .mb-0\!,
.application-settings-workspace label.mb-0\!,
.application-settings-form label.mb-0\! {
margin-bottom: 0 !important;
}
/* Inputs & selects: recessed fill + line border (surface hierarchy) */
.application-settings-workspace .input,
.application-settings-workspace .select,
.application-settings-workspace .listbox-trigger,
.application-settings-form .input,
.application-settings-form .select,
.application-settings-form .listbox-trigger {
height: 2rem;
border-radius: 8px;
border-color: var(--coollabs-line);
background-color: var(--coollabs-recessed);
padding-left: 0.75rem;
padding-right: 0.75rem;
font-size: 0.875rem;
box-shadow: none !important;
}
/* Reserve the trailing area used by the native select chevron. */
.application-settings-workspace .select,
.application-settings-form .select {
padding-right: 2.5rem;
}
@media (max-width: 767px) {
.application-settings-workspace .select,
.application-settings-form .select {
font-size: 0.75rem !important;
line-height: 1.25rem;
}
}
/* Keep password toggle / copy button clear of the value inside denser settings inputs */
.application-settings-workspace .input.input-with-password-toggle,
.application-settings-workspace .input[type="password"],
.application-settings-workspace .input.input-with-copy-button,
.application-settings-form .input.input-with-password-toggle,
.application-settings-form .input[type="password"],
.application-settings-form .input.input-with-copy-button {
padding-right: 2.5rem;
}
.application-settings-workspace .input.input-with-copy-and-peek,
.application-settings-form .input.input-with-copy-and-peek {
padding-right: 4.25rem;
}
.application-settings-workspace .input.input-with-stepper,
.application-settings-form .input.input-with-stepper {
padding-right: 4.25rem;
}
.application-settings-workspace .number-stepper button:last-child,
.application-settings-form .number-stepper button:last-child {
border-radius: 0 7px 7px 0;
}
.application-settings-workspace .input:focus-visible,
.application-settings-workspace .select:focus-visible,
.application-settings-form .input:focus-visible,
.application-settings-form .select:focus-visible {
border-color: var(--color-accent);
box-shadow: none !important;
}
.application-settings-workspace textarea.input,
.application-settings-form textarea.input {
height: auto;
min-height: 5rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
line-height: 1.25rem;
}
.application-settings-workspace .form-control,
.application-settings-form .form-control {
border-radius: 8px;
}
.dark .application-settings-workspace .form-control:hover,
.dark .application-settings-form .form-control:hover {
background: transparent;
}
/* Main sidebar: only the menu scrolls on short or zoomed-in screens; the
footer stays visible. Hide the scrollbar so it does not take width from the
narrow collapsed rail and push the icons aside. */
.sidebar-menu {
scrollbar-width: none; /* Firefox */
}
.sidebar-menu::-webkit-scrollbar {
display: none; /* Chrome/Safari */
}
/* Unified resource heading bar: hide tab-strip scrollbars; menus/actions stay overflow-visible for dropdowns */
.resource-heading-navbar {
overflow: visible;
}
.resource-heading-tabs {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE/legacy Edge */
}
.resource-heading-tabs::-webkit-scrollbar {
display: none; /* Chrome/Safari */
width: 0;
height: 0;
}
/*
* Kumo-style overflow controls for resource heading tabs.
* Circular chevrons sit over gradient fades when more tabs are off-screen.
*/
.resource-heading-tabs-scroller {
--resource-heading-tabs-fade: oklch(97.02% 0 0); /* neutral-100, matches pill track */
}
.dark .resource-heading-tabs-scroller {
/* Matches dark:bg-white/[0.035] pill track + panel so fade blends */
--resource-heading-tabs-fade: color-mix(in srgb, var(--color-panel, #0a0a0a) 96.5%, white);
}
.resource-heading-tabs-control {
position: absolute;
inset-block: 0;
z-index: 3;
display: flex;
align-items: center;
width: 2.5rem;
margin: 0;
padding: 0;
border: 0;
pointer-events: none;
opacity: 0;
transition: opacity 0.15s ease;
}
.resource-heading-tabs-control.is-visible {
pointer-events: auto;
opacity: 1;
}
.resource-heading-tabs-control.is-start {
left: 0;
justify-content: flex-start;
background: linear-gradient(
to right,
var(--resource-heading-tabs-fade) 35%,
color-mix(in srgb, var(--resource-heading-tabs-fade) 85%, transparent) 70%,
transparent
);
}
.resource-heading-tabs-control.is-end {
right: 0;
justify-content: flex-end;
background: linear-gradient(
to left,
var(--resource-heading-tabs-fade) 35%,
color-mix(in srgb, var(--resource-heading-tabs-fade) 85%, transparent) 70%,
transparent
);
}
.resource-heading-tabs-control-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.5rem;
height: 1.5rem;
margin-inline: 0.25rem;
border-radius: 9999px;
color: #737373;
background: var(--coollabs-base);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.06),
0 0 0 1px rgba(0, 0, 0, 0.08);
transition:
background-color 0.15s ease,
color 0.15s ease,
box-shadow 0.15s ease;
}
.dark .resource-heading-tabs-control-icon {
color: var(--color-fg-dim, #a3a3a3);
background: var(--color-raised, #171717);
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.35),
0 0 0 1px rgba(255, 255, 255, 0.1);
}
.resource-heading-tabs-control:hover .resource-heading-tabs-control-icon,
.resource-heading-tabs-control:focus-visible .resource-heading-tabs-control-icon {
color: #000000;
background: #ffffff;
}
.dark .resource-heading-tabs-control:hover .resource-heading-tabs-control-icon,
.dark .resource-heading-tabs-control:focus-visible .resource-heading-tabs-control-icon {
color: var(--color-fg, #fafafa);
background: var(--color-raised, #171717);
}
.resource-heading-tabs-control:focus {
outline: none;
}
.resource-heading-tabs-control:focus-visible .resource-heading-tabs-control-icon {
box-shadow:
0 0 0 2px color-mix(in srgb, var(--color-coollabs, #6b16ed) 45%, transparent),
0 0 0 1px rgba(0, 0, 0, 0.08);
}
.resource-heading-menus,
.resource-heading-actions {
overflow: visible;
}
/* Heading action group: buttons + dropdown triggers styled like the tabs */
.application-heading-actions .button,
.application-heading-actions .app-tab,
.application-heading-actions .relative > button[x-ref='trigger'] {
height: 2rem;
display: inline-flex;
align-items: center;
gap: 0.375rem;
border: none;
background: transparent;
box-shadow: none;
padding: 0 0.625rem;
border-radius: 6px;
font-size: 13px;
font-weight: 500;
color: #737373;
min-width: 0;
white-space: nowrap;
transition: background-color 0.15s, color 0.15s;
}
.dark .application-heading-actions .button,
.dark .application-heading-actions .app-tab,
.dark .application-heading-actions .relative > button[x-ref='trigger'] {
color: var(--color-fg-dim);
}
.application-heading-actions .button:hover:not(:disabled),
.application-heading-actions .app-tab:hover,
.application-heading-actions .relative > button[x-ref='trigger']:hover {
background: rgba(0, 0, 0, 0.05);
color: #000000;
}
.dark .application-heading-actions .button:hover:not(:disabled),
.dark .application-heading-actions .app-tab:hover,
.dark .application-heading-actions .relative > button[x-ref='trigger']:hover {
background: rgba(255, 255, 255, 0.06);
color: var(--color-fg);
}
.application-heading-actions .button-highlighted,
.dark .application-heading-actions .button-highlighted,
.application-heading-actions .button-highlighted:hover:not(:disabled),
.dark .application-heading-actions .button-highlighted:hover:not(:disabled) {
@apply button-highlighted;
}
/*
* Active primary tab styles.
* The base rules above set background/color/box-shadow with higher specificity
* than Tailwind utilities (bg-brand/15, text-brand-light, ring-*), so active
* tabs need an explicit override or they look identical to inactive ones.
*/
.application-heading-actions .app-tab[aria-current='page'],
.application-heading-actions .app-tab.app-tab-active {
background: color-mix(in srgb, var(--color-coollabs) 10%, transparent);
color: var(--color-coollabs);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-coollabs) 25%, transparent);
}
.application-heading-actions .app-tab[aria-current='page']:hover,
.application-heading-actions .app-tab.app-tab-active:hover {
background: color-mix(in srgb, var(--color-coollabs) 15%, transparent);
color: var(--color-coollabs);
}
.dark .application-heading-actions .app-tab[aria-current='page'],
.dark .application-heading-actions .app-tab.app-tab-active {
background: color-mix(in srgb, var(--color-brand) 15%, transparent);
color: var(--color-brand-light);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--color-brand-light) 25%, transparent);
}
.dark .application-heading-actions .app-tab[aria-current='page']:hover,
.dark .application-heading-actions .app-tab.app-tab-active:hover {
background: color-mix(in srgb, var(--color-brand) 20%, transparent);
color: var(--color-brand-light);
}
.application-heading-actions .relative > button[x-ref='trigger'] {
padding-right: 0.625rem;
}
/* dropdown chevron: inline instead of absolutely positioned */
.application-heading-actions .relative > button[x-ref='trigger'] > svg {
position: static;
margin: 0;
width: 0.875rem;
height: 0.875rem;
flex-shrink: 0;
opacity: 0.6;
}
/* dropdown label: kill the h-full/flex-col title wrapper so text centers
on the same baseline as the sibling buttons */
.application-heading-actions .relative > button[x-ref='trigger'] > span {
display: inline-flex;
flex-direction: row;
align-items: center;
height: auto;
line-height: 1.25;
}
.application-heading-actions .button svg,
.application-heading-actions .app-tab svg {
width: 0.875rem;
height: 0.875rem;
flex-shrink: 0;
}
.split-action {
display: inline-flex;
}
.split-action-main,
.split-action-caret {
@apply button-highlighted;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.375rem;
height: 2rem;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
cursor: pointer;
}
.split-action-main {
flex: 1 1 auto;
min-width: 0;
padding: 0 0.625rem;
border-radius: 6px 0 0 6px;
}
.split-action-caret {
flex-shrink: 0;
width: 1.75rem;
border-left: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 0 6px 6px 0;
}
.split-action > .split-action-main:only-of-type {
border-radius: 6px;
}
.split-action-main:disabled,
.split-action-caret:disabled {
cursor: not-allowed;
opacity: 0.45;
}
/* Same physical edge as highlighted .button: hover lifts, press sinks. */
.split-action-main:not(:disabled),
.split-action-caret:not(:disabled) {
--button-depth-color: var(--color-coollabs-300);
box-shadow: 0 2px 0 var(--button-depth-color);
}
.dark .split-action-main:not(:disabled),
.dark .split-action-caret:not(:disabled) {
--button-depth-color: var(--color-brand-depth);
}
.split-action-main:not(:disabled):hover,
.split-action-caret:not(:disabled):hover {
transform: translateY(-1px);
box-shadow: 0 3px 0 var(--button-depth-color);
}
.split-action-main:focus-visible,
.split-action-caret:focus-visible {
outline: 1px solid var(--color-accent);
outline-offset: 2px;
position: relative;
z-index: 1;
}
/* Custom listbox (replaces native <select>) */
.listbox-trigger {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
min-width: 0;
height: 2rem;
padding: 0 0.625rem 0 0.75rem;
overflow: hidden;
border-radius: 8px;
border: 1px solid var(--coollabs-line);
background: var(--coollabs-recessed);
font-size: 0.875rem;
color: #000000;
text-align: left;
cursor: pointer;
}
/* Selected value: fill remaining space and ellipsize long labels
(multi-select summaries, long option names, timezone paths, etc.). */
.listbox-trigger-label {
min-width: 0;
flex: 1 1 0%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
.dark .listbox-trigger {
color: var(--color-fg);
}
.listbox-trigger:disabled {
cursor: not-allowed;
background-color: var(--color-neutral-100);
color: var(--color-neutral-400);
}
.dark .listbox-trigger:disabled {
background-color: color-mix(in oklab, var(--color-white) 3%, transparent);
color: var(--color-fg-faint);
}
.listbox-trigger:focus-visible {
outline: none;
border-color: var(--color-accent);
box-shadow: none;
}
.listbox-panel {
position: absolute;
top: calc(100% + 0.25rem);
left: 0;
z-index: 30;
display: flex;
flex-direction: column;
gap: 1px;
/* At least as wide as the trigger, but never narrower than a readable
menu. Unlayered so it wins over layered Tailwind min-w-* utilities —
floating action menus should still set an explicit width with ! when
they need a fixed size (e.g. w-52!). */
min-width: max(100%, 13rem);
width: max-content;
max-width: min(24rem, calc(100vw - 1.5rem));
max-height: 16rem;
overflow-x: hidden;
overflow-y: auto;
padding: 0.25rem;
border-radius: 10px;
border: 1px solid var(--coollabs-line);
background: var(--coollabs-recessed);
box-shadow: var(--shadow-dropdown);
}
@media (max-width: 767px) {
/* The mobile top bar's backdrop filter makes it the containing block for
fixed descendants. Keep this menu anchored below its trigger instead
of centering it within the short top bar. */
.listbox-panel.top-user-menu-panel {
position: absolute !important;
top: calc(100% + 0.25rem) !important;
right: 0 !important;
left: auto !important;
max-height: calc(100dvh - 4.5rem) !important;
overflow-y: auto !important;
transform: none !important;
}
}
.listbox-option {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
width: 100%;
min-height: 2rem;
height: auto;
padding: 0.375rem 0.5rem;
border-radius: 6px;
font-size: 0.875rem;
line-height: 1.25rem;
color: #000000;
text-align: left;
cursor: pointer;
white-space: nowrap;
}
.dark .listbox-option {
color: var(--color-fg);
}
.listbox-option:hover {
background: var(--coollabs-fill);
}
.data-table-row-active {
background: var(--coollabs-fill);
color: var(--coollabs-fg);
}
.listbox-option-disabled {
cursor: not-allowed;
opacity: 0.4;
}
.listbox-option-disabled:hover {
background: transparent;
}
/* Searchable listbox: sticky search field + scrollable options */
.searchable-listbox-panel {
max-height: 18rem;
overflow: hidden;
padding: 0;
gap: 0;
}
.searchable-listbox-search {
position: relative;
flex-shrink: 0;
border-bottom: 1px solid var(--coollabs-line);
padding: 0.375rem;
}
.searchable-listbox-search-input {
width: 100%;
height: 2rem;
border-radius: 6px;
border: 1px solid var(--coollabs-line);
background: var(--coollabs-recessed);
padding: 0 0.5rem 0 2rem;
font-size: 0.8125rem;
color: #000000;
box-shadow: none;
}
.dark .searchable-listbox-search-input {
color: var(--color-fg);
}
.searchable-listbox-search-input::placeholder {
color: var(--color-fg-faint, #737373);
}
.searchable-listbox-search-input:focus {
outline: none;
border-color: var(--color-accent);
box-shadow: none;
}
/* Hide native WebKit search clear so custom clear buttons are not doubled */
input[type="search"]::-webkit-search-decoration,
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-results-button,
input[type="search"]::-webkit-search-results-decoration {
-webkit-appearance: none;
appearance: none;
display: none;
}
.searchable-listbox-options {
display: flex;
flex-direction: column;
gap: 1px;
max-height: 14rem;
overflow-x: hidden;
overflow-y: auto;
padding: 0.25rem;
}
.searchable-listbox-empty {
padding: 0.75rem 0.5rem;
text-align: center;
font-size: 0.75rem;
color: var(--color-fg-dim, #737373);
}
/* Flush layer-card body (tables and other full-bleed content) */
.application-settings-section-body.is-flush {
padding: 0;
}
/* The well does not clip (floating panels must escape it), so a filled table
header row needs the well's inner top radius, or its corners poke out. */
.application-settings-section-body.is-flush > :first-child,
.application-settings-section-body.is-flush > :first-child > :first-child {
border-top-left-radius: calc(0.5rem - 1px);
border-top-right-radius: calc(0.5rem - 1px);
}
/* Empty states inside cards keep the same inset as other card content. */
.application-settings-section-body:has(> .empty-state:only-child),
.application-settings-section-body.is-flush:has(> .empty-state:only-child),
.application-settings-section-body.is-flush > div:has(> .empty-state:only-child) {
padding: 1rem;
}
/* Standalone empty state: the same outer box and inset well as a layer card */
.empty-state {
border: 1px solid var(--card-border);
border-radius: 0.75rem;
padding: 0.375rem;
background: var(--card-bg);
box-shadow: 0 1px 2px rgb(0 0 0 / 5%);
}
.empty-state-well {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 1px solid var(--well-border);
border-radius: 0.5rem;
background: var(--well-bg);
box-shadow: var(--well-shadow);
padding: 2rem 1.5rem;
text-align: center;
}
.empty-state-title {
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
color: var(--card-fg);
}
.empty-state-description {
margin-top: 0.25rem;
max-width: 28rem;
line-height: 1.25rem;
color: var(--coollabs-subtle);
}
/* Inside a layer card or a well, that box is the frame: show only the centered text. */
:is(.application-settings-section, .application-settings-section-body) .empty-state,
:is(.application-settings-section, .application-settings-section-body) .empty-state-well {
border: 0;
border-radius: 0;
padding-inline: 0;
background: none;
box-shadow: none;
}
:is(.application-settings-section, .application-settings-section-body) .empty-state {
padding: 0;
}
.loading-state-card {
border: 1px dashed var(--coollabs-line);
border-radius: 12px;
background: var(--coollabs-base);
}
.application-settings-section-body.is-flush .empty-state,
.application-settings-section-body:has(> .empty-state:only-child) .empty-state,
.application-settings-section > .empty-state {
margin: 0;
width: 100%;
max-width: none;
box-sizing: border-box;
}
/* Centered process dialog (startup / activity logs) — full-height log body.
Override application-settings-form's `width: auto` so empty/waiting states
keep a usable panel instead of shrinking to the title. */
.process-dialog {
width: 100% !important;
min-height: min(70dvh, 28rem);
}
.process-dialog-body {
border-radius: 0 0 8px 8px;
min-height: 0;
flex: 1 1 auto;
}
.process-dialog-body > * {
min-height: 0;
flex: 1 1 auto;
width: 100%;
}
/* The dialog body is already the well: the log sits in it directly, without
its own box. */
.process-dialog-body .activity-monitor-log {
padding: 0;
border: 0;
border-radius: 0;
background: transparent;
}
/* Show the status of the activity monitor next to the dialog title. */
.process-dialog-status {
display: none;
}
.process-dialog:has([data-activity-status="running"]) .process-dialog-status-running,
.process-dialog:has([data-activity-status="finished"]) .process-dialog-status-finished,
.process-dialog:has([data-activity-status="failed"]) .process-dialog-status-failed {
display: inline-flex;
}
.validation-installation-logs {
border: 1px solid var(--coollabs-fill);
}
@media (max-width: 639px) {
.process-dialog-mobile-fullscreen {
height: 100dvh !important;
min-height: 100dvh;
max-height: 100dvh;
border-radius: 0;
box-shadow: inset 0 0 0 1px var(--coollabs-hairline) !important;
}
.process-dialog-mobile-fullscreen .process-dialog-body {
border-radius: 0;
}
}
/* Data table (layer-card body, full-bleed) */
.data-table {
min-width: 0;
max-width: 100%;
overflow-x: auto;
overscroll-behavior-x: contain;
-webkit-overflow-scrolling: touch;
}
.data-table-header {
display: grid;
align-items: center;
gap: 1rem;
padding: 0 1rem;
height: 2.5rem;
font-size: 13px;
font-weight: 500;
color: var(--coollabs-subtle);
background: rgba(0, 0, 0, 0.02);
border-bottom: 1px solid var(--coollabs-fill);
border-radius: 8px 8px 0 0;
}
.dark .data-table-header {
background: rgba(255, 255, 255, 0.02);
color: var(--color-fg-faint);
}
.data-table > .env-table-item {
border-bottom: 1px solid var(--coollabs-fill);
}
.data-table > .env-table-item:last-child {
border-bottom: none;
}
.data-table-row {
display: grid;
align-items: center;
gap: 1rem;
padding: 0.625rem 1rem;
min-height: 3rem;
transition: background-color 0.15s;
}
.data-table-row:hover {
background: rgba(0, 0, 0, 0.02);
}
.dark .data-table-row:hover {
background: rgba(255, 255, 255, 0.02);
}
.env-table-grid {
grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 6rem 4rem 4.5rem 4.8rem 4.2rem 3rem;
}
/* File browser: icon, name, perms, size, modified, actions.
Every column is a fixed width except the flexible name column, so the
header grid and each row grid resolve identical tracks and stay aligned
(an `auto` column would size to its own content per row and drift). */
.file-table-grid {
grid-template-columns: 1.75rem minmax(10rem, 1fr) 5rem 6rem 6rem 5rem 11rem 10rem;
}
@media (max-width: 768px) {
.data-table-header.file-table-grid {
display: none;
}
.data-table-row.file-table-grid {
grid-template-columns: 1.75rem minmax(0, 1fr) 10rem;
gap: 0.75rem;
}
/* Hide perms, owner, group, size and modified columns on small screens */
.data-table-row.file-table-grid > :nth-child(3),
.data-table-row.file-table-grid > :nth-child(4),
.data-table-row.file-table-grid > :nth-child(5),
.data-table-row.file-table-grid > :nth-child(6),
.data-table-row.file-table-grid > :nth-child(7) {
display: none;
}
}
/* File browser Monaco editor container: fixed viewport-relative height with a
rounded, hairline-framed surface that matches the rest of the settings UI. */
.file-editor-monaco {
height: 60vh;
min-height: 320px;
overflow: hidden;
border-radius: 8px;
box-shadow: inset 0 0 0 1px var(--coollabs-line);
}
.environment-table-scroll {
overflow-x: auto;
overscroll-behavior-x: contain;
-webkit-overflow-scrolling: touch;
}
.environment-table-scroll .env-table-grid {
min-width: 53rem;
}
.env-table-grid.env-table-grid-no-type {
grid-template-columns: minmax(14rem, 2.5fr) 4.8rem 4rem 4.5rem 4.8rem 4.2rem 3rem;
}
@media (max-width: 768px) {
.environment-table-scroll .env-table-grid {
min-width: 0;
}
.data-table-header.env-table-grid {
display: none;
}
.data-table-row.env-table-grid {
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.75rem;
padding: 0.75rem 1rem;
}
.data-table-row.env-table-grid > :not(:first-child):not(:last-child) {
display: none;
}
.data-table-row.env-table-grid > :last-child {
display: block;
justify-self: end;
}
}
/* Shared variables only store value shape (multiline), not per-resource flags. */
.env-table-grid-shared {
grid-template-columns: minmax(0, 1.6fr) 6rem minmax(0, 1fr) 4.5rem 3rem;
}
/* Shared env vars collapse secondary columns on tablet and use cards on phone. */
@media (max-width: 1100px) {
.env-table-grid-shared {
grid-template-columns: minmax(0, 1.4fr) 6rem minmax(0, 1fr) 3rem;
gap: 0.75rem;
}
/* Hide Multiline (4 of 5) */
.env-table-grid-shared > :nth-child(4) {
display: none;
}
}
@media (max-width: 900px) {
.env-table-grid-shared {
grid-template-columns: minmax(0, 1fr) 6rem 3rem;
}
.env-table-grid-shared > :nth-child(3) {
display: none;
}
}
@media (max-width: 640px) {
.data-table-header.env-table-grid-shared {
display: none;
}
.data-table-row.env-table-grid-shared {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
grid-template-areas:
'name actions'
'meta actions';
gap: 0.35rem 0.75rem;
align-items: start;
padding: 0.875rem 1rem;
min-height: 0;
}
/* Name */
.data-table-row.env-table-grid-shared > :nth-child(1) {
grid-area: name;
min-width: 0;
flex-wrap: wrap;
}
.data-table-row.env-table-grid-shared > :nth-child(1) .env-key-label {
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}
/* Type column */
.data-table-row.env-table-grid-shared > :nth-child(2) {
display: none !important;
}
/* Comment / flags already hidden; keep meta area for optional second line */
.data-table-row.env-table-grid-shared > :nth-child(3),
.data-table-row.env-table-grid-shared > :nth-child(4) {
display: none !important;
}
/* Actions */
.data-table-row.env-table-grid-shared > :nth-child(5) {
grid-area: actions;
align-self: center;
justify-self: end;
}
}
.env-managed-desktop {
display: flex;
}
.domains-table-grid {
grid-template-columns: minmax(0, 1.8fr) 8.5rem 10rem 11rem 6.5rem;
}
.domains-table-grid-service {
grid-template-columns: minmax(0, 1.8fr) 8.5rem 10rem 6.5rem;
}
.domains-table-grid-compose {
grid-template-columns: minmax(0, 1.6fr) minmax(6rem, 0.8fr) 8.5rem 10rem 11rem 6.5rem;
}
.domains-mobile-label {
display: none;
}
/* Domains table: collapse secondary columns on tablet/phone */
@media (max-width: 900px) {
.domains-table-grid {
grid-template-columns: minmax(0, 1fr) 8.25rem 9rem 10rem 5.5rem;
gap: 0.75rem;
}
.domains-table-grid-service {
grid-template-columns: minmax(0, 1fr) 8.25rem 9rem 5.5rem;
gap: 0.75rem;
}
.domains-table-grid-compose {
grid-template-columns: minmax(0, 1fr) 8.25rem 9rem 10rem 5.5rem;
gap: 0.75rem;
}
/* Hide "Service" */
.domains-table-grid-compose > :nth-child(2) {
display: none;
}
}
@media (max-width: 768px) {
.data-table-header.domains-table-grid,
.data-table-header.domains-table-grid-service,
.data-table-header.domains-table-grid-compose {
display: none;
}
.data-table-row.domains-table-grid,
.data-table-row.domains-table-grid-service,
.data-table-row.domains-table-grid-compose {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
grid-template-areas:
'domain domain'
'meta actions'
'indexing indexing'
'direction direction';
gap: 0.5rem 0.75rem;
align-items: start;
padding: 0.75rem;
min-height: 0;
}
.data-table-row.domains-row-without-direction {
grid-template-areas:
'domain domain'
'meta actions'
'indexing indexing';
}
.data-table-row.domains-table-grid.domains-row-without-direction > :nth-child(4),
.data-table-row.domains-table-grid-compose.domains-row-without-direction > :nth-child(5) {
display: none;
}
/* Domain cell */
.data-table-row.domains-table-grid > :nth-child(1),
.data-table-row.domains-table-grid-service > :nth-child(1),
.data-table-row.domains-table-grid-compose > :nth-child(1) {
grid-area: domain;
min-width: 0;
}
.data-table-row.domains-table-grid > :nth-child(1) a,
.data-table-row.domains-table-grid-service > :nth-child(1) a,
.data-table-row.domains-table-grid-compose > :nth-child(1) a {
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
}
/* Non-compose: 1 Domain, 2 DNS, 3 Indexing, 4 Direction, 5 Actions */
.data-table-row.domains-table-grid > :nth-child(2) {
grid-area: meta;
display: flex !important;
flex-wrap: wrap;
align-items: center;
gap: 0.375rem;
}
.data-table-row.domains-table-grid > :nth-child(3) {
grid-area: indexing;
}
.data-table-row.domains-table-grid > :nth-child(4) {
grid-area: direction;
}
.data-table-row.domains-table-grid > :nth-child(5) {
grid-area: actions;
align-self: center;
}
/* Compose service group: 1 Domain, 2 DNS, 3 Indexing, 4 Actions */
.data-table-row.domains-table-grid-service > :nth-child(2) {
grid-area: meta;
display: flex !important;
flex-wrap: wrap;
align-items: center;
gap: 0.375rem;
}
.data-table-row.domains-table-grid-service > :nth-child(3) {
grid-area: indexing;
}
.data-table-row.domains-table-grid-service > :nth-child(4) {
grid-area: actions;
align-self: center;
}
/* Compose: 1 Domain, 2 Service, 3 DNS, 4 Indexing, 5 Direction, 6 Actions */
.data-table-row.domains-table-grid-compose > :nth-child(2) {
display: none !important;
}
.data-table-row.domains-table-grid-compose > :nth-child(3) {
grid-area: meta;
display: flex !important;
flex-wrap: wrap;
align-items: center;
gap: 0.375rem;
}
.data-table-row.domains-table-grid-compose > :nth-child(4) {
grid-area: indexing;
}
.data-table-row.domains-table-grid-compose > :nth-child(5) {
grid-area: direction;
}
.data-table-row.domains-table-grid-compose > :nth-child(6) {
grid-area: actions;
align-self: center;
}
.domains-mobile-label {
display: block;
margin-bottom: 0.25rem;
font-size: 0.6875rem;
font-weight: 500;
line-height: 1rem;
color: var(--coollabs-subtle);
}
.data-table-row.domains-table-grid .listbox-trigger,
.data-table-row.domains-table-grid-compose .listbox-trigger {
height: 2rem;
font-size: 0.8125rem;
}
/* Service name as badge under domain on mobile (compose only) */
.data-table-row.domains-table-grid-compose .domains-service-mobile {
display: inline-flex !important;
}
}
.domains-service-mobile {
display: none;
}
@media (max-width: 768px) {
.domains-service-desktop {
display: none !important;
}
}
.backup-table-grid {
grid-template-columns: minmax(10rem, 1.7fr) 6rem minmax(7rem, 0.8fr) 7.5rem 6.5rem minmax(8rem, 1fr) 5rem;
min-width: 50rem;
}
.service-backup-table-grid {
grid-template-columns: minmax(10rem, 1.7fr) 6rem minmax(7rem, 0.8fr) 7.5rem 6.5rem minmax(8rem, 1fr) 12.5rem;
width: 100%;
}
.data-table-header.service-backup-table-grid {
border-bottom: 0;
}
/* Rows are reordered with CSS order, so dividers must not depend on DOM position. */
.data-table-row.service-backup-table-grid {
background: var(--coollabs-base);
border-top: 1px solid var(--coollabs-fill);
}
.data-table-row.service-backup-table-grid:hover {
background: color-mix(in srgb, var(--coollabs-base) 98%, black);
}
.dark .data-table-row.service-backup-table-grid:hover {
background: color-mix(in srgb, var(--coollabs-base) 98%, white);
}
/* Persistent storage volumes: Name | [Source] | Destination | [PR suffix] | Backup | [Actions] */
.volumes-table-grid-readonly {
grid-template-columns: minmax(10rem, 1.4fr) minmax(8rem, 1fr) 5rem;
}
.volumes-table-grid {
grid-template-columns: minmax(9rem, 1.2fr) minmax(6rem, 1fr) 5rem 15rem;
}
.volumes-table-grid-with-pr {
grid-template-columns: minmax(9rem, 1.1fr) minmax(6rem, 1fr) 8.5rem 5rem 15rem;
}
.volumes-table-grid-readonly.has-source {
grid-template-columns: minmax(10rem, 1.2fr) minmax(10rem, 1.2fr) minmax(8rem, 1fr) 5rem;
}
.volumes-table-grid.has-source {
grid-template-columns: minmax(9rem, 1.1fr) minmax(14rem, 1.5fr) minmax(6rem, 1fr) 5rem 15rem;
}
.volumes-table-grid-with-pr.has-source {
grid-template-columns: minmax(9rem, 1fr) minmax(14rem, 1.4fr) minmax(6rem, 1fr) 8.5rem 5rem 15rem;
}
.volumes-mobile-label {
display: none;
}
/* External Compose volumes (read-only): Volume | Docker volume | Mount path | Service */
.external-volumes-table-grid {
grid-template-columns: minmax(8rem, 1fr) minmax(9rem, 1.2fr) minmax(8rem, 1fr) minmax(6rem, 0.8fr);
}
/*
* Same tokens as .application-settings-form label (13px / medium / subtle).
* Do not use text-sm (14px) — settings labels override Tailwind to 13px.
*/
.volumes-mobile-label.is-visible,
.volumes-field-label {
font-size: 13px;
font-weight: 500;
line-height: 1rem;
color: var(--coollabs-subtle);
}
/* Compact inputs inside volume table rows (desktop) */
.data-table-row.volumes-table-grid .input,
.data-table-row.volumes-table-grid-with-pr .input,
.data-table-row.volumes-table-grid .listbox-trigger,
.data-table-row.volumes-table-grid-with-pr .listbox-trigger {
min-height: 2rem;
height: 2rem;
font-size: 12px;
}
.data-table-row.volumes-table-grid .listbox-trigger,
.data-table-row.volumes-table-grid-with-pr .listbox-trigger {
padding-inline: 0.5rem;
}
/* Phone: stacked card rows with per-field labels (table headers hidden) */
@media (max-width: 768px) {
.data-table-header.volumes-table-grid,
.data-table-header.volumes-table-grid-with-pr,
.data-table-header.volumes-table-grid-readonly,
.data-table-header.external-volumes-table-grid {
display: none;
}
.data-table-row.volumes-table-grid,
.data-table-row.volumes-table-grid-with-pr,
.data-table-row.volumes-table-grid-readonly,
.data-table-row.external-volumes-table-grid {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.625rem;
padding: 0.875rem 1rem;
min-height: 0;
}
.data-table-row.volumes-table-grid-with-pr > .volumes-col-pr {
display: flex;
flex-direction: column;
gap: 0.25rem;
min-width: 0;
width: 100%;
}
.data-table-row.volumes-table-grid > *,
.data-table-row.volumes-table-grid-with-pr > *,
.data-table-row.volumes-table-grid-readonly > *,
.data-table-row.external-volumes-table-grid > * {
width: 100%;
min-width: 0;
}
/* Match .application-settings-form label (13px), not Tailwind text-sm (14px) */
.volumes-mobile-label {
display: block;
font-size: 13px;
font-weight: 500;
line-height: 1rem;
color: var(--coollabs-subtle);
}
.volumes-cell-name,
.volumes-cell-source,
.volumes-col-backup,
.volumes-cell-dest,
.volumes-cell-actions {
display: flex;
flex-direction: column;
gap: 0.25rem;
min-width: 0;
width: 100%;
}
.volumes-col-backup {
flex-direction: row;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;
}
.volumes-cell-actions {
flex-direction: row;
flex-wrap: wrap;
align-items: center;
justify-content: flex-start;
gap: 0.5rem;
padding-top: 0.25rem;
}
.data-table-row.volumes-table-grid .input,
.data-table-row.volumes-table-grid-with-pr .input,
.data-table-row.volumes-table-grid .listbox-trigger,
.data-table-row.volumes-table-grid-with-pr .listbox-trigger {
min-height: 2.25rem;
height: 2.25rem;
font-size: 13px;
}
}
.deployment-table-scroll {
overflow-x: auto;
overscroll-behavior-x: contain;
-webkit-overflow-scrolling: touch;
}
.deployment-table-grid {
min-width: 59rem;
grid-template-columns: 7.5rem minmax(7rem, 0.8fr) minmax(12rem, 1.7fr) minmax(8rem, 0.9fr) 6.5rem minmax(7rem, 0.8fr);
}
@media (min-width: 1024px) {
.deployment-table-scroll {
overflow-x: visible;
}
.deployment-table-grid {
min-width: 0;
}
}
/* A full-row link sits behind the cells, so gaps open the deployment too; the cells stay on top for their own links and tooltips. */
.deployment-table-row > :not(.deployment-row-link) {
position: relative;
z-index: 1;
pointer-events: none;
}
.deployment-table-row a:not(.deployment-row-link) {
pointer-events: auto;
}
/* The embedded history shows the start time and a status dot in the last column. */
.deployment-table-embedded .deployment-table-grid {
grid-template-columns: minmax(7rem, 0.8fr) minmax(12rem, 1.7fr) 6.5rem minmax(7rem, 0.8fr) 5rem;
}
@media (min-width: 1536px) {
.deployment-table-embedded .deployment-table-grid {
grid-template-columns: minmax(0, 1fr) 5rem;
}
.deployment-table-embedded .deployment-table-grid > :nth-child(1),
.deployment-table-embedded .deployment-table-grid > :nth-child(3),
.deployment-table-embedded .deployment-table-grid > :nth-child(4) {
display: none;
}
}
/* On the deployment page the history matches the logs height; the page size is fitted to it in JS. */
@media (min-width: 1536px) {
.deployment-history-fit {
position: sticky;
top: 1rem;
display: flex;
flex-direction: column;
height: calc(100dvh - 10rem);
min-height: 32rem;
}
.deployment-history-fit > .application-settings-section,
.deployment-history-fit .application-settings-section-body,
.deployment-history-fit .data-table {
display: flex;
flex: 1;
flex-direction: column;
min-height: 0;
}
.deployment-history-fit .deployment-table-scroll {
flex: 1;
min-height: 0;
overflow: hidden;
}
}
/* Status and Started only fit a badge and a short "3 hours ago"; the names get the rest. */
.dashboard-deployment-table-grid {
grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr) minmax(0, 0.7fr) 6.5rem 6rem;
}
.team-members-table-grid {
grid-template-columns: minmax(0, 1.15fr) minmax(0, 1.55fr) 7rem minmax(10rem, 0.9fr);
}
.team-members-table-grid-2fa {
grid-template-columns: minmax(0, 1.15fr) minmax(0, 1.55fr) 7rem 4rem minmax(10rem, 0.9fr);
}
.admin-users-table-grid {
grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr) 5rem;
}
.api-tokens-table-grid {
grid-template-columns: minmax(9rem, 1.15fr) minmax(14rem, 1.9fr) 7rem 6.5rem 6.5rem 5.5rem;
}
.fleet-servers-table-grid {
grid-template-columns: minmax(9rem, 1.4fr) 8rem 8rem 8rem 5rem 8rem 5.5rem;
}
.fleet-containers-table-grid {
grid-template-columns: 2.25rem minmax(9rem, 1.7fr) minmax(6rem, 0.9fr) repeat(4, minmax(7rem, 1fr));
min-width: 54rem;
}
.scheduled-executions-table-grid {
grid-template-columns: 5.5rem minmax(9rem, 1fr) minmax(7rem, 0.7fr) 8.5rem 5rem minmax(12rem, 1.5fr);
}
.scheduler-runs-table-grid {
grid-template-columns: 9rem minmax(12rem, 1.5fr) 6rem 6rem 6rem;
}
.skipped-jobs-table-grid {
grid-template-columns: 9rem 7rem minmax(10rem, 1fr) minmax(12rem, 1.4fr);
}
.server-resources-managed-table-grid {
grid-template-columns: minmax(10rem, 1.3fr) minmax(8rem, 1fr) minmax(8rem, 1fr) 8rem 9.5rem;
}
.server-resources-unmanaged-table-grid {
grid-template-columns: minmax(9rem, 1fr) minmax(12rem, 1.5fr) 7rem minmax(9rem, auto);
}
.server-images-table-grid {
grid-template-columns: minmax(10rem, 1.6fr) minmax(5rem, 0.8fr) 7.5rem 6.5rem 4.5rem minmax(8rem, 1fr) 6.5rem;
min-width: 55rem;
}
.package-updates-table-grid {
grid-template-columns: minmax(12rem, 1.4fr) minmax(10rem, 1fr) 6rem;
}
.scheduled-backups-table-grid {
grid-template-columns: minmax(10rem, 1.25fr) 8rem minmax(9rem, 1fr) 6rem 5rem;
}
.backup-executions-table-grid {
grid-template-columns: 6.5rem minmax(7rem, 0.8fr) minmax(14rem, 1.5fr) 10rem 5rem minmax(8rem, 1fr) 5rem;
gap: 0.75rem;
min-width: 67.5rem;
}
.backup-executions-table-scroll {
overflow-x: auto;
}
/* Match the grid width so the log output spans the full scrolled row. */
.backup-execution-row {
min-width: 67.5rem;
}
.backup-execution-row:last-child > .data-table-row {
border-bottom: 0;
}
.volume-backup-executions-grid {
grid-template-columns: 7.5rem minmax(14rem, 1fr) 11rem 7rem 9rem minmax(9rem, 0.7fr);
min-width: 52.5rem;
}
.clone-destinations-table-grid {
grid-template-columns: 2rem minmax(10rem, 1fr) minmax(10rem, 1fr);
}
.clone-resources-table-grid {
grid-template-columns: minmax(10rem, 1fr) 7rem minmax(12rem, 1.5fr);
}
.admin-search-table-grid {
grid-template-columns: minmax(10rem, 1fr) minmax(12rem, 1.2fr) 8rem 7rem;
}
@media (max-width: 900px) {
.team-members-table-grid {
grid-template-columns: minmax(0, 1fr) 7rem 7rem;
}
.team-members-table-grid-2fa {
grid-template-columns: minmax(0, 1fr) 7rem 4rem 7rem;
}
.team-members-table-grid > :nth-child(2) {
display: none;
}
.api-tokens-table-grid {
grid-template-columns: minmax(0, 1fr) minmax(9rem, 1.3fr) 6.5rem 4.5rem;
}
.api-tokens-table-grid > :nth-child(3),
.api-tokens-table-grid > :nth-child(4) {
display: none;
}
.server-resources-managed-table-grid {
grid-template-columns: minmax(9rem, 1fr) minmax(8rem, 0.8fr) 8rem 9.5rem;
}
.server-resources-managed-table-grid > :nth-child(3) {
display: none;
}
.backup-executions-table-grid {
grid-template-columns: 7.5rem minmax(9rem, 0.8fr) minmax(14rem, 1.5fr) 10rem minmax(8rem, 1fr) 5rem;
}
.backup-executions-table-grid > :nth-child(5) {
display: none;
}
}
@media (max-width: 640px) {
/* Keep header actions compact; log sections override via .logs-settings-section.
Only stretch when the actions row needs the full width (multi-control toolbars). */
.application-settings-section:not(.logs-settings-section) > :is(header, .application-settings-section-header) > div:nth-child(2) {
width: 100%;
}
.team-members-table-grid {
grid-template-columns: minmax(0, 1fr) 6.5rem;
}
.team-members-table-grid-2fa {
grid-template-columns: minmax(0, 1fr) 4rem 6.5rem;
}
.team-members-table-grid > :nth-child(3) {
display: none;
}
.admin-users-table-grid {
grid-template-columns: minmax(0, 1fr) 5rem;
}
.admin-users-table-grid > :nth-child(2) {
display: none;
}
.api-tokens-table-grid {
grid-template-columns: minmax(0, 1fr) 6.5rem 4.5rem;
}
.api-tokens-table-grid > :nth-child(2) {
display: none;
}
/* Name + Status only — fixed Type/Status tracks were crushing Name into "NameType". */
.server-resources-managed-table-grid {
grid-template-columns: minmax(0, 1fr) auto;
column-gap: 0.75rem;
}
.server-resources-managed-table-grid > :nth-child(2),
.server-resources-managed-table-grid > :nth-child(3),
.server-resources-managed-table-grid > :nth-child(4) {
display: none;
}
/* Name + Actions only — Status badge + action buttons overcrowded the row. */
.server-resources-unmanaged-table-grid {
grid-template-columns: minmax(0, 1fr) auto;
column-gap: 0.75rem;
}
.server-resources-unmanaged-table-grid > :nth-child(2),
.server-resources-unmanaged-table-grid > :nth-child(3) {
display: none;
}
.package-updates-table-grid {
grid-template-columns: minmax(0, 1fr) 5rem;
}
.package-updates-table-grid > :nth-child(2) {
display: none;
}
.scheduled-backups-table-grid {
grid-template-columns: minmax(0, 1fr) 8rem 5rem;
}
.scheduled-backups-table-grid > :nth-child(3),
.scheduled-backups-table-grid > :nth-child(4) {
display: none;
}
.backup-executions-table-grid,
.backup-execution-row {
min-width: 42rem;
}
.backup-executions-table-grid {
grid-template-columns: 7.5rem 9rem minmax(14rem, 1fr) minmax(7rem, auto);
}
.backup-executions-table-grid > :nth-child(4),
.backup-executions-table-grid > :nth-child(5),
.backup-executions-table-grid > :nth-child(6) {
display: none;
}
.clone-destinations-table-grid {
grid-template-columns: 2rem minmax(0, 1fr);
}
.clone-destinations-table-grid > :nth-child(3) {
display: none;
}
.clone-resources-table-grid {
grid-template-columns: minmax(0, 1fr) 7rem;
}
.clone-resources-table-grid > :nth-child(3) {
display: none;
}
.admin-search-table-grid {
grid-template-columns: minmax(0, 1fr) 7rem;
}
.admin-search-table-grid > :nth-child(2),
.admin-search-table-grid > :nth-child(3) {
display: none;
}
}
.data-table-cell-check {
display: flex;
justify-content: center;
color: #171717;
}
.dark .data-table-cell-check {
color: var(--color-fg);
}
.data-table-cell-dash {
display: flex;
justify-content: center;
color: #a3a3a3;
}
.dark .data-table-cell-dash {
color: var(--color-fg-faint);
}
/* Shared logs viewer (deployment + runtime) — mobile-first toolbar
Mobile stacks: search → meta → full-width actions (no side-by-side overlap). */
/* Same border as the application settings cards (--card-border). */
.logs-viewer {
border-color: rgb(0 0 0 / 9%);
background: var(--coollabs-base);
color: #262626;
}
.dark .logs-viewer {
border-color: rgb(255 255 255 / 8%);
background: var(--color-log);
color: #f5f5f5;
}
.logs-viewer-toolbar {
flex-shrink: 0;
border-bottom: 1px solid var(--color-neutral-200);
background: var(--color-neutral-50);
padding: 0.5rem 0.75rem;
}
.dark .logs-viewer-toolbar {
border-bottom-color: rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.035);
}
html[data-theme="custom"] .logs-viewer-toolbar {
border-bottom-color: var(--coollabs-line);
background: var(--color-log-toolbar);
}
html[data-theme="custom"] .logs-viewer-timestamp {
color: var(--color-fg-faint);
}
.logs-viewer-toolbar-controls {
display: flex;
min-width: 0;
width: 100%;
flex-direction: column;
align-items: stretch;
gap: 0.5rem;
}
.logs-viewer-primary {
display: flex;
min-width: 0;
width: 100%;
align-items: center;
gap: 0.5rem;
}
.logs-viewer-primary .logs-viewer-actions {
width: auto;
flex: 1 1 auto;
}
.logs-viewer-search {
position: relative;
min-width: 0;
width: 100%;
}
/* Search field uses the same pl-8!/pr-8! + z-10 icon pattern as server/project
list filters. Avoid .input here — settings form .input padding (0.75rem)
overrides custom padding and makes the search icon overlap the placeholder. */
.logs-viewer-meta {
display: flex;
min-width: 0;
width: 100%;
flex-shrink: 0;
align-items: center;
gap: 0.5rem;
}
/* Compact lines control (runtime/proxy/sentinel logs) */
.logs-viewer-lines {
display: inline-flex;
min-width: 0;
flex-shrink: 0;
align-items: center;
gap: 0.375rem;
}
.logs-viewer-lines-label {
font-size: 0.6875rem;
font-weight: 500;
color: #737373;
white-space: nowrap;
}
.dark .logs-viewer-lines-label {
color: var(--color-fg-faint, #6e6e74);
}
.logs-viewer-lines-input {
width: 4.5rem !important;
min-width: 4.5rem !important;
max-width: 5rem !important;
height: 2rem !important;
padding-left: 0.5rem !important;
padding-right: 0.375rem !important;
font-size: 0.75rem !important;
text-align: center;
appearance: textfield;
-moz-appearance: textfield;
}
.logs-viewer-lines-input::-webkit-outer-spin-button,
.logs-viewer-lines-input::-webkit-inner-spin-button {
margin: 0;
-webkit-appearance: none;
}
/* Runtime logs: "Lines", the number field and "All" as one control, styled like the search field. */
.runtime-log-lines-group {
height: 2rem;
gap: 0;
overflow: hidden;
border: 1px solid var(--color-neutral-200);
border-radius: 0.5rem;
background: #fff;
}
.dark .runtime-log-lines-group {
border-color: rgba(255, 255, 255, 0.08);
background: rgba(255, 255, 255, 0.035);
}
.runtime-log-lines-group:has(.logs-viewer-lines-input:focus) {
border-color: var(--color-coollabs);
}
.runtime-log-lines-group .logs-viewer-lines-label {
padding: 0 0.5rem 0 0.625rem;
}
.runtime-log-lines-group .logs-viewer-lines-input {
width: 3.75rem !important;
min-width: 3.75rem !important;
height: 100% !important;
border: 0 !important;
border-left: 1px solid var(--color-neutral-200) !important;
border-right: 1px solid var(--color-neutral-200) !important;
border-radius: 0 !important;
background: transparent !important;
box-shadow: none !important;
outline: none !important;
}
.dark .runtime-log-lines-group .logs-viewer-lines-input {
border-left-color: rgba(255, 255, 255, 0.08) !important;
border-right-color: rgba(255, 255, 255, 0.08) !important;
}
.runtime-log-lines-all {
height: 100%;
padding: 0 0.625rem;
font-size: 0.6875rem;
font-weight: 500;
color: #737373;
transition: color 0.15s, background-color 0.15s;
}
.runtime-log-lines-all:hover:not(:disabled, .runtime-log-lines-all-active) {
background: rgba(0, 0, 0, 0.05);
color: #171717;
}
.dark .runtime-log-lines-all {
color: var(--color-fg-faint);
}
.dark .runtime-log-lines-all:hover:not(:disabled, .runtime-log-lines-all-active) {
background: rgba(255, 255, 255, 0.06);
color: var(--color-fg);
}
.runtime-log-lines-all.runtime-log-lines-all-active,
.dark .runtime-log-lines-all.runtime-log-lines-all-active {
background: var(--color-coollabs);
color: #fff;
}
.runtime-log-lines-all:disabled {
cursor: not-allowed;
opacity: 0.5;
}
/* Section header status (proxy/sentinel) stays inline with title on mobile */
.logs-settings-section > :is(header, .application-settings-section-header) {
flex-wrap: nowrap;
gap: 0.5rem;
}
.logs-settings-section > :is(header, .application-settings-section-header) > div:first-child {
min-width: 0;
flex: 1 1 auto;
}
.logs-settings-section > :is(header, .application-settings-section-header) > div:nth-child(2) {
width: auto !important;
flex-shrink: 0;
margin-left: auto;
}
.logs-section-status-badge {
height: 1.5rem !important;
padding-inline: 0.5rem !important;
font-size: 0.6875rem !important;
}
/* Full-width action strip on mobile — icons never sit beside Lines */
.logs-viewer-actions {
display: flex;
min-width: 0;
width: 100%;
flex-shrink: 0;
align-items: center;
justify-content: flex-start;
gap: 0.125rem;
overflow-x: auto;
overscroll-behavior-x: contain;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
}
.logs-viewer-actions::-webkit-scrollbar {
display: none;
}
.logs-viewer-actions > * {
flex-shrink: 0;
}
/* Cancel/force-start + search — right-aligned as a group */
.logs-viewer-end {
display: flex;
min-width: 0;
width: 100%;
flex-shrink: 0;
align-items: center;
justify-content: flex-end;
gap: 0.5rem;
}
.logs-viewer-end .logs-viewer-meta {
width: auto;
}
.runtime-logs-viewer-end {
align-items: stretch;
flex-direction: column;
}
.logs-viewer-deployment-actions {
display: flex;
min-width: 0;
flex-shrink: 0;
align-items: center;
justify-content: flex-end;
}
.logs-viewer-deployment-actions:empty {
display: none;
}
.logs-viewer-deployment-btn {
height: 2rem !important;
min-height: 2rem !important;
padding-inline: 0.75rem !important;
font-size: 0.75rem !important;
line-height: 1rem !important;
border-radius: 0.5rem !important;
}
.logs-viewer-cancel-btn {
border-color: color-mix(in oklab, var(--color-error, #ef4444) 55%, transparent) !important;
background: color-mix(in oklab, var(--color-error, #ef4444) 10%, transparent) !important;
color: color-mix(in oklab, var(--color-error, #f87171) 90%, white) !important;
}
.logs-viewer-cancel-btn:hover {
border-color: color-mix(in oklab, var(--color-error, #ef4444) 75%, transparent) !important;
background: color-mix(in oklab, var(--color-error, #ef4444) 18%, transparent) !important;
}
.logs-viewer-btn,
.runtime-log-icon-button {
position: relative;
z-index: 0;
}
.logs-viewer-btn {
display: inline-flex;
flex-shrink: 0;
width: 2.25rem;
height: 2.25rem;
align-items: center;
justify-content: center;
border-radius: 0.5rem;
color: var(--color-neutral-500);
transition: color 0.15s, background-color 0.15s;
}
.logs-viewer-btn:hover {
background: var(--color-neutral-100);
color: var(--color-neutral-900);
}
.dark .logs-viewer-btn {
color: #a3a3a3;
}
.dark .logs-viewer-btn:hover {
background: rgba(255, 255, 255, 0.06);
color: #f5f5f5;
}
.logs-viewer-btn-active {
background: var(--color-coollabs);
color: #fff;
}
.dark .logs-viewer-btn-active {
color: #fff;
}
.logs-viewer-viewport {
min-width: 0;
padding: 0.5rem 0.75rem 0;
}
/* A flex item is reliably included in the scrollable overflow area, unlike
bottom padding on overflow containers in some browser/layout combinations. */
.logs-viewer-viewport::after {
content: "";
flex: 0 0 2rem;
}
.logs-viewer-line {
display: flex;
min-width: 0;
flex-direction: column;
gap: 0.125rem;
padding-block: 0.125rem;
}
.logs-viewer-line.hidden {
display: none;
}
.logs-viewer-timestamp {
flex-shrink: 0;
font-size: 0.625rem;
line-height: 1rem;
color: #737373;
letter-spacing: 0.01em;
}
.logs-viewer-line-text {
min-width: 0;
overflow-wrap: anywhere;
word-break: break-word;
white-space: pre-wrap;
}
@media (min-width: 640px) {
/* Desktop: [actions + status] ... [cancel + search] */
.logs-viewer-toolbar-controls {
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
gap: 0.5rem 0.75rem;
}
.logs-viewer-primary {
width: auto;
flex: 0 1 auto;
}
.logs-viewer-meta {
order: 0;
width: auto;
flex: 0 0 auto;
}
.logs-viewer-search {
order: 1;
width: auto;
max-width: 16rem;
flex: 1 1 12rem;
}
.logs-viewer-actions {
order: 0;
width: auto;
flex: 0 0 auto;
max-width: 100%;
justify-content: flex-end;
overflow: visible;
}
.logs-viewer-end {
order: 1;
width: auto;
flex: 0 0 auto;
margin-left: auto;
}
.logs-viewer-end .logs-viewer-meta {
order: 2;
}
.runtime-logs-viewer-end {
align-items: center;
flex-direction: row;
}
.logs-viewer-deployment-actions {
width: auto;
flex: 0 0 auto;
}
.logs-viewer-btn {
width: 1.75rem;
height: 1.75rem;
border-radius: 0.375rem;
}
.logs-viewer-viewport {
padding: 0.5rem 1rem 0;
}
.logs-viewer-line {
flex-direction: row;
gap: 0.5rem;
padding-block: 0;
}
.logs-viewer-timestamp {
font-size: 0.75rem;
line-height: inherit;
}
}
/* Runtime log viewer */
.runtime-log-shell {
overflow: visible;
border-radius: 8px;
background: var(--coollabs-elevated);
box-shadow: 0 0 0 1px var(--coollabs-hairline);
}
.runtime-log-trigger {
display: flex;
min-height: 2.75rem;
cursor: pointer;
user-select: none;
align-items: center;
gap: 0.5rem;
border-radius: 8px;
padding: 0.625rem 0.875rem;
color: #171717;
transition: background-color 0.15s;
}
.runtime-log-trigger:hover {
background: rgba(0, 0, 0, 0.025);
}
.dark .runtime-log-trigger {
color: var(--color-fg);
}
.dark .runtime-log-trigger:hover {
background: rgba(255, 255, 255, 0.03);
}
.runtime-log-trigger h4 {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
font-size: 0.8125rem;
font-weight: 500;
}
.runtime-log-panel {
display: flex;
min-width: 0;
flex-direction: column;
overflow: visible;
border-radius: 8px;
background: var(--coollabs-base);
box-shadow: 0 0 0 1px var(--coollabs-fill);
}
.settings-log-panel .runtime-log-panel {
box-shadow: none;
}
.runtime-log-toolbar {
position: relative;
z-index: 20;
border-bottom-color: var(--coollabs-fill);
background: transparent;
}
/* Dark mode: the panel matches the cards. Rows are the card well color, and the
toolbar and column header are the darker card outer color. */
html.dark:not([data-theme="custom"]) .runtime-log-panel {
background: var(--color-card);
box-shadow: 0 0 0 1px rgb(255 255 255 / 8%);
}
html.dark:not([data-theme="custom"]) .runtime-log-panel :is(.runtime-log-toolbar, .runtime-log-columns),
html.dark:not([data-theme="custom"]) .logs-viewer > .runtime-log-toolbar {
border-bottom-color: rgb(255 255 255 / 8%);
background: color-mix(in srgb, var(--color-card) 66%, black);
}
html.dark:not([data-theme="custom"]) .runtime-log-panel .runtime-log-toolbar {
border-radius: 8px 8px 0 0;
}
.runtime-log-icon-button {
display: inline-flex;
flex-shrink: 0;
width: 2.25rem;
height: 2.25rem;
align-items: center;
justify-content: center;
border-radius: 0.5rem;
color: #737373;
transition: color 0.15s, background-color 0.15s;
}
@media (min-width: 640px) {
.runtime-log-icon-button {
width: 1.75rem;
height: 1.75rem;
border-radius: 6px;
}
}
.runtime-log-icon-button:hover {
background: rgba(0, 0, 0, 0.05);
color: #171717;
}
.dark .runtime-log-icon-button {
color: var(--color-fg-faint);
}
.dark .runtime-log-icon-button:hover {
background: rgba(255, 255, 255, 0.06);
color: var(--color-fg);
}
.runtime-log-icon-button-active {
background: var(--color-coollabs);
color: #fff;
}
.dark .runtime-log-icon-button-active {
background: var(--color-coollabs);
color: #fff;
}
.runtime-log-menu {
background: var(--coollabs-elevated) !important;
opacity: 1 !important;
}
/* View and Export menus sit after the search field and the Lines control. */
.runtime-log-menus {
display: flex;
order: 3;
align-items: center;
gap: 0.5rem;
}
.runtime-log-menu-label {
padding: 0.5rem 0.5rem 0.25rem;
font-size: 0.6875rem;
font-weight: 500;
color: #737373;
}
.dark .runtime-log-menu-label {
color: var(--color-fg-faint);
}
.runtime-log-viewport {
min-height: 12rem;
border-radius: 0 0 8px 8px;
background: var(--coollabs-elevated);
color: #262626;
}
@media (min-width: 640px) {
.runtime-log-viewport {
min-height: 18rem;
}
}
.dark .runtime-log-viewport {
background: var(--color-log);
color: var(--color-fg-dim);
}
.runtime-log-panel {
--runtime-log-line: rgba(0, 0, 0, 0.08);
--runtime-log-muted: #66666f;
--runtime-log-hover: rgba(0, 0, 0, 0.04);
--runtime-log-detail: rgba(0, 0, 0, 0.03);
--runtime-log-columns: 12.75rem 5.5rem minmax(0, 1fr);
}
.dark .runtime-log-panel {
--runtime-log-line: var(--glass-line, rgba(255, 255, 255, 0.065));
--runtime-log-muted: #a09da5;
--runtime-log-hover: rgba(255, 255, 255, 0.035);
--runtime-log-detail: rgba(0, 0, 0, 0.24);
}
.runtime-log-viewport.logs-viewer-viewport {
container: runtime-log-explorer / inline-size;
padding: 0;
}
.runtime-log-viewport.logs-viewer-viewport::after {
display: none;
}
.runtime-log-columns,
.runtime-log-viewport [data-log-line]:not(.hidden) {
display: grid;
grid-template-columns: var(--runtime-log-columns);
gap: 0.625rem;
box-sizing: border-box;
width: 100%;
align-items: baseline;
padding: 0.125rem 1.875rem 0.125rem 1rem;
}
.runtime-log-columns {
position: sticky;
top: 0;
z-index: 10;
border-bottom: 1px solid var(--runtime-log-line);
background: #f0eff2;
color: var(--runtime-log-muted);
font-family: ui-sans-serif, system-ui, sans-serif;
font-size: 0.75rem;
font-weight: 500;
}
.dark .runtime-log-columns {
background: var(--coollabs-elevated);
}
html[data-theme="custom"] .runtime-log-columns {
background: var(--color-log-toolbar);
}
.runtime-log-viewport [data-log-line] {
position: relative;
border-bottom: 1px solid var(--runtime-log-line);
border-radius: 0;
cursor: default;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 0.8125rem;
line-height: 1.6;
}
.runtime-log-viewport [data-log-line][data-expandable] {
cursor: pointer;
}
.runtime-log-viewport [data-log-line]:hover,
.runtime-log-viewport [data-log-line][aria-expanded="true"] {
background: var(--runtime-log-hover);
}
.runtime-log-viewport [data-log-line]:focus-visible {
outline: 2px solid var(--color-accent, #b93642);
outline-offset: -2px;
}
.runtime-log-viewport [data-log-line]::before {
content: "[" attr(data-log-level) "]";
grid-column: 2;
grid-row: 1;
color: var(--runtime-log-muted);
text-transform: uppercase;
}
.runtime-log-viewport [data-log-line]:not([data-expandable], [aria-expanded="true"])::after {
display: none;
}
.runtime-log-viewport [data-log-line]::after {
content: "";
position: absolute;
top: 0.5rem;
right: 0.75rem;
width: 0.375rem;
height: 0.375rem;
border-right: 1.5px solid var(--runtime-log-muted);
border-bottom: 1.5px solid var(--runtime-log-muted);
transform: rotate(45deg);
}
.runtime-log-viewport [data-log-line][aria-expanded="true"]::after {
top: 0.6875rem;
transform: rotate(225deg);
}
.runtime-log-viewport .log-error::before { color: #e55e73; }
.runtime-log-viewport .log-warning::before { color: #d79945; }
.runtime-log-viewport .log-debug::before { color: #929099; }
.runtime-log-viewport .log-info::before { color: #8891f0; }
.runtime-log-viewport .logs-viewer-timestamp {
grid-column: 1;
grid-row: 1;
color: var(--runtime-log-muted);
font-size: 0.8125rem;
line-height: 1.6;
white-space: nowrap;
}
.runtime-log-viewport [data-line-text] {
display: flex;
align-items: baseline;
gap: 0.75rem;
grid-column: 3;
grid-row: 1;
min-width: 0;
overflow: hidden;
}
.runtime-log-message {
min-width: 0;
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
}
.runtime-log-source {
flex: 0 0 8rem;
width: 8rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.runtime-log-detail {
margin: 0;
padding: 0.5rem 1rem;
border-bottom: 1px solid var(--runtime-log-line);
background: var(--runtime-log-detail);
color: inherit;
overflow-wrap: anywhere;
white-space: pre-wrap;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
font-size: 0.8125rem;
line-height: 1.6;
}
.dark .runtime-log-detail {
color: #adbdc9;
}
.runtime-log-virtual-list {
position: relative;
width: 100%;
}
.runtime-log-virtual-row {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.runtime-log-without-time {
--runtime-log-columns: 5.5rem minmax(0, 1fr);
}
.runtime-log-without-time [data-log-line]::before {
grid-column: 1;
}
.runtime-log-without-time [data-line-text] {
grid-column: 2;
}
/* Text view: one compact, wrapped text line per log entry, like the old viewer. */
.runtime-log-text-view {
padding-block: 0;
}
.runtime-log-viewport .runtime-log-text-view [data-log-line]:not(.hidden) {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.125rem;
min-height: 0;
padding: 0 0.75rem;
border-bottom: 0;
cursor: text;
font-size: 0.75rem;
line-height: 1.45;
text-align: left;
}
.runtime-log-viewport .runtime-log-text-view [data-log-line]::before,
.runtime-log-viewport .runtime-log-text-view [data-log-line]::after {
display: none;
}
.runtime-log-viewport .runtime-log-text-view .logs-viewer-timestamp {
font-size: inherit;
line-height: inherit;
}
.runtime-log-viewport .runtime-log-text-view [data-line-text] {
align-items: flex-start;
flex: 1 1 auto;
overflow: visible;
overflow-wrap: anywhere;
white-space: normal;
}
.runtime-log-viewport .runtime-log-text-view .runtime-log-message {
overflow: visible;
overflow-wrap: anywhere;
text-overflow: unset;
white-space: pre-wrap;
}
@media (min-width: 640px) {
.runtime-log-viewport .runtime-log-text-view [data-log-line]:not(.hidden) {
flex-direction: row;
gap: 0.5rem;
}
}
.runtime-logs-workspace {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.runtime-logs-fill,
.runtime-logs-fill > #screen,
.runtime-logs-fill .runtime-logs-stream,
.runtime-logs-fill .runtime-log-panel {
display: flex;
min-width: 0;
min-height: 0;
flex: 1 1 auto;
flex-direction: column;
}
/* Comes after the shared min-height: 0 so a phone still gets a tall stream. */
.runtime-logs-fill {
min-height: 70dvh;
}
/* The page fills the area inside <main>. Those paddings live in layouts/app.blade.php:
lg:pt-[calc(3rem+1.75rem)] and lg:pb-10. Width stays on that layout's pageWidth wrapper. */
.runtime-logs-page {
display: flex;
flex-direction: column;
}
.runtime-logs-fill .runtime-log-loading,
.runtime-logs-fill .runtime-log-empty {
position: absolute;
inset: 0;
min-height: 0;
}
@media (min-width: 1280px) {
.runtime-logs-page {
height: calc(100dvh - 4.75rem - 2.5rem);
}
.runtime-logs-page > .application-settings-workspace {
display: flex;
min-height: 0;
flex: 1 1 auto;
flex-direction: column;
}
.runtime-logs-page > .application-settings-workspace > .grid {
flex: 1 1 auto;
min-height: 0;
grid-template-rows: minmax(0, 1fr);
}
.runtime-logs-column {
display: flex;
min-height: 0;
flex-direction: column;
}
.runtime-logs-column > .w-full {
display: flex;
min-height: 0;
flex: 1 1 auto;
flex-direction: column;
}
.runtime-logs-workspace {
height: auto;
min-height: 0;
flex: 1 1 auto;
}
.runtime-logs-fill {
min-height: 0;
}
}
.deployment-badge {
@apply inline-flex shrink-0 items-center rounded-md px-1.5 py-0.5 text-[10px] font-semibold tracking-wide;
}
.deployment-badge-production {
@apply bg-success/10 text-success uppercase ring-1 ring-success/20;
}
.deployment-badge-preview {
@apply bg-coollabs/10 text-coollabs ring-1 ring-coollabs/20 dark:bg-brand/15 dark:text-brand-light dark:ring-brand-light/25;
}
.runtime-logs-fill .runtime-log-viewport {
min-height: 0;
flex: 1 1 auto;
max-height: none;
}
.runtime-log-viewport .runtime-log-text-view .log-error [data-line-text] { color: #e55e73; }
.runtime-log-viewport .runtime-log-text-view .log-warning [data-line-text] { color: #d79945; }
.runtime-log-viewport .runtime-log-text-view .log-debug [data-line-text] { color: #929099; }
@container runtime-log-explorer (max-width: 650px) {
.runtime-log-columns,
.runtime-log-viewport [data-log-line]:not(.hidden) {
grid-template-columns: minmax(0, 1fr) 5.5rem;
gap: 0.1875rem 0.5rem;
}
.runtime-log-columns > :last-child,
.runtime-log-viewport [data-line-text] {
grid-column: 1 / -1;
grid-row: 2;
}
.runtime-log-without-time [data-line-text] {
grid-column: 1 / -1;
}
}
.runtime-log-empty {
display: flex;
min-height: 18rem;
align-items: center;
justify-content: center;
padding: 2rem;
color: var(--runtime-log-muted);
text-align: center;
}
.runtime-log-loading {
min-height: 18rem;
align-items: center;
justify-content: center;
gap: 0.625rem;
padding: 2rem;
color: var(--runtime-log-muted);
font-size: 0.8125rem;
font-weight: 500;
}
.runtime-log-empty p {
color: inherit;
font-size: 0.8125rem;
font-weight: 500;
}
.runtime-log-empty div > span {
display: block;
margin-top: 0.125rem;
font-size: 0.75rem;
line-height: 1.25rem;
}
.env-table-detail {
padding: 0.25rem 1rem 1.25rem;
}
/* Small pill badges for table cells */
.table-badge {
display: inline-flex;
align-items: center;
gap: 0.25rem;
height: 20px;
padding: 0 0.5rem;
border-radius: 9999px;
font-size: 11px;
font-weight: 500;
white-space: nowrap;
background: var(--coollabs-fill);
color: #52525b;
}
.dark .table-badge {
color: var(--color-fg-dim);
}
.table-badge-danger {
background: rgba(248, 113, 113, 0.14);
color: #dc2626;
}
.dark .table-badge-danger {
color: #f87171;
}
.table-badge-warning {
background: rgba(245, 158, 11, 0.16);
color: #b45309;
}
.dark .table-badge-warning {
background: rgba(245, 158, 11, 0.14);
color: #fbbf24;
}
.table-badge-success {
background: rgba(16, 185, 129, 0.14);
color: #047857;
}
.dark .table-badge-success {
background: rgba(16, 185, 129, 0.16);
color: #34d399;
}
/* Suggested / not-yet-configured domain rows — distinct from real FQDNs */
.domains-row-suggested {
background: rgba(245, 158, 11, 0.05);
box-shadow: inset 3px 0 0 0 rgba(245, 158, 11, 0.55);
}
.dark .domains-row-suggested {
background: rgba(245, 158, 11, 0.07);
box-shadow: inset 3px 0 0 0 rgba(251, 191, 36, 0.5);
}
/* Chip/tag input (comma-free multi-value entry, e.g. Domains) */
.chip-input {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.375rem;
min-height: 2rem;
width: 100%;
/* 3px block padding: 24px chip row + 6px + 2px border = 32px, matching .input/.button */
padding: 0.1875rem 0.375rem;
border-radius: 8px;
border: 1px solid var(--coollabs-line);
background: var(--coollabs-recessed);
cursor: text;
}
.chip-input:focus-within {
border-color: var(--color-accent);
box-shadow: none;
}
.chip-input input {
flex: 1 1 10rem;
min-width: 10rem;
height: 1.5rem;
margin: 0;
padding: 0 0.25rem;
border: none;
border-radius: 0;
background: transparent;
font-size: 0.875rem;
color: #000000;
outline: none;
box-shadow: none;
}
.dark .chip-input input {
color: var(--color-fg);
}
.chip-input input:focus,
.chip-input input:focus-visible {
outline: none;
box-shadow: none;
border: none;
}
.chip-input input::placeholder {
color: var(--coollabs-subtle);
opacity: 0.7;
}
.chip {
display: inline-flex;
align-items: center;
gap: 0.375rem;
height: 1.5rem;
max-width: 100%;
padding: 0 0.375rem 0 0.625rem;
border-radius: 6px;
border: none;
background: var(--coollabs-fill);
font-size: 0.8125rem;
color: #000000;
white-space: nowrap;
}
.dark .chip {
color: var(--color-fg);
}
.chip > span {
overflow: hidden;
text-overflow: ellipsis;
}
.chip-remove {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1rem;
height: 1rem;
border-radius: 3px;
color: var(--coollabs-subtle);
transition: color 0.15s;
}
.chip-remove:hover {
color: inherit;
}
/* In-card sub-headings (e.g. "Container labels", "Docker Compose") */
.application-settings-form h4,
.application-settings-form .application-settings-section-body h3 {
font-size: 0.875rem;
font-weight: 600;
}
/* Compact resource table on the project environment page. */
.environment-resource-grid {
display: grid;
grid-template-columns:
minmax(240px, 2fr)
minmax(100px, 0.7fr)
minmax(110px, 0.8fr)
minmax(180px, 1.2fr)
minmax(120px, 0.8fr)
minmax(140px, 1fr);
column-gap: 1rem;
}
.environment-resource-grid .mobile-resource-domain {
display: none;
}
.projects-table-grid {
display: grid;
grid-template-columns:
minmax(200px, 1.7fr)
8rem
7rem
minmax(200px, 1.6fr)
5rem;
column-gap: 1rem;
}
.tags-table-grid {
display: grid;
grid-template-columns:
minmax(220px, 1.7fr)
minmax(100px, 0.65fr)
minmax(110px, 0.7fr)
minmax(90px, 0.55fr);
column-gap: 1rem;
}
.environments-table-grid {
display: grid;
grid-template-columns:
minmax(220px, 1.5fr)
minmax(90px, 0.55fr)
minmax(240px, 1.6fr)
6rem;
column-gap: 1rem;
}
@media (max-width: 900px) {
.environments-table-grid {
grid-template-columns: minmax(220px, 1fr) 6rem 6rem;
}
.environments-table-grid .environment-description {
display: none;
}
}
@media (max-width: 640px) {
.environments-table-grid {
grid-template-columns: minmax(0, 1fr) 5rem;
column-gap: 0.75rem;
}
.environments-table-grid .environment-resource-count {
display: none;
}
}
@media (max-width: 1050px) {
.projects-table-grid {
grid-template-columns: minmax(200px, 1fr) 8rem 7rem 5rem;
}
.projects-table-grid .project-description {
display: none;
}
.tags-table-grid {
grid-template-columns: minmax(220px, 1fr) 7rem 6rem 3rem;
}
.tags-table-grid .tag-services {
display: none;
}
}
@media (max-width: 640px) {
.projects-table-grid {
grid-template-columns: minmax(0, 1fr) 5rem;
column-gap: 0.75rem;
}
.projects-table-grid > :nth-child(2),
.projects-table-grid > :nth-child(3) {
display: none;
}
.tags-table-grid {
grid-template-columns: minmax(0, 1fr) 5rem 3rem;
column-gap: 0.75rem;
}
.tags-table-grid > :nth-child(3),
.tags-table-grid > :nth-child(4) {
display: none;
}
}
@media (max-width: 1200px) {
.environment-resource-grid {
grid-template-columns:
minmax(220px, 2fr)
minmax(100px, 0.8fr)
minmax(110px, 0.8fr)
minmax(120px, 1fr);
}
.environment-resource-grid .resource-domain,
.environment-resource-grid .resource-tags {
display: none;
}
}
@media (max-width: 768px) {
.environment-resource-grid {
grid-template-columns: minmax(0, 1fr) 6.5rem;
column-gap: 0.75rem;
}
.environment-resource-grid .resource-type,
.environment-resource-grid .resource-server {
display: none;
}
.environment-resource-grid .mobile-resource-domain {
display: block;
}
}
/* Command palette (global search) */
.command-palette {
display: flex;
width: 100%;
max-width: 42rem;
flex-direction: column;
overflow: hidden;
border-radius: 10px;
background: var(--coollabs-elevated);
box-shadow: 0 0 0 1px var(--coollabs-hairline), var(--shadow-dropdown);
}
.command-palette-header {
display: flex;
align-items: center;
gap: 0.625rem;
min-height: 2.75rem;
padding: 0 0.75rem 0 0.875rem;
background: var(--coollabs-elevated);
}
.command-palette-header-icon {
display: flex;
width: 1rem;
height: 1rem;
flex-shrink: 0;
color: var(--coollabs-subtle);
}
.command-palette-header-icon.is-loading {
color: var(--color-accent);
}
.command-palette-input {
min-width: 0;
flex: 1;
height: 2.75rem;
border: 0;
background: transparent;
font-size: 0.875rem;
line-height: 1.25rem;
color: #171717;
outline: none;
box-shadow: none !important;
}
.dark .command-palette-input {
color: var(--color-fg);
}
.command-palette-input::placeholder {
color: var(--coollabs-subtle);
}
.command-palette-input:focus,
.command-palette-input:focus-visible {
outline: none !important;
box-shadow: none !important;
border: 0 !important;
}
/* Search scope switch (Global / This page), shown on resource settings pages */
.command-palette-scope {
display: inline-flex;
flex-shrink: 0;
align-items: center;
gap: 0.125rem;
padding: 0.125rem;
border-radius: 0.5rem;
border: 1px solid var(--coollabs-hairline);
}
.command-palette-scope-option {
height: 1.375rem;
padding: 0 0.5rem;
border-radius: 0.375rem;
color: var(--coollabs-subtle);
font-size: 0.75rem;
font-weight: 500;
white-space: nowrap;
cursor: pointer;
transition: color 0.15s ease, background 0.15s ease;
}
.command-palette-scope-option:hover {
color: #171717;
}
.command-palette-scope-option[aria-pressed='true'] {
color: #171717;
background: var(--coollabs-fill);
}
.dark .command-palette-scope-option:hover,
.dark .command-palette-scope-option[aria-pressed='true'] {
color: var(--color-fg);
}
.dark .command-palette-scope-option[aria-pressed='true'] {
background: rgb(255 255 255 / 0.1);
}
.command-palette-body {
max-height: min(60vh, 28rem);
overflow-y: auto;
border-radius: 8px;
background: var(--coollabs-base);
box-shadow: 0 0 0 1px var(--coollabs-fill);
scrollbar-width: thin;
scrollbar-color: var(--coollabs-fill) transparent;
}
.command-palette-body::-webkit-scrollbar {
width: 8px;
}
.command-palette-body::-webkit-scrollbar-track {
background: transparent;
}
.command-palette-body::-webkit-scrollbar-thumb {
border-radius: 9999px;
border: 2px solid transparent;
background-clip: padding-box;
background-color: var(--coollabs-fill);
}
.command-palette-section {
display: flex;
flex-direction: column;
gap: 1px;
padding: 0.25rem;
}
.command-palette-section + .command-palette-section {
border-top: 1px solid var(--coollabs-fill);
margin-top: 0;
padding-top: 0.25rem;
}
.command-palette-group-label {
padding: 0.375rem 0.5rem 0.25rem;
font-size: 0.6875rem;
font-weight: 500;
letter-spacing: 0.01em;
color: var(--coollabs-subtle);
}
/* Result rows: inset listbox-style pills. Kill the global button/a
focus-visible ring so keyboard selection is a soft fill, not yellow bars. */
.command-palette-item {
display: flex;
position: relative;
width: 100%;
min-height: 2.5rem;
align-items: center;
gap: 0.625rem;
padding: 0.4375rem 0.5rem;
border-radius: 6px;
text-align: left;
color: inherit;
transition: background 0.1s ease, box-shadow 0.1s ease;
outline: none !important;
border: 0;
box-shadow: none !important;
--tw-ring-shadow: 0 0 #0000 !important;
--tw-ring-offset-shadow: 0 0 #0000 !important;
--tw-ring-offset-width: 0px !important;
}
a.command-palette-item,
button.command-palette-item {
text-decoration: none;
background: transparent;
border: 0;
cursor: pointer;
}
.command-palette-item:hover {
background: var(--coollabs-fill);
}
/* Keyboard / programmatic focus = active selection */
.command-palette-item:focus,
.command-palette-item:focus-visible,
button.command-palette-item:focus-visible,
a.command-palette-item:focus-visible {
outline: none !important;
background: color-mix(in oklab, var(--color-accent) 12%, transparent);
box-shadow: none !important;
--tw-ring-shadow: 0 0 #0000 !important;
--tw-ring-offset-shadow: 0 0 #0000 !important;
--tw-ring-offset-width: 0px !important;
}
/* Soft left accent rail on focused row (matches sidebar language) */
.command-palette-item:focus::before,
.command-palette-item:focus-visible::before {
content: "";
position: absolute;
top: 6px;
bottom: 6px;
left: 0;
width: 2px;
border-radius: 9999px;
background: var(--color-accent);
pointer-events: none;
}
.command-palette-item-main {
min-width: 0;
flex: 1;
}
.command-palette-item-title {
display: flex;
min-width: 0;
flex-wrap: wrap;
align-items: center;
gap: 0.375rem 0.5rem;
}
.command-palette-item-name {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.8125rem;
font-weight: 500;
line-height: 1.25rem;
color: #171717;
}
.dark .command-palette-item-name {
color: var(--color-fg);
}
.command-palette-item:focus .command-palette-item-name,
.command-palette-item:focus-visible .command-palette-item-name {
color: #171717;
}
.dark .command-palette-item:focus .command-palette-item-name,
.dark .command-palette-item:focus-visible .command-palette-item-name {
color: var(--color-fg);
}
.command-palette-item-meta {
margin-top: 0.0625rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.6875rem;
line-height: 1rem;
color: var(--coollabs-subtle);
}
.command-palette-type-badge {
display: inline-flex;
flex-shrink: 0;
align-items: center;
height: 1.25rem;
padding: 0 0.4375rem;
border-radius: 9999px;
background: var(--coollabs-recessed);
color: var(--coollabs-subtle);
font-size: 0.625rem;
font-weight: 500;
line-height: 1;
}
.command-palette-item:focus .command-palette-type-badge,
.command-palette-item:focus-visible .command-palette-type-badge {
background: color-mix(in oklab, var(--color-accent) 14%, transparent);
color: var(--color-accent);
}
/* Shortcut hint chip next to create action titles */
.command-palette-quickcommand {
display: inline-flex;
flex-shrink: 0;
align-items: center;
height: 1.25rem;
padding: 0 0.375rem;
border-radius: 0.25rem;
background: var(--coollabs-recessed);
color: var(--coollabs-subtle);
font-size: 0.625rem;
font-weight: 500;
line-height: 1;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.command-palette-item:focus .command-palette-quickcommand,
.command-palette-item:focus-visible .command-palette-quickcommand {
background: color-mix(in oklab, var(--color-accent) 12%, transparent);
color: color-mix(in oklab, var(--color-accent) 85%, var(--coollabs-subtle));
}
/* Leading icon tile — neutral by default; accent only when the row is active */
.command-palette-item-icon {
display: flex;
width: 1.75rem;
height: 1.75rem;
flex-shrink: 0;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 6px;
background: var(--coollabs-recessed);
box-shadow: inset 0 0 0 1px var(--coollabs-fill);
color: var(--coollabs-subtle);
transition: background 0.1s ease, color 0.1s ease, box-shadow 0.1s ease;
}
.command-palette-item-icon img {
width: 1.125rem;
height: 1.125rem;
object-fit: contain;
}
.command-palette-item-icon.is-create {
background: var(--coollabs-recessed);
color: var(--coollabs-subtle);
box-shadow: inset 0 0 0 1px var(--coollabs-fill);
}
.command-palette-item-icon.is-create svg {
width: 0.875rem;
height: 0.875rem;
}
.command-palette-item:hover .command-palette-item-icon.is-create {
color: #171717;
}
.dark .command-palette-item:hover .command-palette-item-icon.is-create {
color: var(--color-fg);
}
.command-palette-item:focus .command-palette-item-icon,
.command-palette-item:focus-visible .command-palette-item-icon,
.command-palette-item:focus .command-palette-item-icon.is-create,
.command-palette-item:focus-visible .command-palette-item-icon.is-create {
background: color-mix(in oklab, var(--color-accent) 16%, transparent);
color: var(--color-accent);
box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--color-accent) 28%, transparent);
}
.command-palette-item-chevron {
width: 0.875rem;
height: 0.875rem;
flex-shrink: 0;
color: var(--coollabs-subtle);
opacity: 0.4;
transition: opacity 0.1s ease, color 0.1s ease;
}
.command-palette-item:hover .command-palette-item-chevron {
opacity: 0.7;
}
.command-palette-item:focus .command-palette-item-chevron,
.command-palette-item:focus-visible .command-palette-item-chevron {
opacity: 1;
color: var(--color-accent);
}
.command-palette-step-header {
display: flex;
align-items: center;
gap: 0.625rem;
margin-bottom: 0.125rem;
padding: 0.25rem 0.375rem;
}
.command-palette-step-back {
display: inline-flex;
width: 1.75rem;
height: 1.75rem;
flex-shrink: 0;
align-items: center;
justify-content: center;
border-radius: 0.375rem;
color: var(--coollabs-subtle);
transition: background 0.1s ease, color 0.1s ease;
}
.command-palette-step-back:hover {
background: var(--coollabs-recessed);
color: #171717;
}
.dark .command-palette-step-back:hover {
color: var(--color-fg);
}
.command-palette-step-title {
font-size: 0.8125rem;
font-weight: 500;
line-height: 1.25rem;
color: #171717;
}
.dark .command-palette-step-title {
color: var(--color-fg);
}
.command-palette-step-subtitle {
font-size: 0.6875rem;
line-height: 1rem;
color: var(--coollabs-subtle);
}
.command-palette-status {
display: flex;
align-items: center;
gap: 0.625rem;
margin: 0.125rem 0;
padding: 0.625rem 0.75rem;
border-radius: 6px;
background: var(--coollabs-recessed);
color: var(--coollabs-subtle);
font-size: 0.75rem;
line-height: 1rem;
}
.command-palette-status.is-error {
background: color-mix(in oklab, #ef4444 10%, transparent);
color: #b91c1c;
}
.dark .command-palette-status.is-error {
color: #fca5a5;
}
.command-palette-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.25rem;
padding: 2rem 1.25rem;
text-align: center;
}
.command-palette-empty-title {
font-size: 0.8125rem;
font-weight: 500;
color: #171717;
}
.dark .command-palette-empty-title {
color: var(--color-fg);
}
.command-palette-empty-desc {
max-width: 28ch;
font-size: 0.75rem;
line-height: 1.125rem;
color: var(--coollabs-subtle);
}
.command-palette-arch-badge {
display: inline-flex;
flex-shrink: 0;
align-items: center;
height: 1.25rem;
padding: 0 0.4375rem;
border-radius: 9999px;
background: color-mix(in oklab, #f59e0b 16%, transparent);
color: #b45309;
font-size: 0.625rem;
font-weight: 500;
line-height: 1;
}
.dark .command-palette-arch-badge {
color: #fcd34d;
}
/* Service domains prioritize public addresses; configuration lives in settings. */
#service-domains-section,
.domains-overview-container {
container: service-domains / inline-size;
}
.service-domains-overview-grid {
grid-template-columns: minmax(0, 1fr) 7.25rem 7.5rem 5.5rem 6.5rem 8rem 6.5rem;
column-gap: 0.75rem;
}
.data-table-row.service-domains-overview-grid {
padding-block: 0.5rem;
}
.service-domain-detail {
display: flex;
align-items: center;
justify-content: center;
min-width: 0;
font-size: 12px;
}
.service-domains-overview-grid > span:not(:first-child):not(:last-child) {
text-align: center;
}
.service-domain-detail-label {
display: none;
}
.service-domain-mobile-summary {
display: none;
}
.service-domains-https .listbox-trigger {
min-width: 7rem;
}
@container service-domains (max-width: 980px) {
.data-table-header.service-domains-overview-grid {
display: none;
}
.data-table-row.service-domains-overview-grid {
grid-template-columns: minmax(0, 1fr) auto auto;
grid-template-areas: "domain dns actions" "summary summary summary";
gap: 0.625rem 0.75rem;
padding: 0.75rem;
}
.data-table-row.service-domains-overview-grid > :first-child {
grid-area: domain;
}
.service-domain-detail,
.domains-service-desktop {
display: none;
}
.service-domain-mobile-summary {
display: flex;
grid-area: summary;
flex-wrap: wrap;
align-items: center;
gap: 0.375rem 0.75rem;
color: var(--coollabs-fg-dim);
font-size: 12px;
line-height: 1.25rem;
}
.service-domain-mobile-summary > span:not(:last-child)::after {
margin-left: 0.75rem;
color: var(--coollabs-line);
content: "·";
}
.service-domain-dns {
grid-area: dns;
justify-self: end;
}
.service-domain-actions {
grid-area: actions;
justify-self: end;
}
}
@container service-domains (max-width: 600px) {
.data-table-row.service-domains-overview-grid {
grid-template-columns: minmax(0, 1fr) auto;
grid-template-areas: "domain domain" "summary summary" "dns actions";
gap: 0.625rem 0.75rem;
padding: 0.875rem;
}
.data-table-row.service-domains-overview-grid > :first-child {
grid-area: domain;
}
.data-table-row.service-domains-overview-grid > :first-child a,
.data-table-row.service-domains-overview-grid > :first-child span[title] {
overflow: visible;
white-space: normal;
overflow-wrap: anywhere;
line-height: 1.35;
}
.service-domain-dns {
grid-area: dns;
justify-self: start;
}
.service-domain-actions {
grid-area: actions;
justify-self: end;
}
.service-domain-actions .icon-button {
width: 2.5rem;
height: 2.5rem;
}
}
/* Navigation items: each sidebar icon sits in a framed 30px tile with the 3D
depth of .button, like the canvas tool rail. Only the tile reacts; the row
has no background. Applies to both navigation styles. Appearance →
Style → Flat sets data-nav-items="flat" on <html> and turns these
rules off, which brings back the plain rows (see also the .button rules). */
html:where(:not([data-nav-items="flat"])) .menu-item:not(.sidebar-toggle) {
height: 2.25rem;
padding-left: 0.25rem;
}
/* In-page section rows have no icon tile, so they stay compact. */
html:where(:not([data-nav-items="flat"])) .menu-subitem {
height: 1.75rem;
}
/* Only the icon tile reacts: no row background on hover or for the current
item; the current item shows through its tile and its brighter label.
In the utilities layer so it can beat the search row's !bg-* utilities
(a layered !important wins over an unlayered one). */
@layer utilities {
/* The submenu Back row (navbar) keeps its fill. */
html:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle, .menu-back),
html:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle, .menu-back):hover {
background-color: transparent !important;
}
/* The expanded Search field keeps its input fill (see .menu-search below). */
html:where(:not([data-nav-items="flat"])) nav:not(.sidebar-collapsed) .menu-search.menu-item {
background-color: var(--color-neutral-100) !important;
}
html:where(:not([data-nav-items="flat"])) nav:not(.sidebar-collapsed) .menu-search.menu-item:hover {
background-color: var(--color-neutral-200) !important;
}
html.dark:where(:not([data-nav-items="flat"])) nav:not(.sidebar-collapsed) .menu-search.menu-item {
background-color: oklch(100% 0 0 / 0.04) !important;
}
html.dark:where(:not([data-nav-items="flat"])) nav:not(.sidebar-collapsed) .menu-search.menu-item:hover {
background-color: oklch(100% 0 0 / 0.07) !important;
}
/* The collapsed desktop rail shows Search as a tile only, without the row fill. */
html .app-sidebar nav.sidebar-collapsed .menu-search.menu-item,
html .app-sidebar nav.sidebar-collapsed .menu-search.menu-item:hover {
background-color: transparent !important;
}
@media (max-width: 1023.98px) {
html:where(:not([data-nav-items="flat"])) nav.sidebar-collapsed .menu-search.menu-item {
background-color: var(--color-neutral-100) !important;
}
html.dark:where(:not([data-nav-items="flat"])) nav.sidebar-collapsed .menu-search.menu-item {
background-color: oklch(100% 0 0 / 0.04) !important;
}
}
}
/* The current item's tile takes the accent, like the active tab pill. */
html:where(:not([data-nav-items="flat"])) :is(.menu-item-active, .menu-subitem-active) .menu-item-icon {
color: var(--color-coollabs);
border-color: color-mix(in oklab, var(--color-coollabs) 45%, transparent) !important;
--button-depth-color: color-mix(in oklab, var(--color-coollabs) 45%, transparent) !important;
}
html.dark:where(:not([data-nav-items="flat"])) :is(.menu-item-active, .menu-subitem-active) .menu-item-icon:not(.text-error) {
color: var(--color-brand-foreground);
border-color: var(--color-brand-ring) !important;
background: linear-gradient(180deg, var(--color-brand-hover) 0%, var(--color-brand-press) 100%) !important;
--button-depth-color: var(--color-brand-depth) !important;
box-shadow: 0 2px 0 var(--color-brand-depth) !important;
}
html.dark:where(:not([data-nav-items="flat"])) :is(.menu-item-active, .menu-subitem-active):active .menu-item-icon:not(.text-error) {
transform: translateY(2px);
box-shadow: none !important;
}
/* Danger Zone icons (<x-reicon name="danger">) stay red, also when active. */
html:where(:not([data-nav-items="flat"])) :is(.menu-item-active, .menu-subitem-active) .menu-item-icon.text-error {
color: var(--color-error);
border-color: color-mix(in oklab, var(--color-error) 45%, transparent) !important;
--button-depth-color: color-mix(in oklab, var(--color-error) 40%, transparent) !important;
}
/* In dark mode the Danger Zone tile is red-tinted, not the white or purple tile of other items. */
html.dark:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle) .menu-item-icon.text-error {
color: var(--color-error) !important;
border-color: color-mix(in oklab, var(--color-error) 25%, transparent) !important;
background: color-mix(in oklab, var(--color-error) 8%, transparent) !important;
--button-depth-color: color-mix(in oklab, var(--color-error) 20%, transparent) !important;
}
html.dark:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):is(:hover, .menu-item-active, .menu-subitem-active) .menu-item-icon.text-error {
border-color: color-mix(in oklab, var(--color-error) 45%, transparent) !important;
background: color-mix(in oklab, var(--color-error) 15%, transparent) !important;
--button-depth-color: color-mix(in oklab, var(--color-error) 35%, transparent) !important;
}
html:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):focus-visible {
outline: none;
}
html:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):focus-visible .menu-item-icon {
outline: 1px solid var(--color-accent);
outline-offset: 2px;
}
/* Sidebar menu swap: a deeper menu (submenu, resource menu) enters from the right, a lower one from the left.
CSS animations restart when an element is shown again, so x-show replays them on each swap. */
@keyframes nav-swap-forward {
from {
opacity: 0;
transform: translateX(0.5rem);
}
}
@keyframes nav-swap-back {
from {
opacity: 0;
transform: translateX(-0.5rem);
}
}
.nav-swap-forward {
animation: nav-swap-forward 150ms ease-out;
}
.nav-swap-back {
animation: nav-swap-back 150ms ease-out;
}
@media (prefers-reduced-motion: reduce) {
.nav-swap-forward,
.nav-swap-back {
animation: none;
}
}
/* Submenu Back chevron box: one icon wide (flat), or one icon tile wide (16px + padding + border). */
.menu-back-icon {
width: 1rem;
height: 1rem;
}
html:where(:not([data-nav-items="flat"])) .menu-back-icon {
width: calc(1rem + 0.75rem + 2px);
}
html:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle) .menu-item-icon {
box-sizing: content-box;
width: 1rem;
height: 1rem;
padding: 0.375rem;
border: 1px solid oklch(0% 0 0 / 0.09);
border-radius: 8px;
background: #fafafa;
opacity: 1;
/* Same 3D depth as .button: rests on a 2px ledge, lifts on hover, presses on click. */
--button-depth-color: rgb(0 0 0 / 0.22);
box-shadow: 0 2px 0 var(--button-depth-color);
transition: border-color 120ms, background-color 120ms, box-shadow 80ms, transform 80ms;
}
html.dark:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle) .menu-item-icon {
--button-depth-color: rgb(255 255 255 / 0.08);
}
html:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):hover .menu-item-icon {
transform: translateY(-1px);
box-shadow: 0 3px 0 var(--button-depth-color);
}
html:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):active .menu-item-icon {
transform: translateY(2px);
box-shadow: none;
}
html.dark:where(:not([data-nav-items="flat"])) :is(.app-sidebar, .mobile-sidebar-sheet) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):not(.menu-search):not(.menu-item-active):not(.menu-subitem-active) .menu-item-icon {
border-color: rgb(255 255 255 / 0.07);
background: rgb(255 255 255 / 0.03);
color: oklch(72% 0 0);
--button-depth-color: rgb(255 255 255 / 0.05);
}
html:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):hover .menu-item-icon,
html:where(:not([data-nav-items="flat"])) :is(.menu-item-active, .menu-subitem-active) .menu-item-icon {
border-color: oklch(0% 0 0 / 0.18);
background: #fff;
}
html.dark:where(:not([data-nav-items="flat"])) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):not(.menu-item-active):not(.menu-subitem-active):hover .menu-item-icon {
border-color: rgb(255 255 255 / 0.14);
background: rgb(255 255 255 / 0.06);
color: #fff;
}
/* Search keeps its original input look while the sidebar is expanded (always on
mobile, where the rail never collapses). Only the collapsed rail shows the 3D tile.
The icon resets use .menu-search.menu-item to beat the html.dark tile rules. */
html:where(:not([data-nav-items="flat"])) nav:not(.sidebar-collapsed) .menu-search {
height: 2rem;
padding-left: 0.625rem;
}
html:where(:not([data-nav-items="flat"])) nav:not(.sidebar-collapsed) .menu-search.menu-item .menu-item-icon,
html:where(:not([data-nav-items="flat"])) nav:not(.sidebar-collapsed) .menu-search.menu-item:is(:hover, :active, :focus-visible) .menu-item-icon {
box-sizing: border-box;
width: 1rem;
height: 1rem;
padding: 0;
border: 0;
background: transparent;
box-shadow: none;
transform: none;
outline: none;
opacity: 0.9;
}
html:where(:not([data-nav-items="flat"])) nav:not(.sidebar-collapsed) .menu-search:focus-visible {
outline: 1px solid var(--color-accent);
}
@media (max-width: 1023.98px) {
html:where(:not([data-nav-items="flat"])) nav.sidebar-collapsed .menu-search {
height: 2rem;
padding-left: 0.625rem;
}
html:where(:not([data-nav-items="flat"])) nav.sidebar-collapsed .menu-search.menu-item .menu-item-icon,
html:where(:not([data-nav-items="flat"])) nav.sidebar-collapsed .menu-search.menu-item:is(:hover, :active, :focus-visible) .menu-item-icon {
box-sizing: border-box;
width: 1rem;
height: 1rem;
padding: 0;
border: 0;
background: transparent;
box-shadow: none;
transform: none;
outline: none;
opacity: 0.9;
}
}
@media (min-width: 1024px) {
/* The collapsed rail is 64px wide with 40px of content: center the 30px tile. */
html:where(:not([data-nav-items="flat"])) .sidebar-collapsed .menu-item:not(.sidebar-toggle) {
padding-left: 0.3125rem;
padding-right: 0.3125rem;
}
}
/* Secondary navigation (settings rails): text-only rows with a filled pill for
the current item, like Sentry. Only the main sidebar keeps the icon tiles. */
html .application-settings-navigation :is(.menu-item, .menu-subitem) {
height: 2rem;
padding-left: 0.625rem;
}
html .application-settings-navigation .menu-item-icon {
display: none;
}
/* In the utilities layer to beat the transparent row rule above. */
@layer utilities {
html .application-settings-navigation :is(.menu-item, .menu-subitem):hover {
background-color: oklch(0% 0 0 / 0.04) !important;
}
html.dark .application-settings-navigation :is(.menu-item, .menu-subitem):hover {
background-color: oklch(100% 0 0 / 0.05) !important;
}
html .application-settings-navigation :is(.menu-item-active, .menu-subitem-active),
html .application-settings-navigation :is(.menu-item-active, .menu-subitem-active):hover {
background-color: oklch(0% 0 0 / 0.06) !important;
}
html.dark .application-settings-navigation :is(.menu-item-active, .menu-subitem-active),
html.dark .application-settings-navigation :is(.menu-item-active, .menu-subitem-active):hover {
background-color: oklch(100% 0 0 / 0.08) !important;
}
}
/* setCanvasDots() writes these on the canvas root on every pan and zoom step, and
only the root reads them. Inherited, each write restyles every card in the canvas. */
@property --canvas-dots-size {
syntax: '*';
inherits: false;
}
@property --canvas-dots-position {
syntax: '*';
inherits: false;
}
/* Canvases at full zoom: the grid dots turn into small Coolify logos, one
per 20px tile. setCanvasDots() (environment-canvas.js) sets .canvas-zoom-max. */
.canvas-zoom-max {
--canvas-dots-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath transform='translate(8 8) scale(0.0125)' d='M64 256H0V64h64Zm0-192V0h256v64Zm0 192h256v64H64Z' fill='rgb(0 0 0 / 0.13)'/%3E%3C/svg%3E");
}
html.dark .canvas-zoom-max {
--canvas-dots-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath transform='translate(8 8) scale(0.0125)' d='M64 256H0V64h64Zm0-192V0h256v64Zm0 192h256v64H64Z' fill='rgb(255 255 255 / 0.09)'/%3E%3C/svg%3E");
}
.canvas-zoom-max [data-ec-viewport] {
background-image: var(--canvas-dots-image);
}
/* A pan (canvas-core panZoom), also a middle-button pan that started on a card. */
:is([data-ec-viewport], .cv-viewport).panning,
:is([data-ec-viewport], .cv-viewport).panning * {
cursor: grabbing !important;
}
/* "Floating" navigation (Appearance → Navigation style): the top bar, sidebar,
tab strip, and settings rail float as framed panels over a dotted backdrop,
like the canvas islands. Set on <html> by applyStoredTheme() from
localStorage.navStyle; the default style keeps edge-to-edge bars. */
/* Width of the collapsed desktop sidebar and the brand cell above it. */
html {
--sidebar-collapsed-w: 4rem;
}
@media (min-width: 1024px) {
html[data-nav-style="floating"] {
/* The framed rail needs no edge spacing: 1px border + 5px padding + 40px content. */
--sidebar-collapsed-w: 3.25rem;
--floating-gap: 0.5rem;
--floating-radius: 8px;
--floating-line: oklch(0% 0 0 / 0.09);
/* Bottom edge of the top bar panel plus one gap. */
--floating-top: calc(3rem + var(--floating-gap) * 2);
}
html.dark[data-nav-style="floating"] {
--floating-line: oklch(100% 0 0 / 0.08);
--floating-dot: rgb(255 255 255 / 0.09);
}
/* Dotted backdrop behind the floating panels and in the gaps between them,
like the canvas stage. Fixed so the dots stay still while content scrolls. */
html[data-nav-style="floating"] .app-shell {
background-image: radial-gradient(circle, var(--floating-dot, rgb(0 0 0 / 0.13)) 1px, transparent 1.3px);
background-size: 20px 20px;
background-attachment: fixed;
}
html[data-nav-style="floating"] .app-main {
background-color: transparent;
}
html[data-nav-style="floating"] .app-topbar {
background-color: #fff;
}
html.dark[data-nav-style="floating"] .app-topbar {
background-color: var(--color-panel);
}
html[data-nav-style="floating"] :is(.app-topbar, .app-sidebar > div > nav) {
border: 1px solid var(--floating-line);
border-radius: var(--floating-radius);
}
/* The top bar hugs its content (brand, breadcrumb, docked actions)
instead of spanning an empty full-width panel. */
html[data-nav-style="floating"] .app-topbar {
top: var(--floating-gap);
left: var(--floating-gap);
right: auto;
max-width: calc(100vw - var(--floating-gap) * 2);
}
/* The bar is opaque here, so it needs no blur. Without the backdrop filter
the bar is no longer the containing block for fixed children, which lets
the resource actions panel below sit against the screen edge. */
html[data-nav-style="floating"] .app-topbar {
backdrop-filter: none;
}
/* The panel border replaces the inner dividers of the top bar. */
html[data-nav-style="floating"] .app-topbar > div {
border-bottom-width: 0;
}
html[data-nav-style="floating"] .app-topbar > .app-topbar-brand {
border-radius: calc(var(--floating-radius) - 1px) 0 0 calc(var(--floating-radius) - 1px);
}
html[data-nav-style="floating"] .app-sidebar {
top: var(--floating-top);
bottom: var(--floating-gap);
left: var(--floating-gap);
}
html[data-nav-style="floating"] .app-sidebar > div > nav > .sidebar-footer {
border-radius: 0 0 var(--floating-radius) var(--floating-radius);
}
html[data-nav-style="floating"] .app-sidebar nav.sidebar-collapsed {
padding-left: 0.3125rem;
padding-right: 0.3125rem;
}
html[data-nav-style="floating"] .app-sidebar nav.sidebar-collapsed > .sidebar-footer {
margin-left: -0.3125rem;
margin-right: -0.3125rem;
padding-left: 0.3125rem;
padding-right: 0.3125rem;
}
html[data-nav-style="floating"] .app-main {
margin-left: calc(var(--sidebar-w, 14rem) + var(--floating-gap));
/* The content top lines up with the sidebar and settings rail panels.
The cards draw their frame as a 1px ring outside the box, while the
panels use a 1px border inside it, so add 1px to match the edges. */
padding-top: calc(var(--floating-top) + 1px);
}
/* Full-bleed pages (canvases): no top padding gap, and the same gap as the panels. */
html[data-nav-style="floating"] .app-main.app-main-full {
margin-left: calc(var(--sidebar-w, 14rem) + var(--floating-gap) * 2);
padding: var(--floating-top) var(--floating-gap) var(--floating-gap) 0;
}
/* Canvases fill the space under the top bar too, and the canvas root
paints the canvas dots (set by the canvas JS) behind the top bar, so
the dots are the same there and on the canvas. */
html[data-nav-style="floating"] .app-main.app-main-full:has(:is([data-ec-root], .cv-root)) {
padding-top: 0;
}
/* The resource canvases fill the whole screen behind the floating panels
(top bar, toolbar, sidebar), so the panels do not cut off the cards. */
html[data-nav-style="floating"] .app-main.app-main-full:has([data-ec-root]) {
margin-left: 0;
padding: 0;
}
html[data-nav-style="floating"] .app-main-full [data-ec-root] {
padding: 0 !important;
}
html[data-nav-style="floating"] .app-main-full :is([data-ec-root], .cv-root) {
padding-top: calc(var(--floating-top) + 0.5rem);
background-image: var(--canvas-dots-image, radial-gradient(circle, var(--floating-dot, rgb(0 0 0 / 0.13)) 1px, transparent 1px));
background-size: var(--canvas-dots-size, 20px 20px);
background-position: var(--canvas-dots-position, 0 0);
}
/* The deploy canvas Steps panel starts at the same height as the sidebar. */
html[data-nav-style="floating"] .app-main-full .cv-root {
padding-top: var(--floating-top);
}
html[data-nav-style="floating"] .app-main-full [data-ec-viewport] {
background-image: none;
}
/* Full page width: the content is one gap from the sidebar and the screen edge. */
html[data-nav-style="floating"] .app-main.app-main-wide {
padding-right: var(--floating-gap);
padding-left: var(--floating-gap);
padding-bottom: var(--floating-gap);
}
/* Dashboard canvas Deployments panel: one gap from the floating sidebar and the screen bottom. */
/* Bottom-left zoom controls and deployments: the canvas fills the screen behind the sidebar, so start one gap after it. */
html[data-nav-style="floating"] .app-main-full [data-ec-dock] {
left: calc(var(--sidebar-w, 14rem) + var(--floating-gap) * 2);
bottom: var(--floating-gap);
}
}
@media (min-width: 1280px) {
/* Resource actions (Links, Deploy, …) float as their own panel at the top
right. The top bar panel stops one gap before it (the header publishes
--resource-actions-w). */
html[data-nav-style="floating"] #resource-action-hud-slot:not(:empty) {
position: fixed;
top: var(--floating-gap);
right: var(--floating-gap);
height: 3rem;
padding: 0 0.5rem;
border: 1px solid var(--floating-line);
border-radius: var(--floating-radius);
background-color: #fff;
}
html.dark[data-nav-style="floating"] #resource-action-hud-slot:not(:empty) {
background-color: var(--color-panel);
}
html[data-nav-style="floating"] .app-topbar:has(#resource-action-hud-slot:not(:empty)) {
max-width: calc(100vw - var(--floating-gap) * 3 - var(--resource-actions-w, 0px));
}
html[data-nav-style="floating"] .application-settings-navigation {
top: var(--floating-top);
left: calc(var(--sidebar-w, 14rem) + var(--floating-gap) * 2);
/* Hug the menu items; long menus scroll inside the viewport. */
height: auto;
max-height: calc(100dvh - var(--floating-top) - var(--floating-gap));
border: 1px solid var(--floating-line);
border-radius: var(--floating-radius);
}
/* Full page width: the content starts one gap after the settings rail. */
html[data-nav-style="floating"] .app-main-wide :has(> .application-settings-navigation) {
grid-template-columns: calc(210px + 2.5rem) minmax(0, 1fr);
column-gap: var(--floating-gap);
}
/* The canvas fills the screen behind the rail too; the canvas JS keeps cards clear of it. */
html[data-nav-style="floating"] .app-main-full .application-settings-navigation ~ [data-environment-canvas] {
margin-left: 0;
}
}
/* Brand shell (dark): black canvas and top bar, sidebar, scroll hairline.
The default dark theme tints the sidebar purple (below); a custom theme gets
these neutral rules. Light mode keeps the neutral shell. Floating navigation
keeps its own frame. */
html.dark .app-sidebar nav.sidebar-nav,
html.dark .mobile-sidebar-sheet [role="dialog"] {
background-color: var(--color-sidebar);
border-right-color: rgb(255 255 255 / 0.06);
}
/* Same color as the sidebar, so the brand and sidebar read as one surface. */
html.dark .app-topbar-brand {
background-color: var(--color-sidebar);
border-right-color: rgb(255 255 255 / 0.06);
}
/* The drawer carries the brand surface; its nav must not paint over it. */
html.dark .mobile-sidebar-sheet nav.sidebar-nav {
background-color: transparent;
}
html.dark :is(.app-sidebar, .mobile-sidebar-sheet) .sidebar-footer {
background-color: transparent;
}
html.dark :is(.app-sidebar, .mobile-sidebar-sheet) .menu-search {
box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.06) !important;
}
/* The account button is a .button, which is purple in dark mode; keep it neutral in a custom theme sidebar. */
html.dark[data-theme="custom"] :is(.app-sidebar, .mobile-sidebar-sheet) .sidebar-footer .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled) {
--button-depth-color: rgb(255 255 255 / 0.05);
background-color: rgb(255 255 255 / 0.04);
border-color: rgb(255 255 255 / 0.08);
color: var(--color-nav-text);
}
html.dark[data-theme="custom"] :is(.app-sidebar, .mobile-sidebar-sheet) .sidebar-footer .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled):hover {
background-color: rgb(255 255 255 / 0.07);
border-color: rgb(255 255 255 / 0.12);
}
html.dark[data-theme="custom"] :is(.app-sidebar, .mobile-sidebar-sheet) .sidebar-footer .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled) svg {
color: var(--color-nav-muted);
}
/* The collapsed rail shows Search as a tile like the other items: no row frame (the fill is
removed in the utilities layer above), the dark tile. */
html.dark .app-sidebar nav.sidebar-collapsed .menu-search {
box-shadow: none !important;
}
html.dark:where(:not([data-nav-items="flat"])) .app-sidebar nav.sidebar-collapsed .menu-search.menu-item .menu-item-icon {
border-color: rgb(255 255 255 / 0.07);
background: rgb(255 255 255 / 0.03);
color: oklch(72% 0 0);
--button-depth-color: rgb(255 255 255 / 0.05);
}
html.dark :is(.app-sidebar, .mobile-sidebar-sheet) nav:not(.sidebar-collapsed) .nav-section {
font-family: var(--font-mono);
font-size: 10.5px;
font-weight: 500;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--color-nav-muted);
opacity: 1;
}
html.dark .app-sidebar .nav-chevron {
transition: opacity 120ms ease;
}
html.dark .app-sidebar .menu-item:not(:hover):not(:focus-visible):not(.menu-item-active) > .nav-chevron {
opacity: 0;
}
/* Default dark theme sidebar: purple-black with a faint brand wash and lavender
tiles, text, and edges. A custom theme keeps the neutral rules above. */
html.dark:not([data-theme="custom"]) .app-sidebar nav.sidebar-nav,
html.dark:not([data-theme="custom"]) .mobile-sidebar-sheet [role="dialog"] {
background-image: linear-gradient(180deg, rgb(107 22 237 / 0.09), transparent 520px);
border-right-color: rgb(163 145 255 / 0.10);
--color-nav-text: oklch(80% 0.02 300);
--color-nav-muted: oklch(58% 0.04 300);
}
/* Same color as the top of the sidebar gradient, so the brand and sidebar read as one surface. */
html.dark:not([data-theme="custom"]) .app-topbar-brand {
background-image: linear-gradient(rgb(107 22 237 / 0.09), rgb(107 22 237 / 0.09));
border-right-color: rgb(163 145 255 / 0.10);
}
html.dark:not([data-theme="custom"]) :is(.app-sidebar, .mobile-sidebar-sheet) .menu-search {
background-color: rgb(163 145 255 / 0.06) !important;
box-shadow: inset 0 0 0 1px rgb(163 145 255 / 0.10) !important;
}
html.dark:not([data-theme="custom"]) :is(.app-sidebar, .mobile-sidebar-sheet) .menu-search:hover {
background-color: rgb(163 145 255 / 0.10) !important;
}
html.dark:not([data-theme="custom"]) :is(.app-sidebar, .mobile-sidebar-sheet) .menu-search kbd {
border-color: rgb(163 145 255 / 0.18) !important;
}
/* Group dividers use the lavender sidebar edge: the neutral hairline looks olive on the purple-black sidebar. */
html.dark:not([data-theme="custom"]) :is(.app-sidebar, .mobile-sidebar-sheet) .sidebar-nav :is([class~="dark:border-white/[0.06]"], [class~="dark:border-white/[0.07]"], [class~="dark:border-white/[0.08]"]) {
border-color: rgb(163 145 255 / 0.10) !important;
}
/* Admin keeps the neutral tile unless it is the current page. */
html.dark:not([data-theme="custom"]):where(:not([data-nav-items="flat"])) :is(.app-sidebar, .mobile-sidebar-sheet) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):not(.menu-search):not(.nav-admin):not(.menu-item-active):not(.menu-subitem-active) .menu-item-icon,
html.dark:not([data-theme="custom"]):where(:not([data-nav-items="flat"])) .app-sidebar nav.sidebar-collapsed .menu-search.menu-item .menu-item-icon {
border-color: rgb(163 145 255 / 0.13);
background: rgb(107 22 237 / 0.07);
color: #b9adff;
}
html.dark:not([data-theme="custom"]) :is(.app-sidebar, .mobile-sidebar-sheet) nav:not(.sidebar-collapsed) .nav-section {
color: rgb(163 145 255 / 0.55);
}
/* Sidebar account menu: the purple tint of the sidebar it opens from. */
html.dark:not([data-theme="custom"]) .listbox-panel.top-user-menu-panel-sidebar {
background: color-mix(in oklab, #6b16ed 10%, var(--coollabs-elevated));
border-color: rgb(163 145 255 / 0.22);
}
html.dark:not([data-theme="custom"]) .top-user-menu-panel-sidebar .listbox-option:hover {
background: rgb(163 145 255 / 0.10);
}
/* Custom theme sidebar. These rules come after the dark sidebar rules above, so they win. */
/* !important: the light custom theme maps bg-neutral-50 to the canvas with !important. */
html[data-theme="custom"] :is(.app-sidebar nav.sidebar-nav, .app-topbar-brand, .mobile-sidebar-sheet [role="dialog"]) {
background-color: var(--theme-sidebar) !important;
}
html[data-theme="custom"] .mobile-sidebar-sheet nav.sidebar-nav {
background-color: transparent !important;
}
/* The current page in the sidebar uses the Selected color. */
html[data-theme="custom"] :is(.app-sidebar, .mobile-sidebar-sheet) :is(.menu-item-active, .menu-subitem-active) {
background-color: var(--theme-selected);
color: var(--theme-selected-text);
}
/* The 3D style shows the current page as an accent tile, like the Dark theme, on a light or dark sidebar. */
html[data-theme="custom"]:where(:not([data-nav-items="flat"])) :is(.app-sidebar, .mobile-sidebar-sheet) :is(.menu-item-active, .menu-subitem-active) .menu-item-icon:not(.text-error) {
color: var(--color-brand-foreground);
border-color: var(--color-brand-ring) !important;
background: linear-gradient(180deg, var(--color-brand-hover) 0%, var(--color-brand-press) 100%) !important;
--button-depth-color: var(--color-brand-depth) !important;
box-shadow: 0 2px 0 var(--color-brand-depth) !important;
}
/* 3D icon tiles and the search field take their colors from the sidebar text,
so they fit a light or a dark sidebar. */
html[data-theme="custom"]:where(:not([data-nav-items="flat"])) :is(.app-sidebar, .mobile-sidebar-sheet) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):not(.menu-search):not(.menu-item-active):not(.menu-subitem-active) .menu-item-icon {
border-color: color-mix(in oklab, var(--t-text) 15%, transparent);
background: color-mix(in oklab, var(--t-text) 5%, transparent);
color: var(--color-nav-text);
--button-depth-color: color-mix(in oklab, var(--t-text) 12%, transparent);
}
html[data-theme="custom"]:where(:not([data-nav-items="flat"])) :is(.app-sidebar, .mobile-sidebar-sheet) :is(.menu-item, .menu-subitem):not(.sidebar-toggle):not(.menu-search):not(.menu-item-active):not(.menu-subitem-active):hover .menu-item-icon {
border-color: color-mix(in oklab, var(--t-text) 20%, transparent);
background: color-mix(in oklab, var(--t-text) 9%, transparent);
color: var(--color-nav-active);
}
@layer utilities {
html[data-theme="custom"]:where(:not([data-nav-items="flat"])) :is(.app-sidebar, .mobile-sidebar-sheet) nav:not(.sidebar-collapsed) .menu-search.menu-item {
background-color: color-mix(in oklab, var(--t-text) 6%, transparent) !important;
}
html[data-theme="custom"]:where(:not([data-nav-items="flat"])) :is(.app-sidebar, .mobile-sidebar-sheet) nav:not(.sidebar-collapsed) .menu-search.menu-item:hover {
background-color: color-mix(in oklab, var(--t-text) 10%, transparent) !important;
}
}
html[data-theme="custom"] :is(.app-sidebar, .mobile-sidebar-sheet) .sidebar-footer .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled) {
--button-depth-color: color-mix(in oklab, var(--t-text) 8%, transparent);
background-color: color-mix(in oklab, var(--t-text) 5%, transparent);
border-color: color-mix(in oklab, var(--t-text) 12%, transparent);
color: var(--color-nav-text);
}
html[data-theme="custom"] :is(.app-sidebar, .mobile-sidebar-sheet) .sidebar-footer .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled):hover {
background-color: color-mix(in oklab, var(--t-text) 9%, transparent);
border-color: color-mix(in oklab, var(--t-text) 18%, transparent);
}
html.dark:not([data-nav-style="floating"]) .app-topbar {
background-color: var(--color-app);
backdrop-filter: none;
transition: background-color 180ms ease;
}
html.dark:not([data-nav-style="floating"]) .app-topbar > div:not(.app-topbar-brand) {
border-bottom-color: transparent;
transition: border-color 180ms ease, box-shadow 180ms ease, background-color 180ms ease;
}
html.dark.is-scrolled:not([data-nav-style="floating"]) .app-topbar {
background-color: color-mix(in srgb, var(--color-app) 72%, transparent);
backdrop-filter: blur(12px) saturate(140%);
}
html.dark.is-scrolled:not([data-nav-style="floating"]) .app-topbar > .app-topbar-brand {
background-color: var(--color-sidebar);
}
html.dark.is-scrolled:not([data-nav-style="floating"]) .app-topbar > div:not(.app-topbar-brand) {
border-bottom-color: rgb(255 255 255 / 0.06);
box-shadow: 0 10px 30px -14px rgb(0 0 0 / 0.95);
}
/* Canvases (default navigation): the canvas runs under a clear top bar, and the
canvas root paints the dots (set by the canvas JS) behind it, like the
floating navigation does. */
@media (min-width: 1024px) {
html:not([data-nav-style="floating"]) .app-main.app-main-full:has(:is([data-ec-root], .cv-root)) {
padding-top: 0;
}
/* The deploy canvas Steps panel starts below the top bar, and its canvas
runs up under the top bar. */
html:not([data-nav-style="floating"]) .app-main-full .cv-root {
padding-top: calc(3rem + 0.5rem);
}
html:not([data-nav-style="floating"]) .app-main-full .cv-root.cv-docked .cv-stage {
margin-top: calc(-3rem - 0.5rem);
}
/* The resource canvases fill the space under the top bar, so the top bar
does not cut off the cards (fit keeps the cards below it). */
html:not([data-nav-style="floating"]) .app-main-full [data-ec-root] {
padding: 0 !important;
}
/* The deploy canvas (.cv-root) paints its own dots. */
html:not([data-nav-style="floating"]) .app-main-full [data-ec-root] {
background-image: var(--canvas-dots-image, radial-gradient(circle, rgb(0 0 0 / 0.13) 1px, transparent 1px));
background-size: var(--canvas-dots-size, 20px 20px);
background-position: var(--canvas-dots-position, 0 0);
}
html.dark:not([data-nav-style="floating"]) .app-main-full [data-ec-root] {
background-image: var(--canvas-dots-image, radial-gradient(circle, rgb(255 255 255 / 0.09) 1px, transparent 1px));
}
html:not([data-nav-style="floating"]) .app-main-full [data-ec-viewport] {
background-image: none;
}
/* A layout class, not html:has(): an html-level :has() restyles the whole
page on every DOM change, which made chart renders take seconds. */
html:not([data-nav-style="floating"]) .app-shell-canvas > .app-topbar {
background-color: transparent;
backdrop-filter: none;
}
html:not([data-nav-style="floating"]) .app-shell-canvas > .app-topbar > div:not(.app-topbar-brand) {
border-bottom-color: transparent;
}
}
html.dark .view-toggle {
gap: 2px;
padding: 2px;
background-color: color-mix(in srgb, var(--color-brand) 6%, transparent);
border-color: color-mix(in srgb, var(--color-brand-light) 16%, transparent);
}
html.dark .view-toggle :is(a, button) {
color: oklch(from var(--color-brand-light) 80% 0.02 h);
}
html.dark .view-toggle :is(a, button) svg {
color: var(--color-brand-light);
}
html.dark .view-toggle :is(a, button):not(.is-selected):hover {
background-color: color-mix(in srgb, var(--color-brand) 14%, transparent);
color: #fff;
}
html.dark .view-toggle > .is-selected {
background: linear-gradient(180deg, var(--color-brand-hover), var(--color-brand-press));
color: var(--color-brand-foreground);
box-shadow: 0 1px 0 var(--color-brand-depth), inset 0 0 0 1px var(--color-brand-ring);
}
html.dark .view-toggle > .is-selected svg {
color: var(--color-brand-foreground);
}
html.dark .view-toggle .bg-coollabs {
background-color: color-mix(in srgb, var(--color-brand-light) 18%, transparent);
color: oklch(from var(--color-brand-light) 87.5% 0.06 h);
}
/* Toasts: a faint edge that blends into the toast background. */
html.dark .toast-card {
box-shadow: 0 0 0 1px rgb(255 255 255 / 0.05), var(--shadow-dropdown);
}
/* Toast icon tile: the 3D depth of the sidebar tiles, tinted with the icon color of the toast type. */
.toast-icon {
--button-depth-color: color-mix(in oklab, currentColor 30%, transparent);
border: 1px solid color-mix(in oklab, currentColor 22%, transparent);
box-shadow: 0 2px 0 var(--button-depth-color);
}
/* Command palette: the purple brand tint of the dark shell. */
html.dark .command-palette {
background: color-mix(in oklab, var(--color-brand) 10%, var(--coollabs-elevated));
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-brand-light) 22%, transparent), var(--shadow-dropdown);
}
html.dark .command-palette-header {
background: transparent;
}
html.dark .command-palette-header-icon:not(.is-loading) {
color: var(--color-brand-light);
}
html.dark .command-palette-input::placeholder {
color: oklch(from var(--color-brand-light) 72% 0.05 h);
}
html.dark .command-palette-body {
background: color-mix(in oklab, var(--color-brand) 6%, var(--coollabs-base));
box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-brand-light) 12%, transparent);
}
html.dark .command-palette-item:is(:focus, :focus-visible) :is(.command-palette-quickcommand, .command-palette-type-badge, .command-palette-item-chevron, .command-palette-item-icon) {
color: #fff;
}
html.dark .resource-glyph {
border-color: color-mix(in srgb, var(--color-brand-light) 20%, transparent);
background: color-mix(in srgb, var(--color-brand) 10%, transparent);
color: var(--color-brand-light);
}
html.dark .card-icon-button {
color: var(--color-brand-light);
}
html.dark .card-icon-button:hover {
background-color: color-mix(in srgb, var(--color-brand) 16%, transparent);
color: #fff;
}
html.dark .app-main :is(article.group, a.group).rounded-xl {
background-color: var(--color-card) !important;
border-color: rgb(255 255 255 / 0.08) !important;
box-shadow: none;
}
html.dark .app-main :is(article.group, a.group).rounded-xl:hover {
border-color: color-mix(in srgb, var(--color-brand-light) 30%, transparent) !important;
box-shadow: none;
}
.project-card-stats {
color: oklch(40% 0 0);
font-size: 11.5px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.project-card-stats b {
color: oklch(20% 0 0);
font-weight: 600;
}
.project-card-stats-dot {
margin-inline: 5px;
color: oklch(70% 0 0);
}
html.dark .project-card-stats {
color: oklch(72% 0 0);
}
html.dark .project-card-stats b {
color: #f2f2f2;
}
html.dark .project-card-stats-dot {
color: oklch(45% 0 0);
}
.dark .application-settings-form :is(.input, select, textarea):focus,
.dark .application-settings-form :is(.input, select, textarea):focus-visible,
.dark .application-settings-workspace :is(.input, select, textarea):focus,
.dark .application-settings-workspace :is(.input, select, textarea):focus-visible {
border-color: var(--color-brand);
box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-brand) 25%, transparent) !important;
}
/* One page of results: the bar is noise. !important beats the flex utility and Alpine x-show. */
.pager[hidden] {
display: none !important;
}
/* Dark mode (both navigation styles): the floating dotted backdrop shows through
see-through surfaces (dark:bg-white/[x] with or without `!`, .button, …). Give the
outermost ones the same color as an opaque mix with the app background, like the
canvas cards. The normal navigation uses the same colors so both styles match. Nested
surfaces keep their tint on top, also inside opaque CSS-class panels
(settings sections, boxes, tables, …) that are not app-colored. Generated from the dark:bg-white|black/x
utilities used in resources/views and resources/js; in the utilities layer
with !important so it beats the !bg-* utilities. */
@layer utilities {
html.dark .app-main [class*="dark:bg-black/10"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #000 10%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-black/15"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #000 15%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-black/20"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #000 20%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-black/30"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #000 30%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-black/40"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #000 40%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-black/55"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #000 55%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-black/60"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #000 60%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/10"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 10%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/40"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 40%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.025]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 2.5%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.02]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 2%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.035]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 3.5%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.03]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 3%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.045]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 4.5%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.04]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 4%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.05]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 5%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.06]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 6%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.07]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 7%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.08]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 8%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.09]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 9%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.14]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 14%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:bg-white/[0.1]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in oklab, #fff 10%, var(--color-app)) !important; }
html.dark .app-main .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled):not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in srgb, var(--color-brand) 14%, var(--color-app)) !important; border-color: color-mix(in srgb, var(--color-brand-light) 24%, transparent) !important; color: oklch(from var(--color-brand-light) 93.5% 0.03 h) !important; }
html.dark .app-main .view-toggle:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) { background-color: color-mix(in srgb, var(--color-brand) 6%, var(--color-app)) !important; border-color: color-mix(in srgb, var(--color-brand-light) 16%, transparent) !important; }
html.dark .app-main [class*="dark:hover:bg-white/10"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 10%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.025]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 2.5%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.035]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 3.5%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.03]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 3%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.04]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 4%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.05]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 5%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.06]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 6%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.07]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 7%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.08]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 8%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.12]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 12%, var(--color-app)) !important; }
html.dark .app-main [class*="dark:hover:bg-white/[0.1]"]:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in oklab, #fff 10%, var(--color-app)) !important; }
html.dark .app-main .button:not(.button-highlighted):not(.button-error):not([isHighlighted]):not(:disabled):not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *):hover { background-color: color-mix(in srgb, var(--color-brand) 18%, var(--color-app)) !important; border-color: color-mix(in srgb, var(--color-brand-light) 34%, transparent) !important; }
/* Collection shells are #121212. Same selector as the 5% mix, plus .rounded-xl, so it wins. */
html.dark .app-main [class*="dark:bg-white/[0.05]"].rounded-xl:not(:is([class*="dark:bg-"]:not(.app-main), .button, .view-toggle, .application-settings-section, .application-settings-section-body, .box, .coolbox, .data-table, .resource-view, .empty-state, .loading-state-card, .runtime-log-shell, .logs-viewer) *) {
background-color: var(--color-card) !important;
border-color: rgb(255 255 255 / 0.08) !important;
}
}
/* Resource menu in the collapsed desktop sidebar: icons only (the mobile drawer never collapses). */
.app-sidebar .sidebar-collapsed .nav-context-menu :is(.menu-item-label, .nav-children, .menu-item > :not(.menu-item-icon)) {
display: none !important;
}
.app-sidebar .sidebar-collapsed .nav-context-menu .menu-item {
justify-content: center;
padding-left: 0;
padding-right: 0;
}
/* A page menu's own "Back to ..." item: the main sidebar copy has the back row instead (components/main-nav-menu). */
.nav-context-menu [data-page-only] {
display: none !important;
}