/*
 * Logisoft Embed — page-shell stylesheet.
 *
 * Token aliases live in embed.tokens.css (linked before this file in App.razor).
 * Per-tenant brand overrides are written into <head><style> by each page
 * from PortalConfiguration (PrimaryColor / SecondaryColor) so tenants get
 * personality without overriding semantic tokens.
 *
 * Light + dark are first-class (embed.tokens.css already covers both).
 */

* {
    box-sizing: border-box;
}

html, body {
    margin: 0;
    padding: 0;
    background: var(--nova-canvas);
    color: var(--nova-ink);
    font-family: var(--nova-font-sans, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif);
    font-variant-numeric: tabular-nums;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* Native form controls default to their own UA font (notably <textarea>, whose
   UA default is monospace). Inherit the body sans so prose fields (message,
   names, email) read as body text on host sites. Identifier fields (VIN, voyage
   codes) opt back into --nova-font-mono via their higher-specificity scoped rules. */
input,
select,
textarea,
button {
    font-family: inherit;
}

.nova-embed-landing {
    max-width: 720px;
    margin: 64px auto;
    padding: 0 var(--nova-space-5, 24px);
}

.nova-embed-landing h1 {
    font-size: 28px;
    font-weight: 600;
    margin: 0 0 var(--nova-space-4, 16px);
    letter-spacing: -0.01em;
}

.nova-embed-landing p {
    color: var(--nova-ink-muted);
    line-height: 1.55;
}

.nova-embed-landing code {
    background: var(--nova-canvas-sunken);
    padding: 2px 6px;
    border-radius: 4px;
    font-size: 0.9em;
    color: var(--nova-ink);
}

.nova-embed-empty {
    max-width: 480px;
    margin: 96px auto;
    padding: 0 var(--nova-space-5, 24px);
    text-align: center;
}

.nova-embed-empty h1 {
    font-size: 22px;
    font-weight: 600;
    margin: 0 0 var(--nova-space-3, 12px);
}

.nova-embed-empty p {
    color: var(--nova-ink-muted);
    margin: 0;
}

/* ── Shared form vocabulary ──────────────────────────────────────────────
 * One card / control / field language for the embed's data-entry forms
 * (contact + quotation today). Authored once here in the GLOBAL sheet, not
 * per-page scoped .razor.css, so the forms stop drifting into three
 * near-identical copies. Consumes the embed --nova-* alias tokens, so it
 * migrates cleanly when the embed graduates onto DS token names (backlogged
 * approach B). Brand colour is token-only (var(--nova-brand*)), so the
 * per-tenant EmbedTenantBranding :root override keeps working.
 *
 * Tracking + Schedules keep their own scoped control rules for now; adopting
 * this vocabulary there is a logged follow-up (their scoped [b-xxxx] selectors
 * would otherwise outrank these global classes). Page roots (.nova-contact,
 * .nova-quote) and each page's responsive collapse stay page-scoped, since the
 * forms have different content caps and breakpoints.
 */

/* Elevated form surface — the crafted card the flat forms lacked. The border
 * is the primary edge; the subtle shadow adds depth on (predominantly light)
 * host pages. Deliberately NO `overflow` property: it would become the
 * containing block for positioned descendants. */
.nova-form-card {
    border: 1px solid var(--nova-divider);
    border-radius: var(--nova-radius);
    background: var(--nova-canvas);
    box-shadow: var(--nova-shadow-low);
    padding: var(--nova-space-5);
}

/* Vertical rhythm lives on the container (single gap), never per-field, so
 * rows and standalone fields get one even gap (fixes the old field+row
 * double-margin). */
.nova-form {
    display: flex;
    flex-direction: column;
    gap: var(--nova-space-4);
}

.nova-form-row {
    display: grid;
    gap: var(--nova-space-3);
}

.nova-form-row--two { grid-template-columns: 1fr 1fr; }
.nova-form-row--three { grid-template-columns: 1fr 1fr 1fr; }
.nova-form-row--four { grid-template-columns: 1fr 1fr 1fr 1fr; }

.nova-form-field {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0; /* let grid children shrink below intrinsic content width */
}

.nova-form-field--span-2 { grid-column: span 2; }

.nova-form-label {
    font-size: 12px;
    font-weight: 500;
    color: var(--nova-ink-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.nova-form-optional {
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
    color: var(--nova-ink-muted);
    font-size: 11px;
    margin-left: 4px;
}

.nova-form-hint {
    font-size: 11px;
    color: var(--nova-ink-muted);
    line-height: 1.4;
}

.nova-form-hint code {
    background: var(--nova-canvas-sunken);
    padding: 1px 6px;
    border-radius: 4px;
    font-size: 11px;
    color: var(--nova-ink);
}

.nova-form-error-slot:empty { display: none; }

/* Honeypot: off-screen, never shown to a real user, no layout footprint. Both
 * forms carry the hidden Website field; the server drops any submission that
 * fills it. */
.nova-honeypot {
    position: absolute;
    left: -9999px;
    width: 1px;
    height: 1px;
    overflow: hidden;
}

.nova-form-counter {
    align-self: flex-end;
    font-size: 11px;
    color: var(--nova-ink-muted);
    margin-top: -2px;
}

/* One control base for input / select / textarea: uniform 44px height (was a
 * 40/42/44 mix across the forms), visible border, tasteful radius, brand focus
 * ring off the --nova-brand-soft token. */
.nova-form-control {
    width: 100%;
    height: 44px;
    padding: 0 var(--nova-space-4);
    border-radius: var(--nova-radius);
    border: 1px solid var(--nova-divider);
    background: var(--nova-canvas);
    color: var(--nova-ink);
    font-family: inherit;
    font-size: 15px;
    transition: border-color 120ms ease, box-shadow 120ms ease;
}

/* Textarea is the one control that is not a single 44px line. */
textarea.nova-form-control {
    height: auto;
    min-height: 120px;
    padding: 10px var(--nova-space-4);
    line-height: 1.45;
    resize: vertical;
}

.nova-form-control:focus-visible {
    outline: none;
    border-color: var(--nova-brand);
    box-shadow: 0 0 0 3px var(--nova-brand-soft);
}

.nova-form-submit {
    width: 100%;
    height: 44px;
    border: none;
    border-radius: var(--nova-radius);
    background: var(--nova-brand);
    color: var(--nova-brand-foreground);
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
    transition: filter 120ms ease;
}

.nova-form-submit:hover { filter: brightness(1.05); }

.nova-form-submit:focus-visible {
    outline: 2px solid var(--nova-brand);
    outline-offset: 2px;
}

.nova-form-submit:disabled {
    opacity: 0.7;
    cursor: not-allowed;
}

/* Segmented single-choice control (Person / Organization). */
.nova-segmented-group {
    display: flex;
    gap: var(--nova-space-2);
    padding: 0;
    margin: 0;
    border: none;
}

.nova-segmented-group legend {
    display: block;
    margin-bottom: var(--nova-space-2);
}

.nova-segmented {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 44px;
    padding: 0 var(--nova-space-4);
    border-radius: var(--nova-radius);
    border: 1px solid var(--nova-divider);
    background: var(--nova-canvas);
    color: var(--nova-ink-muted);
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    transition: border-color 120ms ease, color 120ms ease, background 120ms ease;
}

.nova-segmented input[type="radio"] {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

/* Active segment is driven by the checked radio, not a server-rendered class, so the
   highlight moves the instant the user clicks. The forms are static-SSR and would otherwise
   only update the class after a POST. */
.nova-segmented:has(input:checked) {
    border-color: var(--nova-brand);
    color: var(--nova-brand);
    background: color-mix(in srgb, var(--nova-brand) 8%, transparent);
}

.nova-segmented:has(input:focus-visible) {
    outline: 2px solid var(--nova-brand);
    outline-offset: 2px;
}

/* Person vs Organization field sets. Both are always in the DOM so the segment switches which
   set shows instantly - the forms are static-SSR, so a server-side @if would only swap after a
   POST. Keyed on the checked segment radio's value, so one rule serves every form that uses the
   segment (Contact + Quotation). Fail-open: the base rule shows BOTH sets and only the
   :has()-guarded block hides the unselected one, so a browser without :has() support renders
   both (still correct - the server keys off form.Type via RequiredWhenType + the DTO mapping)
   instead of hiding both and stranding the user with no name fields. */
.nova-person-fields,
.nova-org-fields {
    display: grid;
}

@supports selector(:has(*)) {
    .nova-person-fields,
    .nova-org-fields {
        display: none;
    }

    form:has(.nova-segmented input[value="Person"]:checked) .nova-person-fields {
        display: grid;
    }

    form:has(.nova-segmented input[value="Organization"]:checked) .nova-org-fields {
        display: grid;
    }
}

.nova-form-checkbox {
    display: flex;
    align-items: center;
    gap: var(--nova-space-3);
    font-size: 13px;
    color: var(--nova-ink);
    cursor: pointer;
}

.nova-form-checkbox input[type="checkbox"] {
    width: 18px;
    height: 18px;
    accent-color: var(--nova-brand);
}

.nova-form-checkbox input[type="checkbox"]:focus-visible {
    outline: 2px solid var(--nova-brand);
    outline-offset: 2px;
}

/* Status panels — validation error strip, success, and unavailable notice. */
.nova-form-error {
    padding: 12px var(--nova-space-4);
    border-radius: var(--nova-radius);
    background: color-mix(in srgb, var(--nova-severity-critical-text) 12%, var(--nova-canvas));
    color: var(--nova-ink);
    font-size: 13px;
}

.nova-form-success {
    padding: var(--nova-space-6);
    border-radius: var(--nova-radius);
    border: 1px solid var(--nova-divider);
    background: color-mix(in srgb, var(--nova-brand) 6%, var(--nova-canvas));
    text-align: center;
}

.nova-form-success h2 {
    margin: 0 0 var(--nova-space-2);
    font-size: 18px;
    font-weight: 600;
    color: var(--nova-ink);
}

.nova-form-success p {
    margin: var(--nova-space-2) 0 0;
    color: var(--nova-ink-muted);
}

.nova-form-success p:first-of-type {
    margin-top: 0;
}

.nova-form-success-cta {
    margin-top: var(--nova-space-4) !important;
}

.nova-form-success-cta a {
    color: var(--nova-brand);
    text-decoration: none;
    font-weight: 500;
}

.nova-form-success-cta a:hover {
    text-decoration: underline;
}

.nova-form-notice {
    padding: var(--nova-space-6);
    text-align: center;
    background: var(--nova-canvas-sunken);
    border-radius: var(--nova-radius);
}

.nova-form-notice h2 {
    margin: 0 0 var(--nova-space-2);
    font-size: 18px;
    font-weight: 600;
    color: var(--nova-ink);
}

.nova-form-notice p {
    margin: 0;
    color: var(--nova-ink-muted);
}

/* DataAnnotations validation text. ValidationMessage renders
 * <div class="validation-message"> inside each field's .nova-form-error-slot,
 * so this targets it without needing a scoped ::deep. */
.nova-form-error-slot .validation-message {
    color: var(--nova-severity-critical-text);
    font-size: 12px;
    margin-top: 4px;
}

/* Typeahead dropdown (progressive enhancement over a plain input; hidden with no JS). The list is
 * absolutely positioned so it overlays the fields below without resizing the iframe, and the form
 * card carries no `overflow` so the dropdown is never clipped. */
.nova-typeahead {
    position: relative;
}

.nova-typeahead-list {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    z-index: 20;
    margin: 0;
    padding: 4px;
    list-style: none;
    max-height: 240px;
    overflow-y: auto;
    background: var(--nova-canvas);
    border: 1px solid var(--nova-divider);
    border-radius: var(--nova-radius);
    box-shadow: var(--nova-shadow-low);
}

.nova-typeahead-list[hidden] {
    display: none;
}

.nova-typeahead-option {
    padding: 8px var(--nova-space-3);
    border-radius: 4px;
    font-size: 14px;
    color: var(--nova-ink);
    cursor: pointer;
}

.nova-typeahead-option.is-active,
.nova-typeahead-option:hover {
    background: color-mix(in srgb, var(--nova-brand) 10%, transparent);
    color: var(--nova-brand);
}

/* "+ Use ..." create row — brand-tinted to read as an action, not a catalog match. */
.nova-typeahead-create {
    color: var(--nova-brand);
    font-weight: 500;
}

/* Date field frame — mirrors the DS nova-datepicker: a leading calendar glyph on the native
 * <input type="date">. The calendar popup itself stays the browser-native one (exactly what the
 * DS NovaDatePicker and atlas do — they render a plain <input type="date"> too). CSS-only via a
 * masked ::before so the glyph follows the theme (--nova-ink-muted); no JS, degrades cleanly. */
.nova-datefield {
    position: relative;
}

.nova-datefield::before {
    content: "";
    position: absolute;
    left: 12px;
    top: 50%;
    width: 18px;
    height: 18px;
    transform: translateY(-50%);
    background-color: var(--nova-ink-muted);
    -webkit-mask: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='3'%20y='4'%20width='18'%20height='18'%20rx='2'/%3E%3Cline%20x1='16'%20y1='2'%20x2='16'%20y2='6'/%3E%3Cline%20x1='8'%20y1='2'%20x2='8'%20y2='6'/%3E%3Cline%20x1='3'%20y1='10'%20x2='21'%20y2='10'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20stroke='%23000'%20stroke-width='2'%20stroke-linecap='round'%20stroke-linejoin='round'%3E%3Crect%20x='3'%20y='4'%20width='18'%20height='18'%20rx='2'/%3E%3Cline%20x1='16'%20y1='2'%20x2='16'%20y2='6'/%3E%3Cline%20x1='8'%20y1='2'%20x2='8'%20y2='6'/%3E%3Cline%20x1='3'%20y1='10'%20x2='21'%20y2='10'/%3E%3C/svg%3E") center / contain no-repeat;
    pointer-events: none;
}

.nova-datefield .nova-form-control {
    padding-left: 38px;
}

/* ── QuoteOffer island (live-rate offer) ─────────────────────────────────
 * Renders unscoped inside Quotation.razor once step-4 autocharge returns a
 * priced offer (see Components/Quotation/QuoteOffer.razor): a sibling of
 * .nova-form-card under .nova-quote, never nested inside it, so the offer
 * root reuses the same elevated-card treatment (border / radius / shadow /
 * padding) rather than duplicating a second card class. Global classes, not
 * a .razor.css: QuoteOffer's root still gets Quotation.razor's CSS-isolation
 * scope attribute, but that only matters for scoped rules - these are plain
 * global selectors and apply regardless. */
.nova-offer {
    display: block;
    border: 1px solid var(--nova-divider);
    border-radius: var(--nova-radius);
    background: var(--nova-canvas);
    box-shadow: var(--nova-shadow-low);
    padding: var(--nova-space-5);
}

.nova-offer h2 {
    margin: 0 0 var(--nova-space-4);
    font-size: 18px;
    font-weight: 600;
    color: var(--nova-ink);
}

/* Charge table: mandatory rows are plain; optional rows (OfferCharge.Selected,
 * toggled by .nova-offer-optional) are muted so mandatory-vs-optional reads at
 * a glance without a second heading splitting the table. */
.nova-offer-charges {
    width: 100%;
    border-collapse: collapse;
    margin: 0 0 var(--nova-space-4);
}

.nova-offer-charge td {
    padding: 10px 0;
    border-bottom: 1px solid var(--nova-divider);
    font-size: 14px;
    color: var(--nova-ink);
}

.nova-offer-charge-name {
    text-align: left;
}

.nova-offer-charge-amount {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.nova-offer-charge--optional td {
    color: var(--nova-ink-muted);
}

.nova-offer-optional-label {
    display: flex;
    align-items: center;
    gap: var(--nova-space-3);
    cursor: pointer;
}

.nova-offer-optional {
    flex: none;
    width: 18px;
    height: 18px;
    accent-color: var(--nova-brand);
}

.nova-offer-optional:focus-visible {
    outline: 2px solid var(--nova-brand);
    outline-offset: 2px;
}

/* Running total - the offer's visual focal point: it sits in <tfoot>, gets a
 * heavier top rule than the charge rows' hairlines and no bottom border (it
 * is the table's last line), and the amount is brand-toned and the largest
 * number on the card so the price the visitor is about to accept pops over
 * the itemized rows above it. */
.nova-offer-total td {
    padding-top: var(--nova-space-3);
    border-top: 2px solid var(--nova-divider);
    border-bottom: none;
    font-size: 15px;
    font-weight: 600;
    color: var(--nova-ink);
}

.nova-offer-total-amount {
    font-size: 20px;
    font-weight: 700;
    color: var(--nova-brand);
}

.nova-offer-countdown {
    margin: 0 0 var(--nova-space-4);
    font-size: 13px;
    color: var(--nova-ink-muted);
    font-variant-numeric: tabular-nums;
}

/* Status strips - same alert/notice language as .nova-form-error, tinted
 * per severity (accept failure vs. expiry) off the DS severity tokens. */
.nova-offer-error {
    margin: 0 0 var(--nova-space-4);
    padding: 12px var(--nova-space-4);
    border-radius: var(--nova-radius);
    background: color-mix(in srgb, var(--nova-severity-critical-text) 12%, var(--nova-canvas));
    color: var(--nova-ink);
    font-size: 13px;
}

.nova-offer-expired {
    margin: 0 0 var(--nova-space-4);
    padding: 12px var(--nova-space-4);
    border-radius: var(--nova-radius);
    background: color-mix(in srgb, var(--nova-severity-warning-text) 12%, var(--nova-canvas));
    color: var(--nova-ink);
    font-size: 13px;
}

.nova-offer-actions {
    display: flex;
    align-items: center;
    gap: var(--nova-space-3);
    margin: 0;
}

/* Reuses the shared primary-button treatment (.nova-form-submit / .nova-quote-next:
 * brand fill, brightness hover, brand focus ring) under the offer's own class name
 * rather than a second copy of those selectors. */
.nova-offer-accept {
    height: 44px;
    padding: 0 var(--nova-space-5);
    border: none;
    border-radius: var(--nova-radius);
    background: var(--nova-brand);
    color: var(--nova-brand-foreground);
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
    transition: filter 120ms ease;
}

.nova-offer-accept:hover { filter: brightness(1.05); }

.nova-offer-accept:focus-visible {
    outline: 2px solid var(--nova-brand);
    outline-offset: 2px;
}

.nova-offer-accept:disabled {
    opacity: 0.7;
    cursor: not-allowed;
}

.nova-offer-cancel,
.nova-offer-resubmit {
    color: var(--nova-ink-muted);
    text-decoration: none;
    font-size: 14px;
    padding: 8px 12px;
    border-radius: var(--nova-radius);
}

.nova-offer-cancel:hover,
.nova-offer-resubmit:hover {
    color: var(--nova-ink);
}

.nova-offer-cancel:focus-visible,
.nova-offer-resubmit:focus-visible {
    outline: 2px solid var(--nova-brand);
    outline-offset: 2px;
}

/* Accepted-reference panel - mirrors .nova-form-success's language (centered,
 * generous padding, muted body copy) so the interactive island's "you're
 * done" moment reads the same as the static wizard's success panel. No
 * border/background of its own: it renders inside the .nova-offer card. */
.nova-offer-accepted {
    padding: var(--nova-space-6);
    text-align: center;
}

.nova-offer-accepted h2 {
    margin: 0 0 var(--nova-space-2);
}

.nova-offer-accepted p {
    margin: var(--nova-space-2) 0 0;
    color: var(--nova-ink-muted);
}

.nova-offer-accepted p:first-of-type {
    margin-top: 0;
}

.nova-offer-ref {
    color: var(--nova-brand);
    font-family: var(--nova-font-mono);
    font-weight: 600;
}

/* ── Blazor reconnect UI ──────────────────────────────────────────────────
 * QuoteOffer is the embed's only InteractiveServer island, so this is the
 * sole surface that can ever show Blazor's default reconnect <dialog>. No
 * custom ReconnectModal component is registered (see App.razor), so
 * blazor.web.js injects the framework-default markup/classes below at
 * runtime; there is nothing to read in this repo to confirm the exact
 * selectors against, so they are taken verbatim from the framework's known
 * default (Microsoft.AspNetCore.Components.Web, .NET 8+ Blazor Web App
 * template) and re-tokened here rather than left with its hardcoded
 * light-only colors, so a dropped circuit still reads as *this* product
 * inside a small host-page iframe instead of the raw framework default.
 * Toggle classes (components-reconnect-show / -retrying / -failed /
 * -paused / -resume-failed) are applied by blazor.web.js to
 * #components-reconnect-modal; the *-visible helper classes below gate each
 * state's inner content the same way. */
.components-reconnect-first-attempt-visible,
.components-reconnect-repeated-attempt-visible,
.components-reconnect-failed-visible,
.components-pause-visible,
.components-resume-failed-visible,
.components-rejoining-animation {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
    display: block;
}

/* Sized and margined for a small embed iframe, not a full page: narrower
 * top margin than the framework default (20vh) and a max-width clamp so it
 * never overflows a host site's short offer-card iframe. */
#components-reconnect-modal {
    width: 20rem;
    max-width: calc(100% - (2 * var(--nova-space-5)));
    margin: 8vh auto;
    padding: var(--nova-space-6);
    border: 0;
    border-radius: var(--nova-radius);
    background-color: var(--nova-canvas);
    color: var(--nova-ink);
    box-shadow: var(--nova-shadow-low);
    opacity: 0;
    transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
    animation: components-reconnect-modal-fade-out 0.5s both;
}

#components-reconnect-modal[open] {
    animation: components-reconnect-modal-slide-up 1.5s cubic-bezier(0.05, 0.89, 0.25, 1.02) 0.3s,
        components-reconnect-modal-fade-in 0.5s ease-in-out 0.3s;
    animation-fill-mode: both;
}

#components-reconnect-modal::backdrop {
    background-color: rgba(0, 0, 0, 0.4);
    animation: components-reconnect-modal-fade-in 0.5s ease-in-out;
    opacity: 1;
}

@keyframes components-reconnect-modal-slide-up {
    0% { transform: translateY(30px) scale(0.95); }
    100% { transform: translateY(0); }
}

@keyframes components-reconnect-modal-fade-in {
    0% { opacity: 0; }
    100% { opacity: 1; }
}

@keyframes components-reconnect-modal-fade-out {
    0% { opacity: 1; }
    100% { opacity: 0; }
}

.components-reconnect-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--nova-space-4);
}

#components-reconnect-modal p {
    margin: 0;
    text-align: center;
    color: var(--nova-ink);
}

#components-reconnect-modal button {
    height: 36px;
    padding: 0 var(--nova-space-4);
    border: none;
    border-radius: var(--nova-radius);
    background-color: var(--nova-brand);
    color: var(--nova-brand-foreground);
    font-family: inherit;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: filter 120ms ease;
}

#components-reconnect-modal button:hover {
    filter: brightness(1.05);
}

#components-reconnect-modal button:focus-visible {
    outline: 2px solid var(--nova-brand);
    outline-offset: 2px;
}

.components-rejoining-animation {
    position: relative;
    width: 64px;
    height: 64px;
}

.components-rejoining-animation div {
    position: absolute;
    border: 3px solid var(--nova-brand);
    opacity: 1;
    border-radius: 9999px; /* pill radius on a square element == a circle */
    animation: components-rejoining-animation-ring 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}

.components-rejoining-animation div:nth-child(2) {
    animation-delay: -0.5s;
}

@keyframes components-rejoining-animation-ring {
    0% {
        top: 32px;
        left: 32px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    4.9% {
        top: 32px;
        left: 32px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    5% {
        top: 32px;
        left: 32px;
        width: 0;
        height: 0;
        opacity: 1;
    }

    100% {
        top: 0;
        left: 0;
        width: 64px;
        height: 64px;
        opacity: 0;
    }
}
