/* ===========================================================================
   VPS
   ---------------------------------------------------------------------------
   Loaded after main.css on vps-ordering.php and vps-manage.php.

   Same construction as web.css, game.css and ssl.css: built from the palette
   already in main.css -- #242943 ground, #9bf1ff / #53e3fb accent, the muted
   status tones the theme already uses. Nothing here is a new colour and
   nothing here is a new breakpoint; the card grid auto-fits, so there is no
   column count to walk back down through media queries.

   WHY A FOURTH FILE RATHER THAN SHARING web.css. The class names would then
   say "web" on a VPS page, and the next person to restyle the hosting shop
   would have no way of knowing this page rides on it. Small prefixed files
   that can diverge beat one shared file nobody dares touch.

   THE main.css RULE THIS FILE WORKS AROUND, same as web.css: every <label> is
   styled as a field label -- uppercase, 600 weight, 0.25em letter-spacing.
   The OS and server-name labels inside a card are real field labels so they
   keep that; the plan feature list must never be marked up with labels or it
   renders as shouting.

   MOST CARDS ON THIS PAGE ARE UNAVAILABLE, WHICH IS UNUSUAL AND DELIBERATE.
   `.is-unavailable` is therefore a legible state rather than a rejection: it
   dims the card slightly and swaps the price for a "by request" line. It must
   not look broken, because on this page it is every card and will be until the
   placement question in ADO #102 is answered.
   =========================================================================== */

:root {
    --vps-panel:       rgba(255, 255, 255, 0.045);
    --vps-panel-hover: rgba(255, 255, 255, 0.075);
    --vps-line:        rgba(255, 255, 255, 0.13);
    --vps-line-strong: rgba(255, 255, 255, 0.3);
    --vps-muted:       rgba(255, 255, 255, 0.62);
    --vps-faint:       rgba(255, 255, 255, 0.42);
    --vps-accent:      #9bf1ff;
    --vps-accent-deep: #53e3fb;
    --vps-good:        #87c5a4;
    --vps-warn:        #e7b788;
    --vps-bad:         #ec8d81;
    /* The page's own ground, from main.css. Named here because the current
       notebook tab paints its bottom border in it to break the strip's rule --
       see .vps-section.is-current. Not a new colour: it is the one this
       stylesheet's header already says the theme uses. */
    --vps-ground:      #242943;
    --vps-radius:      10px;
    --vps-radius-lg:   14px;
}

/* --- shared primitives --------------------------------------------------- */

.vps-page .inner { max-width: 1400px; }

.vps-lede {
    max-width: 62ch;
    color: var(--vps-muted);
    font-size: 1.05rem;
    line-height: 1.65;
}

.vps-panel {
    background: var(--vps-panel);
    border: 1px solid var(--vps-line);
    border-radius: var(--vps-radius-lg);
    padding: 1.5rem 1.75rem;
    margin-top: 2.5rem;
}

.vps-panel h2 {
    font-size: 1.1rem;
    margin-bottom: 0.75rem;
}

.vps-panel-note {
    color: var(--vps-muted);
    font-size: 0.9rem;
    max-width: 62ch;
}

.vps-mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85em;
}

/* --- notices ------------------------------------------------------------- */

.vps-notice {
    border: 1px solid var(--vps-line);
    border-left-width: 3px;
    border-radius: var(--vps-radius);
    padding: 1rem 1.25rem;
    margin: 1.5rem 0 0;
    background: var(--vps-panel);
}

.vps-notice p { margin: 0 0 0.5rem; }
.vps-notice p:last-child { margin-bottom: 0; }

.vps-notice-good { border-left-color: var(--vps-good); }
.vps-notice-warn { border-left-color: var(--vps-warn); }
.vps-notice-bad  { border-left-color: var(--vps-bad); }

.vps-empty {
    border: 1px dashed var(--vps-line);
    border-radius: var(--vps-radius-lg);
    padding: 2.5rem;
    margin-top: 2.5rem;
    text-align: center;
    color: var(--vps-muted);
}

/* --- families and the card grid ------------------------------------------ */

.vps-family { margin-top: 3rem; }

.vps-family-heading {
    font-size: 1.05rem;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--vps-faint);
    border-bottom: 1px solid var(--vps-line);
    padding-bottom: 0.6rem;
    margin-bottom: 1.5rem;
}

.vps-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
    gap: 1.25rem;
}

.vps-card {
    display: flex;
    flex-direction: column;
    background: var(--vps-panel);
    border: 1px solid var(--vps-line);
    border-radius: var(--vps-radius-lg);
    overflow: hidden;
}

.vps-card.is-unavailable { opacity: 0.82; }

.vps-card-head {
    padding: 1.25rem 1.5rem 1rem;
    border-bottom: 1px solid var(--vps-line);
}

/* Always rendered, badge or not, so every card head is the same height and
   the grid does not stagger when only some cards carry a flag. */
.vps-card-flag {
    min-height: 1.6rem;
    margin-bottom: 0.35rem;
}

.vps-card-head h3 {
    font-size: 1.15rem;
    margin: 0;
}

.vps-code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.8rem;
    color: var(--vps-faint);
    margin: 0.2rem 0 0.75rem;
}

.vps-price { margin: 0; }
.vps-price strong { font-size: 1.6rem; }
.vps-price span { color: var(--vps-muted); font-size: 0.9rem; }

.vps-price-tbc span {
    font-size: 1rem;
    color: var(--vps-warn);
}

.vps-card-body {
    padding: 1.25rem 1.5rem 1.5rem;
    display: flex;
    flex-direction: column;
    flex: 1;
}

.vps-features {
    list-style: none;
    padding: 0;
    margin: 0 0 1.25rem;
    flex: 1;
}

.vps-features li {
    padding: 0.3rem 0 0.3rem 1.2rem;
    position: relative;
    color: var(--vps-muted);
    font-size: 0.92rem;
}

.vps-features li::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0.85em;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--vps-accent-deep);
}

.vps-reason {
    color: var(--vps-muted);
    font-size: 0.9rem;
    margin-bottom: 1rem;
}

.vps-order-form label { margin-top: 0.5rem; }
.vps-order-form select,
.vps-order-form input[type="text"] { margin-bottom: 0.75rem; }

/* --- badges -------------------------------------------------------------- */

.vps-badge {
    display: inline-block;
    padding: 0.15rem 0.6rem;
    border-radius: 999px;
    font-size: 0.7rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    border: 1px solid var(--vps-line-strong);
}

.vps-badge-accent { color: var(--vps-accent); border-color: var(--vps-accent); }
.vps-badge-warn   { color: var(--vps-warn);   border-color: var(--vps-warn); }
.vps-badge-good   { color: var(--vps-good);   border-color: var(--vps-good); }
.vps-badge-bad    { color: var(--vps-bad);    border-color: var(--vps-bad); }

/* --- lists --------------------------------------------------------------- */

.vps-order-list,
.vps-os-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.vps-order-list li,
.vps-os-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    padding: 0.7rem 0;
    border-bottom: 1px solid var(--vps-line);
}

.vps-order-list li:last-child,
.vps-os-list li:last-child { border-bottom: 0; }

.vps-order-name,
.vps-os-name { font-weight: 600; }

.vps-order-meta {
    color: var(--vps-muted);
    font-size: 0.88rem;
}

.vps-smallprint {
    margin-top: 3rem;
    padding-top: 1.5rem;
    border-top: 1px solid var(--vps-line);
    color: var(--vps-faint);
    font-size: 0.85rem;
    max-width: 78ch;
}

/* --- management dashboard ------------------------------------------------ */

.vps-server-list { list-style: none; padding: 0; margin: 0; }

.vps-server {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 0.5rem 1rem;
    align-items: center;
    padding: 1rem 0;
    border-bottom: 1px solid var(--vps-line);
}

.vps-server:last-child { border-bottom: 0; }

/* The one card the dashboard's Manage link (?ref=...) pointed at (#301).
   Padding-left/right rather than a border, so the highlighted card does not
   shift the ones around it by the border's own width. */
.vps-server-highlight {
    background: rgba(155, 241, 255, 0.08);
    box-shadow: inset 3px 0 0 var(--vps-accent);
    padding-left: 1rem;
    padding-right: 1rem;
    margin: 0 -1rem;
}

.vps-server-name { font-weight: 600; font-size: 1.05rem; }

.vps-server-meta {
    color: var(--vps-muted);
    font-size: 0.88rem;
    grid-column: 1 / -1;
}

.vps-server-actions {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.vps-server-actions form { margin: 0; }

.vps-server-actions button {
    font-size: 0.8rem;
    padding: 0 1rem;
    height: 2.4rem;
    line-height: 2.4rem;
}

/* A state word, not a traffic light: colour alone must not be the only thing
   carrying the meaning, so the word is always there too. */
.vps-state {
    display: inline-block;
    font-size: 0.75rem;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.vps-state-active   { color: var(--vps-good); }
.vps-state-shutoff  { color: var(--vps-faint); }
.vps-state-error    { color: var(--vps-bad); }
.vps-state-building { color: var(--vps-warn); }

/* A resize Nova has done and nobody has accepted. It is warn-coloured for the
   same reason the card gives it its own two buttons: nothing on this cluster
   auto-confirms a resize, so a server sitting here holds two allocations until
   a person decides. It is not a steady state and must not look like one.
   vps_state_class() lowercases and strips the underscore, so VERIFY_RESIZE
   arrives here as one word. */
.vps-state-verifyresize { color: var(--vps-warn); }

/* --- the section nav: Servers | Firewall ---------------------------------
   Notebook tabs: raised, joined to the rule under the strip, the current one
   filled and sitting a pixel over that rule so it reads as the front page of
   the book rather than one of a row of links.

   That shape is not decoration here. The first version underlined the current
   item instead, and against main.css -- which underlines every <a> and gives
   it a dotted bottom border -- an underline is the one signal that says
   "hyperlink" more loudly than it says "tab". Reported as looking like
   hyperlinks, and it did.

   Plain links between two real pages, and deliberately no script. */

.vps-sections {
    display: flex;
    gap: 0.3rem;
    margin: 2rem 0 1.75rem;
    padding: 0 0.35rem;
    /* The line the tabs stand on. The current tab covers a pixel of it. */
    border-bottom: 1px solid var(--vps-line-strong);

    /* NO overflow HERE, deliberately. `overflow-x: auto` was tried and it puts
       a scrollbar down the right-hand end of the strip: setting either axis to
       a non-visible value makes the other `auto` too, and the tabs deliberately
       hang 1px below this box to sit on that rule. Clipping it with
       `overflow-y: hidden` would take the join with it.

       THREE sections now, and they still fit. Measured 2026-08-27 in a browser
       rather than estimated, because this is the case the previous note said
       to come back and check:

         375px  tabs 258.5 of 327 available  -- 68px spare
         320px  tabs 258.5 of 276 available  -- 17px spare

       No overflow and no horizontal page scroll at either width; the mobile
       query below (0.72rem, 0.06em tracking) is what buys the room. A FOURTH
       section does not fit at 320px, and that is the moment to solve scrolling
       properly rather than now with an artefact to show for it. */
}

/* The way back to the dashboard. A breadcrumb, sitting ABOVE the page title,
   which is what keeps it from reading as one of the tabs below it: it is the
   thing these pages are inside, not a section of them.

   Deliberately quiet -- muted until hovered. It is an exit, and an exit that
   competes with the page's own controls for attention is a mis-set priority.

   main.css underlines every <a> and hangs a dotted border off it, so both have
   to go here as they do on .vps-section, or a breadcrumb reads as body copy
   that happens to be a link. */
.vg-back {
    display: inline-block;
    margin-bottom: 1rem;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--vps-muted);
    text-decoration: none;
    border-bottom: 0;
}

.vg-back:hover {
    color: var(--vps-accent);
    text-decoration: none;
}

.vg-back:focus-visible {
    outline: 2px solid var(--vps-accent);
    outline-offset: 3px;
}

/* The arrow moves, not the text: a breadcrumb whose label shifts on hover
   makes the whole header twitch. */
.vg-back-arrow {
    display: inline-block;
    transition: transform 0.12s ease;
}

.vg-back:hover .vg-back-arrow { transform: translateX(-0.2rem); }

@media (prefers-reduced-motion: reduce) {
    .vg-back-arrow { transition: none; }
    .vg-back:hover .vg-back-arrow { transform: none; }
}

/* UP ONE LEVEL inside the product, as opposed to .vg-back which goes OUT to
   the dashboard. A page that has both draws them in that order, coarser first.
   Same shape and spacing as .game-breadcrumb, because they are the same idea
   and a customer moving between products should not meet two conventions. */
.vps-breadcrumb {
    margin-top: -0.5rem;
    margin-bottom: 2rem;
    font-size: 0.9rem;
    color: var(--vps-muted);
}

.vps-section {
    display: block;
    padding: 0 1.35rem;
    height: 2.85rem;
    line-height: 2.85rem;
    white-space: nowrap;

    background: rgba(255, 255, 255, 0.03);
    border: 1px solid var(--vps-line);
    border-bottom: 1px solid transparent;
    border-radius: var(--vps-radius) var(--vps-radius) 0 0;
    /* Stand ON the strip's rule rather than above it. */
    margin-bottom: -1px;

    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--vps-muted);

    /* main.css underlines every anchor and hangs a dotted border off it. Both
       have to go, or a tab reads as a link sitting in a box. */
    text-decoration: none;
    border-bottom-style: solid;
}

.vps-section:hover {
    background: var(--vps-panel-hover);
    color: #fff;
    text-decoration: none;
}

/* The front page of the notebook: filled to match the panels below it, and its
   bottom border painted in the page's own ground so the strip's rule appears
   to break where this tab sits. aria-current carries the same fact for a
   screen reader -- colour is not the only signal. */
.vps-section.is-current {
    background: var(--vps-panel);
    border-color: var(--vps-line-strong);
    border-bottom-color: var(--vps-ground);
    color: var(--vps-accent);
    cursor: default;
}

.vps-section:focus-visible {
    outline: 2px solid var(--vps-accent);
    outline-offset: -2px;
}

@media screen and (max-width: 736px) {
    .vps-section {
        padding: 0 0.85rem;
        font-size: 0.72rem;
        letter-spacing: 0.06em;
    }

    /* A SERVER ROW IS A ROW ONLY WHERE THERE IS ROOM FOR ONE.
       -------------------------------------------------------------------
       `1fr auto` puts the name and meters in one column and everything else
       in the other. On a 375px screen the second column measured 93px, which
       turned "0 of 20 players online." into three lines and stacked Stop,
       Restart and Manage vertically down a strip narrower than the buttons
       themselves. Nothing overflowed and nothing errored -- it was simply
       unusable, which is why no test caught it.

       One column, and the actions become a row of their own with room to be
       a row. */
    .vps-server {
        grid-template-columns: 1fr;
        align-items: start;
    }

    .vps-server-actions {
        flex-wrap: wrap;
        /* Left, with the text above it, rather than pushed to an edge the
           eye has no reason to be at. */
        justify-content: flex-start;
    }
}

/* --- the firewall dashboard ----------------------------------------------
   Every panel visible at once, because a customer adds a rule by reading the
   rules they already have. The wide panel is the list; the two controls sit
   beside it and share the other column.

   No script, and that is the point -- the tab strip this replaced needed one,
   and the panel it could hide is the way back into a machine you are locked
   out of. auto-fit rather than a media query: below roughly 62rem the grid
   collapses to one column on its own, which is the plain stacked page. */

.vps-dash {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr));
    gap: 1.25rem;
    margin-top: 1.5rem;
    align-items: start;
}

.vps-dash > .vps-panel { margin-top: 0; }

/* The rule list gets the room, and keeps its two neighbours company down the
   second column rather than pushing them off the screen. */
@media screen and (min-width: 62rem) {
    .vps-dash {
        grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
    }

    .vps-dash-wide { grid-row: span 2; }

    /* THE HEIGHT CAP BELONGS TO THE TWO-COLUMN LAYOUT, which is why it lives
       in here with it rather than in the base rule.

       Side by side, an uncapped rule list pushes the add form down the page
       and the panels stop being level with each other -- the exact thing the
       dashboard exists to stop. Stacked in one column there is nothing beside
       the list to push, so the cap buys nothing and costs something real: a
       scrolling box inside a scrolling page, which on a touch screen means a
       flick that moves the wrong one, and a list you can reach the bottom of
       only by landing your thumb outside it. Phones scroll; let them. */
    .vps-panel-scroll { max-height: 28rem; }
}

.vps-panel-title {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0 0 1rem;
    padding-bottom: 0.75rem;
    border-bottom: 1px solid var(--vps-line);
    font-size: 0.8rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--vps-muted);
}

.vps-panel-stat {
    padding: 0 0.55em;
    border: 1px solid var(--vps-line-strong);
    border-radius: 999px;
    font-size: 0.95em;
    line-height: 1.7;
    letter-spacing: 0;
    color: var(--vps-accent);
}

/* The other half of "you should not have to scroll": with the panels side by
   side, a customer with forty rules scrolls the panel and the form beside it
   stays put. Six fit without scrolling at all.

   NO max-height HERE. It is set in the two-column media query above, and
   moving it back into this rule -- which reads like a tidy-up -- reintroduces
   the nested scroll region on phones. Without a cap this declaration does
   nothing, which is the intent: the box only scrolls where something is
   sitting next to it. */
.vps-panel-scroll {
    overflow-y: auto;
}

/* Inside a panel the smallprint is the panel's own footnote, so it does not
   need the page-wide 3rem gap it uses at the bottom of a document. */
.vps-panel .vps-smallprint {
    margin-top: 1.5rem;
    padding-top: 1rem;
}

/* --- the configurator's filter bar ---------------------------------------
   Ships hidden and is revealed by vps-configurator.js. A browser with
   JavaScript off shows the whole catalogue and no controls at all, which is
   better than controls that silently do nothing. */

.vps-filters[data-enhanced="false"] { display: none; }

.vps-filters {
    margin: 2.5rem 0 1.5rem;
    padding: 1.1rem 1.35rem;
    background: var(--vps-panel);
    border: 1px solid var(--vps-line);
    border-radius: var(--vps-radius-lg);
}

.vps-filter-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.6rem;
    margin-bottom: 0.75rem;
}

.vps-filter-row:last-of-type { margin-bottom: 0; }

/* main.css styles every <label> as a shouting field label. These are row
   captions sitting inline with their controls, so they are restated. */
.vps-filter-label {
    font-size: 0.72rem;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--vps-faint);
    font-weight: 600;
    margin: 0 0.35rem 0 0;
    min-width: 4.5rem;
}

.vps-chips { display: flex; flex-wrap: wrap; gap: 0.5rem; }

.vps-chip {
    appearance: none;
    cursor: pointer;
    padding: 0.35rem 0.95rem;
    height: auto;
    line-height: 1.5;
    border-radius: 999px;
    border: 0;
    box-shadow: inset 0 0 0 1px var(--vps-line-strong);
    background: transparent;
    color: inherit;
    font-size: 0.8rem;
    letter-spacing: 0.04em;
    text-transform: none;
}

.vps-chip:hover { box-shadow: inset 0 0 0 1px var(--vps-accent); color: var(--vps-accent); }

.vps-chip.is-on {
    background: var(--vps-accent);
    box-shadow: none;
    color: #242943;
    font-weight: 600;
}

.vps-chip-reset { margin-left: auto; }

/* The theme's select is built for full-width fields; these sit inline. */
.vps-filter-select {
    width: auto;
    min-width: 10rem;
    height: 2.4rem;
    line-height: 2.4rem;
    padding: 0 2.5rem 0 0.9rem;
    font-size: 0.85rem;
}

.vps-filter-count {
    margin: 0;
    font-size: 0.8rem;
    color: var(--vps-faint);
}

.vps-badge-family {
    color: var(--vps-faint);
    border-color: var(--vps-line-strong);
}

/* A button that reads as a link, for "clear the filters" mid-sentence. */
.vps-linklike {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    height: auto;
    line-height: inherit;
    font-size: inherit;
    letter-spacing: inherit;
    text-transform: none;
    color: var(--vps-accent);
    text-decoration: underline;
    cursor: pointer;
}

.vps-card[hidden] { display: none; }

.vps-features strong { font-weight: 700; }

.vps-storage-select { margin-bottom: 1rem; }
/* -------------------------------------------------------------------------
   Game server console -- ADO #183

   Shares the vps-page furniture deliberately. The two manage pages are the
   same idea applied to two products, and giving them separate stylesheets is
   how they drift into looking like different sites.
   ------------------------------------------------------------------------- */

.vps-server-flavour {
    display: inline-block;
    margin-left: 0.5em;
    font-size: 0.8em;
    opacity: 0.7;
}

/* Console output is untrusted game text: it wraps rather than stretching the
   page, and it scrolls rather than growing without limit. */
.game-console {
    max-height: 22em;
    overflow: auto;
    margin-top: 1em;
    padding: 1em;
    background: rgba(0, 0, 0, 0.35);
    border-radius: 4px;
    font-family: "Courier New", monospace;
    font-size: 0.8em;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}

/* --- setup snippets ------------------------------------------------------
   The connection instructions on /object-storage. These carry real config for
   a customer to copy, so two things matter more than looks.

   THEY MUST SCROLL IN THEIR OWN BOX, NOT WIDEN THE PAGE. An endpoint URL plus
   an access key is easily wider than a phone, and a <pre> with no overflow
   rule pushes the whole layout sideways -- every other section on the page
   then scrolls horizontally to accommodate one code block nobody is even
   looking at.

   AND THEY MUST NOT WRAP. .vps-notice-code above uses `white-space: pre-wrap`,
   which is right for a message. It is wrong here: a wrapped config line looks
   like two lines, and somebody pasting it into rclone.conf gets a file that
   silently does not parse. Scroll, do not wrap.
   ------------------------------------------------------------------------- */

.vps-snippet {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.85rem;
    line-height: 1.55;
    background: var(--vps-panel);
    border: 1px solid var(--vps-line);
    border-radius: var(--vps-radius);
    padding: 1rem 1.15rem;
    margin: 0.75rem 0 1rem;
    overflow-x: auto;
    white-space: pre;
}

.vps-snippet code {
    background: none;
    border: none;
    padding: 0;
    font-size: inherit;
}

/* The <details> wrappers. Collapsed by default because most customers use one
   client and should not have to scroll past three they do not. */
.vps-page details {
    border: 1px solid var(--vps-line);
    border-radius: var(--vps-radius);
    padding: 0.75rem 1.15rem;
    margin: 0.75rem 0;
    background: var(--vps-panel);
}

.vps-page details[open] { padding-bottom: 0.35rem; }

.vps-page summary {
    cursor: pointer;
    font-weight: 600;
    /* A summary with no marker reads as a heading nobody knows to click. */
    list-style: revert;
}

.vps-page summary:hover { color: var(--vps-accent); }

/* --- the comparison table -------------------------------------------------
   A second view of the SAME plans the cards show. The cards sell and carry the
   order form; this compares. Ships hidden and is revealed by the toggle, which
   the script draws -- so a browser with JavaScript off gets the card grid and
   never sees a switch that cannot switch anything.

   WIDE CONTENT SCROLLS INSIDE ITSELF, not the page. Seven columns do not fit a
   phone, and a table that widens the document gives every page on the site a
   horizontal scrollbar. */
.vps-table-wrap {
    margin-top: 1.5rem;
    overflow-x: auto;
    border: 1px solid var(--vps-line);
    border-radius: var(--vps-radius);
    background: var(--vps-panel);
}

.vps-table {
    width: 100%;
    margin: 0;
    border-collapse: collapse;
    font-size: 0.9rem;
    white-space: nowrap;
}

.vps-table th,
.vps-table td {
    padding: 0.7rem 0.9rem;
    border-bottom: 1px solid var(--vps-line);
    text-align: left;
    vertical-align: middle;
}

/* STICKY HEADER, so the column you are sorting by is still named when you are
   forty rows down. It needs the wrapper's own scroll context, which is why the
   wrapper scrolls rather than the page. */
.vps-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
    background: var(--vps-panel);
    border-bottom: 1px solid var(--vps-line-strong);
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--vps-muted);
}

/* The header control is a real <button>: it is keyboard reachable and it
   announces itself, which a click handler on a <th> does not. */
.vps-table thead th button {
    all: unset;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    width: 100%;
}

.vps-table thead th button:focus-visible {
    outline: 2px solid var(--vps-accent);
    outline-offset: 2px;
}

.vps-table thead th button::after {
    content: "";
    opacity: 0;
    border: 0.3em solid transparent;
    border-bottom-color: currentColor;
    transform: translateY(-0.15em);
}

.vps-table thead th.is-sorted { color: var(--vps-accent); }
.vps-table thead th.is-sorted button::after { opacity: 1; }
.vps-table thead th.is-desc button::after {
    transform: translateY(0.15em) rotate(180deg);
}

.vps-table tbody tr:last-child th,
.vps-table tbody tr:last-child td { border-bottom: 0; }

.vps-table tbody tr:hover { background: var(--vps-panel-hover); }

/* Numbers right-aligned and tabular, so vCPU, memory, storage and price line
   up as columns of figures rather than ragged text -- the whole reason to
   offer a table at all. */
.vps-num {
    text-align: right !important;
    font-variant-numeric: tabular-nums;
}

.vps-row-code { font-weight: 600; }

.vps-row-label {
    display: block;
    font-size: 0.8rem;
    color: var(--vps-muted);
    white-space: normal;
}

/* Dimmed, not hidden. A plan we cannot sell today is still a plan we run, and
   removing it from the comparison reads as a plan withdrawn. Same judgement
   the card grid makes with .is-unavailable. */
.vps-row.is-unavailable { opacity: 0.55; }

.vps-row-reason,
.vps-row-tbc {
    font-size: 0.8rem;
    color: var(--vps-muted);
    white-space: normal;
    /* A floor, so a narrow screen scrolls the table sideways rather than
       squeezing this one wrapping column to a word per line -- which made
       every row several times taller once the table stopped overflowing its
       panel (order.css, `.order-configure > *`). */
    display: block;
    min-width: 15rem;
}

.vps-row-free {
    font-weight: 600;
    color: var(--vps-good);
}

.vps-view-toggle { display: inline-flex; gap: 0.3rem; }

.vps-filter-search {
    flex: 1 1 12rem;
    min-width: 0;
    margin: 0;
}

/* Where the table sent you. Without it a customer lands mid-page with no idea
   which of fourteen cards they just asked for. */
.vps-card.is-picked {
    outline: 2px solid var(--vps-accent);
    outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
    .vps-card.is-picked { transition: none; }
}

/* Visually hidden but announced -- the Choose column's header has no visible
   text, and an unlabelled column is a column a screen reader cannot place. */
.vps-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* --- the rebuild confirmation --------------------------------------------
   The only screen on this site that destroys a customer's data. It is styled
   to be read rather than skimmed: each acceptance is its own row with the
   consequence spelled out beside it, not a row of bare boxes. */
.vps-confirm-row {
    display: flex;
    gap: 0.75rem;
    align-items: flex-start;
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--vps-line);
}

.vps-confirm-row:last-of-type { border-bottom: 0; }

.vps-confirm-row input[type="checkbox"] {
    margin-top: 0.15rem;
    flex: 0 0 auto;
}

/* main.css styles every <label> as an uppercase field label with wide
   tracking, which is unreadable for a sentence someone has to actually read
   before agreeing to it. */
.vps-confirm-row label {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    line-height: 1.5;
}

.vps-confirm-name { margin: 1.25rem 0; }

.vps-confirm-name label {
    font-size: 0.95rem;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
}

/* Destructive, and it should not look like the buttons that are not. */
.button.vps-danger {
    background-color: var(--vps-bad);
    box-shadow: none;
    color: #fff;
}

.button.vps-danger:hover { filter: brightness(1.1); }

/* --- one server's view: /vps-manage?server=<id>&tab=<tab> (2026-10-14) ------
   Azure's VM layout: a header with the machine's name, state and power
   controls, a menu down the side grouped Server / Connect / Access / Settings /
   Backup / Help, and the tab's content beside it. The firewall, snapshots and
   plan pages draw the same frame when opened for one server
   (vps-server-frame.php), so moving between them changes only the content.

   REDONE 2026-10-16 after "the tabs up top and the tabs in the VM conflict":
   - the account's Servers | Snapshots | Firewall strip is not drawn inside a
     server at all; the account sits in its own block at the foot of the menu,
     drawn quieter and ruled off, so it never reads as one of this machine's;
   - on a phone the menu is a one-row <details> that opens into the same list,
     instead of four rows of wrapped links above the content;
   - on a wide screen the sidebar is sticky, so it stays beside a long page.
   No script in any of it. Every entry is a link (see vps-server-tabs.php). */

.vps-blade-head { margin-bottom: 1.25rem; }
.vps-blade-crumb { margin: 0 0 0.35rem; font-size: 0.9rem; color: var(--vps-muted); }
.vps-blade-crumb a { color: var(--vps-muted); }
.vps-blade-title { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem 1.5rem; }
.vps-blade-head h1 { margin: 0; display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.75rem; overflow-wrap: anywhere; }
.vps-blade-head h1 .vps-state { font-size: 0.85rem; }
.vps-blade-toolbar { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.vps-blade-toolbar form { margin: 0; }

.vps-blade-body { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1.25rem; }
@media screen and (min-width: 48rem) {
    .vps-blade-body { grid-template-columns: 12.5rem minmax(0, 1fr); gap: 1.5rem; align-items: start; }
}

.vps-blade-nav {
    border: 1px solid var(--vps-line);
    border-radius: var(--vps-radius);
    background: var(--vps-panel);
    padding: 0.5rem 0;
}
.vps-blade-nav ul { list-style: none; margin: 0; padding: 0; }
.vps-blade-nav li { padding: 0; }
.vps-blade-side { position: sticky; top: 1rem; }
.vps-blade-nav-group {
    margin: 0.85rem 1rem 0.25rem;
    font-size: 0.68rem;
    font-weight: 600;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    color: var(--vps-faint);
}
.vps-blade-group:first-child .vps-blade-nav-group { margin-top: 0.35rem; }
.vps-blade-tab {
    display: block;
    padding: 0.4rem 1rem;
    border-bottom: 0;
    border-left: 3px solid transparent;
    color: inherit;
    text-decoration: none;
    line-height: 1.5;
}
.vps-blade-tab:hover { background: var(--vps-panel-hover); text-decoration: none; }
.vps-blade-tab.is-current { border-left-color: var(--vps-accent); background: var(--vps-panel-hover); color: var(--vps-accent); font-weight: 600; }
.vps-blade-tab:focus-visible { outline: 2px solid var(--vps-accent); outline-offset: -2px; }

/* The account's sections: a different place, so a different look. */
.vps-blade-account { margin-top: 0.75rem; padding-top: 0.25rem; border-top: 1px solid var(--vps-line); }
.vps-blade-tab-account { color: var(--vps-muted); font-size: 0.9rem; }
.vps-blade-tab-account::after { content: " \2197"; color: var(--vps-faint); }

/* The phone's menu. Hidden on a wide screen, where the sidebar is drawn. */
.vps-blade-menu { border: 1px solid var(--vps-line); border-radius: var(--vps-radius); background: var(--vps-panel); }
.vps-blade-menu > summary {
    display: flex; align-items: center; gap: 0.6rem;
    padding: 0.55rem 1rem;
    margin: 0;
    line-height: 1.4;
    cursor: pointer;
    font-weight: 600;
    color: var(--vps-accent);
    list-style: none;
}
.vps-blade-menu > summary::-webkit-details-marker { display: none; }
.vps-blade-menu > summary::after { content: "\25BE"; margin-left: auto; color: var(--vps-muted); }
.vps-blade-menu[open] > summary::after { content: "\25B4"; }
.vps-blade-menu-label {
    font-size: 0.68rem; letter-spacing: 0.15em; text-transform: uppercase; color: var(--vps-faint); font-weight: 600;
}
.vps-blade-menu .vps-blade-nav { border: 0; border-top: 1px solid var(--vps-line); border-radius: 0; background: none; }
@media screen and (min-width: 48rem) {
    .vps-blade-menu { display: none; }
}
@media screen and (max-width: 47.99rem) {
    .vps-blade-side { display: none; }
    /* Two columns of entries once opened, so the list is half as long. */
    .vps-blade-menu .vps-blade-nav { display: grid; grid-template-columns: 1fr 1fr; column-gap: 0.5rem; padding: 0.25rem 0 0.5rem; }
    .vps-blade-menu .vps-blade-group:nth-child(2) .vps-blade-nav-group { margin-top: 0.35rem; }
    .vps-blade-menu .vps-blade-account { grid-column: 1 / -1; }
    .vps-blade-menu .vps-blade-account ul { display: grid; grid-template-columns: 1fr 1fr; }

    /* The header's buttons become an even row rather than a ragged wrap. */
    .vps-blade-toolbar { width: 100%; gap: 0.4rem; }
    .vps-blade-toolbar > * { flex: 1 1 0; min-width: 0; }
    .vps-blade-toolbar .button, .vps-blade-toolbar form .button { width: 100%; padding: 0 0.3rem; font-size: 0.6rem; letter-spacing: 0.06em; }

    /* Less frame around everything: on 375px every rem of padding is a rem
       the content does not get. */
    .vps-blade-content .vps-panel { padding: 1.1rem 1rem; }
}

.vps-blade-content .vps-panel:first-child,
.vps-blade-content .vps-notice-top + .vps-panel { margin-top: 0; }
.vps-blade-content > .vps-panel + .vps-panel { margin-top: 1.25rem; }
.vps-blade-content > .vps-notice-top { margin: 0 0 1.25rem; }
.vps-blade-content > .major:first-child { margin-bottom: 1rem; }
.vps-blade-content > .major:first-child h2 { margin: 0; }

.vps-visually-hidden {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* The overview's "Manage" tiles. */
.vps-tiles {
    list-style: none; margin: 0; padding: 0;
    display: grid; gap: 0.75rem;
    grid-template-columns: repeat(auto-fill, minmax(min(8.5rem, 100%), 1fr));
}
.vps-tiles li { padding: 0; }
.vps-tile {
    display: flex; flex-direction: column; gap: 0.2rem; height: 100%;
    padding: 0.85rem 1rem;
    border: 1px solid var(--vps-line); border-radius: var(--vps-radius);
    background: rgba(255, 255, 255, 0.025);
    color: inherit; text-decoration: none; border-bottom: 1px solid var(--vps-line);
}
.vps-tile:hover { background: var(--vps-panel-hover); border-color: var(--vps-line-strong); text-decoration: none; }
.vps-tile strong { color: var(--vps-accent); font-weight: 600; }
.vps-tile span { color: var(--vps-muted); font-size: 0.85rem; line-height: 1.4; }

.vps-inline-form { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.75rem; margin: 0.5rem 0 0; }
.vps-inline-form label { flex-basis: 100%; margin: 0; }
.vps-inline-field { display: inline-flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }
.vps-inline-field input { width: 12rem; max-width: 100%; }

.vps-essentials {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    gap: 0.5rem 1.5rem;
    margin: 0;
}
.vps-essentials dt { color: var(--vps-muted); }
.vps-essentials dd { margin: 0; overflow-wrap: anywhere; }

.vps-table { width: 100%; border-collapse: collapse; font-size: 0.95rem; }
.vps-table th, .vps-table td { text-align: left; padding: 0.45rem 0.6rem; border-bottom: 1px solid var(--vps-line); vertical-align: top; }
.vps-table th { color: var(--vps-muted); font-weight: 600; }
.vps-table td form { margin: 0; }
.vps-muted { color: var(--vps-muted); }

.vps-code-block {
    display: inline-block;
    padding: 0.35rem 0.6rem;
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.35);
    font-family: "Courier New", monospace;
    user-select: all;
    overflow-wrap: anywhere;
}
.vps-connect-line { display: flex; flex-wrap: wrap; gap: 0.6rem; align-items: center; }

.vps-log {
    max-height: 32rem;
    overflow: auto;
    padding: 0.75rem;
    border-radius: 6px;
    background: rgba(0, 0, 0, 0.45);
    font-size: 0.8rem;
    line-height: 1.35;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* THE CONSOLE TAKES THE GUEST'S SHAPE (2026-10-16). It was a fixed
   min(70vh, 48rem) box with a 22rem floor, so a 4:3 picture on a phone floated
   in a tall black column -- the "lots of space around it" the customer saw.
   vps-console.js sets --vps-console-ratio from the framebuffer; the box is as
   wide as it can be and exactly as tall as that ratio makes it, and on a wide
   screen the width is capped so the height still fits the viewport. */
.vps-console-bar { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.5rem 1rem; margin-bottom: 0.75rem; }
.vps-console-toolbar { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
.vps-console-toolbar form { margin: 0; }
.vps-console-toolbar .button { padding: 0 0.9rem; letter-spacing: 0.1em; }
.vps-console-status { color: var(--vps-muted); font-size: 0.9rem; line-height: 1.4; }
.vps-console-status[data-tone="good"] { color: var(--vps-good); }
.vps-console-status[data-tone="bad"]  { color: var(--vps-bad); }
.vps-console-screen {
    --vps-console-ratio: 1.3333;
    width: min(100%, calc((100vh - 9rem) * var(--vps-console-ratio)));
    aspect-ratio: var(--vps-console-ratio);
    margin: 0 auto;
    background: #000;
    border-radius: 6px;
    overflow: hidden;
}
/* Real pixels: as tall as the guest's screen or most of the viewport,
   whichever is less, and noVNC's own box scrolls inside it. */
.vps-console-screen.is-actual-size {
    width: 100%;
    aspect-ratio: auto;
    height: min(75vh, var(--vps-console-height, 75vh));
}
.vps-console-screen:fullscreen { width: 100%; height: 100%; aspect-ratio: auto; border-radius: 0; }
.vps-console-type { margin-top: 0.75rem; }
@media screen and (max-width: 47.99rem) {
    /* Edge to edge inside the panel: the picture is small enough already. */
    .vps-blade-content .vps-console-panel { padding: 0.75rem 0.75rem 1rem; }
    .vps-console-bar { flex-direction: column; align-items: stretch; }
    /* Flex, not grid: as a grid each row came out ~90px tall around 34px
       buttons (main.css's .button sizing does not agree with grid tracks). */
    .vps-console-toolbar > * { flex: 1 1 6.5rem; }
    .vps-console-toolbar .button, .vps-console-toolbar form .button { width: 100%; padding: 0 0.4rem; font-size: 0.6rem; letter-spacing: 0.06em; }
    .vps-console-screen { border-radius: 4px; }
}
.vps-console-type textarea { font-family: "Courier New", monospace; margin: 0.5rem 0; }

.vps-steps { margin: 0 0 1.25rem 1.25rem; }
.vps-steps li { margin-bottom: 0.35rem; }
.vps-form fieldset { border: 1px solid var(--vps-line); border-radius: var(--vps-radius); padding: 0.75rem 1rem; margin: 1rem 0; }
.vps-form legend { padding: 0 0.4rem; color: var(--vps-muted); }
.vps-form textarea { margin-bottom: 0.25rem; }
.vps-check { text-transform: none; letter-spacing: 0; font-weight: 400; margin: 1rem 0; }
.vps-secret summary { cursor: pointer; }
.vps-secret-value { font-size: 1.15rem; letter-spacing: 0.08em; user-select: all; }
.vps-plain-list { margin-left: 1.25rem; }
.vps-plain-list li { margin-bottom: 0.4rem; }

/* --- the create page (vps-ordering.php) ----------------------------------
   The shell is order.css's. These are only the VPS-specific joins: the
   configurator's filter bar and table living inside the Plan step, and the
   radio cards hiding when a filter excludes them.

   [hidden] HAS TO BE RESTATED. order.css gives label.order-choice
   `display: flex` and .order-choices `display: grid`, and an author display
   rule beats the user-agent's [hidden] { display: none } -- so without these
   the filters and the cards/table toggle would change nothing on screen. */

.vps-plan-grid[hidden],
.vps-plan-grid > [hidden],
.order-fieldset .vps-no-matches[hidden] { display: none; }

.vps-plan-grid > .is-picked {
    outline: 2px solid var(--vps-accent);
    outline-offset: 3px;
}

.order-fieldset .vps-filters {
    margin: 0 0 1.25rem;
    padding: 0 0 1rem;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--vps-line);
    border-radius: 0;
}

.order-fieldset .vps-filters[data-enhanced="false"] { display: none; }
.order-fieldset .vps-table-wrap { margin: 0 0 1rem; }
.order-fieldset .vps-os-list { margin-bottom: 1.1rem; }

/* The plan code beside its name: a reference, not a second title. */
.order-choice-name small { font-weight: 400; font-size: 0.78rem; }

/* ---- Bastion (ADO #607): the terminal in a server's Connect > Bastion tab,
   and the recording player on /bastion. Reuses the console's frame.

   [hidden] restated for the same reason as above: .vps-console-bar sets
   `display: flex`, which beats the user-agent's [hidden] rule. */
.bastion-panel [hidden],
#player [hidden] { display: none; }

.bastion-connect { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: flex-end; margin-bottom: 0.75rem; }
.bastion-connect label { flex: 1 1 11rem; margin: 0; }
.bastion-connect .button { flex: 0 0 auto; }

/* The terminal is drawn at the box's own size (bastion.js), so the box keeps
   no aspect ratio of its own; the screen scales only when it must shrink. */
.vps-console-screen.bastion-screen { width: 100%; aspect-ratio: auto; min-height: 24rem; overflow: hidden; }
.vps-console-screen.bastion-screen:focus { outline: 2px solid var(--vps-accent); outline-offset: 2px; }
.vps-console-screen[data-bastion-player="screen"] { width: 100%; aspect-ratio: auto; min-height: 18rem; overflow: hidden; }

.bastion-required { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: flex-end; margin-bottom: 0.75rem; }
.bastion-required label { flex: 1 1 11rem; margin: 0; }

#player input[type="range"] { width: 100%; margin: 0.5rem 0 0.75rem; }

.vps-link-list { list-style: none; padding: 0; margin: 0 0 1rem; }
.vps-link-list li { padding: 0.3rem 0; border-bottom: 1px solid var(--vps-line); }
.vps-link-list li:last-child { border-bottom: 0; }
