/* Shared controls and chrome. The visual hierarchy is carried by typography,
   whitespace and rules; containers are used only where interaction needs them. */

/* Buttons */
.btn {
    display: inline-flex;
    min-height: 44px;
    align-items: center;
    justify-content: center;
    gap: 9px;
    padding: 0 19px;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text);
    font-size: var(--portal-small);
    font-weight: 700;
    letter-spacing: 0.045em;
    line-height: 1;
    text-align: center;
    text-transform: uppercase;
    transition:
        transform var(--dur-fast) var(--ease-out-soft),
        border-color var(--dur-fast),
        background var(--dur-fast),
        color var(--dur-fast);
}

.btn:hover {
    transform: translateY(-1px);
}

.btn:active {
    transform: none;
}

.btn-primary {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--accent-ink);
}

.btn-primary:hover {
    border-color: var(--accent-bright);
    background: var(--accent-bright);
}

.btn-secondary {
    border-color: var(--line-strong);
    background: transparent;
    color: var(--text);
}

.btn-secondary:hover {
    border-color: var(--text-faint);
    background: var(--surface-soft);
}

.btn-ghost {
    border-color: transparent;
    color: var(--text-soft);
}

.btn-ghost:hover {
    background: var(--surface-soft);
    color: var(--text);
}

.btn-light {
    border-color: #f7f7f5;
    background: #f7f7f5;
    color: #111217;
}

.btn-premium {
    border-color: var(--pro-from);
    background: var(--pro-gradient);
    color: var(--pro-ink);
}

.btn-lg {
    min-height: 50px;
    padding-inline: 24px;
}

.btn-sm,
.btn-small {
    min-height: 34px;
    padding-inline: 12px;
    font-size: var(--portal-micro);
}

.btn-full {
    width: 100%;
}

.btn-social {
    text-transform: none;
}

.danger-button {
    border-color: var(--error-line);
    color: var(--error);
}

.pill {
    display: inline-flex;
    min-height: 27px;
    align-items: center;
    gap: 7px;
    padding: 0 9px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-soft);
    font-family: var(--font-mono);
    font-size: var(--portal-micro);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* Status badge -------------------------------------------------------------
   ONE badge for every status the site shows. The variant is chosen by MEANING,
   not by which screen it is on:

     ok       online, active, paid, healthy, resolved, reachable
     warn     trial, pending, expiring, degraded, needs review, open
     danger   offline, failed, blocked, expired, revoked, unreachable, invalid
     info     granted, premium, selected, beta, new
     neutral  free, archived, closed, unknown, everything else

   This is the home of the family; portal-specific aliases for the older badge
   class names live in account-portal.css. Do not start a tenth family. */
.tg-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 3px 9px;
    border: 1px solid transparent;
    border-radius: var(--radius-pill);
    font-size: var(--portal-micro);
    font-weight: 600;
    line-height: 1.55;
    white-space: nowrap;
}

.tg-badge--ok {
    border-color: var(--success-line);
    background: var(--success-soft);
    color: var(--success);
}

.tg-badge--warn {
    border-color: var(--warning-line);
    background: var(--warning-soft);
    color: var(--warning);
}

.tg-badge--danger {
    border-color: var(--error-line);
    background: var(--error-soft);
    color: var(--error);
}

.tg-badge--info {
    border-color: var(--accent-strong);
    background: var(--accent-soft);
    color: var(--accent);
}

.tg-badge--neutral {
    border-color: var(--line);
    background: var(--surface-soft);
    color: var(--text-soft);
}

.tg-badge--dot::before {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: currentcolor;
    content: '';
}

/* ── Animated glass backdrop ────────────────────────────────────────────────
   Fixed behind every page. Fractures are generated and animated by
   js/glass-backdrop.js; this only positions the layer and sets its ink.

   z-index -1 puts it behind all in-flow content but still above the <body>
   background, which is what lets it sit under the whole page without a
   stacking-context fight. pointer-events:none is load-bearing — a full-viewport
   fixed layer would otherwise swallow every click on the site. */
.tg-backdrop {
    position: fixed;
    z-index: -1;
    inset: 0;
    width: 100%;
    height: 100%;
    color: var(--accent-bright);
    /* Tuned by eye against the real background, not guessed. A 1px indigo line
       at 0.16 over #08090c is below the perceptual floor — it was rendering
       correctly and still looked like nothing at all. */
    opacity: 0.34;
    pointer-events: none;
}

/* A crack in a dark pane catches the light; a crack in a pale one is a dark
   hairline — so light mode switches the ink, not just the alpha.
   It does NOT get less alpha, which was the wrong assumption first time round:
   the taper puts many strokes below 1px, and a sub-pixel antialiased hairline
   loses most of its contrast, so a "dark on pale carries further" discount left
   it invisible. Measured against the real page, not reasoned about. */
:root[data-theme='light'] .tg-backdrop {
    color: var(--text-strong);
    opacity: 0.34;
}

.tg-backdrop path {
    fill: none;
    stroke: currentcolor;
    stroke-width: 1;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}

/* Marketing navigation */
.site-nav {
    position: sticky;
    top: 0;
    z-index: 100;
    border-bottom: 1px solid var(--line);
    background: color-mix(in srgb, var(--bg) 92%, transparent);
    backdrop-filter: blur(var(--blur-strong));
}

.nav-row {
    display: grid;
    min-height: 72px;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 30px;
}

.nav-links {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: clamp(18px, 2.2vw, 34px);
}

.nav-links a {
    position: relative;
    display: inline-flex;
    min-height: 72px;
    align-items: center;
    color: var(--text-soft);
    font-size: var(--portal-small);
    font-weight: 600;
}

.nav-links a::after {
    position: absolute;
    right: 0;
    bottom: -1px;
    left: 0;
    height: 2px;
    background: var(--accent);
    content: '';
    opacity: 0;
    transform: scaleX(0.4);
    transition:
        opacity var(--dur-fast),
        transform var(--dur-fast);
}

.nav-links a:hover,
.nav-links a.active {
    color: var(--text-strong);
}

.nav-links a:hover::after,
.nav-links a.active::after {
    opacity: 1;
    transform: scaleX(1);
}

/* Must out-specify `.nav-links a` (0,1,1), which sets display:inline-flex.
   As a bare class (0,1,0) this rule LOST, so the "mobile only" links rendered
   in the desktop bar too — which is why Sign in appeared both as a nav link and
   as a button, the duplication the nav cleanup was meant to remove. */
.nav-links a.nav-links-mobile-only {
    display: none;
}

.nav-actions,
.utility-topbar-actions {
    display: flex;
    align-items: center;
    gap: 7px;
}

.nav-actions .btn {
    min-height: 44px;
}

.theme-toggle,
.nav-toggle,
.portal-bell,
.tg-nav-collapse {
    display: inline-grid;
    width: 44px;
    height: 44px;
    flex: 0 0 auto;
    place-items: center;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-soft);
}

.theme-toggle:hover,
.portal-bell:hover,
.tg-nav-collapse:hover {
    border-color: var(--line-strong);
    background: var(--surface-soft);
    color: var(--text);
}

.theme-icon {
    grid-area: 1 / 1;
}

:root[data-theme='dark'] .theme-icon-moon,
:root:not([data-theme='dark']) .theme-icon-sun {
    display: none;
}

.nav-toggle {
    display: none;
    gap: 5px;
}

.nav-toggle span {
    width: 17px;
    height: 1px;
    background: currentColor;
}

.trial-banner {
    border-bottom: 1px solid var(--line);
    background: var(--surface-soft);
}

.trial-banner-inner {
    display: grid;
    min-height: 38px;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 13px;
}

.trial-banner-badge {
    color: var(--accent-link);
    font-family: var(--font-mono);
    font-size: var(--portal-micro);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.trial-banner-copy {
    margin: 0;
    color: var(--text-soft);
    font-size: 0.72rem;
}

.trial-banner-copy strong {
    color: var(--text);
    font-weight: 600;
}

.trial-banner-cta {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: var(--accent-link);
    font-size: var(--portal-micro);
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

/* Utility and portal chrome */
.utility-topbar,
.portal-appbar {
    position: relative;
    z-index: 100;
    border-bottom: 1px solid var(--line);
    background: color-mix(in srgb, var(--bg) 94%, transparent);
    backdrop-filter: blur(var(--blur-strong));
}

.utility-topbar-row,
.portal-appbar-row {
    display: flex;
    min-height: 68px;
    align-items: center;
    justify-content: space-between;
    gap: 20px;
}

/* Forms */
.field {
    display: grid;
    gap: 7px;
}

/* A field label reads as a label, not as code: sentence case, sans, no
   tracking. It was 9.92px uppercase mono in --text-faint, which failed
   contrast and made every form look like a config dump. */
.field > span,
.field > label,
.field-label {
    color: var(--text-soft);
    font-size: var(--portal-micro);
    font-weight: 600;
}

input,
select,
textarea {
    width: 100%;
    min-height: 48px;
    padding: 11px 13px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    outline: 0;
    background: var(--surface-soft);
    color: var(--text);
    transition:
        border-color var(--dur-fast),
        background var(--dur-fast),
        box-shadow var(--dur-fast);
}

textarea {
    min-height: 112px;
    resize: vertical;
}

input::placeholder,
textarea::placeholder {
    color: var(--text-faint);
}

input:focus,
select:focus,
textarea:focus {
    border-color: var(--accent);
    background: var(--surface-strong);
    box-shadow: var(--focus-ring);
}

input[type='checkbox'],
input[type='radio'] {
    width: 17px;
    min-height: 17px;
    padding: 0;
    accent-color: var(--accent);
}

.field-hint,
.consent-copy,
.auth-helper-copy {
    margin: 0;
    color: var(--text-faint);
    font-size: 0.76rem;
}

/* One repeatable input plus its Remove button (e.g. the backup server URLs). */
.field-row {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 8px;
}

.field-row input {
    flex: 1 1 auto;
    min-width: 0;
}

.field-row button {
    flex: 0 0 auto;
}

.field-password {
    position: relative;
}

.field-password input {
    padding-right: 48px;
}

.field-password-toggle {
    position: absolute;
    right: 5px;
    bottom: 4px;
    display: grid;
    width: 40px;
    height: 40px;
    place-items: center;
    border: 0;
    background: transparent;
    color: var(--text-faint);
}

.form-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 15px;
}

.form-grid--three {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

.span-2 {
    grid-column: span 2;
}

.button-stack,
.social-auth,
.otp-actions {
    display: grid;
    gap: 9px;
}

.mode-switch,
.tabs,
.portal-segmented,
.range-tabs {
    display: flex;
    gap: 0;
    border-bottom: 1px solid var(--line);
}

.mode-switch-button,
.tab,
.portal-segmented button,
.range-tabs button {
    min-height: 42px;
    flex: 1;
    padding: 0 14px;
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    background: transparent;
    color: var(--text-soft);
    font-size: 0.74rem;
    font-weight: 600;
}

.mode-switch-button.is-active,
.tab.is-active,
.portal-segmented button.is-active,
.range-tabs button.is-active {
    border-bottom-color: var(--accent);
    background: transparent;
    color: var(--text);
}

.consent-field,
.remember-field,
.pref-toggle {
    display: flex;
    align-items: flex-start;
    gap: 11px;
}

.consent-field input,
.remember-field input,
.pref-toggle input {
    margin-top: 3px;
}

/* "Keep me signed in" on the sign-in forms. Same shape as the consent box so
   the two read as one family; the strong line is the control's name, the
   faint line is the one sentence that tells a shared-computer user to leave
   it alone. */
.remember-field {
    cursor: pointer;
}

.remember-copy {
    margin: 0;
    color: var(--text-faint);
    font-size: 0.76rem;
    line-height: 1.45;
}

.remember-copy strong {
    display: block;
    color: var(--text);
    font-size: 0.86rem;
    font-weight: 600;
}

/* The consent box a buyer has to tick, highlighted after they press Continue without it
   (TG-0041). It is a pointer to the control they missed, not an error about something they did
   wrong, so it is a ring rather than red text. */
.consent-field.needs-attention {
    border-radius: 10px;
    box-shadow: 0 0 0 2px var(--accent);
    padding: 8px;
    margin: -8px;
}

/* Not disabled — a buyer must be able to press the button they came to press and be TOLD what is
   missing. `disabled` fires no click event, so the explanation never ran and the press answered
   with silence (TG-0041). Same call, and the same reason, as .reseller-product-option
   .is-unaffordable. */
.btn.is-blocked {
    opacity: 0.62;
}

/* Generic surfaces and data */
.card,
.glass-card,
.portal-card,
.account-panel,
.support-panel,
.beta-panel,
.onboard-card {
    border: 1px solid var(--line);
    border-radius: var(--radius-md);
    background: var(--surface);
}

.card-pad,
.portal-card,
.account-panel,
.support-panel,
.beta-panel,
.onboard-card {
    padding: 22px;
}

.section-head,
.section-row,
.portal-topbar,
.console-view-head,
.admin-overview-head,
.portal-overview-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 20px;
}

.section-title,
.console-view-title {
    margin: 0;
}

.stack-list,
.item-list,
.release-list {
    display: grid;
    gap: 0;
    padding: 0;
    margin: 0;
    border-top: 1px solid var(--line);
    list-style: none;
}

.stack-item,
.item-row {
    padding: 15px 0;
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    background: transparent;
}

.item-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
}

.item-meta,
.detail-label,
.summary-label {
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: var(--portal-micro);
    letter-spacing: 0.07em;
    text-transform: uppercase;
}

.item-actions,
.portal-inline-actions,
.console-view-actions,
.admin-overview-actions,
.portal-overview-actions,
.admin-toolbar,
.bulk-actions,
.export-group {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 7px;
}

.status-banner,
.success-card {
    padding: 13px 14px;
    border: 1px solid var(--line-strong);
    border-left: 2px solid var(--accent);
    border-radius: var(--radius-sm);
    background: var(--surface);
}

.status-banner.is-success,
.success-card {
    border-color: var(--success-line);
    background: var(--success-soft);
}

.status-banner.is-warning {
    border-color: var(--warning-line);
    background: var(--warning-soft);
}

.status-banner.is-error {
    border-color: var(--error-line);
    background: var(--error-soft);
}

.table-scroll,
.table-wrap {
    overflow-x: auto;
    border-block: 1px solid var(--line);
    border-radius: 0;
    background: transparent;
}

table {
    width: 100%;
    border-collapse: collapse;
}

th,
td {
    padding: 13px 14px;
    border-bottom: 1px solid var(--line);
    text-align: left;
    vertical-align: middle;
}

th {
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: var(--portal-micro);
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
}

tbody tr:hover {
    background: var(--accent-soft);
}

.code-mono,
code,
.detail-code {
    font-family: var(--font-mono);
}

code {
    padding: 2px 5px;
    border-radius: var(--radius-xs);
    background: var(--accent-soft);
    color: var(--accent-link);
    font-size: 0.9em;
}

/* Dialogs */
.tg-dialog {
    position: fixed;
    z-index: 1500;
    display: block;
    width: min(100vw - 32px, var(--dialog-w, 520px));
    max-width: none;
    max-height: min(86vh, 800px);
    margin: auto;
    padding: 0;
    overflow: auto;
    overscroll-behavior: contain;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-lg);
    background: var(--surface);
    color: var(--text);
    box-shadow: var(--shadow-lg);
    inset: 0;
}

.tg-dialog:not([open]) {
    display: none;
}

/* Width is a variable on the dialog, not a width on the backdrop. Pick the
   smallest step the content actually needs; a modal is not a page. */
.tg-dialog--sm {
    --dialog-w: 420px;
}

.tg-dialog--md {
    --dialog-w: 560px;
}

.tg-dialog--wide {
    --dialog-w: 720px;
}

/* The dim layer is the native ::backdrop, not the dialog's own background — the
   dialog element IS the panel now, so a background on it would only paint the
   panel. */
.tg-dialog::backdrop {
    background: rgb(2 3 6 / 72%);
    backdrop-filter: blur(10px);
}

/* The page behind a modal must not scroll. */
html:has(dialog[open]) {
    overflow: hidden;
}

/* .tg-dialog is the single scroll region; the panel is a pass-through that
   carries the body gutter, so head and actions can pin against the dialog's
   own scrollport. Do NOT give the panel an overflow — that would create a
   second scroll container and unstick both. */
/* Deliberately `block`, not `grid`. A sticky GRID ITEM cannot move: its
   containing block is its own grid area, which is exactly its own height, so
   `position: sticky` on the head and the actions row silently does nothing.
   As a block box the panel's content box is the full scroll height, which is
   the room sticky needs. Spacing therefore comes from child margins, not gap. */
.tg-dialog-panel {
    display: block;
    width: 100%;
    padding: 0 24px;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
}

.tg-dialog-panel > * + * {
    margin-top: 14px;
}

.tg-dialog-head {
    position: sticky;
    top: 0;
    z-index: 2;
    display: block;
    margin-inline: -24px;
    padding: 20px 24px 14px;
    border-bottom: 1px solid var(--line);
    background: var(--surface);
}

.tg-dialog-head h2 {
    margin: 0;
    font-size: var(--portal-section);
    line-height: 1.3;
}

.tg-dialog-head p {
    max-width: 60ch;
    margin: 6px 0 0;
    color: var(--text-soft);
    font-size: var(--portal-small);
}

.tg-dialog-body {
    display: grid;
    align-content: start;
    gap: 14px;
}

.tg-dialog-actions {
    position: sticky;
    bottom: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
    margin-inline: -24px;
    padding: 14px 24px;
    border-top: 1px solid var(--line);
    background: var(--surface);
}

.tg-dialog-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: 14px;
    padding: 0;
    border-bottom: 0;
}

/* Progressive disclosure inside a form or dialog. The alternative to showing
   every field of every protocol at once — which is what made the playlist and
   Add-staff modals fill the viewport. Native <details>: keyboard-reachable,
   findable by in-page search when open, no JS. */
.tg-advanced {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--surface-soft);
}

.tg-advanced > summary {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 12px;
    color: var(--text-soft);
    font-size: var(--portal-small);
    font-weight: 600;
    cursor: pointer;
    list-style: none;
}

.tg-advanced > summary::-webkit-details-marker {
    display: none;
}

/* The affordance. Without it a closed <details> reads as a dead label — which
   is exactly how the collapsed 16-checkbox permission grid shipped. */
.tg-advanced > summary::after {
    content: '';
    width: 7px;
    height: 7px;
    margin-left: auto;
    border-right: 1.5px solid currentcolor;
    border-bottom: 1.5px solid currentcolor;
    transform: rotate(45deg) translate(-2px, -2px);
    transition: transform var(--dur-fast) var(--ease-out-soft);
}

.tg-advanced[open] > summary::after {
    transform: rotate(-135deg) translate(-2px, -2px);
}

.tg-advanced > summary:hover {
    color: var(--text);
}

.tg-advanced > summary:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
}

.tg-advanced > :not(summary) {
    padding: 0 12px 12px;
}

.tg-advanced-count {
    color: var(--text-faint);
    font-weight: 500;
}

/* A textarea that should own the vertical space in its form (KB article body,
   long notes) rather than sitting at the default input height. */
.field--grow textarea {
    min-height: 220px;
    resize: vertical;
}

/* Footer */
.site-footer {
    padding: 88px 0 28px;
    border-top: 1px solid var(--line);
    background: var(--bg-deep);
}

.footer-inner {
    display: grid;
    grid-template-columns: minmax(280px, 1.15fr) minmax(420px, 1fr);
    gap: clamp(50px, 8vw, 120px);
}

.footer-tagline {
    max-width: 44ch;
    margin: 20px 0 14px;
    color: var(--text-soft);
}

.footer-compliance {
    max-width: 56ch;
    color: var(--text-faint);
    font-size: 0.74rem;
}

.footer-social {
    margin-top: 14px;
}

.footer-links {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 30px;
}

.footer-col h3 {
    margin-bottom: 18px;
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: var(--portal-micro);
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.footer-col nav {
    display: grid;
    gap: 10px;
}

.footer-col a {
    color: var(--text-soft);
    font-size: 0.84rem;
}

.footer-col a:hover {
    color: var(--text);
}

.footer-bottom {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 58px;
    padding-top: 20px;
    border-top: 1px solid var(--line);
    color: var(--text-faint);
    font-size: 0.72rem;
}

/* Floating utilities */
.cookie-consent {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 1210;
    display: grid;
    width: min(calc(100% - 32px), 420px);
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 13px;
    padding: 13px 13px 13px 15px;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-md);
    background: var(--surface-strong);
    box-shadow: var(--shadow-lg);
}

.cookie-consent-copy {
    margin: 0;
    color: var(--text-soft);
    font-size: 0.72rem;
    line-height: 1.5;
}

.cookie-consent-copy a {
    color: var(--accent-link);
}

.cookie-consent-actions {
    display: flex;
}

.has-cookie-consent .tg-assistant {
    visibility: hidden;
    opacity: 0;
    pointer-events: none;
}

@media (max-width: 1080px) {
    .nav-links {
        gap: 16px;
    }
}

/* ONE collapse point for the marketing bar, and it is 920 — the width at which
   marketing.css reveals the hamburger (with !important, so it wins) and the
   width js/site.js closes the menu at on resize. Keep these three in lockstep.
   Before this, `.nav-actions` held on until 860 while `.nav-cta-secondary` was
   hidden from 1080, so between 861px and 1080px "Sign in" existed NOWHERE: not
   as a button, and not in a menu that had no burger to open it. The 1080 hide
   was sized for six nav links; there are four now, so the bar fits. */
@media (max-width: 920px) {
    .nav-actions {
        display: none;
    }

    .nav-links a.nav-links-mobile-only {
        display: inline-flex;
    }
}

@media (max-width: 860px) {
    /* Marketing navigation collapses once, at 920px, in marketing.css. Keep
       this narrower breakpoint for the shared footer only. The old nav rules
       here used a different `.open` class from the JS (`.is-open`) and forced
       the menu to display:none below 860px, so the hamburger opened an
       invisible panel on phones. */
    .footer-inner {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 600px) {
    .nav-row {
        min-height: 64px;
    }

    .nav-links {
        top: 64px;
    }

    .brand-mark {
        width: 29px;
        height: 29px;
    }

    .brand-wordmark {
        font-size: 0.94rem;
    }

    .trial-banner-inner {
        min-height: 42px;
        grid-template-columns: 1fr auto;
        gap: 8px;
    }

    .trial-banner-badge,
    .trial-banner-copy span {
        display: none;
    }

    .trial-banner-copy {
        font-size: var(--portal-micro);
    }

    .trial-banner-cta {
        font-size: var(--portal-micro);
    }

    .form-grid,
    .form-grid--three {
        grid-template-columns: 1fr;
    }

    .span-2 {
        grid-column: auto;
    }

    .footer-links {
        grid-template-columns: 1fr 1fr;
        gap: 28px 18px;
    }

    .footer-bottom {
        align-items: flex-start;
        flex-direction: column;
        gap: 10px;
    }

    .cookie-consent {
        right: 10px;
        bottom: 10px;
        width: calc(100% - 20px);
        grid-template-columns: minmax(0, 1fr) auto;
        gap: 10px;
        padding: 11px;
    }

    .cookie-consent .btn {
        min-height: 36px;
        width: auto;
        padding-inline: 12px;
    }

    .tg-dialog {
        width: min(100vw - 16px, var(--dialog-w, 520px));
        max-height: min(92vh, 800px);
    }

    .tg-dialog-panel {
        padding: 0 16px;
    }

    .tg-dialog-head {
        margin-inline: -16px;
        padding: 16px 16px 12px;
    }

    .tg-dialog-actions {
        margin-inline: -16px;
        padding: 12px 16px;
    }
}

@media (max-width: 420px) {
    .cookie-consent {
        grid-template-columns: 1fr;
    }

    .cookie-consent .btn {
        width: 100%;
    }
}

/* ===== BEGIN restored-from-HEAD (redesign drop recovery) ===== */
/* Rules that existed before the redesign and that the app still depends on:
   every class targeted here had NO styling at all afterwards, so the markup
   was rendering unstyled. Appended so they win on equal specificity.
   Delete everything between the BEGIN/END markers to undo the restoration. */
.btn:not(.is-busy) .btn-spinner {
display: none;
}
/* Badge family — status + premium markers. */
.badge {
display: inline-flex;
align-items: center;
gap: 6px;
min-height: 26px;
padding: 0 10px;
border-radius: var(--radius-pill);
border: 1px solid var(--line);
background: var(--surface-soft);
color: var(--text-soft);
font-size: 0.74rem;
font-weight: 600;
letter-spacing: 0.02em;
}
.badge-premium {
background: var(--pro-soft);
border-color: var(--pro-line);
color: var(--pro-from);
}
/* Invalid state (set by components.js validators). */
.field.is-invalid input,
.field.is-invalid select,
.field.is-invalid textarea {
border-color: var(--error);
box-shadow: 0 0 0 3px var(--error-soft);
}
.field-error {
margin: 2px 0 0;
color: var(--error);
font-size: 0.84rem;
line-height: 1.45;
display: none;
}
.field.is-invalid .field-error {
display: block;
}
.consent-copy a,
.utility-legal-links a,
.legal-card a,
.portal-beta-banner a {
color: var(--accent);
font-weight: 600;
transition: color var(--dur-fast) var(--ease-out-soft);
}
:root[data-theme='light'] .consent-copy a,
:root[data-theme='light'] .utility-legal-links a,
:root[data-theme='light'] .legal-card a,
:root[data-theme='light'] .portal-beta-banner a {
color: var(--accent-link);
}
.consent-copy a:hover,
.utility-legal-links a:hover,
.legal-card a:hover,
.portal-beta-banner a:hover {
color: var(--accent-bright);
}
.consent-disclosure {
margin: 0;
color: var(--text-faint);
font-size: 0.88rem;
line-height: 1.58;
}
/* Site-wide signup-promo banner (injected by components.js on marketing pages). */
.promo-banner {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 14px;
padding: 12px 20px;
background: var(--pro-gradient, linear-gradient(135deg, #f59e0b, #d97706));
color: #1a1206;
font-size: 0.95rem;
text-align: center;
}
.promo-banner-text {
max-width: 90ch;
}
.promo-banner-cta {
display: inline-block;
padding: 6px 16px;
border-radius: 999px;
background: rgba(0, 0, 0, 0.85);
color: #fff;
font-weight: 600;
text-decoration: none;
white-space: nowrap;
}
.promo-banner-close {
background: transparent;
border: 0;
color: inherit;
font-size: 1.25rem;
line-height: 1;
padding: 0 4px;
cursor: pointer;
}
/* Toast host (transient feedback, injected/used by components.js tgToast). */
.toast-host {
position: fixed;
left: 50%;
bottom: 24px;
transform: translateX(-50%);
z-index: 1200;
display: grid;
gap: 10px;
width: min(100% - 32px, 420px);
pointer-events: none;
}
.toast {
pointer-events: auto;
padding: 14px 16px;
border-radius: var(--radius-md);
border: 1px solid var(--line);
background: var(--surface-strong);
backdrop-filter: blur(var(--blur-strong));
-webkit-backdrop-filter: blur(var(--blur-strong));
color: var(--text);
box-shadow: var(--shadow-soft);
font-size: 0.92rem;
line-height: 1.5;
animation: tg-toast-in var(--dur-med) var(--ease-out-soft);
}
.toast[data-kind='success'] {
border-color: var(--success-line);
color: var(--success);
}
.toast[data-kind='warning'] {
border-color: var(--warning-line);
color: var(--warning);
}
.toast[data-kind='error'] {
border-color: var(--error-line);
color: var(--error);
}
.tab-panel {
display: grid;
gap: 12px;
margin-top: 14px;
}
/* Enrolment QR. The code is dark-on-transparent, so it needs an opaque white
   plate with quiet-zone padding — on the dark dialog it would otherwise be
   dark-on-dark and unscannable. */
.tg-dialog-qr {
display: flex;
justify-content: center;
}
.tg-dialog-qr img {
width: 200px;
height: 200px;
padding: 12px;
border-radius: 12px;
background: #fff;
}
.utility-grid {
display: grid;
grid-template-columns: minmax(300px, 0.92fr) minmax(0, 1.08fr);
gap: 24px;
align-items: stretch;
}
.utility-card {
padding: clamp(24px, 3vw, 34px);
display: grid;
gap: 20px;
align-content: start;
border: 1px solid var(--line);
background: var(--surface);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-soft);
}
.summary-grid.secondary {
margin-top: 16px;
}
.summary-value.secondary {
font-size: 1rem;
}
.utility-legal-links {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.help-list {
display: grid;
gap: 10px;
margin: 12px 0 0;
padding-left: 18px;
color: var(--text-soft);
}
.help-list li {
line-height: 1.6;
}
/* Stack list (saved items, devices, generic rows) — used by pricing.js host
   and the portal device/playlist lists. */
.stack-list {
display: grid;
gap: 12px;
}
/* ===== Skeleton loading placeholders ===== */
.skeleton {
position: relative;
overflow: hidden;
background: var(--surface-soft);
border-radius: var(--radius-xs);
}
.skeleton::after {
content: '';
position: absolute;
inset: 0;
transform: translateX(-100%);
background: linear-gradient(90deg, transparent, var(--line-strong), transparent);
animation: tg-shimmer 1.3s linear infinite;
}
/* ===== Site footer (injected by components.js on every page) ===== */
.site-footer {
padding: clamp(56px, 8vw, 100px) 0 28px;
border-top: 1px solid var(--line);
margin-top: clamp(40px, 7vw, 80px);
background: linear-gradient(180deg, transparent, color-mix(in srgb, var(--surface-soft) 58%, transparent));
}
.footer-brand {
display: grid;
gap: 14px;
align-content: start;
}
.footer-copy {
margin: 0;
color: var(--text-faint);
}
/* ===== Cookie / storage notice (injected by cookie-consent.js on every page) ===== */
.cookie-consent {
position: fixed;
left: 50%;
bottom: 20px;
transform: translateX(-50%);
z-index: 1180;
width: min(100% - 32px, 680px);
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
padding: 16px 20px;
border: 1px solid var(--line);
border-radius: var(--radius-lg);
background: var(--surface-strong);
box-shadow: var(--shadow-lg);
animation: tg-toast-in var(--dur-med) var(--ease-out-soft);
}
@media (prefers-reduced-motion: reduce) {
    .toast {
    animation: none;
    }
    .skeleton::after {
    animation: none;
    }
}
@media (max-width: 720px) {
    .utility-card {
    padding: 20px;
    border-radius: var(--radius-lg);
    }
    .utility-grid {
    gap: 14px;
    }
}
@media (max-width: 640px) {
    .utility-legal-links {
    display: grid;
    grid-template-columns: 1fr;
    }
}
/* ===== END restored-from-HEAD ===== */

/* Beta marker -------------------------------------------------------------
   Shared inline "Beta" tag for programmes that are live but still changing
   (currently the partner programme: /reseller and /reseller-portal). Kept in
   components.css because both the marketing page and the signed-in portal load
   it, and the two must read identically. Removing a programme from beta is a
   markup change only — delete the <span class="beta-tag"> instances; this rule
   can stay for the next one. */
.beta-tag {
    display: inline-flex;
    align-items: center;
    padding: 0 7px;
    border: 1px solid var(--warning-line);
    border-radius: var(--radius-pill);
    background: var(--warning-soft);
    color: var(--warning);
    font-family: var(--font-mono);
    font-size: var(--portal-micro);
    font-weight: 600;
    line-height: 1.9;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    white-space: nowrap;
    vertical-align: middle;
}

/* Standalone/leading use — a little more presence when it labels a whole
   block rather than sitting inside a line of eyebrow text. */
.beta-tag-lg {
    padding: 0 11px;
    font-size: var(--portal-micro);
    line-height: 2.1;
}

/* Nav marker: the primary nav links are plain text, so the tag rides as a
   superscript rather than a pill to avoid stretching the bar's row height. */
.nav-links a .beta-tag,
.footer-links a .beta-tag {
    margin-left: 6px;
    padding: 0 5px;
    /* 8px was unreadable. The row-height concern the comment above describes is
       handled by the line-height, not by shrinking the text below legibility. */
    font-size: var(--portal-micro);
    line-height: 1.5;
}

/* Manual advertising ------------------------------------------------------
   Shared by marketing and legal pages. Slots stay in normal document flow,
   never overlay content, and collapse completely when no ad fills. */
.ad-placement {
    width: var(--wrap-wide);
    min-height: 122px;
    margin: clamp(30px, 5vw, 64px) auto;
    padding: 18px;
    overflow: hidden;
    border: 1px solid var(--line);
    border-radius: var(--radius-lg);
    background: color-mix(in srgb, var(--surface-soft) 52%, transparent);
}

.ad-placement[hidden],
.ad-placement.is-empty {
    display: none;
}

.ad-placement-label {
    display: block;
    margin-bottom: 10px;
    color: var(--text-faint);
    font-family: var(--font-mono);
    font-size: 0.65rem;
    letter-spacing: 0.12em;
    text-align: center;
    text-transform: uppercase;
}

.ad-placement .adsbygoogle {
    min-height: 78px;
}

.ad-placement.is-loading .adsbygoogle {
    background: linear-gradient(90deg, transparent, var(--line), transparent);
    background-size: 220% 100%;
    animation: tg-ad-wait 1.8s linear infinite;
}

@keyframes tg-ad-wait {
    to {
        background-position: -220% 0;
    }
}

@media (max-width: 720px) {
    .ad-placement {
        width: calc(100% - 24px);
        min-height: 118px;
        margin-block: 28px;
        padding: 14px 10px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .ad-placement.is-loading .adsbygoogle {
        animation: none;
    }
}

/* Cloudflare Turnstile (docs/TURNSTILE-ROLLOUT.md). website/js/turnstile-client.js creates these
   elements only while the worker has switched Turnstile on for the page. */
.tg-turnstile {
    margin-block: var(--sp-3);
    min-height: 65px;
    max-width: 100%;
}

.tg-turnstile-hint {
    margin-block: 0 var(--sp-3);
    color: var(--warning);
    font-size: 0.875rem;
}
