:root {
    /* ── Sizing, derived rather than guessed ──────────────────────────────
       Everything below is a continuous function of the available width, so
       the layout eases as the viewport changes instead of stepping. The one
       thing that genuinely can't be continuous — swapping a label for a
       shorter string — is handled by the media queries at the bottom of this
       file, and nothing else. */

    /* Padding inside each display box. A token (rather than a value repeated
       in a media query) because the type formula below has to subtract it:
       if the two ever disagreed, the "never wraps" guarantee would quietly
       break at whatever width they diverged. */
    --cidrip-box-padding: clamp(0.25rem, 1vw, 0.75rem);

    /* Allowance for the vertical scrollbar. `html` is `overflow-y: scroll`
       (nav.css), so on any browser with classic scrollbars a gutter is always
       reserved and 100vw overstates the usable width by its thickness.

       Deliberately a CONSTANT, not a clamp: a scrollbar is ~17px whether the
       window is 400px or 1400px wide, so scaling this with vw under-subtracts
       at exactly the mid widths where the type is largest — which is a
       horizontal scrollbar, the one failure this formula must never produce.
       17px is the widest in common use, so where the real scrollbar is
       narrower (or absent, as with overlay scrollbars on phones and macOS)
       the only cost is under a pixel of type. */
    --cidrip-gutter: 1.0625rem;

    /* Vertical rhythm. Every child of #mainContainer is separated by this;
       .group-start (below) adds a second one to mark the break between the
       mask group, the IP group and the buttons — so both the small and the
       large spacing come from this single value. */
    --cidrip-gap: clamp(0.85rem, 1.5vw + 0.6rem, 1.5rem);

    /* Button text. Unlike the displays there's no exact divisor available —
       button labels are set in a proportional font — so this is an empirical
       clamp rather than derived geometry. The 0.9rem floor is what keeps the
       label from sitting as small text inside a much taller control at phone
       widths. */
    --cidrip-btn-font-size: clamp(0.9rem, 2.6vw, 1.4rem);
    /* Shared by both the persistent highlight ring and the transient
       boundary-crossing ring below — same visual, two different reasons to
       show it. 1px is already as thin as a hard-edged ring can get before it
       stops reading as a line, so it's translucent rather than solid.

       Defined twice, and it must stay that way. The ring is drawn in a
       box-shadow that ALSO carries the button's depth shadow, so an
       unresolvable var() would invalidate that whole declaration at
       computed-value time and reset box-shadow to its initial `none` — a
       highlighted button would lose its shadow entirely, not merely its
       translucency. This solid accent is the floor for browsers without
       color-mix(); the @supports block upgrades it where color-mix() works. */
    --ring-color: var(--accent);
}

@supports (color: color-mix(in srgb, red 50%, transparent)) {
    :root {
        --ring-color: color-mix(in srgb, var(--accent) 45%, transparent);
    }
}

#mainContainer {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--cidrip-gap);
    width: 100%;

    /* --main-font-size is this tool's alone: it is defined here rather than
       in base/nav.css, and every element that reads it (#cidrLabel, the
       display boxes, .button-container's max-width) lives inside
       #mainContainer.

       This is the heart of the layout. formatBinary() in cidrip.js emits, per
       octet, 8 digits plus a 0.5ch spacer after the 4th bit, and between
       octets a spacer + "." + spacer:

           4 octets x 8.5ch  +  3 separators x 2ch  =  40ch, exactly

       and in a 0.6em-advance monospace 40ch is 24em — the same for every
       possible address, since every octet is always 8 digits wide. So
       dividing the real available width by 24 gives the largest size the
       binary rows can take without ever wrapping. Nothing here is tuned by
       eye; it's the geometry of the markup.

       The subtracted terms are the actual chrome between the viewport edge
       and the text, written as the very tokens that create it, so this can't
       drift out of sync with layout.css the way a hardcoded number would.

       Why 100vw is sound even though the card stops growing at 1280px: the
       2.25rem ceiling is reached at roughly 1000px of viewport, so the
       middle term is only ever *live* below that — precisely the range where
       card width does track the viewport. Above it the value is pinned to
       the max and the divergence never matters. (Which is also why container
       query units aren't needed: they'd be more accurate only in a range
       this formula never evaluates.)

       The floor is 0.65rem rather than something more comfortable because a
       floor that is too high defeats the whole guarantee: below roughly
       330px the width genuinely cannot fit 40 characters at a larger size,
       and a higher floor would force an overflow instead of shrinking. It
       never engages above that, so it costs nothing in practice. */
    --main-font-size: clamp(
        0.65rem,
        calc((100vw
              - 2 * var(--tool-padding-x)
              - 2 * var(--cidrip-box-padding)
              - var(--cidrip-gutter)) / 24),
        2.25rem
    );
}

.slider-container {
    width: 60%;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.slider-controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    margin-top: 0.5rem;
}

.control-button {
    /* Same --btn-bg indirection as .action-button, for the same reason: these
       two arrows flash on press and dim at /0 and /32, so their keyframes need
       a resting colour that tracks the state. */
    --btn-bg: var(--bg-control);
    /* Sized from its own text, so the arrows keep pace with the "/24" label
       between them (which tracks --main-font-size) instead of shrinking into
       insignificance as the rest of the page grows. The rem floors hold them
       at 30px at the small end, which is the minimum touch target; all the
       scaling from there is upward. */
    font-size: var(--cidrip-btn-font-size);
    width: 2em;
    height: 2em;
    min-width: 1.875rem;
    min-height: 1.875rem;
    border-radius: 50%;
    border: none;
    background-color: var(--btn-bg);
    box-shadow: var(--shadow);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-mono);
    transition: transform 0.1s, box-shadow 0.1s;
}

.control-button:hover {
    transform: scale(1.05);
}

.control-button:active {
    transform: scale(0.95);
    box-shadow: var(--shadow-pressed);
}

#cidrRange {
    width: 100%;
}

#cidrLabel {
    font-family: var(--font-mono);
    font-size: var(--main-font-size);
    font-weight: bold;
}

.display-container {
    background-color: var(--bg-control);
    /* Must stay --cidrip-box-padding: the type formula in #mainContainer
       subtracts exactly this value, so any other padding here breaks the
       no-wrap guarantee at whatever width the two disagree. */
    padding: var(--cidrip-box-padding);
    border-radius: var(--radius-control);
    box-shadow: var(--shadow);
    box-sizing: border-box;
}

.display-wrapper {
    width: 100%;
    display: flex;
    justify-content: center;
    font-family: var(--font-mono);
    font-size: var(--main-font-size);
}

.display-content {
    white-space: nowrap;
    font-weight: bold;
}

.ones {
    color: var(--color-one);
    font-weight: 900;
}

.zeros {
    color: var(--color-zero);
}

.char-spacer {
    display: inline-block;
    width: 0.5ch;
}

.dotted-decimal-ip {
    width: 100%;
    margin: 0 auto;
    font-size: var(--main-font-size);
}

.octetCell {
    display: inline-block;
    width: 4ch;
    text-align: center;
}

/* Both button rows are one grid each: the label spans a full row of its own,
   with four equal button columns beneath it. Buttons are direct children —
   no wrapper — so the two independent grids share a column template and stay
   aligned with each other.

   The label takes a full row of its own at every width rather than a column
   beside the buttons. That hands its ~112px back to the four columns, which
   is what lets "Prev Network" stay spelled out down to ~540px, and it means
   the grid never restructures at a breakpoint — only the label text changes
   (see "Label tiers" below). */
.button-container {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    align-items: stretch;
    gap: 0.4rem 0.5rem;
    width: 100%;
    /* Exactly the width of a display box, computed rather than hardcoded:
       the binary row is always 24em (see --main-font-size) plus the box's
       own padding. Deriving it means the button rows stay flush with the
       boxes above them at every width automatically — a literal value here
       would only agree with them at whichever width it happened to be
       measured for, and would silently drift the moment the type ceiling or
       the box padding changed. */
    max-width: calc(24 * var(--main-font-size) + 2 * var(--cidrip-box-padding));
    font-family: var(--font-nomono);
}

.button-label {
    grid-column: 1 / -1;
    justify-self: center;
    text-align: center;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.action-button {
    /* Every resting appearance is expressed by redefining --btn-bg rather
       than `background-color` directly. The flash keyframes below animate
       *to* var(--btn-bg), so a press always settles back into whichever state
       the button is actually in — enabled, disabled or highlighted — with no
       inline style to write or clean up. */
    --btn-bg: var(--bg-control);
    /* em, not rem, so the padding is a property of the label rather than a
       fixed slab the label sits inside; it stays proportionate at every width
       with no media query to special-case it. min-height likewise.

       Vertical padding is pure em. Horizontal carries a vw term as well,
       because once --cidrip-btn-font-size reaches its floor on small phones
       the em stops shrinking, and there a fixed 0.7em per side is the
       difference between a label fitting and ellipsizing. */
    padding: 0.45em clamp(0.5em, 1.1vw, 0.75em);
    border: none;
    border-radius: var(--radius-control);
    background-color: var(--btn-bg);
    box-shadow: var(--shadow);
    cursor: pointer;
    transition: transform 0.1s, box-shadow 0.1s;
    min-height: 2.2em;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.action-button:hover {
    transform: scale(1.05);
}

.action-button:active {
    transform: scale(0.95);
    box-shadow: var(--shadow-pressed);
}

/* ── Button states ────────────────────────────────────────────────────────
   Four states, and the rule that keeps them legible: a RING means state
   (this address is that thing), a FILL means event (you just pressed this).
   They're never confusable even though --accent and --flash-success are both
   blue, because they're different kinds of mark rather than different hues.

   Disabled and Highlighted are mutually exclusive by construction — the JS
   only highlights an action it also considers available. */

/* Disabled: the action is impossible at this CIDR/IP. Still clickable, so a
   press can explain itself with the (softened) red refusal flash — silence
   would teach nothing. */
.action-button.is-disabled,
.control-button.is-disabled {
    --btn-bg: var(--surface-hover);
    color: var(--text-muted);
    box-shadow: none;
    cursor: not-allowed;
    /* --text-muted and --surface-hover are both WCAG-safe tokens (>=4.5:1
       contrast), which is exactly why they don't read as faint on their own
       — they're tuned for legible secondary copy, not for looking disabled.
       A disabled control isn't required to meet that floor, so opacity is
       the one property that reliably fades everything (bg, text, whatever's
       left of the shadow) together, in both themes, without a second token. */
    opacity: 0.6;
}

.action-button.is-disabled:hover,
.action-button.is-disabled:active,
.control-button.is-disabled:hover,
.control-button.is-disabled:active {
    transform: none;
    box-shadow: none;
}

/* Highlighted: the IP already *is* this button's address. Static — no
   entrance animation — so it reads as a quiet fact rather than a second
   event competing with the press flash. The ring alone carries this state —
   no background tint — so the button's resting fill stays the same
   var(--bg-control) as an ordinary enabled button, and a press flash settles
   back to that same plain color rather than a tinted one.

   .is-crossing shares this exact ring for a different reason: when stepping
   with Prev/Next IP walks the address across a subnet boundary, the
   direction button that *wasn't* pressed (e.g. Next Network, when you
   stepped there via Next IP) gets this ring for as long as the pressed
   button's own press flash lasts (see signalBoundaryCrossing in cidrip.js).
   A ring rather than a fill, per the rule above: a crossing is a fact about
   where the address now sits, not an action the user took. It needs no
   keyframes of its own — the same instant show/hide as is-disabled, removed
   again on a timeout. Guarded with :not(.is-disabled) so a crossing that
   lands on the first or last network can't ring a button that is
   simultaneously unavailable. */
.action-button.is-current:not(.is-disabled),
.action-button.is-crossing:not(.is-disabled) {
    box-shadow: var(--shadow), inset 0 0 0 1px var(--ring-color);
}

.action-button.is-current:not(.is-disabled):active,
.action-button.is-crossing:not(.is-disabled):active {
    box-shadow: var(--shadow-pressed), inset 0 0 0 1px var(--ring-color);
}

/* Press feedback — the "event" half of ring-vs-fill. Both animations end at
   var(--btn-bg) rather than a hardcoded color, so the button settles back into
   its actual resting state instead of snapping to the plain background. */
@keyframes buttonSuccessFlash {
    0% {
        background-color: var(--flash-success);
    }

    100% {
        background-color: var(--btn-bg);
    }
}

@keyframes buttonErrorFlash {
    0% {
        background-color: var(--flash-error);
    }

    100% {
        background-color: var(--btn-bg);
    }
}

/* Classes to apply the animations. */
.button-success-animating {
    animation-name: buttonSuccessFlash;
    animation-duration: 0.3s;
    animation-timing-function: ease-out;
    animation-fill-mode: forwards;
}

/* Shorter and gentler than the success flash: the persistent dimmed state
   already says "you can't do that", so this only confirms the press landed. */
.button-error-animating {
    animation-name: buttonErrorFlash;
    animation-duration: 0.2s;
    animation-timing-function: ease-out;
    animation-fill-mode: forwards;
}

/* Opens a new group. #mainContainer's gap already separates every child by
   --cidrip-gap; this adds a second one on top, so a group boundary reads as
   twice the spacing of a gap within a group. */
.group-start {
    margin-top: var(--cidrip-gap);
}

#newNetworkBtn {
    color: var(--color-one);
}

#sameNetworkBtn {
    color: var(--color-zero);
}

/* Eight View buttons and four Randomize buttons share the width of a single
   display box, so their type ceiling sits well below the displays' own
   2.25rem. */
.button-container,
.button-label,
.action-button {
    font-size: var(--cidrip-btn-font-size);
}

/* ── Label tiers ──────────────────────────────────────────────────────────
   The only thing on this page that can't scale continuously: you can't
   interpolate between two different strings, so swapping one for a shorter
   one needs a real breakpoint. Each button carries its variants as sibling
   spans (see cidrip.njk) and the tiers below choose which is displayed.

   All four columns are 1fr, so only the LONGEST surviving label sets the
   column width — shortening anything else buys nothing. That's what decides
   which labels appear in which tier:

     tier A  everything spelled out            longest 12  "Prev Network"
     tier B  + Prev/Next Net                   longest 10  "Network ID"
     tier C  + Net ID, BC IP, All, First, Last longest  8  "Prev Net"

   Tier C is why "Net ID" and "BC IP" exist at all: on their own they'd change
   nothing, because "First Host" and "Everything" would still hold the column
   at 10 characters. It's also why "First Host"/"Last Host" stay spelled out
   until tier C — they only give way at the width where they genuinely stop
   fitting. */
.lbl-mid,
.lbl-short {
    display: none;
}

/* Responsive.

   These are the only two breakpoints in this file, and both exist solely to
   swap a label for a shorter one — the single thing here that can't be a
   clamp. Everything else (gap, display padding, button padding, both
   type scales, both row widths) is continuous.

   The boundaries are computed, not picked: each is the width at which the
   longest label in the outgoing tier stops fitting a quarter of the row, plus
   ~6% headroom so the swap happens just before it would look tight rather
   than at the cliff edge. Recompute them if the type scale, the padding or
   the label set changes — full labels fit to ~531px, tier B to ~465px. */

/* Tier B */
@media (max-width: 540px) {
    .lbl-long {
        display: none;
    }

    .lbl-mid {
        display: inline;
    }
}

/* Tier C. Everything above this keeps "First Host", "Last Host" and
   "Broadcast" spelled out. Below roughly 360px even these give up and
   ellipsize: four columns of 8-character labels simply do not fit at the
   type floor, and the ellipsis is the graceful way to lose that fight. */
@media (max-width: 470px) {
    .lbl-mid {
        display: none;
    }

    .lbl-short {
        display: inline;
    }
}

/* Strip the *movement* only — the button growing under the cursor, and the
   press shrink. The colour flashes and the highlight ring stay: they're the
   feedback, not the decoration, and removing them would leave a refused
   press with no response at all. */
@media (prefers-reduced-motion: reduce) {

    .action-button:hover,
    .action-button:active,
    .control-button:hover,
    .control-button:active {
        transform: none;
    }
}
