A free, open, self-hosted app platform (GNU AGPLv3): one-click app deploys, Traefik reverse proxy with automatic SSL, rootless Docker support, gluetun VPN routing, and a web dashboard to manage it all. Free & open forever to self-host; optional paid hosted services fund it. See PROMISE.md. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
1055 lines
28 KiB
CSS
Executable File
1055 lines
28 KiB
CSS
Executable File
/* ──────────────────────────────────────────────────────────────────────
|
|
Setup Wizard — multi-step slide-right
|
|
Reuses the shared .aurora-bg + .aurora-stars from aurora-background.css
|
|
so it shares the loading screen's visual identity. The wizard itself is
|
|
a translucent shell over that background, with a horizontal track of
|
|
step panels that slides as the user advances.
|
|
────────────────────────────────────────────────────────────────────── */
|
|
|
|
body.setup-wizard-open {
|
|
overflow: hidden;
|
|
}
|
|
|
|
.setup-wizard {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 9999;
|
|
color: var(--text-primary);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
overflow-y: auto;
|
|
padding: 2rem 1.5rem;
|
|
opacity: 0;
|
|
animation: setupFadeIn 0.6s ease forwards;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.setup-wizard.hiding {
|
|
animation: setupFadeOut 0.5s ease forwards;
|
|
}
|
|
|
|
.setup-wizard.setup-launched .setup-card {
|
|
transform: scale(0.96);
|
|
opacity: 0.5;
|
|
filter: blur(2px);
|
|
transition: all 0.5s ease;
|
|
}
|
|
|
|
@keyframes setupFadeIn { from { opacity: 0; } to { opacity: 1; } }
|
|
@keyframes setupFadeOut { from { opacity: 1; } to { opacity: 0; } }
|
|
|
|
/* Vertical stack: logo header on top, card below — same shape as
|
|
`.login-content` in the login overlay so the two surfaces share a
|
|
visual identity. The aurora-header / aurora-logo / aurora-subtitle
|
|
classes themselves are inherited from aurora-background.css so the
|
|
logo treatment is byte-identical to login + loading. */
|
|
.setup-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
width: 100%;
|
|
max-width: 620px;
|
|
margin: 1rem;
|
|
}
|
|
|
|
/* Header inherits .aurora-header / .aurora-logo sizing from
|
|
aurora-background.css so it matches the loading screen byte-for-byte —
|
|
single source of truth. The only wizard-specific tweak is a slightly
|
|
tighter bottom margin since a card sits below it. */
|
|
.setup-content .aurora-header {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
/* Card — translucent panel matching .login-card / loading screen style */
|
|
.setup-card {
|
|
width: 100%;
|
|
background: rgba(var(--text-rgb), 0.06);
|
|
border: 1px solid rgba(var(--text-rgb), 0.12);
|
|
border-radius: 14px;
|
|
padding: 24px 28px 20px;
|
|
backdrop-filter: blur(14px);
|
|
-webkit-backdrop-filter: blur(14px);
|
|
animation: setupCardRise 0.4s cubic-bezier(0.16, 1, 0.3, 1) 0.05s both;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 20px;
|
|
}
|
|
|
|
@keyframes setupCardRise {
|
|
from { transform: translateY(16px) scale(0.97); opacity: 0; }
|
|
to { transform: translateY(0) scale(1); opacity: 1; }
|
|
}
|
|
|
|
/* Progress bar — same look as loading screen's progress section */
|
|
.setup-progress {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
.setup-progress-bar {
|
|
background: rgba(var(--text-rgb), 0.10);
|
|
border-radius: 8px;
|
|
padding: 2px;
|
|
border: 1px solid rgba(var(--text-rgb), 0.08);
|
|
height: 12px;
|
|
box-sizing: border-box;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.setup-progress-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, var(--accent), var(--accent-hover));
|
|
border-radius: 6px;
|
|
width: 0%;
|
|
transition: width 0.45s cubic-bezier(0.16, 1, 0.3, 1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.setup-progress-fill::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(90deg, transparent, rgba(var(--text-rgb),0.3), transparent);
|
|
animation: setupShimmer 2s infinite;
|
|
}
|
|
|
|
@keyframes setupShimmer {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
|
|
.setup-progress-text {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-size: 12px;
|
|
color: rgba(var(--text-rgb), 0.78);
|
|
font-family: 'SF Mono', Menlo, monospace;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.setup-progress-text > #sw-progress-step {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.setup-progress-sep {
|
|
color: rgba(var(--text-rgb), 0.20);
|
|
margin: 0 2px;
|
|
}
|
|
|
|
.setup-progress-icon {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.setup-progress-name {
|
|
color: var(--text-primary);
|
|
font-weight: 600;
|
|
letter-spacing: 0.3px;
|
|
}
|
|
|
|
/* Step transitions — fade in / fade out. The previous slide-track approach
|
|
fought browser flex-basis math whenever step content height varied
|
|
(e.g. step 2 reveals the domain field when Public is toggled). Fade is
|
|
simpler: only the active step is in the layout, the card naturally
|
|
heights to its content, and the swap feels like the wizard "settles
|
|
into the next thought" rather than swinging horizontally. */
|
|
.setup-track-wrap {
|
|
position: relative;
|
|
width: 100%;
|
|
}
|
|
|
|
.setup-track {
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
.setup-step {
|
|
display: none;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
.setup-step.active {
|
|
display: flex;
|
|
animation: stepFadeIn 0.32s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
@keyframes stepFadeIn {
|
|
from { opacity: 0; transform: translateY(8px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
|
|
.setup-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 18px;
|
|
}
|
|
|
|
/* Multi-domain editor — list of removable rows + an "Add domain" button. */
|
|
.setup-domain-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
.setup-domain-row {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
|
|
/* Empty status pill collapses so blank-domain rows don't claim vertical space */
|
|
.setup-domain-row .setup-dns-status:empty {
|
|
display: none;
|
|
}
|
|
|
|
.setup-domain-row .setup-input-row {
|
|
align-items: stretch;
|
|
}
|
|
|
|
.setup-domain-remove {
|
|
flex: 0 0 auto;
|
|
width: 36px;
|
|
background: rgba(var(--text-rgb), 0.04);
|
|
border: 1px solid rgba(var(--text-rgb), 0.10);
|
|
border-radius: 8px;
|
|
color: rgba(var(--text-rgb), 0.65);
|
|
font-size: 18px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
|
}
|
|
|
|
.setup-domain-remove:hover {
|
|
background: rgba(var(--status-danger-rgb), 0.18);
|
|
border-color: rgba(var(--status-danger-rgb), 0.45);
|
|
color: var(--status-danger);
|
|
}
|
|
|
|
.setup-domain-add {
|
|
align-self: flex-start;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
background: rgba(var(--accent-rgb), 0.10);
|
|
border: 1px dashed rgba(var(--accent-rgb), 0.40);
|
|
border-radius: 10px;
|
|
padding: 8px 14px;
|
|
color: var(--accent);
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
margin-top: 8px;
|
|
transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
|
|
}
|
|
|
|
.setup-domain-add:hover {
|
|
background: rgba(var(--accent-rgb), 0.20);
|
|
border-color: rgba(var(--accent-rgb), 0.65);
|
|
border-style: solid;
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.setup-domain-add span {
|
|
font-size: 16px;
|
|
line-height: 1;
|
|
}
|
|
|
|
.setup-step-note {
|
|
font-size: 12px;
|
|
color: rgba(var(--text-rgb), 0.65);
|
|
margin: 12px 0 0;
|
|
font-style: italic;
|
|
}
|
|
|
|
.setup-section-hint {
|
|
font-size: 12px;
|
|
color: rgba(var(--text-rgb), 0.62);
|
|
margin: 0 0 10px;
|
|
}
|
|
|
|
/* Field styling — matches the login form's compact subtle look:
|
|
small lowercase labels, translucent inputs with cyan focus glow. */
|
|
.setup-field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.35rem;
|
|
}
|
|
|
|
.setup-field label {
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
color: var(--text-secondary);
|
|
letter-spacing: 0.02em;
|
|
text-transform: none;
|
|
margin: 0;
|
|
}
|
|
|
|
.setup-field input[type=text],
|
|
.setup-field input[type=email],
|
|
.setup-field select {
|
|
width: 100%;
|
|
background: rgba(var(--text-rgb), 0.06);
|
|
border: 1px solid rgba(var(--text-rgb), 0.12);
|
|
border-radius: 8px;
|
|
padding: 0.6rem 0.875rem;
|
|
color: var(--text-primary);
|
|
font-size: 0.95rem;
|
|
font-family: inherit;
|
|
box-sizing: border-box;
|
|
backdrop-filter: blur(8px);
|
|
-webkit-backdrop-filter: blur(8px);
|
|
transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
|
|
}
|
|
|
|
.setup-field input[type=text]:focus,
|
|
.setup-field input[type=email]:focus,
|
|
.setup-field select:focus {
|
|
outline: none;
|
|
background: rgba(var(--text-rgb), 0.10);
|
|
border-color: rgba(var(--accent-rgb), 0.55);
|
|
box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.18);
|
|
}
|
|
|
|
.setup-field input::placeholder {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* Live validation — green border + glow when valid, red when invalid.
|
|
The error message is held on data-error and surfaced via a tooltip
|
|
that floats ABOVE the input on focus/hover (like the ? badge does).
|
|
Uses a bright mint #86efac (134,239,172) so the border reads against
|
|
the dark wizard backdrop — the theme's #28a745 is too muddy here. */
|
|
.setup-field input.is-valid,
|
|
.setup-field select.is-valid {
|
|
border-color: #86efac;
|
|
box-shadow: 0 0 0 3px rgba(134, 239, 172, 0.22);
|
|
}
|
|
|
|
.setup-field input.is-invalid,
|
|
.setup-field select.is-invalid {
|
|
border-color: rgba(var(--status-danger-rgb), 0.85);
|
|
box-shadow: 0 0 0 3px rgba(var(--status-danger-rgb), 0.22);
|
|
}
|
|
|
|
/* Mirror the valid/invalid state onto the custom-select button when the
|
|
native <select> has been enhanced. The native element keeps the .is-*
|
|
class (and stays in the DOM hidden) so :has() still resolves. */
|
|
.setup-input-row:has(select.is-valid) .custom-select-button {
|
|
border-color: #86efac;
|
|
box-shadow: 0 0 0 3px rgba(134, 239, 172, 0.22);
|
|
}
|
|
|
|
.setup-input-row:has(select.is-invalid) .custom-select-button {
|
|
border-color: rgba(var(--status-danger-rgb), 0.85);
|
|
box-shadow: 0 0 0 3px rgba(var(--status-danger-rgb), 0.22);
|
|
}
|
|
|
|
/* Make room for the left field icon on the themed dropdown button. */
|
|
.setup-input-row > .custom-select .custom-select-button {
|
|
padding-left: 2.25rem;
|
|
}
|
|
|
|
/* The custom-select button has its own chevron + we paint the validity
|
|
state via its border, so the right-side ✓/✕ badge is redundant. */
|
|
.setup-input-row:has(select.custom-select-native)::after {
|
|
display: none;
|
|
}
|
|
|
|
/* Valid checkmark + invalid cross indicator on the right side of the input */
|
|
.setup-input-row::after {
|
|
content: '';
|
|
position: absolute;
|
|
right: 14px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 16px;
|
|
height: 16px;
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
background-size: contain;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.setup-input-row:has(.is-valid)::after {
|
|
opacity: 1;
|
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2348c774' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><polyline points='20 6 9 17 4 12'/></svg>");
|
|
}
|
|
|
|
.setup-input-row:has(.is-invalid)::after {
|
|
opacity: 1;
|
|
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ef4444' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'><line x1='18' y1='6' x2='6' y2='18'/><line x1='6' y1='6' x2='18' y2='18'/></svg>");
|
|
}
|
|
|
|
.setup-input-row:has(.is-valid) .setup-input-with-icon,
|
|
.setup-input-row:has(.is-invalid) .setup-input-with-icon {
|
|
padding-right: 2.5rem !important;
|
|
}
|
|
|
|
/* Shift the tick/cross left when sitting on a <select> — the browser's
|
|
native chevron occupies the right edge, so right:14px collides with it. */
|
|
.setup-input-row:has(select.is-valid)::after,
|
|
.setup-input-row:has(select.is-invalid)::after {
|
|
right: 32px;
|
|
}
|
|
|
|
.setup-input-row:has(select.is-valid) .setup-input-with-icon,
|
|
.setup-input-row:has(select.is-invalid) .setup-input-with-icon {
|
|
padding-right: 3.5rem !important;
|
|
}
|
|
|
|
/* Error message floating above the input — same visual language as the
|
|
? tooltip but anchored to the input row. We read the message from
|
|
data-error which the JS mirrors from the input onto the row (pseudo
|
|
elements can only read attrs from their own host element). */
|
|
.setup-input-row[data-error]::before {
|
|
content: attr(data-error);
|
|
position: absolute;
|
|
bottom: calc(100% + 8px);
|
|
left: 14px;
|
|
background: rgba(var(--bg-rgb), 0.45);
|
|
color: var(--status-danger);
|
|
font-size: 0.72rem;
|
|
font-weight: 500;
|
|
letter-spacing: 0;
|
|
padding: 7px 10px;
|
|
border-radius: 8px;
|
|
border: 1px solid rgba(var(--status-danger-rgb), 0.45);
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
z-index: 11;
|
|
transition: opacity 0.18s ease, transform 0.18s ease;
|
|
transform: translateY(4px);
|
|
}
|
|
|
|
.setup-input-row[data-error]:hover::before,
|
|
.setup-input-row[data-error]:focus-within::before {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* Input with leading icon — icon sits absolutely positioned over the
|
|
left padding zone of the input. Reroll button (when present) sits to
|
|
the right of the input via the row's flex layout. */
|
|
.setup-input-row {
|
|
position: relative;
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.setup-field-icon {
|
|
position: absolute;
|
|
left: 12px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: var(--accent);
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
transition: color 0.2s ease, filter 0.2s ease;
|
|
}
|
|
|
|
.setup-input-row .setup-input-with-icon:focus ~ .setup-field-icon,
|
|
.setup-input-row:focus-within .setup-field-icon {
|
|
color: var(--accent);
|
|
}
|
|
|
|
.setup-input-with-icon {
|
|
flex: 1;
|
|
padding-left: 2.25rem !important;
|
|
}
|
|
|
|
.setup-field-icon svg { display: block; }
|
|
|
|
.setup-field-icon-emoji {
|
|
font-size: 16px;
|
|
line-height: 1;
|
|
}
|
|
|
|
.setup-input-row:focus-within .setup-field-icon-emoji {
|
|
}
|
|
|
|
#sw-name.setup-input-with-icon {
|
|
font-family: 'SF Mono', Menlo, monospace;
|
|
letter-spacing: 0.3px;
|
|
color: var(--accent);
|
|
padding-right: 7.75rem !important;
|
|
}
|
|
|
|
.setup-input-row:has(#sw-name.is-valid) #sw-name,
|
|
.setup-input-row:has(#sw-name.is-invalid) #sw-name {
|
|
padding-right: 7.75rem !important;
|
|
}
|
|
|
|
.setup-input-row:has(#sw-name)::after {
|
|
display: none;
|
|
}
|
|
|
|
/* Tooltip "?" badge after the label. Hover or keyboard-focus reveals
|
|
a small floating tip with the description text. */
|
|
.setup-tooltip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 14px;
|
|
height: 14px;
|
|
border-radius: 50%;
|
|
background: rgba(var(--accent-rgb), 0.15);
|
|
color: var(--accent);
|
|
font-size: 0.65rem;
|
|
font-weight: 700;
|
|
margin-left: 6px;
|
|
cursor: help;
|
|
position: relative;
|
|
user-select: none;
|
|
vertical-align: middle;
|
|
border: 1px solid rgba(var(--accent-rgb), 0.35);
|
|
transition: background 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
|
|
}
|
|
|
|
.setup-tooltip:hover,
|
|
.setup-tooltip:focus {
|
|
outline: none;
|
|
background: rgba(var(--accent-rgb), 0.30);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.setup-tooltip::after {
|
|
content: attr(data-tip);
|
|
position: absolute;
|
|
bottom: calc(100% + 8px);
|
|
left: 50%;
|
|
transform: translateX(-50%) translateY(4px);
|
|
background: rgba(var(--bg-rgb), 0.45);
|
|
color: var(--text-primary);
|
|
font-size: 0.72rem;
|
|
font-weight: 400;
|
|
letter-spacing: 0;
|
|
text-transform: none;
|
|
padding: 8px 10px;
|
|
border-radius: 8px;
|
|
border: 1px solid rgba(var(--accent-rgb), 0.40);
|
|
width: max-content;
|
|
max-width: 240px;
|
|
white-space: normal;
|
|
text-align: left;
|
|
line-height: 1.35;
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease, transform 0.15s ease;
|
|
z-index: 10;
|
|
}
|
|
|
|
.setup-tooltip::before {
|
|
content: '';
|
|
position: absolute;
|
|
bottom: calc(100% + 2px);
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
border: 5px solid transparent;
|
|
border-top-color: rgba(var(--accent-rgb), 0.55);
|
|
pointer-events: none;
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
|
|
.setup-tooltip:hover::after,
|
|
.setup-tooltip:focus::after {
|
|
opacity: 1;
|
|
transform: translateX(-50%) translateY(0);
|
|
}
|
|
|
|
.setup-tooltip:hover::before,
|
|
.setup-tooltip:focus::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
.setup-name-pulse {
|
|
animation: setupNamePulse 0.6s ease;
|
|
}
|
|
|
|
@keyframes setupNamePulse {
|
|
0% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.55); }
|
|
60% { box-shadow: 0 0 0 12px rgba(var(--accent-rgb), 0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0); }
|
|
}
|
|
|
|
.setup-manifest {
|
|
position: absolute;
|
|
right: 6px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
height: calc(100% - 12px);
|
|
z-index: 2;
|
|
background: rgba(var(--accent-rgb), 0.12);
|
|
color: var(--accent);
|
|
border: 1px solid rgba(var(--accent-rgb), 0.32);
|
|
border-radius: 8px;
|
|
padding: 0 12px;
|
|
font-size: 13px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
transition: background 0.18s ease, border-color 0.18s ease, transform 0.15s ease, box-shadow 0.18s ease, color 0.18s ease;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.setup-manifest .setup-manifest-icon {
|
|
color: var(--accent);
|
|
transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1), color 0.2s ease, filter 0.2s ease;
|
|
}
|
|
|
|
.setup-manifest:hover {
|
|
background: rgba(var(--accent-rgb), 0.22);
|
|
border-color: rgba(var(--accent-rgb), 0.55);
|
|
color: var(--text-primary);
|
|
transform: translateY(calc(-50% - 1px));
|
|
}
|
|
|
|
.setup-manifest:hover .setup-manifest-icon {
|
|
color: var(--accent);
|
|
}
|
|
|
|
/* Click animation: full-spin icon + cosmic burst halo around the button */
|
|
.setup-manifest.manifesting {
|
|
animation: manifestBurst 0.7s ease;
|
|
}
|
|
|
|
.setup-manifest.manifesting .setup-manifest-icon {
|
|
transform: rotate(360deg);
|
|
}
|
|
|
|
@keyframes manifestBurst {
|
|
0% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.75), 0 0 0 0 rgba(var(--accent-rgb), 0.55); }
|
|
60% { box-shadow: 0 0 0 14px rgba(var(--accent-rgb), 0), 0 0 0 28px rgba(var(--accent-rgb), 0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0), 0 0 0 0 rgba(var(--accent-rgb), 0); }
|
|
}
|
|
|
|
/* DNS check status */
|
|
.setup-dns-status {
|
|
font-size: 12px;
|
|
margin-top: 8px;
|
|
padding: 6px 10px;
|
|
border-radius: 6px;
|
|
font-family: 'SF Mono', Menlo, monospace;
|
|
min-height: 14px;
|
|
}
|
|
|
|
.setup-dns-status.checking {
|
|
background: rgba(var(--text-rgb), 0.05);
|
|
color: rgba(var(--text-rgb), 0.6);
|
|
}
|
|
|
|
.setup-dns-status.ok {
|
|
background: rgba(var(--status-success-rgb), 0.12);
|
|
color: var(--status-success);
|
|
border: 1px solid rgba(var(--status-success-rgb), 0.3);
|
|
}
|
|
|
|
.setup-dns-status.warn {
|
|
background: rgba(var(--status-warning-rgb), 0.10);
|
|
color: var(--status-warning);
|
|
border: 1px solid rgba(var(--status-warning-rgb), 0.3);
|
|
}
|
|
|
|
/* App selection sections */
|
|
.setup-section {
|
|
border-top: 1px solid rgba(var(--text-rgb), 0.06);
|
|
padding-top: 14px;
|
|
}
|
|
.setup-section:first-child { border-top: none; padding-top: 0; }
|
|
|
|
.setup-section-title {
|
|
font-size: 12px;
|
|
font-weight: 700;
|
|
letter-spacing: 1.5px;
|
|
text-transform: uppercase;
|
|
color: rgba(var(--accent-rgb), 1);
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.setup-app {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 14px;
|
|
padding: 12px 14px;
|
|
background: rgba(var(--text-rgb), 0.03);
|
|
border: 1px solid rgba(var(--text-rgb), 0.08);
|
|
border-radius: 12px;
|
|
cursor: pointer;
|
|
margin-bottom: 8px;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.setup-app:hover {
|
|
background: rgba(var(--text-rgb), 0.06);
|
|
border-color: rgba(var(--accent-rgb), 0.25);
|
|
}
|
|
|
|
.setup-app input[type=checkbox] {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 20px;
|
|
height: 20px;
|
|
flex-shrink: 0;
|
|
cursor: pointer;
|
|
border-radius: 6px;
|
|
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;
|
|
}
|
|
|
|
.setup-app:hover input[type=checkbox] {
|
|
border-color: rgba(var(--accent-rgb), 0.55);
|
|
box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.08);
|
|
}
|
|
|
|
.setup-app input[type=checkbox]:focus-visible {
|
|
outline: none;
|
|
border-color: rgba(var(--accent-rgb), 0.85);
|
|
box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.20);
|
|
}
|
|
|
|
.setup-app input[type=checkbox]:checked {
|
|
background: linear-gradient(135deg, var(--accent), var(--accent));
|
|
border-color: rgba(var(--accent-rgb), 0.9);
|
|
box-shadow: 0 0 0 1px rgba(var(--accent-rgb), 0.35);
|
|
}
|
|
|
|
.setup-app 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: 14px 14px;
|
|
animation: setupCheckPop 0.22s cubic-bezier(0.34, 1.56, 0.64, 1);
|
|
}
|
|
|
|
@keyframes setupCheckPop {
|
|
0% { transform: scale(0.4); opacity: 0; }
|
|
100% { transform: scale(1); opacity: 1; }
|
|
}
|
|
|
|
.setup-app:has(input:checked) {
|
|
background: rgba(var(--accent-rgb), 0.10);
|
|
border-color: rgba(var(--accent-rgb), 0.45);
|
|
}
|
|
|
|
.setup-app-icon-wrap {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 9px;
|
|
background: rgba(var(--text-rgb), 0.06);
|
|
border: 1px solid rgba(var(--text-rgb), 0.08);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.setup-app-icon-wrap .setup-app-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.setup-app:has(input:checked) .setup-app-icon-wrap {
|
|
background: rgba(var(--accent-rgb), 0.20);
|
|
border-color: rgba(var(--accent-rgb), 0.45);
|
|
}
|
|
|
|
.setup-app-info { flex: 1; min-width: 0; }
|
|
.setup-app-name { font-size: 14px; font-weight: 600; color: var(--text-primary); }
|
|
.setup-app-desc { font-size: 12px; color: rgba(var(--text-rgb), 0.82); margin-top: 2px; }
|
|
|
|
/* Parent tile + sub-option as one merged card. Parent loses its bottom
|
|
radius; sub-option is a flush drawer below with only the bottom corners
|
|
rounded. Shared horizontal bounds so the two pieces read as one. */
|
|
.setup-app-group { margin-bottom: 8px; }
|
|
|
|
.setup-app-group .setup-app {
|
|
margin-bottom: 0;
|
|
border-bottom-left-radius: 0;
|
|
border-bottom-right-radius: 0;
|
|
border-bottom-color: rgba(var(--text-rgb), 0.04);
|
|
}
|
|
|
|
.setup-app-suboption {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
/* Left padding lines the sub-checkbox up under the parent's checkbox
|
|
(parent: 14px padding + ~3px to centre the smaller 14px box). */
|
|
padding: 7px 14px 8px 17px;
|
|
margin: 0;
|
|
background: rgba(var(--text-rgb), 0.035);
|
|
border: 1px solid rgba(var(--text-rgb), 0.08);
|
|
border-top: none;
|
|
border-radius: 0 0 12px 12px;
|
|
font-size: 12px;
|
|
color: rgba(var(--text-rgb), 0.82);
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
/* When the parent is selected, the drawer picks up the same blue tint so
|
|
the merged card reads as one selected unit. */
|
|
.setup-app-group:has(.setup-app input[type=checkbox]:checked) .setup-app {
|
|
border-bottom-color: rgba(var(--accent-rgb), 0.30);
|
|
}
|
|
.setup-app-group:has(.setup-app input[type=checkbox]:checked) .setup-app-suboption {
|
|
background: rgba(var(--accent-rgb), 0.08);
|
|
border-color: rgba(var(--accent-rgb), 0.40);
|
|
border-top: none;
|
|
}
|
|
|
|
.setup-app-suboption:hover {
|
|
background: rgba(var(--accent-rgb), 0.12);
|
|
}
|
|
.setup-app-suboption.disabled {
|
|
opacity: 0.35;
|
|
pointer-events: none;
|
|
}
|
|
.setup-app-suboption input[type=checkbox] {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
width: 14px;
|
|
height: 14px;
|
|
flex-shrink: 0;
|
|
cursor: pointer;
|
|
border-radius: 4px;
|
|
background: rgba(var(--text-rgb), 0.04);
|
|
border: 1.4px solid rgba(var(--text-rgb), 0.20);
|
|
position: relative;
|
|
transition: background 0.15s ease, border-color 0.15s ease;
|
|
}
|
|
.setup-app-suboption:hover input[type=checkbox] {
|
|
border-color: rgba(var(--accent-rgb), 0.55);
|
|
}
|
|
.setup-app-suboption input[type=checkbox]:checked {
|
|
background: linear-gradient(135deg, var(--accent), var(--accent));
|
|
border-color: rgba(var(--accent-rgb), 0.9);
|
|
}
|
|
.setup-app-suboption 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: 10px 10px;
|
|
}
|
|
.setup-app-suboption-label { font-weight: 500; }
|
|
|
|
/* Navigation */
|
|
.setup-nav {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.setup-btn-back,
|
|
.setup-btn-next {
|
|
background: rgba(var(--text-rgb), 0.06);
|
|
color: var(--text-primary);
|
|
border: 1px solid rgba(var(--text-rgb), 0.12);
|
|
border-radius: 12px;
|
|
padding: 14px 18px;
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
transition: all 0.15s ease;
|
|
}
|
|
|
|
.setup-btn-back { flex: 0 0 auto; }
|
|
.setup-btn-next { flex: 1; }
|
|
|
|
.setup-btn-back:hover:not(:disabled),
|
|
.setup-btn-next:hover:not(:disabled) {
|
|
background: rgba(var(--accent-rgb), 0.14);
|
|
border-color: rgba(var(--accent-rgb), 0.40);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.setup-btn-back:disabled {
|
|
opacity: 0.35;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.setup-launch {
|
|
flex: 1;
|
|
background: linear-gradient(135deg, var(--accent), var(--accent-hover));
|
|
border: none;
|
|
border-radius: 12px;
|
|
padding: 14px 18px;
|
|
color: var(--text-primary);
|
|
font-size: 15px;
|
|
font-weight: 700;
|
|
letter-spacing: 0.5px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 10px;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.setup-launch:hover:not(:disabled) {
|
|
transform: translateY(-2px);
|
|
filter: brightness(1.05);
|
|
}
|
|
|
|
.setup-launch:active:not(:disabled) { transform: translateY(0); }
|
|
.setup-launch:disabled { opacity: 0.6; cursor: not-allowed; }
|
|
|
|
.setup-launch-arrow { transition: transform 0.2s ease; }
|
|
.setup-launch:hover:not(:disabled) .setup-launch-arrow { transform: translateX(4px); }
|
|
|
|
/* Error */
|
|
.setup-error {
|
|
background: rgba(var(--status-danger-rgb), 0.10);
|
|
border: 1px solid rgba(var(--status-danger-rgb), 0.3);
|
|
color: var(--status-danger);
|
|
padding: 10px 14px;
|
|
border-radius: 10px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
/* Top-nav disabled state — applied while setup isn't complete. */
|
|
.topbar-nav.setup-needed .nav-item {
|
|
opacity: 0.35;
|
|
pointer-events: none;
|
|
filter: grayscale(60%);
|
|
}
|
|
|
|
/* Setup-in-progress banner — pinned to top of viewport while the wizard's
|
|
tasks are still running (any page). Auto-removed when finalize completes. */
|
|
.setup-progress-banner {
|
|
position: fixed;
|
|
top: 14px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
z-index: 9000;
|
|
background: rgba(var(--bg-rgb), 0.45);
|
|
border: 1px solid rgba(var(--accent-rgb), 0.40);
|
|
border-radius: 12px;
|
|
padding: 10px 16px;
|
|
color: var(--text-primary);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
font-size: 13px;
|
|
min-width: 320px;
|
|
max-width: min(520px, 92vw);
|
|
animation: setupBannerIn 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
}
|
|
|
|
.setup-progress-banner.leaving {
|
|
animation: setupBannerOut 0.35s ease both;
|
|
}
|
|
|
|
.setup-progress-banner.failed {
|
|
border-color: rgba(var(--status-danger-rgb), 0.5);
|
|
}
|
|
|
|
.setup-progress-banner-inner {
|
|
display: grid;
|
|
grid-template-columns: 18px 1fr;
|
|
grid-template-rows: auto auto;
|
|
column-gap: 12px;
|
|
align-items: center;
|
|
}
|
|
|
|
.setup-progress-banner-icon {
|
|
grid-row: 1 / span 2;
|
|
color: var(--accent);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.setup-progress-banner.failed .setup-progress-banner-icon {
|
|
color: var(--status-danger);
|
|
}
|
|
|
|
.setup-progress-banner-text {
|
|
grid-column: 2;
|
|
grid-row: 1;
|
|
letter-spacing: 0.2px;
|
|
}
|
|
|
|
.setup-progress-banner-text strong {
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.setup-progress-banner-count {
|
|
color: rgba(var(--text-rgb), 0.78);
|
|
font-family: 'SF Mono', Menlo, monospace;
|
|
font-size: 12px;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.setup-progress-banner-bar {
|
|
grid-column: 2;
|
|
grid-row: 2;
|
|
height: 4px;
|
|
background: rgba(var(--text-rgb), 0.08);
|
|
border-radius: 999px;
|
|
overflow: hidden;
|
|
margin-top: 6px;
|
|
}
|
|
|
|
.setup-progress-banner-fill {
|
|
height: 100%;
|
|
width: 0%;
|
|
background: linear-gradient(90deg, var(--accent), var(--accent-hover));
|
|
border-radius: 999px;
|
|
transition: width 0.5s cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
.setup-progress-banner.failed .setup-progress-banner-fill {
|
|
background: linear-gradient(90deg, var(--status-danger), var(--status-danger-hover));
|
|
}
|
|
|
|
@keyframes setupBannerIn {
|
|
from { opacity: 0; transform: translate(-50%, -16px); }
|
|
to { opacity: 1; transform: translate(-50%, 0); }
|
|
}
|
|
|
|
@keyframes setupBannerOut {
|
|
from { opacity: 1; transform: translate(-50%, 0); }
|
|
to { opacity: 0; transform: translate(-50%, -16px); }
|
|
}
|
|
|
|
@media (max-width: 600px) {
|
|
.setup-shell { padding: 22px 18px 18px; border-radius: 14px; }
|
|
.setup-logo h1 { font-size: 20px; }
|
|
.setup-input-row { flex-direction: column; }
|
|
.setup-input-row .setup-field-icon { top: 22px; transform: none; }
|
|
.setup-input-row .setup-input-with-icon { padding-left: 2.25rem !important; }
|
|
.setup-reroll { padding: 10px; }
|
|
.setup-nav { flex-direction: column; }
|
|
.setup-btn-back { order: 2; }
|
|
.setup-btn-next, .setup-launch { order: 1; }
|
|
}
|