/* masternode.space - hand-written stylesheet, no framework.
 *
 * Structure: tokens -> reset -> layout -> components -> pages -> utilities.
 * Theme is a class on <html> ('dark'), set before first paint by the inline script in
 * templates/header.html, so there is no light-to-dark flash on load.
 *
 * The look is "matte glass": translucent surfaces over a blurred, slowly drifting
 * gradient. Every glass surface goes through .glass rather than re-declaring
 * backdrop-filter, because that property is the expensive one and it is also the one
 * that needs the no-support fallback at the bottom of this file.
 *
 * And it goes on surfaces only - a card, the header, the footer, a popover. Never on
 * something that sits on one: a backdrop-filter inside a backdrop-filter is a nested
 * backdrop root, which some compositors render as a blurred rectangle where the control
 * should be. That is why buttons and fields have translucent backgrounds and no filter
 * of their own.
 */

/* ---------------------------------------------------------------- tokens ---- */
:root {
    color-scheme: light;

    --primary: #5b5bd6;
    --primary-hover: #4a4ac4;
    --primary-contrast: #ffffff;
    --accent: #16b1c4;

    --success: #12a67c;
    --danger: #dc2f45;
    --warning: #d18700;

    --page-bg: #e7ebf7;
    --blob-1: rgba(91, 91, 214, 0.55);
    --blob-2: rgba(22, 177, 196, 0.45);
    --blob-3: rgba(214, 91, 173, 0.34);

    /* Glass surfaces. The alpha is deliberately low - the blur, not the fill, is what
       makes text readable on top of the gradient. */
    --glass-bg: rgba(255, 255, 255, 0.58);
    --glass-bg-soft: rgba(255, 255, 255, 0.38);
    --glass-bg-hover: rgba(255, 255, 255, 0.72);
    --glass-border: rgba(255, 255, 255, 0.75);
    --glass-border-strong: rgba(17, 22, 41, 0.10);
    --glass-blur: 20px;

    --text: #171b2c;
    --text-muted: #5a6076;
    --text-faint: #858ba1;

    --field-bg: rgba(255, 255, 255, 0.70);
    --field-border: rgba(17, 22, 41, 0.14);

    --shadow-sm: 0 1px 2px rgba(16, 20, 40, 0.06);
    --shadow: 0 8px 24px -12px rgba(16, 20, 40, 0.28);
    --shadow-lg: 0 24px 60px -24px rgba(16, 20, 40, 0.40);

    --radius-sm: 10px;
    --radius: 16px;
    --radius-lg: 24px;
    --radius-full: 999px;

    --container: 1120px;
    --gutter: clamp(16px, 4vw, 32px);
    --header-h: 68px;

    --ease: cubic-bezier(0.4, 0, 0.2, 1);
}

html.dark {
    color-scheme: dark;

    --primary: #7c7cf0;
    --primary-hover: #8f8ff5;
    --primary-contrast: #0d1020;
    --accent: #2ad0e4;

    --success: #2fd39f;
    --danger: #ff6b7f;
    --warning: #f0b429;

    --page-bg: #080c18;
    --blob-1: rgba(99, 99, 235, 0.55);
    --blob-2: rgba(22, 177, 196, 0.34);
    --blob-3: rgba(196, 74, 158, 0.30);

    --glass-bg: rgba(255, 255, 255, 0.065);
    --glass-bg-soft: rgba(255, 255, 255, 0.04);
    --glass-bg-hover: rgba(255, 255, 255, 0.10);
    --glass-border: rgba(255, 255, 255, 0.12);
    --glass-border-strong: rgba(255, 255, 255, 0.18);

    --text: #e9ecf7;
    --text-muted: #a2a9c0;
    --text-faint: #767e97;

    --field-bg: rgba(255, 255, 255, 0.06);
    --field-border: rgba(255, 255, 255, 0.16);

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow: 0 10px 30px -14px rgba(0, 0, 0, 0.7);
    --shadow-lg: 0 30px 70px -30px rgba(0, 0, 0, 0.85);
}

/* ----------------------------------------------------------------- reset ---- */
*, *::before, *::after {
    box-sizing: border-box;
}

* {
    -webkit-tap-highlight-color: transparent;
}

html {
    /* Painted from <html> until <body> is parsed; without it Firefox shows a white
       frame before the body's background lands. */
    background-color: var(--page-bg);
    -webkit-text-size-adjust: 100%;
    /* The index page's "Supported coins" button is an anchor link. */
    scroll-behavior: smooth;
    scroll-padding-top: calc(var(--header-h) + 16px);
}

body {
    margin: 0;
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    font-size: 15px;
    line-height: 1.6;
    color: var(--text);
    /* Deliberately no background: <html> paints the base colour, and body::before below
       paints the gradient at z-index -1. A background here would sit on top of that
       pseudo-element - body is not a stacking context - and hide the gradient entirely,
       which is exactly what made the first build look flat. */
    background: transparent;
    overflow-x: hidden;
}

/* The drifting gradient every glass surface samples. Fixed and behind everything, so
   scrolling does not repaint it. */
body::before {
    content: '';
    position: fixed;
    inset: -25vmax;
    z-index: -1;
    background:
        radial-gradient(38vmax 38vmax at 18% 12%, var(--blob-1), transparent 62%),
        radial-gradient(42vmax 42vmax at 82% 22%, var(--blob-2), transparent 60%),
        radial-gradient(46vmax 46vmax at 50% 96%, var(--blob-3), transparent 62%);
    animation: drift 34s ease-in-out infinite alternate;
    pointer-events: none;
}

@keyframes drift {
    0% { transform: translate3d(0, 0, 0) scale(1); }
    50% { transform: translate3d(2.5%, -2%, 0) scale(1.06); }
    100% { transform: translate3d(-2%, 2.5%, 0) scale(1.02); }
}

h1, h2, h3, h4 {
    margin: 0;
    line-height: 1.2;
    letter-spacing: -0.02em;
    font-weight: 700;
}

p {
    margin: 0;
}

a {
    color: var(--primary);
    text-decoration: none;
    transition: color 0.15s var(--ease), opacity 0.15s var(--ease);
}

@media (hover: hover) {
    a:hover {
        color: var(--primary-hover);
    }
}

img, svg {
    max-width: 100%;
    display: block;
}

button, input, select, textarea {
    font: inherit;
    color: inherit;
}

:focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
    border-radius: 4px;
}

::selection {
    background: color-mix(in srgb, var(--primary) 30%, transparent);
}

::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    background: color-mix(in srgb, var(--text) 22%, transparent);
    border-radius: var(--radius-full);
    border: 3px solid transparent;
    background-clip: content-box;
}

/* Components below set display on classes that also get toggled with the `hidden`
   attribute (.rules, .auth__panel, .alert). A class's display would win over the UA's
   [hidden] rule, so state it once here rather than per component. */
[hidden] {
    display: none !important;
}

/* ---------------------------------------------------------------- layout ---- */
.container {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--gutter);
}

.page {
    flex: 1 0 auto;
    padding-block: clamp(32px, 6vw, 72px);
}

.stack {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

/* ------------------------------------------------------------------ glass ---- */
.glass {
    background: var(--glass-bg);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius);
    backdrop-filter: blur(var(--glass-blur)) saturate(150%);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
    box-shadow: var(--shadow);
}

/* ----------------------------------------------------------------- header ---- */
.site-header {
    position: sticky;
    top: 0;
    z-index: 30;
    background: var(--glass-bg-soft);
    border-bottom: 1px solid var(--glass-border);
    backdrop-filter: blur(18px) saturate(160%);
    -webkit-backdrop-filter: blur(18px) saturate(160%);
}

.site-header__inner {
    min-height: var(--header-h);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.brand {
    display: inline-flex;
    align-items: center;
    color: var(--text);
}

.brand:hover {
    color: var(--text);
    opacity: 0.82;
}

/* The monogram carries the header on its own, so it is sized to the header rather than
   set in a tile: 2.3:1, height matched to the buttons beside it, and coloured by the
   text so it holds in both themes. The name is left for screen readers only. */
.brand__mark {
    width: 74px;
    height: 32px;
    flex: none;
    display: block;
}

.header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

/* --------------------------------------------------------------- controls ---- */
.btn {
    --btn-bg: var(--glass-bg);
    --btn-fg: var(--text);
    --btn-border: var(--glass-border);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 10px 18px;
    border-radius: var(--radius-full);
    border: 1px solid var(--btn-border);
    background: var(--btn-bg);
    color: var(--btn-fg);
    font-size: 14px;
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    /* No backdrop-filter here, and none on .field__input either. A button is always on a
       surface that has one already - a card, the header, a dialog - and a backdrop-filter
       inside a backdrop-filter is a nested backdrop root: expensive on a phone, invisible
       when it works, and rendered as a blurred smear where the whole control should be
       when it does not. Telegram's webview is where it does not. What the button would
       have blurred, the surface under it has blurred already. */
    /* background is not in this list, for the reason spelled out on .lang__item: every
       button here sits on a backdrop-filter surface, and a background caught mid-fade on
       one of those is what flickers in Chrome. Press and focus still animate - those are
       transform and box-shadow, which are composited and do not have the problem. */
    transition: transform 0.12s var(--ease),
                box-shadow 0.18s var(--ease), opacity 0.18s var(--ease);
}

.btn .material-icons-round {
    font-size: 19px;
}

@media (hover: hover) {
    .btn:hover {
        background: var(--glass-bg-hover);
        color: var(--btn-fg);
    }
}

.btn:active {
    transform: scale(0.97);
}

.btn[disabled],
.btn.is-disabled {
    opacity: 0.55;
    cursor: not-allowed;
    pointer-events: none;
}

.btn--primary {
    --btn-bg: linear-gradient(135deg, var(--primary), color-mix(in srgb, var(--primary) 55%, var(--accent)));
    --btn-fg: var(--primary-contrast);
    --btn-border: transparent;
    box-shadow: 0 10px 24px -12px color-mix(in srgb, var(--primary) 85%, transparent);
}

@media (hover: hover) {
    .btn--primary:hover {
        background: linear-gradient(135deg, var(--primary-hover), var(--accent));
        color: var(--primary-contrast);
    }
}

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

.btn--icon {
    width: 40px;
    height: 40px;
    padding: 0;
    flex: none;
}

.btn--block {
    width: 100%;
}

.btn--lg {
    padding: 13px 26px;
    font-size: 15px;
}

/* The submit button swaps its label for a spinner while a request is in flight. */
.btn__spinner {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 2px solid currentColor;
    border-top-color: transparent;
    animation: spin 0.7s linear infinite;
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Select styled to match .btn: the native arrow is removed and redrawn so the control
   looks the same in every browser. */
/* ------------------------------------------------------- language switcher ---- */
/* Prominent but small: the header keeps a globe with the current code, and the list of
   languages lives in a popover - a card under the button on a desktop, a bottom sheet on
   a phone, where a dropdown next to the wordmark would either overflow the header or
   shrink the brand to nothing. */
.lang__btn {
    gap: 7px;
    padding: 9px 13px;
}

.lang__btn .material-icons-round {
    font-size: 19px;
}

.lang__code {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.05em;
}

/* The popover itself. Reset the UA's centred box first - [popover] arrives with
   position:fixed, inset:0 and margin:auto. */
.lang__sheet {
    position: fixed;
    inset: auto;
    top: calc(var(--header-h) + 10px);
    /* The container's own right edge, so the card hangs under the button rather than
       floating in from the viewport corner. */
    right: max(var(--gutter), calc((100vw - var(--container)) / 2 + var(--gutter)));
    width: 232px;
    max-height: min(70vh, 460px);
    /* The sheet itself never scrolls - .lang__list below does. A backdrop-filter element
       has to recompute its blurred backdrop for every scroll offset it is given, so
       scrolling the blurred surface makes the browser re-blur on every frame of the
       scroll, and anything transitioning inside it - the hover on a row - lands in the
       middle of that. It only became reachable when the language list went from two rows
       to thirteen and the card started overflowing at all.
       It also fixes the grip and the title row in place on the phone layout, where they
       are above the list and used to scroll away with it. */
    overflow: hidden;
    /* flex-direction only - display belongs on :popover-open below. A closed [popover]
       is display:none by the UA stylesheet, and setting display here overrides that: the
       sheet stays laid out and clickable at opacity 0, so a click meant for the page
       lands on an invisible language row instead. */
    flex-direction: column;
    margin: 0;
    padding: 8px;
    border: 1px solid var(--glass-border);
    border-radius: var(--radius);
    background: var(--glass-bg);
    backdrop-filter: blur(var(--glass-blur)) saturate(150%);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
    box-shadow: var(--shadow-lg);
    color: var(--text);
    /* Entry/exit animation. allow-discrete lets display participate, and
       @starting-style below gives the "from" state; browsers without either simply get
       the popover with no transition. */
    opacity: 0;
    transform: translateY(-8px);
    transition: opacity 0.18s var(--ease), transform 0.18s var(--ease), overlay 0.18s allow-discrete,
                display 0.18s allow-discrete;
}

.lang__sheet:popover-open {
    display: flex;
    opacity: 1;
    transform: translateY(0);
}

@starting-style {
    .lang__sheet:popover-open {
        opacity: 0;
        transform: translateY(-8px);
    }
}

.lang__sheet::backdrop {
    background: transparent;
}

/* The grip and the header row belong to the phone layout; on a desktop the card is
   small enough to speak for itself. */
.lang__grip,
.lang__head {
    display: none;
}

.lang__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 2px;
    overflow-y: auto;
    /* Without this a grid item's automatic minimum keeps the list at its full content
       height and the sheet grows past its max instead of the list scrolling. */
    min-height: 0;
}

.lang__item {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 10px 12px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text);
    font-size: 14px;
    text-align: left;
    cursor: pointer;
    /* No transition on the background, deliberately.
       A 0.15s fade means that for 150ms after the pointer leaves, the row is still
       painting a part-way background - and on a backdrop-filter surface Chrome keeps
       re-rasterising that part-way value instead of finishing it. Filmed at 240fps it is
       unmistakable: one clean hover holds 26% above the row's resting brightness, and
       every hover after it wobbles between 3% and 13% while a row the pointer left two
       rows ago lights back up for 140ms. Rows that were never hovered never move, which
       is what says it is the unfinished transition being repainted rather than the blur
       underneath. Firefox composites it differently and shows none of this.
       A dropdown row does not need to fade. Instant has no intermediate state to get
       stuck on. */
}

@media (hover: hover) {
    .lang__item:hover {
        background: var(--glass-bg-hover);
    }
}

.lang__item.is-current {
    color: var(--primary);
    font-weight: 600;
}

.lang__check {
    font-size: 18px;
}

@media (max-width: 599px) {
    /* Bottom sheet: thumb-reachable, full width, and it never competes with the
       wordmark for header space. */
    .lang__sheet {
        top: auto;
        right: 0;
        left: 0;
        bottom: 0;
        /* The insets define the width. `width: 100%` measured against the top layer's
           containing block, which is not the visual viewport when a scrollbar is in play,
           and the sheet came out a few pixels wider than the screen. */
        width: auto;
        max-width: none;
        max-height: 72vh;
        padding: 8px 12px calc(12px + env(safe-area-inset-bottom));
        border-radius: var(--radius-lg) var(--radius-lg) 0 0;
        border-bottom: 0;
        transform: translateY(14px);
    }

    @starting-style {
        .lang__sheet:popover-open {
            transform: translateY(14px);
        }
    }

    .lang__sheet::backdrop {
        background: rgba(8, 12, 24, 0.45);
        backdrop-filter: blur(2px);
        -webkit-backdrop-filter: blur(2px);
    }

    .lang__grip {
        display: block;
        width: 40px;
        height: 4px;
        margin: 4px auto 8px;
        border-radius: var(--radius-full);
        background: color-mix(in srgb, var(--text) 22%, transparent);
    }

    .lang__head {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 4px;
    }

    .lang__title {
        font-weight: 700;
        font-size: 15px;
    }

    .lang__item {
        padding: 14px 12px;
        font-size: 15px;
    }
}

/* Below this the header is tight: the code drops off the language button, which buys
   back enough for brand, account, language and theme to sit on one line at 360px. */
@media (max-width: 479px) {
    .lang__btn {
        width: 40px;
        height: 40px;
        padding: 0;
        justify-content: center;
    }

    .lang__code {
        display: none;
    }
}

/* --------------------------------------------------------------- privacy ---- */
.privacy {
    grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}
.privacy__card {
    padding: 22px;
    border-radius: var(--radius);
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
}
.privacy__icon {
    font-size: 26px;
    color: var(--primary);
    margin-bottom: 4px;
}

/* The onion address: unmemorable by design, so it is laid out to be copied rather than
   read - monospace so the characters are distinguishable, breaking anywhere because it
   has no spaces to break at, and a copy button beside it. */
.privacy__onion {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    margin-top: auto;
    padding-top: 14px;
}
.privacy__onion-url {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
    font-size: 12px;
    line-height: 1.5;
    word-break: break-all;
    color: var(--text-muted);
    min-width: 0;
}
.privacy__onion-copy {
    flex: none;
    width: 30px;
    height: 30px;
    padding: 0;
}
.privacy__onion-copy .material-icons-round {
    font-size: 16px;
}

/* ----------------------------------------------------------------- guide ---- */
/* /guide and /guide/<coin>: long-form prose, so it gets a narrower measure than the
   dashboard and a bit more line height than the rest of the site. */
.container--narrow {
    max-width: 760px;
}

.guide__coins {
    display: grid;
    gap: 14px;
}

.guide-coin {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 18px 20px;
    border-radius: var(--radius-lg);
    color: inherit;
    /* The per-coin accent the index page already defines, used here as a left edge so the
       two cards are told apart before the name is read. */
    border-left: 3px solid var(--accent-coin, var(--primary));
}
.guide-coin__logo {
    flex: none;
}
.guide-coin__body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1 1 auto;
    min-width: 0;
}
.guide-coin__name {
    font-weight: 700;
    font-size: 17px;
}
.guide-coin__meta {
    font-size: 13px;
    color: var(--text-muted);
}
.guide-coin__go {
    flex: none;
    pointer-events: none;
}

.guide__head {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    margin-bottom: 28px;
}
.guide__logo {
    flex: none;
    margin-top: 2px;
}

.guide__steps {
    list-style: none;
    margin: 0 0 36px;
    padding: 0;
    display: grid;
    gap: 28px;
}
.guide__step {
    display: flex;
    gap: 16px;
    align-items: flex-start;
    /* A grid item's automatic minimum is its min-content, and the min-content of a step
       is whatever the longest unbreakable thing inside it happens to be. */
    min-width: 0;
}
.guide__step-number {
    flex: none;
    width: 30px;
    height: 30px;
    display: grid;
    place-items: center;
    border-radius: var(--radius-full);
    background: var(--primary);
    color: var(--primary-contrast);
    font-size: 14px;
    font-weight: 700;
}
.guide__step-body {
    flex: 1 1 auto;
    min-width: 0;
}
.guide__step-title {
    margin: 3px 0 8px;
    font-size: 18px;
}
.guide__text {
    margin: 0 0 10px;
    line-height: 1.7;
    color: var(--text-muted);
}
.guide__text:last-child {
    margin-bottom: 0;
}
.guide__text strong {
    color: var(--text);
    font-weight: 600;
}

/* The two ways to pay, side by side, because they are alternatives and the reader is
   choosing between them. */
.guide__ways {
    display: grid;
    /* min(240px, 100%), not 240px: a bare minimum in minmax() is a floor the column can
       never go under, so on a 320px phone the two panels would push the whole page wide
       rather than stack. */
    grid-template-columns: repeat(auto-fit, minmax(min(240px, 100%), 1fr));
    gap: 12px;
    margin: 14px 0;
}
.guide__way {
    padding: 16px;
    border-radius: var(--radius);
}
.guide__way-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}
.guide__way-title {
    font-weight: 600;
}
.guide__way .material-icons-round {
    font-size: 20px;
    color: var(--primary);
}

.guide__tip,
.guide__note {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    margin: 14px 0 0;
    padding: 12px 14px;
    border-radius: var(--radius);
    border: 1px solid var(--glass-border);
    background: var(--glass-bg-soft);
    line-height: 1.6;
    font-size: 14px;
}
.guide__tip .material-icons-round,
.guide__note .material-icons-round {
    flex: none;
    font-size: 19px;
    color: var(--primary);
}

.guide__cta {
    margin: 14px 0;
}
.guide__cta .material-icons-round {
    font-size: 18px;
}
/* Buttons do not wrap, because a button label is normally a word or two. This one is a
   sentence - "Official Firo masternode guide" - and left unbreakable it sets a 283px
   floor that the whole column then has to honour on a 320px phone. */
.guide__cta .btn {
    white-space: normal;
    text-align: center;
}

.guide__after-title {
    margin: 0 0 14px;
    font-size: 20px;
}
.guide__after {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
    gap: 14px;
    margin-bottom: 28px;
}
.guide__after-card {
    padding: 18px;
    border-radius: var(--radius);
}
.guide__after-icon {
    font-size: 24px;
    color: var(--primary);
}
.guide__after-name {
    margin: 8px 0 6px;
    font-size: 16px;
}
.guide__after-card {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}
.guide__after-link {
    margin-top: auto;
    padding-top: 12px;
    align-self: stretch;
    justify-content: center;
    white-space: normal;
    text-align: center;
}
.guide__after-link .material-icons-round {
    font-size: 17px;
}

.guide__back {
    margin: 0;
}

/* The dashboard pair: the guide and the button it explains. */
.node-head__pair {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
}

@media (max-width: 599px) {
    .guide-coin {
        flex-wrap: wrap;
    }
    .guide-coin__go {
        width: 100%;
        justify-content: center;
    }
    .guide__step {
        gap: 12px;
    }
}

/* ------------------------------------------------------------------ tags ---- */
.tag {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 12px;
    border-radius: var(--radius-full);
    border: 1px solid var(--glass-border);
    background: var(--glass-bg-soft);
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--text-muted);
    white-space: nowrap;
}

.tag .material-icons-round {
    font-size: 15px;
}

.tag--live {
    color: var(--success);
    border-color: color-mix(in srgb, var(--success) 40%, transparent);
    background: color-mix(in srgb, var(--success) 12%, transparent);
}

.tag--soon {
    color: var(--warning);
    border-color: color-mix(in srgb, var(--warning) 40%, transparent);
    background: color-mix(in srgb, var(--warning) 12%, transparent);
}

.dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: currentColor;
    flex: none;
}

.tag--live .dot {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 70%, transparent);
    animation: pulse 2.2s var(--ease) infinite;
}

@keyframes pulse {
    70% { box-shadow: 0 0 0 7px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}

/* ------------------------------------------------------------------ hero ---- */
.hero {
    display: grid;
    gap: clamp(28px, 5vw, 56px);
    align-items: center;
    grid-template-columns: 1fr;
}

@media (min-width: 900px) {
    .hero {
        grid-template-columns: 1.05fr 0.95fr;
    }
}

.hero__title {
    font-size: clamp(32px, 6vw, 52px);
    margin-bottom: 18px;
}

.hero__title em {
    font-style: normal;
    background: linear-gradient(120deg, var(--primary), var(--accent));
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
}

.hero__lead {
    font-size: clamp(15px, 2.2vw, 18px);
    color: var(--text-muted);
    max-width: 56ch;
    margin-bottom: 28px;
}

.hero__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 28px;
}
/* The closing call to action uses this and nothing defined it, so the button sat on the
   left of a card whose text is centred. text-align does not reach a flex item - the
   container has to place it. */
.hero__actions--center {
    justify-content: center;
    /* No trailing margin either: in the hero the row has content under it, in the card it
       is the last thing in the box. */
    margin-bottom: 0;
}

.hero__points {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 22px;
    padding: 0;
    margin: 0;
    list-style: none;
    color: var(--text-muted);
    font-size: 14px;
}

.hero__points li {
    display: inline-flex;
    align-items: center;
    gap: 7px;
}

.hero__points .material-icons-round {
    font-size: 18px;
    color: var(--success);
}

/* The illustration beside the hero copy: a rack that is working, and pays out.
   Decorative - the template marks it aria-hidden - and every animation here is covered
   by the prefers-reduced-motion block at the end of this file, which leaves a still
   rack and no coins. */
.hero__panel {
    padding: 18px;
    display: grid;
    place-items: center;
}

.rig {
    width: 100%;
    max-width: 360px;
    height: auto;
    overflow: visible;
}

/* Whole rack, breathing. Two pixels: enough to read as "running", not enough to
   distract from the text next to it. */
.rig__rack {
    animation: rig-breathe 4.5s var(--ease) infinite alternate;
}

@keyframes rig-breathe {
    from { transform: translateY(0); }
    to { transform: translateY(-3px); }
}

.rig__body {
    fill: var(--glass-bg-hover);
    stroke: var(--glass-border);
    stroke-width: 1.5;
}

.rig__unit rect {
    fill: color-mix(in srgb, var(--text) 7%, transparent);
    stroke: var(--glass-border);
    stroke-width: 1;
}

.rig__vent {
    stroke: color-mix(in srgb, var(--text) 22%, transparent);
    stroke-width: 2;
    stroke-linecap: round;
}

.rig__shadow {
    fill: color-mix(in srgb, var(--text) 10%, transparent);
    /* fill-box, so the squash happens around the ellipse's own centre rather than the
       SVG's origin. */
    transform-box: fill-box;
    transform-origin: center;
    animation: rig-shadow 4.5s var(--ease) infinite alternate;
}

@keyframes rig-shadow {
    from { transform: scaleX(1); opacity: 0.9; }
    to { transform: scaleX(0.94); opacity: 0.6; }
}

/* One steady power light per unit, one blinking activity light - at three different
   rates, because three identical blinks read as a decoration rather than as work. */
.led--power {
    fill: var(--success);
}

.led--a, .led--b, .led--c {
    fill: var(--primary);
    animation: led-blink 1.7s steps(1, end) infinite;
}

.led--b {
    fill: var(--accent);
    animation-duration: 2.3s;
    animation-delay: 0.4s;
}

.led--c {
    animation-duration: 1.3s;
    animation-delay: 0.9s;
}

@keyframes led-blink {
    0%, 45% { opacity: 1; }
    46%, 100% { opacity: 0.22; }
}

/* Payouts. Each coin starts at the top of the rack, drifts off on its own path and
   fades; the three are staggered so something leaves every couple of seconds. */
.coin {
    transform-box: fill-box;
    transform-origin: center;
    opacity: 0;
}

.coin__face {
    fill: url(#coin-face);
}

.coin__stop-a {
    stop-color: var(--accent);
}

.coin__stop-b {
    stop-color: var(--primary);
}

.coin__ring {
    fill: none;
    stroke: #fff;
    stroke-width: 2;
    opacity: 0.85;
}

.coin--1 {
    animation: coin-left 7s var(--ease) infinite;
}

.coin--2 {
    animation: coin-right 8.5s var(--ease) infinite 2.2s;
}

.coin--3 {
    animation: coin-up 9.5s var(--ease) infinite 4.6s;
}

@keyframes coin-left {
    0% { opacity: 0; transform: translate(0, 0) scale(0.3); }
    12% { opacity: 1; transform: translate(-26px, -22px) scale(1); }
    58% { opacity: 0.9; transform: translate(-72px, -44px) scale(0.95); }
    100% { opacity: 0; transform: translate(-108px, -74px) scale(0.7); }
}

@keyframes coin-right {
    0% { opacity: 0; transform: translate(0, 0) scale(0.3); }
    14% { opacity: 1; transform: translate(30px, -18px) scale(1); }
    60% { opacity: 0.9; transform: translate(82px, -34px) scale(0.92); }
    100% { opacity: 0; transform: translate(122px, -58px) scale(0.7); }
}

@keyframes coin-up {
    0% { opacity: 0; transform: translate(0, 0) scale(0.3); }
    16% { opacity: 1; transform: translate(6px, -26px) scale(1); }
    62% { opacity: 0.9; transform: translate(-4px, -52px) scale(0.95); }
    100% { opacity: 0; transform: translate(10px, -84px) scale(0.7); }
}

/* --------------------------------------------------------------- sections ---- */
.section {
    margin-top: clamp(48px, 8vw, 88px);
}

.section__head {
    max-width: 62ch;
    margin-bottom: clamp(20px, 3vw, 32px);
}

.section__title {
    font-size: clamp(22px, 3.4vw, 30px);
    margin-bottom: 10px;
}

.section__lead {
    color: var(--text-muted);
}

.eyebrow {
    display: inline-block;
    margin-bottom: 12px;
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--primary);
}

.grid {
    display: grid;
    gap: 18px;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
}

/* Coins are a catalogue, not a feature row: the cards keep one size and the row takes as
   many as it fits - four across a full-width page - rather than two coins stretching to
   half the page each. Centred while the list is short, so a pair does not sit in the
   corner of an empty row. */
.grid--coins {
    /* auto-fit, not auto-fill: the empty tracks collapse, so two coins are a centred pair
       rather than a pair pushed into the left corner of a four-track row. The track is a
       fixed width, so they never stretch to fill it either. */
    grid-template-columns: repeat(auto-fit, 250px);
    justify-content: center;
}

@media (max-width: 559px) {
    .grid--coins {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* ------------------------------------------------------------ coin cards ---- */
.coin-card {
    /* --accent-coin is set per card from COINS[]['accent'], so a new coin needs no CSS. */
    --accent-coin: var(--primary);
    position: relative;
    padding: 18px;
    display: flex;
    flex-direction: column;
    gap: 14px;
    overflow: hidden;
    transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease),
                background 0.25s var(--ease);
}

.coin-card::before {
    content: '';
    position: absolute;
    top: -50px;
    right: -50px;
    width: 150px;
    height: 150px;
    border-radius: 50%;
    background: var(--accent-coin);
    opacity: 0.20;
    filter: blur(46px);
    pointer-events: none;
    transition: opacity 0.25s var(--ease);
}

@media (hover: hover) {
    .coin-card:hover {
        transform: translateY(-4px);
        box-shadow: var(--shadow-lg);
        background: var(--glass-bg-hover);
    }

    .coin-card:hover::before {
        opacity: 0.34;
    }
}

.coin-card--soon {
    opacity: 0.86;
}

/* The "ask for a coin" card. Dashed and unaccented so it does not read as one more coin
   on the shelf, and centred because it has no logo or ticker to line up against. */
.coin-card--request {
    align-items: center;
    justify-content: center;
    text-align: center;
    gap: 10px;
    border-style: dashed;
    background: var(--glass-bg-soft);
}
.coin-card__request-icon {
    font-size: 30px;
    color: var(--primary);
}
.coin-card__request-text {
    margin: 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--text-muted);
}
.coin-card__request-link {
    margin-top: 4px;
    white-space: normal;
}
.coin-card__request-link svg {
    width: 18px;
    height: 18px;
    flex: none;
}

.coin-card__head {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.coin-card__title {
    min-width: 0;
}

/* The price sits in the corner of the header, where the first question about a hosting
   service is answered before anything else on the card is read. Right-aligned and never
   wrapped: it is two short lines, and a price broken across three is not a price. */
.coin-card__price {
    margin-left: auto;
    display: grid;
    justify-items: end;
    gap: 1px;
    text-align: right;
    white-space: nowrap;
}

.coin-card__price b {
    font-size: 14px;
    color: var(--text);
}

/* What that is in the coin today. Quieter than the price, because it is the one of the
   two that moves. */
.coin-card__price-coin {
    font-size: 12px;
    color: var(--text-faint);
}

/* The coin marks are the projects' own artwork and carry their own disc and colours,
   so this is a plain frame - a tinted square behind them would fight the logo. */
.coin-card__logo {
    width: 36px;
    height: 36px;
    flex: none;
    display: grid;
    place-items: center;
    border-radius: 50%;
    box-shadow: 0 4px 14px -6px color-mix(in srgb, var(--accent-coin) 80%, transparent);
}

/* object-fit, because a coin's own artwork is whatever shape the project drew it: the
   PIVX mark is 87x100, and without this the square box stretches it. */
.coin-card__logo img {
    width: 36px;
    height: 36px;
    object-fit: contain;
    border-radius: 50%;
}

.coin-card__name {
    font-size: 16px;
}

.coin-card__ticker {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.08em;
    color: var(--text-faint);
}

/* Collateral on the left, the hosting status on the right, wrapping to two lines on a
   card too narrow for both. */
.coin-card__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 8px 12px;
    font-size: 13px;
    color: var(--text-muted);
    border-top: 1px solid var(--glass-border);
    padding-top: 12px;
    margin-top: auto;
}

/* When the row is too narrow for both and the tag drops to its own line, it keeps to the
   right rather than sitting under the label. */
.coin-card__meta .tag {
    margin-left: auto;
}

.coin-card__meta b {
    color: var(--text);
    font-weight: 600;
}
/* Its own line under the collateral and the tag: those two are a pair the eye reads
   across, and the count is a different kind of fact. Empty for a coin that is not live
   yet, and an empty span with no width leaves the row exactly as it was. */
.coin-card__hosted {
    flex-basis: 100%;
}

/* ------------------------------------------------------------------ steps ---- */
.steps {
    counter-reset: step;
}

@media (min-width: 620px) {
    .steps {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (min-width: 1000px) {
    .steps {
        grid-template-columns: repeat(4, 1fr);
    }
}

.step {
    position: relative;
    padding: 24px;
}

.step__num {
    counter-increment: step;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    margin-bottom: 14px;
    border-radius: 12px;
    font-size: 14px;
    font-weight: 700;
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 14%, transparent);
    border: 1px solid color-mix(in srgb, var(--primary) 28%, transparent);
}

.step__num::before {
    content: counter(step);
}

.step__title {
    font-size: 16px;
    margin-bottom: 8px;
}

.step__text {
    color: var(--text-muted);
    font-size: 14px;
}

/* Closing call to action - a step card with room to breathe. */
.cta {
    padding: clamp(28px, 5vw, 44px);
}

/* ------------------------------------------------------------- error pages ---- */
.error-icon {
    font-size: 44px;
    color: var(--text-faint);
}

.error-icon--danger {
    color: var(--danger);
}

/* ------------------------------------------------------------------ auth ---- */
.auth {
    width: 100%;
    max-width: 460px;
    margin-inline: auto;
    padding: clamp(22px, 4vw, 32px);
}

.auth__head {
    text-align: center;
    margin-bottom: 22px;
}

.auth__title {
    font-size: 24px;
    margin-bottom: 6px;
}

.auth__lead {
    color: var(--text-muted);
    font-size: 14px;
}

/* Segmented control: login method, and inside the email panel, sign in vs sign up.
   The moving highlight is a pseudo-element so switching costs one transform. */
.seg {
    position: relative;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 4px;
    padding: 4px;
    border-radius: var(--radius-full);
    background: var(--glass-bg-soft);
    border: 1px solid var(--glass-border);
}

.seg::before {
    content: '';
    position: absolute;
    z-index: 0;
    top: 4px;
    bottom: 4px;
    left: 4px;
    width: calc((100% - 8px) / var(--seg-count, 2));
    transform: translateX(calc(100% * var(--seg-active, 0)));
    border-radius: var(--radius-full);
    background: var(--glass-bg-hover);
    border: 1px solid var(--glass-border);
    box-shadow: var(--shadow-sm);
    transition: transform 0.25s var(--ease);
}

.seg__btn {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 9px 12px;
    border: 0;
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--text-muted);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: color 0.2s var(--ease);
}

.seg__btn .material-icons-round {
    font-size: 18px;
}

.seg__btn[aria-selected="true"] {
    /* Colour as well as the moving highlight: on a translucent surface the highlight
       alone is easy to miss, and this control decides what the form does. */
    color: var(--primary);
}

.auth__panel {
    margin-top: 22px;
}

.auth__panel[hidden] {
    display: none;
}

/* ----------------------------------------------------------------- forms ---- */
.field {
    display: block;
    margin-bottom: 18px;
}

.field__label {
    display: block;
    margin-bottom: 7px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-muted);
}

.field__label .req {
    color: var(--danger);
}

.field__wrap {
    position: relative;
    display: flex;
    align-items: center;
}

.field__wrap > .material-icons-round {
    position: absolute;
    left: 13px;
    /* Above the input rather than merely before it in the source. It was a fix once: the
       field carried a backdrop-filter, which blurs whatever is painted behind it, and a
       positioned sibling painted first counts as behind - the icon came out smeared into
       the field, edge contrast 2.9 against 14.8 with this line. The filter is gone now
       and this is the guard that keeps a control drawn over a field from ever being read
       as part of it. */
    z-index: 1;
    font-size: 19px;
    /* --text-faint is tuned for hint text; at 19px on the translucent field it washes
       out, so the leading icon gets its own slightly stronger value. */
    color: color-mix(in srgb, var(--text) 48%, transparent);
    pointer-events: none;
    transition: color 0.15s var(--ease);
}

.field__wrap:focus-within > .material-icons-round {
    color: var(--primary);
}

.field__input {
    width: 100%;
    padding: 12px 14px 12px 42px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--field-border);
    background: var(--field-bg);
    /* See .btn: a field is always on a surface that is already blurred. */
    font-size: 15px;
    transition: border-color 0.15s var(--ease), box-shadow 0.15s var(--ease);
}

.field__input::placeholder {
    color: var(--text-faint);
}

.field__input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 22%, transparent);
}

/* Trailing control inside a field (the password reveal toggle). z-index for the same
   reason as the leading icon above, and it matters more here: this one is pressed. */
.field__action {
    position: absolute;
    right: 6px;
    z-index: 1;
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--text-faint);
    cursor: pointer;
}

.field__action .material-icons-round {
    font-size: 19px;
}

.field__input--action {
    padding-right: 46px;
}

/* PIN entry: fixed width per character, so 8 digits read as a code, not a sentence. */
.field__input--code {
    letter-spacing: 0.4em;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    text-align: center;
    padding-left: 42px;
    padding-right: 14px;
}

.field__hint {
    margin-top: 7px;
    font-size: 12.5px;
    color: var(--text-faint);
}

.field__error {
    display: none;
    margin-top: 7px;
    font-size: 12.5px;
    color: var(--danger);
}

.field.is-invalid .field__input {
    border-color: var(--danger);
}

/* Except the search box inside that field's combo. It is a descendant of the field - the
   popover is markup inside it, wherever the browser paints it - and it is not the thing
   that was answered wrongly: a coin nobody has chosen yet was putting a red outline
   around the box for finding one. */
.field.is-invalid .combo__search .field__input {
    border-color: var(--glass-border-strong);
}

.field.is-invalid .field__error {
    display: block;
}

.field.is-invalid .field__hint {
    display: none;
}

/* Password rule checklist - live feedback while typing, and the same three rules the
   server enforces in validatePassword(). */
.rules {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    margin-top: 9px;
    padding: 0;
    list-style: none;
    font-size: 12.5px;
    color: var(--text-faint);
}

.rules li {
    display: inline-flex;
    align-items: center;
    gap: 5px;
}

.rules li::before {
    content: 'radio_button_unchecked';
    font-family: 'Material Icons Round', sans-serif;
    font-size: 14px;
    line-height: 1;
}

.rules li.is-ok {
    color: var(--success);
}

.rules li.is-ok::before {
    content: 'check_circle';
}

.form__row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 18px;
    font-size: 13.5px;
    color: var(--text-muted);
}

.checkbox {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    cursor: pointer;
    user-select: none;
}

.checkbox input {
    appearance: none;
    -webkit-appearance: none;
    width: 18px;
    height: 18px;
    flex: none;
    margin: 0;
    border-radius: 6px;
    border: 1px solid var(--field-border);
    background: var(--field-bg);
    display: grid;
    place-items: center;
    cursor: pointer;
    transition: background 0.15s var(--ease), border-color 0.15s var(--ease);
}

.checkbox input::after {
    content: '';
    width: 9px;
    height: 5px;
    border-left: 2px solid var(--primary-contrast);
    border-bottom: 2px solid var(--primary-contrast);
    transform: rotate(-45deg) scale(0);
    transition: transform 0.15s var(--ease);
}

.checkbox input:checked {
    background: var(--primary);
    border-color: var(--primary);
}

.checkbox input:checked::after {
    transform: rotate(-45deg) scale(1);
}

/* The one checkbox on a form of text fields - "renew from my balance" under the
   credentials. Top-aligned, because the label is a sentence and the box belongs beside
   its first line rather than in the middle of three. */
.field__check {
    align-items: flex-start;
}

.field__check input {
    margin-top: 2px;
}

/* Under the words, not under the box: the hint explains the sentence beside the
   checkbox, and starting it further left reads as a note about something else. */
.field__check + .field__hint {
    margin-left: 27px;
}

/* ------------------------------------------------------------------ nodes ---- */
.node-list {
    display: grid;
    gap: 14px;
}

/* --accent-coin comes from the per-coin rules the index page emits; a node row reuses
   them through the same coin-card--<key> class. */
.node {
    --accent-coin: var(--primary);
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 16px 18px;
}

.node__logo {
    width: 40px;
    height: 40px;
    flex: none;
    border-radius: 50%;
    box-shadow: 0 4px 12px -6px color-mix(in srgb, var(--accent-coin) 80%, transparent);
}

.node__body {
    flex: 1;
    min-width: 0;
}

.node__title {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 6px;
}

.node__alias {
    font-size: 16px;
    margin: 0;
}

/* A definition list rather than a row of spans: each value has a label, and on a narrow
   screen the labels are what make the wrapped values readable. */
.node__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 22px;
    margin: 0;
    font-size: 13px;
    color: var(--text-muted);
}

.node__meta > div {
    display: flex;
    align-items: baseline;
    gap: 6px;
}

.node__meta dt {
    color: var(--text-faint);
}

.node__meta dd {
    margin: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.node__index {
    color: var(--text-faint);
}

.node__action {
    margin: 0;
    flex: none;
}

.node--empty {
    flex-direction: column;
    text-align: center;
    padding: 36px 24px;
    gap: 10px;
}

.node--empty__icon {
    font-size: 34px;
    color: var(--text-faint);
}

/* Values worth taking away (an address, a txid) are buttons: click copies. */
.copy {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--text);
    font: inherit;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    cursor: pointer;
    border-radius: 4px;
}

.copy .material-icons-round {
    font-size: 14px;
    color: var(--text-faint);
    transition: color 0.15s var(--ease);
}

@media (hover: hover) {
    .copy:hover .material-icons-round {
        color: var(--primary);
    }
}

.copy.is-copied .material-icons-round {
    color: var(--success);
}

.btn--danger {
    color: var(--danger);
}

@media (hover: hover) {
    .btn--danger:hover {
        background: color-mix(in srgb, var(--danger) 14%, transparent);
        color: var(--danger);
    }
}

/* Paying is the one action on a row that a user actually wants to take, so it carries the
   accent while the rest of the row's controls stay quiet. */
.btn--pay {
    color: var(--accent);
}

@media (hover: hover) {
    .btn--pay:hover {
        background: color-mix(in srgb, var(--accent) 14%, transparent);
        color: var(--accent);
    }
}

/* --------------------------------------------------------------- add node ---- */
.add-node {
    padding: 0;
    overflow: hidden;
}

.add-node__summary {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 18px 20px;
    cursor: pointer;
    list-style: none;
    user-select: none;
}

/* Safari draws its own marker unless both of these are set. */
.add-node__summary::-webkit-details-marker {
    display: none;
}

.add-node__summary > .material-icons-round:first-child {
    color: var(--primary);
}

.add-node__title {
    display: block;
    font-weight: 700;
    letter-spacing: -0.01em;
}

.add-node__lead {
    display: block;
    font-size: 13px;
    color: var(--text-muted);
}

.add-node__chevron {
    margin-left: auto;
    color: var(--text-faint);
    transition: transform 0.2s var(--ease);
}

.add-node[open] .add-node__chevron {
    transform: rotate(180deg);
}

.add-node__body {
    padding: 4px 20px 20px;
    border-top: 1px solid var(--glass-border);
    padding-top: 20px;
}

.coin-fields {
    border: 0;
    padding: 0;
    margin: 0;
}

/* One per node state, alongside .tag--live / .tag--soon. Unpaid is deliberately quiet:
   it is where every node starts, and it is not a fault. */
.tag--unpaid {
    color: var(--text-muted);
}

/* Paid for, and waiting for us: the node has done nothing wrong, so it reads as
   information rather than as an alarm. */
.tag--awaiting {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 40%, transparent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}

/* Waiting on its owner. The one state with something for them to do, so it carries the
   primary colour the setup button on the same row does. */
.tag--setup {
    color: var(--primary);
    border-color: color-mix(in srgb, var(--primary) 40%, transparent);
    background: color-mix(in srgb, var(--primary) 12%, transparent);
}

.tag--starting {
    color: var(--warning);
    border-color: color-mix(in srgb, var(--warning) 40%, transparent);
    background: color-mix(in srgb, var(--warning) 12%, transparent);
}

.tag--running {
    color: var(--success);
    border-color: color-mix(in srgb, var(--success) 40%, transparent);
    background: color-mix(in srgb, var(--success) 12%, transparent);
}

.tag--expired {
    color: var(--danger);
    border-color: color-mix(in srgb, var(--danger) 40%, transparent);
    background: color-mix(in srgb, var(--danger) 12%, transparent);
}

.tag--running .dot {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--success) 70%, transparent);
    animation: pulse 2.2s var(--ease) infinite;
}

@media (max-width: 599px) {
    .node {
        flex-wrap: wrap;
    }

    .node__body {
        flex-basis: calc(100% - 96px);
    }
}

/* ------------------------------------------------------------------ table ---- */
.node-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 18px;
}

.node-head__title {
    margin: 0 0 4px;
}

.node-head__action {
    flex: none;
}

.node-panel {
    padding: 6px;
}

/* Search on the left, the two filters on the right; stacked on a phone, where three
   controls in a row would each be too narrow to read. */
.node-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 10px 10px 6px;
}

.node-tools__search {
    flex: 1 1 240px;
    /* Without this the search cannot shrink below the intrinsic width of the input in it
       - about twenty characters, whatever width:100% says - and the row has to take the
       overflow out of something else. Firefox takes it out of the filters, which then
       overflow their own box and hang past the right edge of the card; Chrome happens to
       leave the search too wide instead. Neither is a layout, so the field is told it may
       shrink. */
    min-width: 0;
}

.node-tools__search .field__input {
    padding-right: 44px;
}

.node-tools__filters {
    display: flex;
    gap: 10px;
    flex: 1 1 auto;
    /* The same reason the search box above needs it: a flex item's automatic minimum is
       its min-content, and the min-content of this row is the two filters side by side at
       their full label width. Without this the row refuses to go under about 350px, and
       on a 320px phone it is the whole page that scrolls sideways rather than the row
       that shrinks. English hid it - "All coins" and "Any state" are short enough to fit -
       and every wordier language did not. */
    min-width: 0;
    /* Below that the two of them stop being readable side by side, so they stack. */
    flex-wrap: wrap;
}
.node-tools__filters .combo {
    flex: 1 1 140px;
}
/* The ellipsis only engages once the text is allowed to be narrower than its content. */
.combo__button .combo__text {
    min-width: 0;
}

.node-tools__filters .combo {
    flex: 1 1 0;
    min-width: 0;
}

/* The filters are secondary to the list they narrow, so they sit a size down from the
   search box rather than matching it. */
.node-tools__filters .combo__button {
    padding: 9px 10px 9px 12px;
    font-size: 13px;
}

@media (min-width: 760px) {
    .node-tools__filters {
        flex: 0 0 auto;
    }

    .node-tools__filters .combo {
        /* A width, not just a flex-basis. Firefox sizes the box around these two from
           what is inside their buttons - the words "All coins" and "Any state" - and
           ignores a basis it has not been given as a width, which left the box narrower
           than the controls in it and the second one hanging past the card. A definite
           width is a number both engines measure the parent with. */
        flex: none;
        width: 168px;
    }
}

/* The table scrolls inside its own box rather than widening the page: on a phone the
   address column alone is wider than the screen. Positioned so it is also the containing
   block for what it holds: the .sr-only labels on the action buttons are absolutely
   positioned, and without this they resolve against the page instead - out past the
   right edge, where they widen the document and leave the header short of the scroll. */
.table-wrap {
    position: relative;
    overflow-x: auto;
    padding: 4px;
}

.table {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
}

.table th {
    padding: 10px 12px;
    text-align: left;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-faint);
    white-space: nowrap;
    border-bottom: 1px solid var(--glass-border);
}

.table td {
    padding: 12px;
    border-bottom: 1px solid var(--glass-border);
    vertical-align: middle;
}

.table tr:last-child td {
    border-bottom: 0;
}

@media (hover: hover) {
    .table tbody tr:hover {
        background: var(--glass-bg-soft);
    }
}

/* The name the node has in masternode.conf - monospace, because that is what it is. */
.table__name {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
    font-weight: 600;
}

.table__addr {
    display: block;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px;
    color: var(--text-muted);
    margin-top: 2px;
}

/* Said in place of an address while the node holds none. */
.table__addr--none {
    font-family: inherit;
    font-size: 12.5px;
    color: var(--text-faint);
}

.table__coin {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    white-space: nowrap;
}

.table__coin img {
    object-fit: contain;
    border-radius: 50%;
}

.table__paid {
    white-space: nowrap;
    color: var(--text-muted);
}

.table__actions {
    text-align: right;
    white-space: nowrap;
}

.table__actions form {
    display: inline-flex;
    margin: 0;
}

.btn--tiny {
    width: 28px;
    height: 28px;
    border: 0;
    background: transparent;
}

.btn--tiny .material-icons-round {
    font-size: 16px;
}

.node-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 40px 24px;
    text-align: center;
}

.node-empty__icon {
    font-size: 34px;
    color: var(--text-faint);
}

.node-empty__title {
    font-size: 15px;
    font-weight: 600;
}

/* ----------------------------------------------------------------- pager ---- */
.pager {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
    padding: 10px 14px;
    border-top: 1px solid var(--glass-border);
    font-size: 13px;
    color: var(--text-muted);
}

.pager__buttons {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.pager__page {
    font-variant-numeric: tabular-nums;
}

/* --------------------------------------------------------- node details ---- */
.node-details {
    width: min(94vw, 520px);
    text-align: left;
    padding: 22px 24px 24px;
}

.node-details__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 12px;
}

.node-details .notice__title {
    margin: 0;
}

.node-details .datalist__row {
    gap: 8px 16px;
}

.node-details dd {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
}

.secret {
    letter-spacing: 0.08em;
}

/* --------------------------------------------------------------- combobox ---- */
/* One component for every searchable list: the coin picker and the time zone picker. */
.combo {
    position: relative;
}

.combo__button {
    display: flex;
    align-items: center;
    gap: 10px;
    padding-left: 12px;
    text-align: left;
    cursor: pointer;
}

.combo__text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Nothing chosen yet: the button is showing a prompt, so it reads like a placeholder
   rather than like an answer. */
.combo__button--empty .combo__text {
    color: var(--text-faint);
}

.combo__logo {
    object-fit: contain;
    border-radius: 50%;
    flex: none;
}

.combo__icon {
    font-size: 18px;
    color: var(--text-faint);
    flex: none;
}

.combo__chevron {
    margin-left: auto;
    font-size: 20px;
    color: var(--text-faint);
    flex: none;
}

/* Placed under the button by js/main.js, which measures it on open: CSS anchor
   positioning is still Chromium-only, and without it the popover sat in the viewport's
   corner. The script sets top and left only - setting bottom as well over-constrains a
   fixed box and the browser then ignores one of them. */
.combo__list {
    position: fixed;
    inset: auto;
    margin: 0;
    padding: 8px;
    width: min(92vw, 340px);
    max-height: min(60vh, 380px);
    overflow: hidden;
    border: 1px solid var(--glass-border);
    border-radius: var(--radius);
    /* Nearly opaque, where the rest of the app's surfaces are barely there: this one
       floats over buttons and cards, and at the usual alpha their colours came through
       the option labels. */
    background: color-mix(in srgb, var(--page-bg) 92%, transparent);
    backdrop-filter: blur(var(--glass-blur)) saturate(150%);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
    box-shadow: var(--shadow-lg);
    color: var(--text);
    /* Nothing to look at until js/main.js has measured the button and put this under it.
       The browser shows a popover first and fires `toggle` afterwards, in a task of its
       own, so an unplaced popover gets a frame on screen to itself - in the top-left
       corner, where a fixed box with `inset: auto` lands. That frame was the flicker.

       Keyed on the class the script adds rather than on :popover-open, because being
       open and being in the right place are two different moments here. */
    opacity: 0;
    transform: translateY(-4px);
    transition: opacity 0.12s var(--ease), transform 0.12s var(--ease);
}

.combo__list.is-placed {
    opacity: 1;
    transform: translateY(0);
}

/* A column of two: the search box, then the part that scrolls.

   On :popover-open and not on the element itself, because the browser hides a closed
   popover with `[popover]:not(:popover-open) { display: none }` in its own stylesheet -
   and any display an author sets beats it, which left every list rendered down the page
   from the moment it loaded. */
.combo__list:popover-open {
    display: flex;
    flex-direction: column;
}

/* The search box keeps its place while the options scroll - with four hundred zones, a
   search that scrolls away is a search you cannot correct.

   It sits outside the scrolling area rather than sticking to the top of it. As a sticky
   header it only stayed legible for as long as its own background painted over the rows
   passing beneath, and on a popover that is already blurred that could not be relied on:
   the rows showed through it. Nothing scrolls under this. */
.combo__search {
    flex: none;
    margin-bottom: 8px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--glass-border-strong);
}

.combo__scroll {
    /* A basis of auto, not 0. WebKit gives a `flex: 1` child of an auto-height column
       nothing to grow into - the container's height is worked out from bases of zero, so
       the box lands four pixels tall and `overflow: hidden` above takes the options away
       with it. That was a list with a search field and no list under it on iOS. From its
       own content instead, shrunk by the max-height above, which every engine agrees on. */
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    /* Room for the focus ring of the last option, which a scroll box would otherwise cut. */
    padding: 2px;
    margin: -2px;
}

/* Solid, bordered and a shade off the bar it sits in: a search box has to look like
   somewhere to type, and the standard translucent field does not read as one here. */
.combo__search .field__input {
    padding: 9px 12px 9px 38px;
    background: color-mix(in srgb, var(--text) 7%, var(--page-bg));
    border-color: var(--glass-border-strong);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    font-size: 14px;
}

.combo__items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    /* One column that is allowed to be narrower than what is in it. A bare `grid` sizes
       its track to the widest row in WebKit, so "(UTC-11:00) Pacific, Midway" made the
       whole list wider than the popover holding it and the popover's overflow cut the
       ends off. The label ellipsises inside the row instead. */
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
}

.combo__option {
    width: 100%;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text);
    font-size: 14px;
    text-align: left;
    cursor: pointer;
}

.combo__option-text {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.combo__option-meta {
    margin-left: auto;
    font-size: 12px;
    color: var(--text-faint);
}

.combo__check {
    margin-left: auto;
    font-size: 18px;
    color: var(--primary);
    visibility: hidden;
}

/* Both present: the meta pushes itself right, so the check must not do it again. */
.combo__option-meta ~ .combo__check {
    margin-left: 0;
}

.combo__option.is-current .combo__check {
    visibility: visible;
}

@media (hover: hover) {
    .combo__option:hover {
        background: var(--glass-bg-hover);
    }
}

.combo__option:focus-visible {
    background: var(--glass-bg-hover);
}

.combo__empty {
    padding: 12px;
    text-align: center;
    font-size: 13px;
    color: var(--text-faint);
}

/* ------------------------------------------------------------------- hint ---- */
/* A value with something behind it: "5 days" that knows the date it counts to. Text with
   a dotted underline rather than a control, because it is part of the sentence it sits
   in - but a button all the same, so a phone can reach it. */
.hint {
    padding: 0;
    border: 0;
    border-bottom: 1px dotted var(--text-faint);
    background: none;
    color: inherit;
    font: inherit;
    cursor: help;
}

/* The paid period running down. Amber at ten days, red at three - and the same red for a
   period that has already run out. */
.hint--warning {
    color: var(--warning);
    border-bottom-color: color-mix(in srgb, var(--warning) 50%, transparent);
}

.hint--urgent,
.hint--expired {
    color: var(--danger);
    border-bottom-color: color-mix(in srgb, var(--danger) 50%, transparent);
}

.hint-bubble {
    position: fixed;
    inset: auto;
    margin: 0;
    padding: 8px 12px;
    max-width: min(80vw, 320px);
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-sm);
    /* Opaque, unlike every other surface here: this one is small, sits over a table, and
       has to be read at a glance - a glass fill leaves the row showing through the text. */
    background: var(--page-bg);
    box-shadow: var(--shadow-lg);
    color: var(--text);
    font-size: 13px;
    line-height: 1.4;
}

.auth--wide {
    max-width: 560px;
}

/* ---------------------------------------------------------------- settings ---- */
.settings {
    display: grid;
    gap: 18px;
}

/* A grid item will not go below the intrinsic width of its content, and a text input
   reports about twenty characters of it however narrow the card around it gets. Without
   this the cards stayed 328px wide on a 320px screen and pushed the page sideways. */
.settings > * {
    min-width: 0;
}

/* Two columns where there is room: the five cards are short and independent, and a
   single column leaves the page mostly empty on a desktop. */
@media (min-width: 900px) {
    .settings {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        align-items: start;
    }
}

/* The one card that is a list rather than a form: it wants the full width, and it goes
   last so the two-column grid above it stays even. */
.settings__span {
    grid-column: 1 / -1;
}

.settings__toggle {
    align-items: flex-start;
    margin-bottom: 14px;
}

.settings__toggle input {
    margin-top: 2px;
}

/* The reason a channel cannot be switched on belongs under its label, not beside it. */
.settings__hint {
    display: block;
    margin-top: 2px;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--text-faint);
}

/* A channel whose identity is missing is shown, disabled, so the page says what is
   possible rather than hiding half of itself. */
.settings__toggle:has(input:disabled) {
    opacity: 0.6;
    cursor: not-allowed;
}

.settings__toggle input:disabled {
    cursor: not-allowed;
}

/* -------------------------------------------------------------- data lists ---- */
/* Label/value pairs on the dashboard. A <dl> rather than a table: it is one column of
   pairs, and a table would need a header row to be honest markup. */
.datalist {
    margin: 0;
    display: grid;
    gap: 1px;
}

.datalist__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 4px 16px;
    padding: 10px 0;
    border-bottom: 1px solid var(--glass-border);
    font-size: 14px;
}

.datalist__row:last-child {
    border-bottom: 0;
}

.datalist dt {
    color: var(--text-muted);
}

.datalist dd {
    margin: 0;
    text-align: right;
    /* Identities are long and unbreakable (an address, a handle); let them wrap rather
       than push the card wider than its grid track. */
    overflow-wrap: anywhere;
}

.datalist__strong {
    font-weight: 600;
}

.datalist__icon {
    font-size: 16px;
    vertical-align: -3px;
    color: var(--text-faint);
}

/* The header's sign-out control is a form, so it needs to sit like the buttons it
   replaces rather than as a block. */
.header-form {
    display: inline-flex;
    margin: 0;
}

/* ----------------------------------------------------------------- notice ---- */
/* The modal used for "check your inbox" and "your account is active". */
.notice {
    width: min(92vw, 420px);
    padding: 28px 26px 22px;
    border: 1px solid var(--glass-border);
    border-radius: var(--radius-lg);
    background: var(--glass-bg-hover);
    backdrop-filter: blur(var(--glass-blur)) saturate(150%);
    -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(150%);
    box-shadow: var(--shadow-lg);
    color: var(--text);
    text-align: center;
    opacity: 0;
    transform: translateY(10px) scale(0.98);
    transition: opacity 0.2s var(--ease), transform 0.2s var(--ease), overlay 0.2s allow-discrete,
                display 0.2s allow-discrete;
}

.notice[open] {
    opacity: 1;
    transform: translateY(0) scale(1);
}

@starting-style {
    .notice[open] {
        opacity: 0;
        transform: translateY(10px) scale(0.98);
    }
}

.notice::backdrop {
    background: rgba(8, 12, 24, 0.45);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}

.notice__icon {
    width: 56px;
    height: 56px;
    margin: 0 auto 16px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    color: var(--primary);
    background: color-mix(in srgb, var(--primary) 14%, transparent);
}

.notice__icon .material-icons-round {
    font-size: 28px;
}

.notice--ok .notice__icon {
    color: var(--success);
    background: color-mix(in srgb, var(--success) 14%, transparent);
}

.notice--warn .notice__icon {
    color: var(--warning);
    background: color-mix(in srgb, var(--warning) 14%, transparent);
}

.notice__title {
    font-size: 19px;
    margin-bottom: 8px;
}

.notice__text {
    font-size: 14.5px;
    line-height: 1.6;
    color: var(--text-muted);
    margin-bottom: 10px;
}

.notice__text b {
    color: var(--text);
    overflow-wrap: anywhere;
}

.notice__hint {
    font-size: 13px;
    line-height: 1.55;
    color: var(--text-faint);
    margin-bottom: 20px;
}

.notice__hint[hidden] {
    display: none;
}

/* ---------------------------------------------------------------- captcha ---- */
/* The widget is a fixed-size iframe from Google; all this does is give it room and
   centre it, since it cannot be styled from here. */
.captcha-slot {
    margin-bottom: 18px;
}

.captcha-slot[hidden] {
    display: none;
}

.captcha {
    display: flex;
    justify-content: center;
}

/* The pale edge on a dark page is the embedded document's canvas showing past Google's
   anchor: the frame is 304x78 and the anchor 302x76, so a two-pixel band runs along the
   right and bottom, and the frame's square corners leave that canvas visible at the
   anchor's rounded ones - the four specks.
 
   Trimming the frame to the anchor's own size removes the band, and rounding the frame
   clips the corners, because an iframe clips its content to its border-radius. Both act on
   the frame itself, so nothing is painted over the widget - which is what every earlier
   attempt got wrong.
 
   Scoped by title as well as by container: reCAPTCHA also injects an untitled iframe for
   the challenge overlay, and that one must keep its own size.
 
   No !important, deliberately: this element carries presentation attributes (width,
   height, frameborder) and no inline styles, and attributes lose to author CSS. Should
   Google ever start setting style="" on it, these five declarations would need it. */
.captcha iframe[title="reCAPTCHA"] {
    max-width: 302px;
    max-height: 76px;
    border: none;
    border-radius: 4px;
    background-color: transparent;
}

/* Right-hand link in a form row - "forgot your password?". */
.form__link {
    font-size: 13.5px;
}

.form__link[hidden] {
    display: none;
}

/* ------------------------------------------------------------------ note ---- */
.note {
    display: flex;
    gap: 12px;
    padding: 14px 16px;
    border-radius: var(--radius-sm);
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--text-muted);
    background: color-mix(in srgb, var(--primary) 9%, transparent);
    border: 1px solid color-mix(in srgb, var(--primary) 22%, transparent);
}

.note .material-icons-round {
    font-size: 19px;
    color: var(--primary);
    flex: none;
}

/* The same block, for something the user has to act on rather than just read. */
.note--warn {
    color: var(--text);
    background: color-mix(in srgb, var(--warning) 10%, transparent);
    border-color: color-mix(in srgb, var(--warning) 34%, transparent);
}

.note--warn .material-icons-round {
    color: var(--warning);
}

/* And the same block for something being given away rather than asked for. Green, which
   on this page means "this went well" everywhere else too. */
.note--offer {
    color: var(--text);
    background: color-mix(in srgb, var(--success) 10%, transparent);
    border-color: color-mix(in srgb, var(--success) 32%, transparent);
}

.note--offer .material-icons-round {
    color: var(--success);
}

/* In the hero it sits between the lead and the buttons, and .hero__actions carries only
   a bottom margin - so without this the offer and the button it is meant to lead into are
   touching. The same 28px the rest of the hero is spaced by. */
.hero .note--offer {
    margin-bottom: 28px;
}

.note code {
    padding: 1px 6px;
    border-radius: 6px;
    background: color-mix(in srgb, var(--text) 10%, transparent);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12.5px;
}

.alert {
    display: flex;
    gap: 12px;
    padding: 13px 15px;
    margin-bottom: 18px;
    border-radius: var(--radius-sm);
    font-size: 13.5px;
    color: var(--danger);
    background: color-mix(in srgb, var(--danger) 10%, transparent);
    border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent);
}

.alert[hidden] {
    display: none;
}

.alert .material-icons-round {
    font-size: 19px;
    flex: none;
}

.alert ul {
    margin: 0;
    padding-left: 18px;
}

.alert li + li {
    margin-top: 4px;
}

/* ---------------------------------------------------------------- footer ---- */
.site-footer {
    margin-top: auto;
    padding-block: 22px;
    background: var(--glass-bg-soft);
    border-top: 1px solid var(--glass-border);
    backdrop-filter: blur(16px) saturate(150%);
    -webkit-backdrop-filter: blur(16px) saturate(150%);
    font-size: 13.5px;
    color: var(--text-muted);
}

.site-footer__inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px 24px;
}

.site-footer nav {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 20px;
}

.site-footer nav a {
    display: inline-flex;
    align-items: center;
    gap: 8px;
}

.site-footer nav svg {
    width: 18px;
    height: 18px;
    flex: none;
}

.site-footer a {
    color: var(--text-muted);
}

@media (hover: hover) {
    .site-footer a:hover {
        color: var(--text);
    }
}

/* ------------------------------------------------------------- preloader ---- */
#page-preloader {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: grid;
    place-items: center;
    background: color-mix(in srgb, var(--page-bg) 55%, transparent);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: opacity 0.35s var(--ease), visibility 0.35s var(--ease);
}

#page-preloader::after {
    content: '';
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 3px solid color-mix(in srgb, var(--text) 15%, transparent);
    border-top-color: var(--primary);
    animation: spin 0.9s linear infinite;
}

body:not(.loading) #page-preloader {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}

/* ---------------------------------------------------------------- sessions ---- */
.sessions {
    list-style: none;
    margin: 16px 0 0;
    padding: 0;
    display: grid;
    gap: 8px;
}

.session {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 14px;
    border: 1px solid var(--glass-border-strong);
    border-radius: var(--radius-sm);
    background: var(--glass-bg-soft);
}

/* The session doing the asking, marked so the list answers "which of these is me?"
   before anybody has to work it out. */
.session--current {
    border-color: color-mix(in srgb, var(--success) 40%, transparent);
    background: color-mix(in srgb, var(--success) 7%, transparent);
}

.session__icon {
    font-size: 20px;
    color: var(--text-faint);
    flex: none;
}

.session__body {
    min-width: 0;
    flex: 1;
}

.session__kind {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin: 0;
    font-size: 14px;
    font-weight: 500;
}

.session__meta {
    margin: 2px 0 0;
    font-size: 12.5px;
    color: var(--text-muted);
}

.session__close {
    flex: none;
}

.sessions__actions {
    display: grid;
    gap: 10px;
    margin-top: 16px;
}

@media (min-width: 560px) {
    .sessions__actions {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    /* With nothing else signed in the first form is hidden, and the remaining button
       should not stretch across both columns on its own. */
    .sessions__actions > form[hidden] + form {
        grid-column: 2;
    }
}

/* ------------------------------------------------------------------- pay ---- */
/* Wider than a notice: it holds an address, an amount and a QR code, and an address that
   wraps to three lines is an address nobody can check against their wallet. */
.pay {
    max-width: 460px;
    text-align: left;
}

.pay__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 6px;
}

.pay__lead {
    margin: 0 0 18px;
    color: var(--text-muted);
    font-size: 14px;
}

.pay__screen[hidden] {
    display: none;
}

.pay__screen--status {
    text-align: center;
    padding: 12px 0 4px;
}

/* The price, which is the one thing on this screen anybody reads twice. */
.pay__total {
    margin-top: 16px;
    padding: 14px 16px;
    border: 1px solid var(--glass-border-strong);
    border-radius: var(--radius-sm);
    background: var(--glass-bg-soft);
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

.pay__amount {
    font-size: 20px;
    font-weight: 700;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

.pay__usd {
    color: var(--text-muted);
    font-size: 14px;
}

.pay__note {
    display: flex;
    gap: 10px;
    margin: 14px 0 0;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--text-faint);
}

.pay__note .material-icons-round {
    font-size: 17px;
    flex: none;
}

/* Amount and memo side by side where there is room: they are short, and the address
   above them is not. */
.pay__row {
    display: grid;
    gap: 0 14px;
}

@media (min-width: 420px) {
    .pay__row {
        grid-template-columns: 1fr 1fr;
    }

    /* A top-up has no amount to send, so the memo has the row to itself and takes the
       width of the address above it rather than half of it. */
    .pay__row--single {
        grid-template-columns: 1fr;
    }
}

.pay__value {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px;
}

.pay__or {
    margin: 18px 0 10px;
    text-align: center;
    font-size: 13px;
    color: var(--text-faint);
}

/* White behind the code whatever the theme: a dark QR on a dark card is a QR that will
   not scan, and the quiet zone is part of the symbol. */
.pay__qr {
    display: flex;
    justify-content: center;
    padding: 10px;
    margin-bottom: 18px;
    border-radius: var(--radius-sm);
    background: #ffffff;
}

.pay__qr canvas,
.pay__qr img {
    display: block;
}

.alert--warning {
    color: var(--warning);
    background: color-mix(in srgb, var(--warning) 10%, transparent);
    border-color: color-mix(in srgb, var(--warning) 30%, transparent);
}

.pay__countdown {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 16px;
    font-size: 13.5px;
    color: var(--text-muted);
}

.pay__countdown .material-icons-round {
    font-size: 18px;
}

.pay__countdown b {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--text);
}

.pay__status-icon {
    font-size: 46px;
    color: var(--primary);
}

.pay__status-icon--ok {
    color: var(--success);
}

.pay__status-icon--warn {
    color: var(--warning);
}

.pay__status-title {
    margin: 10px 0 6px;
    font-size: 17px;
}

.pay__status-text {
    margin: 0;
    color: var(--text-muted);
    font-size: 14px;
}

.pay__next {
    margin: 14px 0 0;
    font-size: 12.5px;
    color: var(--text-faint);
}

/* ---------------------------------------------------------------- orders ---- */
.order__amount {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-weight: 600;
    font-size: 13px;
}

.order__tx {
    display: block;
    margin-top: 2px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11.5px;
    color: var(--text-faint);
}

/* A truncated transaction id with the way to take the whole of it. The button sits on the
   baseline of the hash rather than under it, and shrinks to the text it is beside. */
/* The balances card's own tools row. Outside the panel, so it has none of the padding
   the same row has when it sits inside one. */
.balance-tools {
    padding: 0 0 12px;
}
.balance-tools__zero {
    flex: none;
}

/* The row shown when a search or the checkbox has left the table with nothing in it.
   Centred and quiet: it is the absence of rows, not a row. */
.table__none {
    padding: 18px 14px;
    text-align: center;
    color: var(--text-muted);
}

.tx-copy {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

.tx-copy .order__tx {
    display: inline;
    margin-top: 0;
}

.tx-copy__btn {
    width: 28px;
    height: 28px;
    padding: 0;
    flex: none;
}

.tx-copy__btn .material-icons-round {
    font-size: 15px;
}

/* Confirmation that the click did something, for the moment the icon is a tick. */
.tx-copy__btn.is-copied {
    color: var(--success);
    border-color: color-mix(in srgb, var(--success) 40%, transparent);
}

.tag--order-unpaid {
    color: var(--warning);
    border-color: color-mix(in srgb, var(--warning) 40%, transparent);
    background: color-mix(in srgb, var(--warning) 12%, transparent);
}

.tag--order-confirming {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 40%, transparent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.tag--order-confirmed {
    color: var(--success);
    border-color: color-mix(in srgb, var(--success) 40%, transparent);
    background: color-mix(in srgb, var(--success) 12%, transparent);
}

.tag--order-orphaned {
    color: var(--warning);
    border-color: color-mix(in srgb, var(--warning) 40%, transparent);
    background: color-mix(in srgb, var(--warning) 12%, transparent);
}

.tag--order-expired {
    color: var(--text-muted);
}

/* The node it was for is gone, so neither is this an alarm nor anything to act on. */
.tag--order-cancelled {
    color: var(--text-muted);
}

/* Balance history. The same three states an order goes through, so the same three
   colours: money on its way, money settled, money that left the chain. */
.tag--balance-confirming {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 40%, transparent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}

.tag--balance-confirmed {
    color: var(--success);
    border-color: color-mix(in srgb, var(--success) 40%, transparent);
    background: color-mix(in srgb, var(--success) 12%, transparent);
}

.tag--balance-orphaned {
    color: var(--danger);
    border-color: color-mix(in srgb, var(--danger) 40%, transparent);
    background: color-mix(in srgb, var(--danger) 12%, transparent);
}

/* Money in and money out, in a column where both appear. Not colour alone - the sign is
   on the number itself, which is what a screen reader and a colour-blind reader get. */
.order__amount--in {
    color: var(--success);
}

.order__amount--out {
    color: var(--text-muted);
}

.tag--order-awaiting_ip {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 40%, transparent);
    background: color-mix(in srgb, var(--accent) 12%, transparent);
}

/* ------------------------------------------------------------------ toast ---- */
.toast {
    position: fixed;
    left: 50%;
    bottom: 24px;
    z-index: 60;
    display: flex;
    align-items: center;
    gap: 10px;
    max-width: min(92vw, 420px);
    padding: 12px 18px;
    border-radius: var(--radius-full);
    color: #fff;
    font-size: 14px;
    font-weight: 500;
    background: color-mix(in srgb, var(--success) 92%, black);
    box-shadow: var(--shadow-lg);
    transform: translate(-50%, 16px);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.25s var(--ease), transform 0.25s var(--ease);
}

.toast.is-error {
    background: color-mix(in srgb, var(--danger) 92%, black);
}

.toast.is-shown {
    opacity: 1;
    transform: translate(-50%, 0);
    pointer-events: auto;
}

/* On a phone - which every Mini App is - the toast spans the screen instead of hugging
   its text. A pill sized to "Saved." is a small target in the middle of nowhere, and a
   message of any length wrapped inside 92vw of centred pill reads worse than the same
   words in a bar. Pinned to both edges, so nothing has to be centred by transform. */
@media (max-width: 599px) {
    .toast {
        left: 12px;
        right: 12px;
        max-width: none;
        border-radius: var(--radius);
        transform: translateY(16px);
    }

    .toast.is-shown {
        transform: translateY(0);
    }
}

/* -------------------------------------------------------------- utilities ---- */
.text-center { text-align: center; }
.muted { color: var(--text-muted); }
.mt-0 { margin-top: 0; }
.mt-8 { margin-top: 8px; }
.mt-16 { margin-top: 16px; }
.mt-24 { margin-top: 24px; }
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

@media (max-width: 599px) {
    .hide-xs { display: none; }
}

/* Inside a Mini App. Set in the document head so nothing marked this way is ever painted:
   a webview opened from a Telegram account is signed into that account and stays signed
   in, so the ways in and out of one are not offered - and neither is registering a second
   account with an email address, which is what the account behind the app is not. */
.in-tg [data-tg-hide] { display: none !important; }

/* Signing in, with the page it was opened at held back until that resolves. Gone rather
   than dimmed: a form nobody should be filling in is worse half-visible. The spinner is
   left to .loading and main.js, which puts it up a few milliseconds later - one mechanism
   for the preloader, and those milliseconds are now empty rather than a sign-in form. */
.tg-signing-in .auth { display: none; }

/* --------------------------------------------------------------- fallback ---- */
/* Every glass surface is translucent on the assumption that the blur behind it makes
   text readable. Where backdrop-filter is unsupported (older Firefox with the pref off,
   some embedded webviews) that assumption fails, so those surfaces become near-opaque. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    :root {
        --glass-bg: rgba(255, 255, 255, 0.93);
        --glass-bg-soft: rgba(255, 255, 255, 0.88);
        --glass-bg-hover: rgba(255, 255, 255, 0.97);
        --field-bg: rgba(255, 255, 255, 0.96);
    }

    html.dark {
        --glass-bg: rgba(24, 29, 48, 0.94);
        --glass-bg-soft: rgba(24, 29, 48, 0.88);
        --glass-bg-hover: rgba(32, 38, 60, 0.97);
        --field-bg: rgba(32, 38, 60, 0.96);
    }
}

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}
