librelad 06a0e9de3c style(config): soften section divider; align toggle box with input fields
Divider: .domains-divider was a bold 2px accent bar under every section header,
which read as a stray line. Drop it to a subtle 1px low-opacity neutral rule so
it separates without shouting.

Toggle: the boxed config toggle (.checkbox-label) used a different radius (10px),
fill (0.04) and border (0.10) than the .form-control inputs beside it (8px /
0.05 / 0.20), so it looked off and out of line. Match it to the input field box
exactly so toggles and inputs read as the same surface. The app-config
borderless toggle override is unaffected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-23 01:17:48 +01:00

3817 lines
78 KiB
CSS
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

/* Reset */
* { box-sizing: border-box; margin: 0; padding: 0; }
/* ----------------------------------------------------------------------
Global themed scrollbars.
- Firefox uses scrollbar-color (and scrollbar-width: thin).
- WebKit/Blink uses the ::-webkit-scrollbar pseudo elements.
The thumb's transparent border + background-clip: padding-box gives
the thumb breathing room (the visible thumb is thinner than the
8px channel) so it feels less heavy. Hover swaps the thumb to the
theme accent. Track is transparent so the scrollbar floats over
whatever surface it's on, matching the cosmic glass elsewhere.
---------------------------------------------------------------------- */
* {
scrollbar-width: thin;
scrollbar-color: rgba(var(--text-rgb), 0.20) transparent;
}
*::-webkit-scrollbar {
width: 10px;
height: 10px;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-thumb {
background: rgba(var(--text-rgb), 0.20);
border: 2px solid transparent;
background-clip: padding-box;
border-radius: 8px;
transition: background-color 0.18s ease;
}
*::-webkit-scrollbar-thumb:hover {
background: rgba(var(--accent-rgb), 0.55);
background-clip: padding-box;
}
*::-webkit-scrollbar-thumb:active {
background: rgba(var(--accent-rgb), 0.75);
background-clip: padding-box;
}
*::-webkit-scrollbar-corner {
background: transparent;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
display: flex;
flex-direction: column;
min-height: 100vh;
padding-top: 60px;
background: var(--surface-bg);
background-attachment: fixed;
color: var(--text-primary);
}
/* Nebula body — same recipe as .aurora-bg.aurora-static used on the
loading + login screens, so the chrome and main content share one
atmosphere. Three layers cover the viewport: the base radial +
linear gradient on html, a static cyan-blob plume on ::before
(mirrors aurora-bg::after), and the star-particle pattern on
::after (mirrors .aurora-stars::before). No animation. */
html[data-theme="nebula"] {
background:
radial-gradient(ellipse at 20% 30%, var(--gradient-mid) 0%, transparent 55%),
radial-gradient(ellipse at 80% 70%, var(--gradient-to) 0%, transparent 55%),
linear-gradient(135deg, var(--gradient-from) 0%, var(--gradient-from) 40%, var(--gradient-mid) 100%);
background-attachment: fixed;
}
html[data-theme="nebula"] body {
background: transparent;
}
html[data-theme="nebula"]::before {
content: '';
position: fixed;
inset: -10%;
z-index: -2;
background:
/* Warm cosmic accents — magenta + violet bloom for nebula richness */
radial-gradient(circle at 12% 88%, rgba(180, 90, 220, 0.32) 0%, transparent 42%),
radial-gradient(circle at 88% 12%, rgba(255, 120, 180, 0.22) 0%, transparent 38%),
/* Cyan accent plumes (theme accent colour) */
radial-gradient(circle at 18% 22%, rgba(var(--accent-rgb), 0.42) 0%, transparent 45%),
radial-gradient(circle at 78% 18%, rgba(var(--accent-rgb), 0.34) 0%, transparent 42%),
radial-gradient(circle at 30% 78%, rgba(var(--accent-rgb), 0.30) 0%, transparent 48%),
radial-gradient(circle at 82% 80%, rgba(var(--accent-rgb), 0.40) 0%, transparent 46%),
radial-gradient(circle at 50% 50%, rgba(var(--accent-rgb), 0.14) 0%, transparent 60%);
pointer-events: none;
}
html[data-theme="nebula"]::after {
content: '';
position: fixed;
inset: 0;
z-index: -1;
background-image:
radial-gradient(1.5px 1.5px at 12px 18px, rgba(var(--text-rgb), 0.9), transparent 60%),
radial-gradient(1px 1px at 47px 92px, rgba(var(--accent-rgb), 0.85), transparent 60%),
radial-gradient(1.2px 1.2px at 110px 40px, rgba(var(--text-rgb), 0.75), transparent 60%),
radial-gradient(1px 1px at 165px 130px, rgba(var(--accent-rgb), 0.70), transparent 60%);
background-size: 200px 200px;
pointer-events: none;
}
.mobile-menu-toggle {
display: none;
background: none;
border: none;
color: inherit;
cursor: pointer;
padding: 8px;
border-radius: 6px;
transition: background 0.2s;
}
.mobile-menu-toggle:hover {
background: rgba(var(--text-rgb), 0.1);
}
.theme-selector {
padding: 6px 12px;
border: 1px solid;
border-radius: 4px;
font-size: 14px;
cursor: pointer;
}
/* Layout — config page (and any other page using .container/.main) uses
a viewport-locked flex row so the sidebar paints its background the
full column height and the main pane scrolls independently. Same
pattern as .tasks-layout and .apps-layout. */
.container {
display: flex;
width: 100%;
height: calc(100vh - 60px);
overflow: hidden;
}
.mobile-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(var(--bg-rgb), 0.5);
z-index: 99;
opacity: 0;
transition: opacity 0.3s ease;
}
.mobile-overlay.active {
display: block;
opacity: 1;
}
/* Main content — fills the remaining width inside .container/.apps-layout
and scrolls internally so the sidebar can stay locked at viewport
height. */
.main {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
padding: 0px;
overflow-y: auto;
}
.advanced-field.is-hidden {
display: none;
}
.mullvad-generate-field .mullvad-generate-actions {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.mullvad-generate-field .mullvad-generate-btn { margin: 0; }
.mullvad-generate-status {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
padding: 4px 10px;
border-radius: 12px;
border: 1px solid rgba(var(--text-rgb), 0.15);
background: rgba(var(--text-rgb), 0.05);
color: rgba(var(--text-rgb), 0.6);
}
.mullvad-generate-status.is-configured {
background: rgba(var(--status-success-rgb), 0.12);
border-color: rgba(var(--status-success-rgb), 0.35);
color: var(--status-success);
}
.mullvad-generate-status .mullvad-generate-tick {
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
border-radius: 3px;
border: 1px solid currentColor;
font-size: 11px;
line-height: 1;
}
.mullvad-generate-status.is-configured .mullvad-generate-tick {
background: var(--status-success);
border-color: var(--status-success);
color: #0b3d1c;
}
.gluetun-countries-field {
display: flex;
align-items: center;
gap: 12px;
}
.gluetun-countries-display {
flex: 1;
min-width: 0;
display: flex;
flex-wrap: nowrap;
gap: 6px;
height: 32px;
padding: 6px 10px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: var(--card-bg);
overflow: hidden;
white-space: nowrap;
-webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
}
.gluetun-country-chip { flex-shrink: 0; }
.gluetun-country-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 2px 10px;
border-radius: 12px;
background: rgba(52, 152, 219, 0.15);
border: 1px solid rgba(52, 152, 219, 0.3);
color: var(--text-color);
font-size: 12px;
}
.gluetun-flag {
font-size: 14px;
line-height: 1;
font-family: 'Twemoji Country Flags', 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif;
}
.gluetun-country-empty {
color: var(--text-secondary, #888);
font-style: italic;
font-size: 12px;
}
.gluetun-countries-edit { flex-shrink: 0; }
.gluetun-modal .modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--border-color);
}
.gluetun-modal .modal-close {
background: none;
border: none;
font-size: 24px;
color: var(--text-color);
cursor: pointer;
padding: 0;
}
.gluetun-modal .modal-body {
padding: 20px;
overflow-y: auto;
}
.gluetun-modal .modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 16px 20px;
border-top: 1px solid var(--border-color);
}
.gluetun-provider-card {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 14px;
background: rgba(56, 189, 248, 0.10);
border: 1px solid rgba(56, 189, 248, 0.30);
border-radius: 10px;
margin-bottom: 14px;
}
.gluetun-provider-icon-wrap {
width: 44px;
height: 44px;
border-radius: 9px;
background: var(--surface-bg-solid);
border: 1px solid rgba(var(--text-rgb), 0.10);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
overflow: hidden;
}
.gluetun-provider-icon { width: 32px; height: 32px; object-fit: contain; }
.gluetun-provider-text { flex: 1; min-width: 0; }
.gluetun-provider-label { margin: 0; font-size: 12px; color: rgba(var(--text-rgb), 0.60); text-transform: uppercase; letter-spacing: 0.5px; }
.gluetun-provider-name { margin: 2px 0 0 0; font-size: 16px; font-weight: 600; color: var(--text-primary); text-transform: capitalize; }
.gluetun-search-card {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px 12px;
background: rgba(var(--text-rgb), 0.03);
border: 1px solid rgba(var(--text-rgb), 0.08);
border-radius: 10px;
margin-bottom: 12px;
}
.gluetun-search-row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.10);
border-radius: 8px;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.gluetun-search-row:focus-within {
border-color: rgba(56, 189, 248, 0.55);
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.12);
}
.gluetun-search-icon { color: rgba(var(--text-rgb), 0.55); flex-shrink: 0; }
.gluetun-country-search {
flex: 1;
background: transparent;
border: none;
outline: none;
color: var(--text-primary);
font-size: 14px;
padding: 2px 0;
}
.gluetun-search-actions {
display: flex;
gap: 8px;
}
.gluetun-search-actions .btn { flex: 1 1 0; }
.gluetun-modal .modal-footer {
display: flex;
gap: 12px;
}
.gluetun-modal .modal-footer .btn { flex: 1 1 0; }
.gluetun-country-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 6px 14px;
max-height: 45vh;
overflow-y: auto;
padding: 4px 2px;
}
.gluetun-country-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
background: rgba(var(--text-rgb), 0.03);
border: 1px solid rgba(var(--text-rgb), 0.08);
border-radius: 10px;
cursor: pointer;
transition: background 0.15s ease, border-color 0.15s ease;
}
.gluetun-country-item:hover {
background: rgba(var(--text-rgb), 0.06);
border-color: rgba(56, 189, 248, 0.25);
}
.gluetun-country-item:has(input:checked) {
background: rgba(56, 189, 248, 0.10);
border-color: rgba(56, 189, 248, 0.45);
}
.gluetun-country-item input[type=checkbox] {
-webkit-appearance: none;
appearance: none;
width: 18px;
height: 18px;
flex-shrink: 0;
cursor: pointer;
border-radius: 5px;
background: rgba(var(--text-rgb), 0.04);
border: 1.5px solid rgba(var(--text-rgb), 0.18);
box-shadow: inset 0 0 0 1px rgba(var(--text-rgb), 0.02);
position: relative;
transition: background 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease, transform 0.12s ease;
margin: 0;
}
.gluetun-country-item:hover input[type=checkbox] {
border-color: rgba(56, 189, 248, 0.55);
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.08);
}
.gluetun-country-item input[type=checkbox]:focus-visible {
outline: none;
border-color: rgba(56, 189, 248, 0.85);
box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.20);
}
.gluetun-country-item input[type=checkbox]:checked {
background: linear-gradient(135deg, #38bdf8, #818cf8);
border-color: rgba(56, 189, 248, 0.9);
box-shadow: 0 0 0 1px rgba(56, 189, 248, 0.35);
}
.gluetun-country-item input[type=checkbox]:checked::after {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.2' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>");
background-repeat: no-repeat;
background-position: center;
background-size: 13px 13px;
animation: gluetunCheckPop 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes gluetunCheckPop {
0% { transform: scale(0.4); opacity: 0; }
100% { transform: scale(1); opacity: 1; }
}
.gluetun-country-name {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 15px;
color: var(--text-primary);
font-weight: 500;
}
.gluetun-country-name .gluetun-flag {
font-size: 18px;
line-height: 1;
}
.gluetun-country-empty-msg {
grid-column: 1 / -1;
color: var(--text-secondary, #888);
font-style: italic;
}
/* Output console */
.console {
background: var(--console-bg);
color: var(--console-text);
border: 1px solid var(--border);
border-radius: 12px;
padding: 5px; /* Further reduced to 5px */
height: 125px; /* Reduced from 250px to half */
overflow-y: auto;
white-space: pre-wrap;
font-size: 14px;
font-family: 'Courier New', monospace;
margin: 0;
position: relative; /* Ensure proper positioning */
top: 0; /* Force to top */
}
.console-section {
margin-top: 20px;
}
/* Remove gaps in console output */
.log-entry {
margin: 0;
padding: 2px 8px;
border-radius: 4px;
display: block;
line-height: 1.4;
}
.log-entry:first-child {
padding-top: 0;
margin-top: 0;
border-top: none;
}
.log-entry:last-child {
padding-bottom: 0;
}
.log-timestamp {
color: rgba(var(--text-rgb), 0.5);
font-size: 10px;
margin-right: 8px;
display: inline;
}
.tabs-wrapper {
display: block;
width: 100%;
}
.tabs-list {
display: flex;
background: var(--hover-bg);
border-bottom: 1px solid var(--border-color);
padding: 0;
margin: 0;
width: 100%;
overflow-x: auto;
scrollbar-color: rgba(var(--text-rgb), 0.4) rgba(var(--text-rgb), 0.08);
}
/* Tabs inside .tabs-wrapper or .tab-navigation share the row evenly so
the bar fills its container instead of leaving empty space on the right.
Children also get centered so labels (and any leading icon/emoji) sit
in the middle of each tab. */
.tabs-wrapper .tabs-list .tab-button,
.tab-navigation .tab-button {
flex: 1 1 0;
min-width: 0;
text-align: center;
white-space: nowrap;
justify-content: center;
}
/* Task Status Indicator */
.task-status-indicator {
position: fixed;
top: 20px;
right: 20px;
background: rgba(33, 150, 243, 0.95);
color: var(--text-primary);
padding: 12px 16px;
border-radius: 8px;
border: 1px solid rgba(76, 175, 80, 0.3);
z-index: 1000;
font-size: 14px;
font-weight: 500;
backdrop-filter: blur(10px);
animation: slideIn 0.3s ease-out;
}
.task-status-content {
display: flex;
align-items: center;
gap: 8px;
}
.spinner-small {
width: 16px;
height: 16px;
border: 2px solid rgba(var(--text-rgb), 0.3);
border-top: 2px solid #4CAF50;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Button States */
.task-running {
opacity: 0.7;
cursor: not-allowed !important;
position: relative;
overflow: hidden;
}
.task-running::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(76, 175, 80, 0.1));
animation: loadingShimmer 1.5s infinite;
}
@keyframes loadingShimmer {
0% { left: -100%; }
50% { left: 100%; }
100% { left: 100%; }
}
/* App Header Enhancement */
.app-header {
position: relative;
}
.task-highlighted {
background: linear-gradient(135deg, rgba(76, 175, 80, 0.2), rgba(33, 150, 243, 0.2));
border: 2px solid #4CAF50;
border-radius: 8px;
transition: all 0.3s ease;
}
.task-highlighted:hover {
background: linear-gradient(135deg, rgba(76, 175, 80, 0.3), rgba(33, 150, 243, 0.3));
}
/* Clean scrollbar from scratch - higher specificity */
.tabs-wrapper .tabs-list::-webkit-scrollbar {
height: 12px !important;
}
.tabs-wrapper .tabs-list::-webkit-scrollbar-track {
background: rgba(var(--text-rgb), 0.08) !important;
border-radius: 9px !important;
}
.tabs-wrapper .tabs-list::-webkit-scrollbar-thumb {
background: rgba(var(--text-rgb), 0.4) !important;
border-radius: 9px !important;
border: none !important;
}
.tabs-wrapper .tabs-list::-webkit-scrollbar-thumb:hover {
background: rgba(var(--text-rgb), 0.5) !important;
}
/* Dynamic scrollbar enhancement for when tabs-list exists - higher specificity */
.tabs-wrapper .tabs-list[data-scrollable="true"]::-webkit-scrollbar {
height: 16px !important;
}
.tabs-wrapper .tabs-list[data-scrollable="true"]::-webkit-scrollbar-track {
background: rgba(var(--text-rgb), 0.1) !important;
border-radius: 10px !important;
margin: 15px 0 8px 0 !important;
}
.tabs-wrapper .tabs-list[data-scrollable="true"]::-webkit-scrollbar-thumb {
background: rgba(var(--text-rgb), 0.5) !important;
border-radius: 10px !important;
border: none !important;
}
.tabs-wrapper .tabs-list[data-scrollable="true"]::-webkit-scrollbar-thumb:hover {
background: rgba(var(--text-rgb), 0.6) !important;
}
.tabs-wrapper .tabs-list[data-scrollable="true"]::-webkit-scrollbar-thumb:hover::-webkit-scrollbar {
height: 11px !important; /* 16px * 2/3 = ~11px */
}
.tab-emoji {
font-size: 14px;
/* Coerce the OS to render these as text-presentation glyphs rather
than colour-emoji bitmaps (⚙ instead of ⚙️ etc.). Result is a
monochrome glyph we can theme with `color`, which reads way
better against the dark cosmic gradient than the platform's
greyish emoji bitmaps did. */
font-variant-emoji: text;
color: var(--accent);
line-height: 1;
}
/* Active tab uses text-primary so the icon doesn't disappear behind
the accent-tinted pill background. */
.tab-button.active .tab-emoji,
.tab-button.nav-active .tab-emoji,
.main-tab-button.active .tab-emoji,
.main-tab-button.nav-active .tab-emoji {
color: var(--text-primary);
}
.tab-name {
font-weight: 500;
}
.tabs-content {
display: block;
width: 100%;
background: var(--card-bg);
padding: 30px 10px 20px 10px;
border-radius: 0px 0px 12px 12px;
}
.tab-panel {
display: none;
padding: 5px 24px 5px 24px;
min-height: auto;
animation: fadeIn 0.2s ease;
}
.tab-panel.active {
display: block;
}
.panel-header {
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 1px solid var(--border-color);
display: none; /* Hide duplicate headers */
}
.panel-header h4 {
margin: 0 0 6px 0;
color: var(--text-primary, #fff);
font-size: 16px;
font-weight: 600;
}
.panel-header p {
margin: 0;
color: var(--text-secondary, #ccc);
font-size: 13px;
}
.panel-fields {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 16px;
}
@keyframes configDirtyIn {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
.dep-required-card {
display: flex;
align-items: center;
gap: 14px;
padding: 12px 14px;
background: rgba(245, 158, 11, 0.08);
border: 1px solid rgba(245, 158, 11, 0.30);
border-radius: 10px;
margin-bottom: 10px;
}
.dep-required-icon {
width: 40px;
height: 40px;
border-radius: 8px;
object-fit: contain;
background: rgba(var(--text-rgb), 0.04);
padding: 4px;
flex-shrink: 0;
}
.dep-required-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.dep-required-title {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
}
.dep-required-reason {
font-size: 12px;
color: rgba(var(--text-rgb), 0.70);
line-height: 1.4;
}
.dep-required-action {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
@media (max-width: 560px) {
.dep-required-card {
flex-direction: column;
align-items: stretch;
text-align: center;
}
.dep-required-icon { align-self: center; }
.dep-required-action { justify-content: center; }
}
.nav-button {
margin-left: auto;
padding: 4px 8px;
background: var(--primary-color);
color: var(--text-primary);
border: none;
border-radius: 4px;
font-size: 10px;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
transition: all 0.2s ease;
}
.nav-button:hover {
background: var(--accent-hover);
transform: translateY(-1px);
}
.nav-button svg {
flex-shrink: 0;
}
.nav-button.install-button {
background: var(--status-success);
}
.nav-button.install-button:hover {
background: var(--status-success-hover);
transform: translateY(-1px);
}
/* Confirmation Dialog - Simple Working Version */
.confirmation-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(var(--bg-rgb), 0.7);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
z-index: 99999;
display: none;
opacity: 0;
transition: opacity 0.3s ease;
}
.confirmation-overlay.active {
display: block;
opacity: 1;
}
.confirmation-dialog {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--surface-bg-solid);
background: var(--bg-primary, #1a1a1a);
border: 2px solid var(--border-strong);
border: 2px solid var(--border-color, #444);
border-radius: 8px;
max-width: 400px;
width: 90%;
z-index: 100000;
opacity: 1;
transition: all 0.3s ease;
display: none;
}
.confirmation-overlay.active .confirmation-dialog {
display: block;
}
.confirmation-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 20px;
border-bottom: 1px solid var(--border-strong);
border-bottom: 1px solid var(--border-color, #444);
}
.confirmation-header h3 {
margin: 0;
color: var(--text-primary);
color: var(--text-primary, #fff);
font-size: 16px;
font-weight: 600;
}
.confirmation-close {
background: none;
border: none;
color: var(--text-secondary);
color: var(--text-secondary, #ccc);
font-size: 20px;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.confirmation-close:hover {
color: var(--text-primary);
color: var(--text-primary, #fff);
}
.confirmation-body {
padding: 20px;
}
.confirmation-content {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 15px;
}
.confirmation-icon {
font-size: 20px;
flex-shrink: 0;
margin-top: 2px;
}
.confirmation-text {
color: var(--text-primary);
color: var(--text-primary, #fff);
line-height: 1.4;
flex: 1;
}
.confirmation-checkbox {
padding-top: 15px;
border-top: 1px solid var(--border-strong);
border-top: 1px solid var(--border-color, #444);
display: flex;
justify-content: space-between;
align-items: center;
}
.confirmation-checkbox label {
display: flex;
align-items: center;
gap: 8px;
color: var(--text-primary);
color: var(--text-primary, #fff);
cursor: pointer;
font-size: 14px;
order: 1;
}
.confirmation-checkbox input {
width: 16px;
height: 16px;
}
.confirmation-footer {
display: flex;
gap: 10px;
justify-content: flex-end;
padding: 15px 20px;
}
.confirmation-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.confirmation-btn-cancel {
background: var(--text-muted);
color: var(--text-primary);
}
.confirmation-btn-cancel:hover {
background: var(--text-secondary);
}
.confirmation-btn-ticked {
background: var(--status-success) !important;
color: #ffffff !important;
}
.confirmation-btn-ticked:hover {
background: var(--status-success-hover) !important;
}
.confirmation-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.tab-panel#panel-advanced .panel-header {
background: rgba(255, 107, 53, 0.1);
border-left: 4px solid var(--status-warning);
border-bottom: 1px solid var(--border-color);
}
.tab-panel#panel-advanced .panel-header h4 {
color: var(--status-warning);
display: flex;
align-items: center;
gap: 8px;
}
.tab-panel#panel-advanced .panel-header p {
color: #d84315;
font-style: italic;
font-size: 12px;
margin: 4px 0 0 0;
}
.no-fields {
text-align: center;
padding: 32px;
color: var(--text-secondary, #ccc);
font-style: italic;
background: var(--hover-bg);
border-radius: 6px;
border: 1px dashed var(--border-color);
}
/* Responsive Design */
@media (max-width: 768px) {
.tab-button {
flex: 1;
min-width: 60px;
justify-content: center;
font-size: 11px;
padding: 8px 12px;
}
.main-tab-button {
flex: 1;
min-width: 60px;
justify-content: center;
font-size: 11px;
padding: 8px 12px;
}
.tab-emoji {
font-size: 12px;
}
.tab-name {
display: none;
}
.config-title {
padding: 16px;
}
.tab-panel {
padding: 24px 16px 16px 16px;
min-height: auto;
}
.panel-fields {
grid-template-columns: 1fr;
gap: 12px;
}
.form-field {
gap: 4px;
}
.form-input,
.form-select,
.form-textarea {
padding: 8px;
font-size: 16px; /* Prevent zoom on iOS */
}
}
.timeout {
font-weight: bold;
}
/* App Configuration Page */
.app-header {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 24px;
margin-bottom: 30px;
}
.app-info {
display: flex;
align-items: center;
gap: 20px;
}
.app-details h1 {
font-size: 28px;
font-weight: 700;
margin-bottom: 8px;
color: var(--text-color);
}
.app-details h2 {
font-size: 24px;
font-weight: 600;
color: var(--text-color);
margin-bottom: 8px;
}
.app-details .app-long-description {
font-size: 14px;
color: var(--text-secondary, #ccc);
line-height: 1.4;
}
/* Responsive config field rules moved to config.css (where the base
.config-fields { repeat(3, 1fr) } lives — config.css loads after
style.css so keeping these here got overridden by the unscoped
base rule). */
/* Section Toggle Functionality */
.hidden {
display: none !important;
}
.section-content {
transition: opacity 0.3s ease, transform 0.3s ease;
}
.section-content.disabled {
opacity: 0.5;
pointer-events: none;
}
/* App Header with Actions */
.app-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
padding: 20px;
background: rgba(var(--text-rgb), 0.05);
border-radius: 12px;
border: 1px solid rgba(var(--text-rgb), 0.1);
}
.app-info {
display: flex;
align-items: center;
flex: 1;
}
.backup-btn, .uninstall-btn {
padding: 8px 16px;
border: 1px solid rgba(var(--text-rgb), 0.3);
background: transparent;
color: rgba(var(--text-rgb), 0.9);
border-radius: 6px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.backup-btn:hover, .uninstall-btn:hover {
background: rgba(var(--text-rgb), 0.1);
transform: translateY(-1px);
}
.password-field {
position: relative;
display: flex;
align-items: center;
}
/* Old notification styles removed - using newer notification system */
/* REMOVED OLD CHECKBOX STYLES - REPLACED WITH TOGGLE SWITCH */
/* Info tooltip badge. Markup is <span class="tooltip" title="…"></span>.
The emoji is drawn by the OS as a multi-color bitmap that doesn't
match our theme. We clip the host (overflow:hidden + text-indent to
push it off-screen) so the emoji is invisible no matter how the
platform fonts behave, then draw a clean italic 'i' via ::after
absolutely positioned inside the cyan circle. */
.tooltip {
display: inline-block;
width: 16px;
height: 16px;
background: var(--primary-color, var(--accent));
border-radius: 50%;
cursor: help;
position: relative;
margin-left: 4px;
flex-shrink: 0;
overflow: hidden;
text-indent: 100%;
white-space: nowrap;
font-size: 0;
color: transparent;
vertical-align: middle;
/* Sits below where it looks aligned with adjacent label text. */
margin-top: -2px;
transition: background 0.18s ease, transform 0.18s ease;
}
.tooltip::after {
content: 'i';
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: Georgia, 'Times New Roman', serif;
font-style: italic;
font-weight: 700;
font-size: 11px;
line-height: 1;
color: var(--text-on-accent, #ffffff);
text-indent: 0;
/* Drops the 'i' inside the circle so it doesn't sit too high.
Tuned with the host's -2px margin-top above. */
padding-bottom: 0;
}
.tooltip:hover {
background: var(--accent-hover, var(--primary-color-hover, #4169e1));
transform: scale(1.08);
}
.tooltip::before {
content: attr(title);
position: absolute;
bottom: 125%;
left: 50%;
transform: translateX(-50%);
background: rgba(var(--bg-rgb), 0.9);
color: var(--text-primary);
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
white-space: normal;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
max-width: 300px;
word-wrap: break-word;
min-width: 200px;
}
.tooltip:hover::before {
opacity: 1;
visibility: visible;
z-index: 99999;
}
/* REMOVED OLD CHECKBOX STYLES - REPLACED WITH TOGGLE SWITCH */
/* REMOVED OLD CHECKBOX STYLES - REPLACED WITH TOGGLE SWITCH */
#hidden-options-content {
padding: 20px;
}
#hidden-options-content h3 {
color: var(--text-secondary, #ccc);
font-size: 16px;
margin-bottom: 16px;
font-style: italic;
}
.install-btn {
background: var(--status-success);
color: #ffffff;
border: 1px solid var(--status-success);
padding: 14px 28px;
border-radius: 12px;
font-weight: 600;
}
.install-btn:hover {
background: var(--status-success-hover);
transform: translateY(-2px);
}
/* Base Button Styles */
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.log-entry {
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
margin-bottom: 5px;
padding: 5px;
border-radius: 4px;
background: rgba(var(--bg-rgb), 0.2);
color: rgba(var(--text-rgb), 0.8);
word-break: break-all;
}
.log-entry.error {
background: rgba(var(--status-danger-rgb), 0.2);
color: var(--status-danger);
}
.log-entry.success {
background: rgba(var(--status-success-rgb), 0.2);
color: #51cf66;
}
.log-entry.warning {
background: rgba(var(--status-warning-rgb), 0.2);
color: #ffd43b;
}
.log-entry.info {
background: rgba(var(--accent-rgb), 0.2);
color: #74c0fc;
}
/* Error state */
.error {
padding: 20px;
border-radius: 8px;
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
margin: 20px;
}
.tab-content {
}
.tab-pane {
display: none;
animation: fadeIn 0.3s ease-in-out;
}
.tab-pane.active {
display: block;
}
.tab-pane h4 {
margin: 0 0 20px 0;
color: rgba(var(--text-rgb), 0.9);
font-size: 16px;
font-weight: 600;
padding-bottom: 10px;
border-bottom: 1px solid rgba(var(--text-rgb), 0.1);
}
/* Responsive Design */
@media (max-width: 1024px) {
.apps-section {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
}
@media (max-width: 768px) {
/* Mobile menu toggle */
.mobile-menu-toggle {
display: block;
}
/* Sidebar mobile styles */
.sidebar {
position: fixed;
top: 60px;
left: 0;
height: calc(100vh - 60px);
transform: translateX(-100%);
z-index: 100;
}
.sidebar.mobile-open {
transform: translateX(0);
}
/* App Configuration Page Styles */
.app-info {
display: flex;
align-items: flex-start;
gap: 24px;
padding: 32px;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
transition: all 0.2s ease;
}
.app-info:hover {
transform: translateY(-2px);
box-shadow: var(--card-shadow-hover);
border-color: var(--primary-color);
}
.app-icon {
width: 64px;
height: 64px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
flex-shrink: 0;
transition: all 0.2s ease;
}
.app-icon img {
width: 48px;
height: 48px;
object-fit: contain;
transition: transform 0.2s ease;
}
.app-icon:hover img {
transform: scale(1.1);
}
.app-details {
flex: 1;
min-width: 0;
}
.app-details h2 {
font-size: 24px;
font-weight: 600;
color: var(--text-color);
margin: 0 0 8px 0;
line-height: 1.2;
}
.app-description {
font-size: 16px;
color: var(--text-secondary, #ccc);
margin: 0 0 16px 0;
line-height: 1.5;
}
.app-meta {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
}
.category-tag {
background: var(--primary-color);
color: var(--text-primary);
padding: 4px 8px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
.status-tag {
padding: 4px 8px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
}
.status-tag.installed {
background: var(--status-success);
color: var(--text-primary);
}
.status-tag.available {
background: var(--text-muted);
color: var(--text-primary);
}
.app-not-found {
text-align: center;
padding: 60px 20px;
color: var(--text-color);
}
.app-not-found h2 {
font-size: 24px;
font-weight: 600;
margin: 0 0 16px 0;
}
.config-section {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 22px;
margin-top: 24px;
}
.config-placeholder {
text-align: center;
}
.config-placeholder h3 {
font-size: 18px;
font-weight: 600;
color: var(--text-color);
margin: 0 0 16px 0;
}
.config-placeholder p {
color: var(--text-secondary, #ccc);
margin: 0 0 24px 0;
line-height: 1.5;
}
.config-actions {
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
margin-top: 24px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: var(--primary-color);
color: var(--text-primary);
}
.btn-primary:hover {
background: var(--primary-hover);
transform: translateY(-1px);
}
.btn-secondary {
color: var(--text-color);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: var(--border-color);
transform: translateY(-1px);
}
/* Main content mobile — go edge-to-edge so config/forms inside
don't get squeezed by stacked padding from .main + .config-section. */
.main {
padding: 0;
}
/* App cards mobile */
.apps-section {
grid-template-columns: 1fr;
gap: 16px;
}
.app-card {
flex-direction: column;
padding: 16px;
gap: 12px;
}
.app-card-top {
flex-direction: column;
align-items: center;
gap: 12px;
}
.app-card-icon {
width: 80px;
height: 80px;
align-self: center;
}
.app-card-content {
text-align: center;
width: 100%;
}
.app-card-actions {
width: 100%;
min-width: auto;
flex-direction: row;
}
.app-card-actions button {
flex: 1;
width: 50%;
}
.app-card-title {
font-size: 16px;
white-space: normal;
line-height: 1.3;
}
.app-card-description {
font-size: 13px;
}
.app-card button {
width: 100%;
padding: 14px 20px;
font-size: 15px;
min-height: 48px;
}
/* Topbar mobile */
.topbar {
padding: 0 16px;
}
.topbar-controls {
gap: 8px;
}
.topbar-nav {
display: none;
}
.theme-selector {
font-size: 12px;
padding: 4px 8px;
}
.donate-btn {
padding: 6px 12px;
font-size: 12px;
}
/* Category tags mobile */
.app-card-tags {
justify-content: center;
flex-wrap: wrap;
}
.app-tag {
font-size: 11px;
padding: 3px 6px;
}
}
@media (max-width: 480px) {
/* Extra small screens */
.topbar {
padding: 0 12px;
}
.main {
padding: 0;
}
.app-card {
padding: 12px;
}
.app-card-icon {
width: 50px;
height: 50px;
}
.app-card-title {
font-size: 15px;
}
.app-card-description {
font-size: 12px;
}
.donate-btn {
display: none;
}
.logo {
font-size: 18px;
}
}
.dashboard-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
padding: 22px;
}
.dashboard-content {
/* Remove background - let main container show through */
border-radius: 12px;
border: 1px solid var(--border-color);
padding: 24px;
margin-bottom: 16px; /* Reduced from 32px to reduce gap */
}
/* Dashboard Front Page Installed Apps */
.frontpage-apps-section {
padding: 0 22px 22px;
}
.frontpage-apps-grid {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.frontpage-app-tile {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
cursor: pointer;
}
.frontpage-app-icon-wrap {
position: relative;
width: 132px;
height: 132px;
background: rgba(var(--text-rgb), 0.08);
border-radius: 22px;
border: 1px solid rgba(var(--text-rgb), 0.15);
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
overflow: hidden;
}
.frontpage-app-tile:hover .frontpage-app-icon-wrap {
transform: translateY(-4px);
border-color: rgba(var(--text-rgb), 0.25);
}
.frontpage-app-icon-wrap img {
width: 100%;
height: 100%;
object-fit: contain;
}
/* Overlay that appears on hover when services are available — frosted
veil so the icon underneath still reads through, not a black slab. */
.frontpage-app-overlay {
display: none;
position: absolute;
inset: 0;
background: rgba(var(--bg-rgb), 0.45);
border-radius: 18px;
backdrop-filter: blur(8px) saturate(140%);
-webkit-backdrop-filter: blur(8px) saturate(140%);
flex-direction: column;
gap: 6px;
padding: 10px;
overflow-y: auto;
justify-content: space-between;
}
.frontpage-app-icon-wrap:hover .frontpage-app-overlay {
display: flex;
}
.frontpage-app-overlay a {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
border-radius: 5px;
background: rgba(var(--status-success-rgb), 0.15);
border: 1px solid rgba(var(--status-success-rgb), 0.3);
color: var(--text-primary);
text-decoration: none;
font-size: 10px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: background 0.15s ease;
flex-shrink: 0;
}
.frontpage-app-overlay a:hover {
background: rgba(var(--status-success-rgb), 0.3);
}
.frontpage-app-overlay a svg {
flex-shrink: 0;
opacity: 0.8;
width: 11px;
height: 11px;
}
.frontpage-app-name {
display: none;
}
.frontpage-app-manage-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 8px 12px;
border-radius: 6px;
background: var(--accent);
border: 1px solid var(--accent);
color: var(--text-primary);
text-decoration: none;
font-size: 11px;
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: background 0.15s ease;
flex-shrink: 0;
cursor: pointer;
/* Pin to the bottom of the overlay even when no service buttons are above it.
With justify-content: space-between, a single child sticks to the top —
margin-top: auto consumes the free space and pushes the manage button down. */
margin-top: auto;
}
.frontpage-app-manage-btn:hover {
background: var(--accent-hover);
border-color: var(--accent-hover);
}
.install-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 16px;
background: var(--status-success);
color: #ffffff;
border: 1px solid var(--status-success);
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.install-btn:hover {
background: var(--status-success-hover);
transform: translateY(-1px);
}
.stat-card {
background: var(--card-bg);
border: 1px solid rgba(var(--text-rgb), 0.15);
border-radius: 12px;
padding: 24px;
text-align: center;
transition: transform 0.2s ease;
min-height: 120px;
display: flex;
flex-direction: column;
justify-content: center;
}
.stat-number {
font-size: 36px;
font-weight: bold;
color: var(--primary-color);
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: var(--text-color);
opacity: 0.8;
}
.disk-chart {
position: relative;
display: inline-block;
}
/* Remove old disk chart styles that might conflict */
#disk-circle {
display: none; /* Hide old SVG circle */
}
.disk-circle-container {
width: 60px;
height: 60px;
border-radius: 50%;
background: rgba(var(--text-rgb), 0.1);
position: relative;
overflow: hidden;
border: 2px solid rgba(var(--text-rgb), 0.2);
}
.disk-circle-fill {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: var(--status-success);
transition: height 0.5s ease-in-out;
border-radius: 0 0 50% 50%;
}
.disk-percentage {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
font-weight: bold;
color: var(--text-primary);
pointer-events: none;
z-index: 10;
}
.chart-label {
position: relative;
text-align: center;
margin-top: 10px;
pointer-events: none;
}
.chart-text {
font-size: 10px;
color: var(--text-color);
opacity: 0.7;
margin-top: 2px;
}
.system-info-card {
text-align: left;
padding: 20px;
position: relative;
}
.system-details {
display: flex;
flex-direction: column;
gap: 8px;
}
.system-item {
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
font-size: 12px;
}
.system-label {
color: var(--text-color);
opacity: 0.7;
font-weight: 500;
}
.system-refresh-btn {
position: absolute;
top: 12px;
right: 12px;
background: rgba(var(--text-rgb), 0.1);
border: 1px solid rgba(var(--text-rgb), 0.15);
border-radius: 6px;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(var(--text-rgb), 0.7);
cursor: pointer;
transition: all 0.2s ease;
}
.system-refresh-btn:hover {
background: rgba(var(--text-rgb), 0.15);
color: rgba(var(--text-rgb), 0.9);
}
.system-refresh-btn svg {
width: 16px;
height: 16px;
stroke-width: 2;
}
.system-refresh-tooltip {
position: absolute;
top: 100%;
right: 0;
margin-top: 8px;
background: rgba(var(--bg-rgb), 0.8);
color: rgba(var(--text-rgb), 0.9);
padding: 6px 10px;
border-radius: 4px;
font-size: 11px;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
z-index: 10;
}
.system-refresh-btn:hover .system-refresh-tooltip {
opacity: 1;
}
/* Category descriptions */
.category-description {
font-size: 14px;
color: var(--text-secondary, #888);
margin: 8px 0 16px 0;
line-height: 1.4;
font-weight: 400;
}
.git-section-content {
transition: all 0.3s ease;
border-radius: 8px;
overflow: hidden;
margin-top: 14px;
}
.git-section-content.hidden {
max-height: 0;
padding: 0;
margin: 0;
opacity: 0;
pointer-events: none;
}
.info-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
margin-bottom: 16px;
}
.info-card h5 {
margin: 0 0 12px 0;
color: var(--primary-color);
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.info-card p {
margin: 0;
color: var(--text-secondary);
line-height: 1.5;
}
/* Domain Building Blocks */
.domains-wrapper {
margin-bottom: 0px;
}
.domains-header {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: flex-start;
gap: 12px;
margin-bottom: 16px;
}
.domains-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--text-primary, #fff);
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
.domains-divider {
width: 100%;
height: 1px;
background: rgba(var(--text-rgb), 0.08);
margin-bottom: 18px;
}
.add-domain-btn {
gap: 8px;
padding: 8px 20px;
margin-top: 12px;
margin-bottom: 12px;
min-width: 140px;
background: var(--primary-color);
color: var(--text-primary);
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
@media (max-width: 1600px) {
.domain-building-blocks {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 800px) {
.domain-building-blocks {
grid-template-columns: 1fr;
gap: 16px;
}
}
.delete-domain-btn {
background: var(--status-danger);
color: #ffffff;
border: none;
border-radius: 4px;
padding: 4px 8px;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
}
.delete-domain-btn:hover {
background: var(--status-danger-hover);
transform: scale(1.05);
}
.delete-domain-btn.disabled {
background: var(--text-muted);
color: #adb5bd;
cursor: not-allowed;
opacity: 0.6;
transform: none;
}
.delete-domain-btn.disabled:hover {
background: var(--text-muted);
transform: none;
}
.delete-icon {
font-size: 16px;
font-weight: bold;
line-height: 1;
}
/* Reusable spacer component */
.spacer {
display: block;
width: 100%;
}
.spacer-lg { height: 22px; }
/* Mail Configuration Master Toggle */
.mail-master-toggle {
margin-bottom: 0;
border-radius: 8px;
width: 100%;
box-sizing: border-box;
}
/* Generic Configuration Master Toggle */
.generic-master-toggle {
margin-bottom: 20px;
border-radius: 8px;
width: 100%;
box-sizing: border-box;
}
.add-domain-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 20px;
min-width: 140px;
background: var(--status-success);
color: #ffffff;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.add-domain-btn.disabled {
background: var(--text-muted);
cursor: not-allowed;
opacity: 0.7;
}
.add-domain-btn.disabled:hover {
background: var(--text-muted);
transform: none;
}
.add-domain-btn:hover {
background: var(--status-success-hover);
transform: translateY(-1px);
}
.add-icon {
font-size: 18px;
font-weight: bold;
}
/* Flash animation for empty domain warning */
@keyframes flash {
0%, 100% {
background-color: transparent;
border-color: var(--border-color);
}
25%, 75% {
background-color: rgba(var(--status-warning-rgb), 0.1);
border-color: var(--status-warning);
}
50% {
background-color: rgba(var(--status-warning-rgb), 0.2);
border-color: var(--status-warning);
}
}
/* Password input styling */
.password-input {
display: flex;
gap: 8px;
}
.password-input input {
flex: 1;
}
.password-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: var(--hover-bg);
border: 1px solid var(--border-color);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
}
.password-toggle:hover {
background: var(--primary-color);
border-color: var(--primary-color);
}
.password-toggle:hover svg {
stroke: white;
}
.password-field-wrapper {
position: relative;
display: block;
}
.password-field-wrapper .password-field {
width: 100%;
padding-right: 40px;
}
.password-field-wrapper .password-toggle {
position: absolute;
top: 50%;
right: 6px;
transform: translateY(-50%);
width: 28px;
height: 28px;
background: transparent;
border: none;
padding: 0;
}
.password-field-wrapper .password-toggle:hover {
background: rgba(var(--text-rgb), 0.08);
border: none;
}
.password-toggle-icon {
font-size: 14px;
line-height: 1;
user-select: none;
color: var(--text-secondary, #a0a0a0);
}
/* Responsive design */
@media (max-width: 768px) {
.info-card {
padding: 16px;
margin-bottom: 12px;
}
.group-header {
padding: 12px 16px;
}
.group-fields {
padding: 16px;
}
.field-group {
margin-bottom: 20px;
}
.group-fields .form-field {
margin-bottom: 16px;
}
}
.system-value {
color: var(--text-color);
font-weight: 600;
}
.install-btn {
background: var(--status-success);
border: 1px solid var(--status-success);
border-radius: 8px;
padding: 10px 16px;
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
white-space: nowrap;
}
.install-btn:hover {
background: var(--status-success-hover);
border-color: var(--status-success-hover);
transform: translateY(-1px);
}
.action-btn {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 12px 20px;
font-size: 14px;
font-weight: 500;
color: var(--text-color);
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.2s ease;
}
.action-btn:hover {
background: var(--hover-bg);
transform: translateY(-1px);
}
.action-btn.primary {
background: var(--primary-color);
color: var(--text-primary);
border-color: var(--primary-color);
}
.action-btn.primary:hover {
background: var(--accent-hover);
border-color: var(--accent-hover);
}
.action-btn.secondary {
background: rgba(var(--text-rgb), 0.1);
border-color: rgba(var(--text-rgb), 0.2);
}
.action-btn.secondary:hover {
background: rgba(var(--text-rgb), 0.2);
}
.installed-apps {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
/* Remove background - let main container show through */
}
.empty-state {
grid-column: 1 / -1;
text-align: center;
padding: 60px 20px;
color: var(--text-color);
}
.empty-state svg {
margin-bottom: 16px;
opacity: 0.5;
}
.empty-state h3 {
font-size: 20px;
font-weight: 600;
margin-bottom: 12px;
}
.empty-state p {
font-size: 16px;
opacity: 0.8;
}
.empty-state a {
color: var(--primary-color);
text-decoration: none;
font-weight: 500;
}
.empty-state a:hover {
text-decoration: underline;
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
.status-indicator.running {
background: var(--status-success);
}
.status-indicator.stopped {
background: var(--status-danger);
}
.status-text {
font-size: 12px;
font-weight: 500;
}
/* Dashboard Mobile Responsive */
@media (max-width: 768px) {
.dashboard-stats {
grid-template-columns: repeat(2, 1fr);
}
.dashboard-actions {
flex-direction: column;
}
.action-btn {
width: 100%;
justify-content: center;
}
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 12px;
}
.filter-controls {
width: 100%;
flex-direction: column;
}
.search-input {
min-width: auto;
width: 100%;
}
.installed-apps {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
.dashboard-stats {
grid-template-columns: 1fr;
}
.stat-number {
font-size: 24px;
}
}
/* New disk circle chart styles */
.disk-chart {
position: relative;
display: inline-block;
vertical-align: middle;
}
.disk-circle-container {
width: 60px;
height: 60px;
border-radius: 50%;
background: rgba(var(--text-rgb), 0.1);
position: relative;
overflow: hidden;
border: 2px solid rgba(var(--text-rgb), 0.2);
margin: 0 auto; /* Center horizontally */
}
.disk-circle-fill {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: var(--status-success);
transition: height 0.5s ease-in-out;
border-radius: 0 0 50% 50%; /* Rounded bottom only */
}
.disk-percentage {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 12px;
font-weight: bold;
color: var(--text-primary);
pointer-events: none; /* Prevent mouse interactions */
z-index: 10; /* Ensure it's on top */
}
/* LibrePortal logo styles */
.libreportal-logo {
display: flex;
align-items: center;
margin-right: 15px;
padding: 4px;
border-radius: 6px;
background: rgba(var(--text-rgb), 0.1);
transition: all 0.3s ease;
}
.libreportal-logo:hover {
background: rgba(var(--text-rgb), 0.2);
transform: scale(1.05);
}
.libreportal-logo img {
width: 32px;
height: 32px;
}
/* Notification container positioning - ensure bottom-right */
.notification-container {
position: fixed !important;
bottom: 20px !important;
right: 20px !important;
top: auto !important;
left: auto !important;
z-index: 10000 !important;
pointer-events: none;
display: flex;
flex-direction: column-reverse;
gap: 10px;
}
.notification {
background: var(--card-bg, #2d3748);
border: 1px solid var(--border-color, #4a5568);
border-radius: 8px;
padding: 16px;
width: auto;
min-width: 350px;
max-width: 700px;
pointer-events: all;
transform: translateY(100%);
opacity: 0;
transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
/* Dynamic width based on content */
.notification[data-has-app="true"] {
min-width: 450px;
max-width: 650px;
}
.notification[data-has-action="true"] {
min-width: 500px;
max-width: 700px;
}
.notification[data-has-app="true"][data-has-action="true"] {
min-width: 550px;
max-width: 750px;
}
.notification-show {
transform: translateY(0);
opacity: 1;
}
.notification-hide {
transform: translateY(100%);
opacity: 0;
}
.notification-content {
display: flex;
align-items: flex-start;
gap: 12px;
}
.notification-app-icon {
flex-shrink: 0;
margin-right: 12px;
width: 36px;
height: 36px;
border-radius: 6px;
overflow: hidden;
background: rgba(var(--text-rgb), 0.15);
border: 1px solid rgba(var(--text-rgb), 0.25);
display: flex;
align-items: center;
justify-content: center;
}
.notification-app-icon img {
width: 28px;
height: 28px;
object-fit: contain;
border-radius: 4px;
}
.notification-icon {
flex-shrink: 0;
/* Container uses align-items: flex-start so the message stays top-aligned
for multi-line text. Override here so the status icon (20px) sits
vertically centered against the 36px app icon next to it. */
align-self: center;
}
.notification-icon svg {
display: block;
}
.notification-message {
flex: 1;
color: var(--text-color, #e2e8f0);
font-size: 14px;
line-height: 1.4;
margin-top: 2px;
}
.notification-action-btn {
background: var(--primary-color, var(--accent));
color: #ffffff;
border: none;
padding: 8px 16px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
flex-shrink: 0;
margin-left: 12px;
/* Container is `align-items: flex-start` so multi-line message text stays
anchored at the top; override here so the button lines up with the
status / app icons next to it. */
align-self: center;
}
.notification-action-btn:hover {
background: var(--primary-hover, #3182ce);
transform: translateY(-1px);
}
.notification-close {
background: none;
border: none;
color: var(--text-muted, #a0aec0);
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
flex-shrink: 0;
/* Same reasoning as `.notification-action-btn` above — center against the
icons rather than top-aligning with the message. */
align-self: center;
}
.notification-close:hover {
background: rgba(var(--text-rgb), 0.1);
color: var(--text-color, #e2e8f0);
}
.notification-success .notification-icon {
color: #48bb78;
}
.notification-error .notification-icon {
color: var(--status-danger);
}
.notification-warning .notification-icon {
color: var(--status-warning);
}
.notification-info .notification-icon {
color: var(--accent);
}
.notification-uninstall .notification-icon {
color: var(--status-danger);
}
/* Mobile positioning */
@media (max-width: 768px) {
.notification-container {
bottom: 10px !important;
right: 10px !important;
left: 10px !important;
}
}
/* Tablet positioning */
@media (max-width: 1024px) and (min-width: 769px) {
.notification-container {
bottom: 15px !important;
right: 15px !important;
top: auto !important;
}
}
.notification-container .notification {
pointer-events: auto;
}
/* Hide all content (icons, text, etc.) for install, manage, and uninstall buttons when loading */
.btn-loading.btn-install,
.btn-loading.btn-manage,
.btn-loading.btn-uninstall,
.btn-loading.manage-btn {
color: transparent !important;
}
.btn-loading.btn-install *,
.btn-loading.btn-manage *,
.btn-loading.btn-uninstall *,
.btn-loading.manage-btn * {
opacity: 0 !important;
visibility: hidden !important;
}
/* Show only spinner for install, manage, and uninstall buttons */
.btn-loading.btn-install::after,
.btn-loading.btn-manage::after,
.btn-loading.btn-uninstall::after,
.btn-loading.manage-btn::after,
.app-card-actions .btn-loading.manage-btn::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 16px;
height: 16px;
margin: -8px 0 0 -8px;
border: 2px solid transparent;
border-top: 2px solid currentColor;
border-radius: 50%;
animation: spin 1s linear infinite;
color: var(--text-primary) !important;
opacity: 1 !important;
visibility: visible !important;
}
/* Loading text for non-install buttons */
.btn-loading:not(.btn-install):not(.btn-manage):not(.btn-uninstall):not(.manage-btn)::before {
content: attr(data-loading-text);
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: inherit;
font-size: inherit;
white-space: nowrap;
}
.loading-initial .loading-spinner {
width: 60px;
height: 60px;
border: 4px solid rgba(var(--text-rgb), 0.1);
border-top: 4px solid var(--primary-color, var(--accent));
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 24px;
}
.loading-initial .loading-subtitle {
font-size: 14px;
color: var(--text-secondary, #ccc);
font-weight: normal;
margin-top: 8px;
opacity: 0.8;
}
/* Loading spinner styles */
.loading-categories .loading-spinner,
.loading-apps .loading-spinner {
width: 20px;
height: 20px;
border: 2px solid #e3e3e3;
border-top: 2px solid var(--accent);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
/* Loading containers */
.loading-categories,
.loading-apps {
text-align: center;
padding: 20px;
color: var(--text-color, #666);
}
.loading-categories p,
.loading-apps p {
margin: 0;
font-size: 14px;
}
/* Update needed and warning styles */
.warning-banner {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
margin-bottom: 20px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
color: #856404;
font-size: 14px;
}
.warning-banner svg {
flex-shrink: 0;
}
.warning-banner span {
flex: 1;
}
.warning-banner .btn-small {
flex-shrink: 0;
padding: 4px 12px;
font-size: 12px;
background: var(--status-warning);
color: #212529;
border: 1px solid var(--status-warning);
}
.warning-banner .btn-small:hover {
background: #e0a800;
border-color: #e0a800;
}
.btn-copy.copied {
background: var(--status-success);
border-color: var(--status-success);
}
.toggle-content {
display: flex;
flex-direction: column;
margin-left: 12px;
flex: 1;
}
.toggle-section input[type="checkbox"] {
display: none;
}
/* Section Dividers */
.section-divider {
margin: 32px 0 24px 0;
padding: 16px 0;
border-bottom: 2px solid var(--border-color);
}
.section-divider h3 {
margin: 0 0 8px 0;
color: var(--text-primary);
font-size: 18px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.section-divider p {
margin: 0;
color: var(--text-secondary);
font-size: 14px;
font-style: italic;
}
/* Advanced and Unused Sections */
.advanced-sections,
.unused-sections {
margin-bottom: 24px;
}
.advanced-section {
border-left: 4px solid #f39c12;
padding-left: 16px;
padding-right: 16px;
padding-top: 22px;
}
.unused-section {
border-left: 4px solid #e74c3c;
padding-left: 16px;
padding-right: 16px;
opacity: 0.8;
}
.advanced-section h3,
.unused-section h3 {
color: var(--text-primary);
}
/* Mail Connection Test Button */
.test-connection-btn {
background: var(--accent);
color: #ffffff;
border: 1px solid var(--accent);
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
font-size: 14px;
transition: all 0.2s ease;
}
.test-connection-btn:hover:not(:disabled) {
background: var(--accent-hover);
border-color: var(--accent-hover);
}
.test-connection-btn:disabled {
background: var(--text-muted);
border-color: var(--text-muted);
cursor: not-allowed;
}
.test-icon {
font-size: 16px;
}
.test-text {
font-weight: 500;
}
/* Test Result Display */
.test-result {
margin-top: 8px;
padding: 8px 12px;
border-radius: 4px;
font-size: 13px;
font-weight: 500;
display: block;
width: 100%;
box-sizing: border-box;
}
.test-result.testing {
background: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
}
.test-result.success {
background: #d4edda;
border: 1px solid #c3e6cb;
color: #155724;
}
.test-result.error {
background: #f8d7da;
border: 1px solid #f5c6cb;
color: #721c24;
}
/* ==============================================
TABBED INTERFACE STYLES
============================================== */
/* Tabbed Interface Container */
.tabbed-interface {
margin-top: 20px;
}
/* Tab Navigation */
.tab-navigation {
display: flex;
border-bottom: 1px solid rgba(var(--text-rgb), 0.10);
background: rgba(var(--text-rgb), 0.04);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
border-radius: 12px 12px 0px 0px;
}
.tab-button {
background: transparent;
border: none;
padding: 12px 16px;
cursor: pointer;
border-radius: 8px 8px 0px 0px;
font-size: 14px;
font-weight: 500;
color: rgba(var(--text-rgb), 0.7);
transition: all 0.3s ease;
border-bottom: 2px solid transparent;
}
.tab-button:hover {
color: var(--accent);
background: rgba(var(--accent-rgb), 0.1);
}
.tab-button.active {
color: var(--accent);
border-bottom-color: var(--accent);
background: rgba(var(--accent-rgb), 0.1);
}
/* Flatten the .tab-button default 8px-on-both-top-corners inside any
tab container — we want only the FIRST tab's top-left and LAST
tab's top-right to follow the strip's curve. Without the reset,
the inner corner of the active tab still picks up the default
8px and looks rounded against an unrounded sibling. */
.tab-navigation > .tab-button,
.tabs-wrapper .tabs-list .tab-button {
border-radius: 0;
}
/* When the first / last tab is the active one, its filled background
should follow the parent strip's 12px top-corner radius. Without
this, the active tab squares off in the corner of the strip,
leaving an L-shaped notch against the rounded chrome. */
.tab-navigation > .tab-button:first-child,
.tab-navigation > .main-tab-button:first-child,
.tabs-wrapper .tabs-list .tab-button:first-child {
border-top-left-radius: 12px;
}
.tab-navigation > .tab-button:last-child,
.tab-navigation > .main-tab-button:last-child,
.tabs-wrapper .tabs-list .tab-button:last-child {
border-top-right-radius: 12px;
}
.main-tab-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 12px 20px;
background: transparent;
border: none;
cursor: pointer;
font-size: 12px;
font-weight: 500;
color: var(--text-secondary, #ccc);
transition: all 0.2s ease;
white-space: nowrap;
border-bottom: 2px solid transparent;
flex: 1;
}
.main-tab-button:hover {
color: var(--accent);
background: rgba(var(--accent-rgb), 0.1);
}
.main-tab-button.active {
color: var(--accent);
border-bottom-color: var(--accent);
background: rgba(var(--accent-rgb), 0.1);
}
.tab-button svg {
width: 16px;
height: 16px;
}
/* Tab Content */
.tab-content {
min-height: 400px;
}
.tab-pane {
display: none;
padding: 20px;
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.10);
border-top: none;
border-radius: 0px 0px 12px 12px;
box-shadow: inset 0 1px 0 rgba(var(--text-rgb), 0.04);
}
.tab-pane.active {
display: block;
}
.backups-section h3 {
color: var(--accent);
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.backups-section p {
color: var(--text-muted);
font-style: italic;
}
/* Padding stays at 0 so .tasks-title's own 20px provides the inset —
same recipe as .services-section / .config-section. */
.tasks-section {
display: flex;
flex-direction: column;
padding: 0;
}
.tasks-container {
/* Make app tasks look like main tasks page */
flex: 1;
overflow-y: auto;
padding: 16px;
margin: 16px;
background: rgba(var(--bg-rgb), 0.2);
border-radius: 8px;
}
/* Hide scrollbar when not needed, show only when scrolling */
.tasks-container::-webkit-scrollbar {
width: 8px;
}
.tasks-container::-webkit-scrollbar-track {
background: transparent;
}
.tasks-container::-webkit-scrollbar-thumb {
background: var(--input-bg);
border-radius: 4px;
}
.tasks-container::-webkit-scrollbar-thumb:hover {
background: var(--text-secondary);
}
/* ==============================================
TASK ITEM AND DETAILS STYLING
============================================== */
.task-item {
background: rgba(var(--text-rgb), 0.05);
border: 1px solid rgba(var(--text-rgb), 0.1);
border-radius: 8px;
margin-bottom: 12px;
overflow: hidden;
transition: all 0.2s ease;
padding: 0px;
}
.task-item:hover {
background: rgba(var(--text-rgb), 0.08);
border-color: rgba(var(--text-rgb), 0.15);
transform: translateY(-1px);
}
.task-details {
display: none;
background: rgba(var(--bg-rgb), 0.2);
border-top: 1px solid rgba(var(--text-rgb), 0.1);
padding: 16px;
}
.task-details.task-details-open {
display: block;
}
.task-meta {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 12px;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(var(--text-rgb), 0.1);
}
.meta-item {
font-size: 12px;
color: var(--text-muted);
}
.meta-item strong {
color: var(--text-primary);
font-weight: 500;
}
.task-id-link,
.task-app-link {
color: inherit;
text-decoration: none;
}
.task-id-link:hover,
.task-app-link:hover {
color: var(--accent);
text-decoration: underline;
}
.task-logs h4,
.task-output h4,
.task-error h4,
.task-running h4 {
color: var(--text-primary);
font-size: 14px;
margin: 16px 0 8px 0;
display: flex;
align-items: center;
gap: 8px;
}
.task-output .output-content,
.task-error .error-content {
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.10);
box-shadow: inset 0 1px 0 rgba(var(--text-rgb), 0.04);
border-radius: 10px;
padding: 12px;
font-family: 'Courier New', monospace;
font-size: 12px;
line-height: 1.4;
white-space: pre-wrap;
overflow-x: auto;
}
.task-error .error-content {
color: var(--status-danger);
border-color: rgba(var(--status-danger-rgb), 0.3);
}
.task-running .running-indicator {
display: flex;
align-items: center;
gap: 12px;
color: var(--status-info);
font-style: italic;
}
.task-running .spinner {
width: 16px;
height: 16px;
border: 2px solid rgba(23, 162, 184, 0.3);
border-top: 2px solid var(--status-info);
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* ==============================================
TASK HEADER ENHANCEMENTS
============================================== */
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
padding: 12px;
cursor: pointer;
}
.task-info {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
}
.task-app-icon {
width: 32px;
height: 32px;
border-radius: 8px;
object-fit: cover;
border: 1px solid #f0f0f03d;
background: #f8f9fa24;
padding: 3px;
}
.task-type-icon {
font-size: 16px;
margin-left: 6px;
margin-right: 6px;
display: flex;
align-items: center;
justify-content: center;
}
.task-title {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-weight: 400;
color: #c5c8ca;
font-size: 15px;
line-height: 1.2;
}
.task-status {
font-size: 12px;
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
display: inline-flex;
align-items: center;
gap: 4px;
text-transform: uppercase !important;
}
.status-queued {
background: rgba(255, 189, 46, 0.2);
color: #ffbd2e;
border: 1px solid rgba(255, 189, 46, 0.3);
text-transform: uppercase !important;
}
.status-running {
background: rgba(40, 202, 66, 0.2);
color: #28ca42;
border: 1px solid rgba(40, 202, 66, 0.3);
text-transform: uppercase !important;
}
.status-completed {
background: rgba(0, 255, 0, 0.2);
color: #00ff00;
border: 1px solid rgba(0, 255, 0, 0.3);
text-transform: uppercase !important;
}
.status-failed {
background: rgba(var(--status-danger-rgb), 0.2);
color: var(--status-danger);
border: 1px solid rgba(var(--status-danger-rgb), 0.3);
text-transform: uppercase !important;
}
.status-cancelled {
background: rgba(var(--text-rgb), 0.2);
color: var(--text-muted);
border: 1px solid rgba(var(--text-rgb), 0.3);
text-transform: uppercase !important;
}
.task-time {
font-size: 11px;
color: var(--text-muted);
margin-left: auto;
margin-right: 8px;
}
.task-duration {
font-size: 11px;
color: var(--text-muted);
margin-right: 8px;
}
.task-title,
.task-time,
.task-duration {
color: var(--text-muted);
}
.task-app-icon {
border-color: var(--border);
background: var(--surface-elevated);
}
/* Mirrors .config-title — see config.css. */
.tasks-title {
padding: 20px;
background: transparent;
border-bottom: 1px solid var(--border-color);
margin-bottom: 0;
}
.tasks-title h3 {
margin: 0 0 8px 0;
color: var(--text-primary, #fff);
font-size: 18px;
font-weight: 600;
}
.tasks-title p {
margin: 0;
color: var(--text-secondary, #ccc);
font-size: 13px;
}
/* ==============================================
TASK ACTIONS STYLING
============================================== */
.task-actions {
display: flex;
gap: 12px;
align-items: center;
}
.task-btn {
background: rgba(var(--text-rgb), 0.1);
border: 1px solid rgba(var(--text-rgb), 0.2);
color: var(--text-muted);
padding: 6px 10px;
border-radius: 6px;
font-size: 11px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 4px;
}
.task-btn:hover {
background: rgba(var(--text-rgb), 0.2);
border-color: rgba(var(--text-rgb), 0.3);
transform: translateY(-1px);
}
.task-btn.retry {
background: rgba(var(--status-danger-rgb), 0.1);
border-color: rgba(var(--status-danger-rgb), 0.2);
color: var(--status-danger);
}
.task-btn.retry:hover {
background: rgba(var(--status-danger-rgb), 0.2);
border-color: rgba(var(--status-danger-rgb), 0.3);
}
.task-btn.view-logs {
background: rgba(var(--status-info-rgb), 0.12);
border-color: rgba(var(--status-info-rgb), 0.30);
color: var(--text-primary);
}
.task-btn.view-logs:hover {
background: rgba(var(--status-info-rgb), 0.22);
border-color: rgba(var(--status-info-rgb), 0.50);
}
.task-btn.toggle-details {
background: rgba(var(--text-rgb), 0.06);
border-color: rgba(var(--text-rgb), 0.18);
color: var(--text-secondary);
}
.task-btn.toggle-details:hover {
background: rgba(var(--text-rgb), 0.12);
border-color: rgba(var(--text-rgb), 0.32);
color: var(--text-primary);
}
.task-btn.toggle-details.expanded {
background: rgba(var(--text-rgb), 0.14);
border-color: rgba(var(--text-rgb), 0.36);
color: var(--text-primary);
}
.task-btn.delete {
background: rgba(var(--status-danger-rgb), 0.14);
border-color: rgba(var(--status-danger-rgb), 0.40);
color: var(--text-primary);
}
.task-btn.delete:hover {
background: rgba(var(--status-danger-rgb), 0.28);
border-color: rgba(var(--status-danger-rgb), 0.65);
color: var(--text-primary);
}
/* Text label sitting next to the SVG inside any .task-btn (Restart,
Logs, Delete, etc.). Buttons that only carry an icon don't include
the span, so they stay icon-only. */
.task-btn .task-btn-label {
margin-left: 4px;
font-size: 11px;
font-weight: 500;
line-height: 1;
}
.task-btn:has(.task-btn-label) {
padding-right: 12px;
}
/* ==============================================
TERMINAL STYLING FOR TASK LOGS
============================================== */
.terminal-style {
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.10);
box-shadow: inset 0 1px 0 rgba(var(--text-rgb), 0.04);
color: var(--text-primary);
font-family: 'Courier New', 'Monaco', 'Menlo', 'Consolas', monospace;
font-size: 15px;
line-height: 1.3;
border-radius: 10px;
padding: 16px;
overflow: auto;
white-space: pre;
word-wrap: normal;
overflow-wrap: normal;
/* Browsers only honour `resize` when overflow != visible. Vertical
only — drag the bottom handle down to extend the log viewport. */
resize: vertical;
min-height: 120px;
max-height: none;
}
.terminal-style .log-line {
margin: 0;
padding: 0;
line-height: 1.3;
height: auto;
margin-bottom: 4px !important;
}
.terminal-style .log-entry {
margin: 0;
padding: 0;
line-height: 1.3;
height: auto;
margin-bottom: 4px !important;
}
.terminal-style div {
margin: 0;
padding: 0;
line-height: 1.3;
height: auto;
}
/* Force remove any default spacing but allow gap */
.terminal-style * {
margin: 0;
padding: 0;
line-height: 1.3;
}
.terminal-style .log-line,
.terminal-style .log-entry {
margin-bottom: 4px !important;
}
/* ANSI color styles for terminal */
.terminal-style span[style*="color: green"] {
color: #00ff00 !important;
}
.terminal-style span[style*="color: red"] {
color: var(--status-danger) !important;
}
.terminal-style span[style*="color: yellow"] {
color: #ffd93d !important;
}
.terminal-style span[style*="color: blue"] {
color: #6bb6ff !important;
}
.terminal-style span[style*="color: cyan"] {
color: #4ecdc4 !important;
}
.terminal-style span[style*="color: magenta"] {
color: #ff6ec7 !important;
}
.terminal-style span[style*="color: white"] {
color: var(--text-primary) !important;
}
.terminal-style span[style*="color: black"] {
color: #000000 !important;
}
.terminal-style span[style*="background-color: black"] {
background-color: #000000 !important;
padding: 0 2px;
}
.terminal-style span[style*="background-color: red"] {
background-color: #ff0000 !important;
padding: 0 2px;
}
.terminal-style span[style*="background-color: green"] {
background-color: #00ff00 !important;
padding: 0 2px;
}
.terminal-style span[style*="background-color: yellow"] {
background-color: #ffff00 !important;
padding: 0 2px;
}
.terminal-style span[style*="background-color: blue"] {
background-color: #0000ff !important;
padding: 0 2px;
}
.terminal-style span[style*="background-color: cyan"] {
background-color: #00ffff !important;
padding: 0 2px;
}
.terminal-style span[style*="background-color: magenta"] {
background-color: #ff00ff !important;
padding: 0 2px;
}
.terminal-style span[style*="background-color: white"] {
background-color: var(--text-primary) !important;
color: #000000 !important;
padding: 0 2px;
}
/* Modal log viewer enhancements */
.task-logs-modal .log-viewer.terminal-style {
max-height: 400px;
overflow-y: auto;
overflow-x: auto;
border: 1px solid var(--border-strong);
white-space: pre;
word-wrap: normal;
overflow-wrap: normal;
}
.task-logs-modal .log-viewer.terminal-style::-webkit-scrollbar {
width: 8px;
}
.task-logs-modal .log-viewer.terminal-style::-webkit-scrollbar-track {
background: var(--surface-elevated);
}
.task-logs-modal .log-viewer.terminal-style::-webkit-scrollbar-thumb {
background: var(--text-secondary);
border-radius: 4px;
}
.task-logs-modal .log-viewer.terminal-style::-webkit-scrollbar-thumb:hover {
background: #777;
}
/* Task preview log container styling — initial height is set inline
(200px) so the user lands on a familiar size, but `resize: vertical`
from .terminal-style lets them drag the bottom handle down to grow
it. We deliberately don't set a max-height here. */
.task-logs .log-container.terminal-style {
overflow-y: auto;
border: 1px solid var(--border);
margin: 8px 0;
}
.task-logs .log-container.terminal-style::-webkit-scrollbar {
width: 6px;
}
.task-logs .log-container.terminal-style::-webkit-scrollbar-track {
background: var(--surface-elevated);
}
.task-logs .log-container.terminal-style::-webkit-scrollbar-thumb {
background: var(--text-secondary);
border-radius: 3px;
}
.task-logs .log-container.terminal-style::-webkit-scrollbar-thumb:hover {
background: #777;
}
.task-logs .log-entry {
margin-bottom: 0;
line-height: 1.2;
}
.task-output .output-content.terminal-style {
max-height: 150px;
overflow-y: auto;
border: 1px solid var(--border);
margin: 8px 0;
}
/* Update Indicator Animations */
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
@keyframes requiredFlash {
0%, 100% { box-shadow: 0 0 0 3px rgba(var(--status-danger-rgb), 0.20); }
50% { box-shadow: 0 0 0 6px rgba(var(--status-danger-rgb), 0.35); }
}
/* Welcome chip styles live in css/service-buttons.css. */
/* ===== Mobile (≤768px) — safety net + app detail page ===== */
@media (max-width: 768px) {
/* Keep the page from ever scrolling horizontally on mobile —
long URLs, wide tables, or rogue inline widths still scroll
inside their own containers if they need to. */
html, body {
overflow-x: hidden;
max-width: 100%;
}
/* Stack the app header so service buttons fall below app info. */
.app-header {
flex-direction: column;
align-items: stretch;
padding: 16px;
gap: 16px;
}
.app-header .app-info {
flex-direction: column;
align-items: center;
text-align: center;
gap: 12px;
padding: 0;
background: transparent;
border: none;
}
.app-header .app-info .app-card-icon {
align-self: center;
}
.app-header .app-details {
width: 100%;
}
.app-header .app-details h2 {
font-size: 20px;
}
.app-header .app-description {
font-size: 14px;
}
.app-header .app-meta {
justify-content: center;
flex-wrap: wrap;
gap: 8px;
}
/* Service buttons stack vertically full-width below app info. */
.service-buttons-container {
flex-direction: column;
width: 100%;
margin-top: 0;
align-items: stretch;
}
.service-buttons-container .service-button {
width: 100%;
justify-content: center;
}
.service-buttons-container .service-trigger,
.service-buttons-container .service-trigger-icon {
width: 100%;
}
}