/* Components and layout of the public site.
 *
 * The classes are named the way they are in the mock-ups (`.btn`, `.zone`,
 * `.money`, `.cap`, `.card`, `.code`, `.notice`): matching names are the only
 * way to check a page against its mock-up by reading, without a browser.
 *
 * The layout is grid and flex with `gap`. Widths are percentages and
 * `minmax(0, 1fr)` rather than fixed values: there are two mock-ups (1440 and
 * 390) and infinitely many visitor widths, and everything in between has to
 * simply stretch.
 *
 * The page has no horizontal scroll. Anything that does not compress — tables,
 * code, the chart — scrolls inside its own container (`.scroll-x`, `.code`).
 */

/* ── Base ──────────────────────────────────────────────────────────────── */

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

/* `hidden` is how both scripts turn a piece of the page off, and it has to
   mean it.

   The browser's own `[hidden] { display: none }` lives in the user agent
   stylesheet at specificity (0,1,0), so any rule written here at the same
   weight beats it. `.th-basis { display: block }` did, and the sub-line under
   the "per energy unit" header — «live now · average elsewhere» — stood on the
   page in every state of it: a refused network, a schedule of one segment,
   autopricing off, an API carrying none of the fields. The property was set,
   every test that read it back was told `true`, and the words were on screen.
   `.menu` and `.zone-window` were one keystroke from the same thing.

   Nothing in the suite could see it — the fake page in `site/tests/page.js`
   has no computed style — so `site/tests/schedule.test.js` walks the cascade
   instead and asks for this rule by name.

   It has one exception, and it is written down here rather than left to be
   rediscovered: `hidden="until-found"` is specified to keep the element
   rendered but unpainted, so find-in-page can reach the text inside it and the
   browser can reveal it. `display: none` takes it out of the box tree instead,
   and `!important` means no author rule wins it back — so an `until-found`
   section on this site would be hidden, which is what it looks like it should
   be, and unfindable, which is the whole point of the value. Nothing uses it;
   `schedule.test.js` refuses the day something does, and the fix at that point
   is `[hidden]:not([hidden="until-found"])` here. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  /* The page never moves sideways, at any width. `clip` and not `hidden`:
     `hidden` makes the body a scroll container, and the header's
     `position:sticky` stops sticking — silently, and only on a phone. */
  overflow-x: clip;
}

a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accentH); }

/* A link in the page's own copy is underlined, because colour alone does not
   carry it (WCAG 1.4.1). The accent measures 1.24:1 against the body text it
   sits in — a reader who cannot separate those two greens cannot see that
   «How the sandbox works →» is a link at all. The content pages have had this
   since they shipped; `legal.css` underlines `.legal-doc a` with the same two
   declarations, and this is that rule taken out of its scope.

   Written as everything inside `main` less two exceptions rather than as a
   list of prose containers, because the list does not reach the links that
   need it. Counted on the six product pages: 21 links inside `<main>`, of
   which `p a, li a, .faq-a a, .step-text a` reaches 3. The other five that
   want it — «See the API →» in each of the three service cards, «See all
   prices →» in the FAQ tile and in the Auto-refill rate card — are a direct
   `<a>` child of the card body, sitting as the last line of its copy, and on
   `/faq` and `/auto-refill` they are the *only* content links there are. A
   list of containers would have left the finding open on two of the six
   pages it names.

   The two exceptions are the two links that are not text in a block: a button
   is a shape, and `.section-head-link` stands on its own line beside an `h2`.
   Nav, menu, footer columns and the language switch are outside `main` and
   are not reached from here at all. On the 19 content pages this changes
   nothing: every link there is already inside `article.legal-doc`. */
main a:not(.btn):not(.section-head-link) {
  text-decoration: underline;
  text-underline-offset: 2px;
}

h1, h2, h3, p { margin: 0; }
img, svg { max-width: 100%; }

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

.wrap {
  max-width: var(--wrap);
  margin: 0 auto;
  padding: 0 16px;
}

/* Classes that go on `.wrap` alongside it (`class="wrap section"`) set only
   vertical spacing — `padding-top`/`padding-bottom`, never `padding`. The
   shorthand here used to override `.wrap`'s own side padding (equal
   specificity, so whichever comes later wins), and on a phone the text ran
   into the edges of the screen. There is nothing in the markup to notice it
   by: at 1440 the mock-up has margins anyway, because the page is already
   centred. */

/* The keyboard link: visible only once tabbing has reached it. */
.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 40;
  padding: 10px 16px;
  background: var(--accent);
  color: var(--accentInk);
  font-size: 13px;
}
.skip:focus { left: 0; color: var(--accentInk); }

/* ── Atoms from the mock-up ────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 0 16px;
  height: 44px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 500;
  line-height: 1;
  /* The label holds its line at every width, and the height above is why: 44px
     with `line-height: 1` has room for two lines of 15px and none at all for
     three, so a label that ever wrapped that far would be drawn outside the
     button it belongs to.
   *
   * It was `normal` on a phone for one revision, as part of the F-01 fix —
   * three declarations landed together and the page came back inside the
   * viewport. Reverted one at a time against the same measurement (fourteen
   * pages at 320/360/390, the zone countdown pinned at «1h 45m», 42 readings),
   * the credit is not shared: `.hero-foot .money` accounts for 25 of the 42
   * and `.hero > * { min-width: 0 }` for the other 3. Taking `.btn` back to
   * `nowrap` leaves 0 of 42 past the viewport — it was carrying nothing. */
  white-space: nowrap;
  cursor: pointer;
}
.btn-primary { background: var(--accent); border-color: var(--accent); color: var(--accentInk); }
.btn-primary:hover { background: var(--accentH); border-color: var(--accentH); color: var(--accentInk); }
.btn-secondary { background: var(--surface); border-color: var(--line); color: var(--text); }
.btn-secondary:hover { background: var(--hover); color: var(--text); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn-ghost:hover { color: var(--text); }
.btn-lg { height: 48px; font-size: 15px; font-weight: 600; padding: 0 22px; }
/* The "transparent secondary" button from the hero. */
.btn-outline { background: transparent; border-color: var(--line); color: var(--text); }
.btn-outline:hover { background: var(--surface); color: var(--text); }

.zone {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 3px 8px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--surface2);
  font-family: var(--mono);
  font-size: 11px;
  color: var(--muted);
}
.zone-offpeak { border-color: var(--infoSoft); background: var(--infoSoft); }
.zone-peak { border-color: var(--warnSoft); background: var(--warnSoft); }

/* The zone cell of the price table: the display label, the contract value the
   API returns, and — on the segment running right now — a word saying so.
   `standard` is shown as "Mid-peak" because in energy retail a standard plan
   is the flat one; the raw token stays beside it because an order comes back
   carrying it and the cabinet prints it verbatim. */
/* The column is narrow and the three parts never fit on one line, so they
   stack on purpose rather than wrapping wherever they happen to break: the
   name first, then the contract value and the marker together underneath. */
.zone-cell { display: flex; flex-direction: column; align-items: flex-start; gap: 5px; }
.zone-tokens { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.zone-label { font-weight: 500; }
.zone-now {
  padding: 2px 7px;
  border-radius: var(--radius);
  border: 1px solid var(--okSoft);
  background: var(--okSoft);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ok);
}
/* The row of the running zone. The tint is the redundant half of the marking:
   `aria-current` and the word above carry it where colour does not reach. */
tr[aria-current="true"] > td { background: var(--surface2); }

/* The row of a zone that is not running, showing what its last completed
   window actually cost.

   The whole row, because three of its four number columns are computed from
   the first one: "one transfer", "fresh address" and "vs burn" are the same
   average times an energy figure, so they are one statement about one window
   that has closed. While the mark was a token in front of the price alone, the
   other three sat under their ordinary headers in their ordinary colours and
   read as a quote — the cheapest number on the page, for a price nobody could
   be charged.

   Three signals, and every one of them is text:

     · the "avg" token in the identity cell, beside where "now" goes;
     · the window line under the hours — «average over 08-30 01:00 → 12:00 utc»,
       which says what the number is an average *of*;
     · `aria-describedby` on the row, pointing at the note under the table:
       only the row marked now carries a live price.

   There used to be a fourth: a 2px rule down the left edge of the row, in
   `--dim` for the 3:1 that 1.4.11 asks of a graphic carrying meaning, and
   redrawn as a border under forced colours because a `box-shadow` is dropped
   there. It is gone (30.08), and not because the contrast argument was wrong.
   It is gone because it did not read: the owner met it on his own page and
   asked what the stripe between the zones was. It stood at the outer edge of
   the card with nothing beside it, and two adjacent averaged rows made two
   collinear segments that read as one broken line. A mark whose own reader
   takes it for an artefact is not a signal, whatever its contrast — and with
   the three above all being text, there is no graphic left for 1.4.11 to ask
   about, and nothing that forced colours can drop.

   The tint on an averaged row stays and is still not counted: `--surface2` on
   `--surface` is four steps a channel, 1.05:1, texture rather than a signal.
   `site/tests/schedule.test.js` holds the three to the page. */
tr[data-averaged] > td { color: var(--muted); }
/* The two cells that used to colour themselves inline. An inline style outranks
   any rule written here, so the "vs burn" cell stayed the good-news green
   inside a marked row; the colours live here now and the mark can reach them. */
.zone-hours { color: var(--muted); }
.zone-vs { color: var(--ok); }
tr[data-averaged] > td.zone-vs { color: var(--muted); }

/* Which window an averaged row is the average of. Its own line under the
   hours, because "24.10" without "when" is a price a visitor thinks they can
   have.

   The column used to be held open at 136px to stop the stamp breaking inside a
   timestamp — `08-` on one line and `26 00:00` on the next. It did not work:
   at 136px the midnight-crossing window still came out `average over 08-25` /
   `14:00 → 08-26` / `00:00 utc`, with the arrow's target date on one line and
   its time on the next. Fighting a line break with a column width means
   guessing a width that beats every translation of "average over", which is
   not a thing that can be guessed.

   The break is stopped where it happens instead: `js.zone_avg_window` is
   filled in with a no-break space between each date and its time
   (`assets/site.js`), so `08-26 00:00` is one word to the line breaker and the
   column may be any width at all. Which is the other half of it — the pin was
   17px of the 842 this table demanded of a 644px card. */
.zone-window {
  display: block;
  margin-top: 3px;
  font-size: 11px;
  color: var(--dim);
  /* The stamp asks the column for nothing and takes what it is given.
     `average over 08-26 00:00 → 01:00 utc` is forty characters, and a column
     sized to hold that on one line is 242px of the table — which it took, out
     of the headers, once they were allowed to wrap for it. On the home page
     that turned three of five headers into two lines each with the hours
     column at 182px of 644. `width: 0` keeps it out of the column's natural
     width; `min-width` puts it back once the column has one.

     The floor is one timestamp: `MM-DD HH:MM` is eleven characters of a
     monospace face, and 11ch here is exactly that. It is the one run in the
     stamp that cannot break (`windowText` binds it), so a column narrower than
     this would push it out of the cell. That is what the 136px pin was reaching
     for, said as the thing it is instead of as a number. */
  width: 0;
  min-width: max(100%, 11ch);
}
/* The token that says a row is an average of the past, not a price. It sits
   beside "now" in the identity cell — the row's label, not one cell's. */
.zone-basis {
  padding: 2px 7px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--dim);
}

.money {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* What a price is worth in dollars, beside the price — «2.115 TRX ≈ $0.71».
   `site.js` builds it, puts it after the amount's unit, and takes it out again
   when the tariff answers without a rate.

   A real element, not `::after` on the amount. Generated content put the
   dollars between the figure and its unit, because the unit is the amount's
   sibling rather than part of it, and nothing could see that it had: generated
   content is not in the DOM, cannot be selected or copied, and does not reach
   anything that reads the page as text.

   A dollar equivalent has a floor of 13px and is drawn in `--muted`, not in
   `--dim` (operator, 2026-09-18). It is secondary to the figure it qualifies
   and it is still the figure most visitors price the product in — at `.85em`
   of a 15px line it came out at 12.75px, which is under the floor, and it was
   the least legible text on the page. The relative size is kept for the
   places where the amount beside it is large: `max()` only bites where the
   parent is small. */
.usd-eq {
  margin-left: .4em;
  color: var(--muted);
  font-size: max(13px, .85em);
  font-weight: 400;
  letter-spacing: 0;
  white-space: nowrap;
}

.cap {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim);
}
.cap-accent { color: var(--accent); }

.h2 { font-size: 20px; font-weight: 600; letter-spacing: -.02em; }
.h3 { font-size: 24px; font-weight: 600; letter-spacing: -.02em; }

.card { border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.card-accent { border-color: var(--accent); }

.notice {
  display: flex;
  gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface2);
  color: var(--muted);
  font-size: 13px;
}
.notice-sm { font-size: 12px; }

/* A `.notice` that is a `details`. `.notice` is a flex row, and a flex
   container makes the summary and the disclosed paragraph into two columns
   side by side — so this one is a block, and the marker is its own.

   `details` rather than a script: the specification behind it is the honest
   half of a promise, and a promise that only opens once JavaScript has arrived
   is a promise conditional on JavaScript arriving. */
.notice-more {
  display: block;
  color: var(--dim);
}
.notice-more > summary {
  cursor: pointer;
  list-style: none;
  color: var(--muted);
  /* Room for the marker drawn below, and a target big enough to hit. */
  padding-left: 16px;
  position: relative;
}
.notice-more > summary::-webkit-details-marker { display: none; }
/* The triangle, drawn rather than taken from the user agent: the default one
   sits outside the padding box and is a different shape in every browser. */
.notice-more > summary::before {
  content: "";
  position: absolute;
  left: 2px;
  top: .45em;
  border: 4px solid transparent;
  border-left-color: currentColor;
}
.notice-more[open] > summary::before {
  left: 0;
  top: .6em;
  border-left-color: transparent;
  border-top-color: currentColor;
}
.notice-more > p { margin: 10px 0 0; text-wrap: pretty; }

.code {
  margin: 0;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface2);
  color: var(--text);
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.65;
  overflow-x: auto;
  tab-size: 2;
}

/* Anything that does not fit scrolls inside its own frame rather than moving
   the page. On macOS the scrollbar that would say so is an overlay: it is not
   drawn until the pointer is already on the element, so a table with two
   columns past its right edge looks exactly like a table that ends there.

   The shadows say it instead. Two pairs of backgrounds — a solid one pinned to
   each edge of the *element* and a gradient pinned to each edge of the
   *content* — sit on top of each other: `background-attachment: local` moves
   the gradient with the scroll, so the covering solid slides away from an edge
   there is more content past and back over it when there is not. No script,
   and it answers the question at the moment a reader has it. */
.scroll-x {
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--surface) 30%, rgba(17, 21, 20, 0)) left center,
    linear-gradient(to left, var(--surface) 30%, rgba(17, 21, 20, 0)) right center,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .55), rgba(0, 0, 0, 0)) left center,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .55), rgba(0, 0, 0, 0)) right center;
  background-repeat: no-repeat;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}

table { width: 100%; border-collapse: collapse; font-size: 13px; }
th {
  text-align: left;
  padding: 8px 16px;
  background: var(--surface2);
  border-bottom: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
}
td { padding: 15px 16px; border-bottom: 1px solid var(--lineS); vertical-align: middle; }
tr:last-child td { border-bottom: none; }
.num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* The zone table, which is the one table on the site with six columns of
   translated headers in it.

   `th { white-space: nowrap }` above makes a header one unbreakable run, and
   for a table of two or three short columns that is right — a header split
   over two lines for no reason reads as a mistake. For this one it meant the
   table had a *rigid* width: 842px, the same at 1024 and at 1920, because the
   headers decide it and headers that cannot break cannot give anything up. A
   card of 644px showed four of the six columns and nothing said the other two
   existed. `Fresh address` and `vs burn` were off the edge at every desktop
   size the site is used at, and `vs burn` carries the green «−85%» that is one
   of the strongest things separating the live row from the averaged ones.

   Allowed to break, the same headers give the table a floor of about 500px and
   a ceiling of about 900: it lays flat when the page has room and reflows when
   it does not, instead of hanging two columns off the side either way. On a
   phone that is the difference between dragging 500px sideways to reach the
   last column and dragging 160.

   `site/tests/layout.test.js` computes both numbers out of this stylesheet and
   the shipped markup, in all eleven locales, and holds the arithmetic against
   widths measured in a browser. Nothing else in the suite can see a column
   that is off the edge: every cell is in the DOM and every string in it is
   correct. */
.zone-table th { white-space: normal; }
/* And on a phone the padding comes in. 16px a side of a 341px window is a
   tenth of the screen given to each column's margins, and what got pushed off
   the edge for it was the price. The wide value comes back at 720px, where
   there is room for it.
   8px and not 10 since the 12px text floor (2026-09-18): a point of type on
   every header buys back ~17px across the first three columns, and in German
   «Je Energieeinheit» — floored at `Energieeinheit`, fourteen unbreakable
   characters — put the price column's right edge 0.44px past a 341px window
   for it. Two pixels a side over three columns is 12 of the 17 back, and the
   same argument the paragraph above makes: the margins are what the column
   can give up, the price is not. Measured, not guessed —
   `site/tests/layout.test.js` computes all eleven locales at the phone's own
   type. */
.zone-table th,
.zone-table td { padding-left: 8px; padding-right: 8px; }

/* The lease table borrows the zone table's frame — four columns instead of
   six, and the same rules about headers that may break. What is its own is
   the first column: a lease is a name rather than a token, and it is the one
   cell of the row that is not a number.

   The line under it says where the figures came from, in the mono face every
   other provenance line on the site uses (`.ticker-src`, `.chart-src`), and on
   its own line under the notes: it is about the table above it, not about the
   paragraph beside it. */
.lease-table td:first-child { white-space: nowrap; }
.lease-name { color: var(--text); }
.lease-src {
  display: block;
  margin-top: 4px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim);
}

/* The "tiles in a frame" grid: the background line shows through the gaps. */
.tiles {
  display: grid;
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.tiles > * { background: var(--surface); }

.dash { display: flex; gap: 10px; }
.dash::before { content: "—"; color: var(--accent); }

/* The icons speak the same language as the frames: a 1.6 stroke, no fills and
   no rounding, and the colour is inherited from the heading line
   (`currentColor`), so an icon changes together with its text rather than
   through a rule of its own per block. */
.icon { display: block; flex: none; }
.step-head, .service-head, .trust-head, .point-head { display: flex; align-items: center; gap: 10px; }
.step-head, .service-head, .point-head { color: var(--accent); }
.trust-head { color: var(--dim); }
.trust-head .trust-title { color: var(--text); }

/* ── Language strip ────────────────────────────────────────────────────── */

/* The strip sits above the ticker, as the first child of the body, and is in
   the markup on every page — hidden. The script that decides whether to show
   it only removes the attribute, so the page never jumps: a strip inserted
   after the page is on the screen pushes everything below it down by its own
   height, and that is the one moment the reader is already reading.

   `[hidden]` needs saying out loud because `display: flex` on the row would
   otherwise win over the browser's `display: none` on the parent — the same
   trap `.menu[hidden]` sits in above. */
.langbar { border-bottom: 1px solid var(--line); background: var(--surface2); }
.langbar[hidden] { display: none; }
/* The offer and the way out of it stand together in the middle of the strip.
   They used to sit at its two ends — `margin-left: auto` on the button — and
   on a wide screen that is a sentence at one edge and a lone word at the
   other, with a thousand pixels of nothing between them: it reads as two
   things that happen to be on the same line rather than as one offer. The
   strip is a bar the full width of the window and its content is two short
   runs of text; centring is what makes the pair look chosen. */
.langbar-row {
  padding-top: 8px;
  padding-bottom: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px 16px;
  font-size: 13px;
  color: var(--muted);
}
.langbar-link { color: var(--accent); }
.langbar-link:hover { color: var(--accentH); }
/* A button, not a link: it dismisses a suggestion, it does not go anywhere.
   The rule on its left is a divider, not decoration — it keeps the offer and
   its refusal legible as two separate targets now that they are neighbours,
   and it is drawn rather than typed so no screen reader has to read it. */
.langbar-close {
  padding: 2px 0 2px 16px;
  border: 0;
  border-left: 1px solid var(--line);
  background: none;
  font: inherit;
  font-size: 12px;
  color: var(--dim);
  cursor: pointer;
}
.langbar-close:hover { color: var(--text); }

/* ── Ticker ────────────────────────────────────────────────────────────── */

.ticker { border-bottom: 1px solid var(--line); background: var(--surface); }
/* The tracking and the gaps are the phone's here, and the design's from 1024
   up. Five facts set in uppercase mono at .12em fall into four ragged lines on
   a 390px screen: «тариф · peak» and «31 sun / единица» share the first one
   across a 28px hole, the next two lines carry one item each, and the source
   takes the fourth. Nothing is broken and nothing lines up either.

   At .04em the two long items — «1 перевод = 2.015000 trx» and «следующая
   смена 04:47:32» — come down from 190px to about 169px, and two of them fit
   the 358px a phone gives this row. Three even lines instead of four ragged
   ones, and no fact hidden to get there: each of them is the live proof the
   strip exists for. From 1024 the whole row fits at the design's own tracking
   anyway. */
.ticker-row {
  padding-top: 8px;
  padding-bottom: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--dim);
}
.ticker-row .lit { color: var(--text); }
.ticker-row .accent { color: var(--accent); }
/* Where the data came from: on a wide screen it joins the row on the right,
   on a narrow one it takes a line of its own on the left. A stray tail of a
   second line pushed to the right edge reads as broken layout rather than as
   a decision. */
.ticker-src { flex-basis: 100%; }

/* ── Header ────────────────────────────────────────────────────────────── */

.nav {
  border-bottom: 1px solid var(--line);
  background: var(--bg);
  position: sticky;
  top: 0;
  z-index: 20;
}
.nav-row { padding-top: 12px; padding-bottom: 12px; display: flex; align-items: center; gap: 12px; }
.logo { font-family: var(--mono); font-size: 13px; font-weight: 600; letter-spacing: .02em; color: var(--text); }
.logo:hover { color: var(--text); }
.logo .dot { color: var(--accent); }
.nav-links { display: none; gap: 20px; font-size: 13px; }
.nav-links a { color: var(--muted); }
.nav-links a:hover { color: var(--text); }
/* «Docs ↗»: the one row item that leaves the site, and the arrow says so
   before the click. The glyph is markup — `<span class="ext"
   aria-hidden="true">` after the word — rather than generated content, so the
   guard that pins the header (`site/tests/chrome.test.js`) reads it off the
   page, and a screen reader is not read a symbol it cannot name. Smaller and
   dimmer than the word: a mark, not a second word — 12px, which is the
   phone's floor for text and is not asked for one. */
.nav-links .ext,
.menu .ext { margin-left: .3em; font-size: 12px; color: var(--dim); }
/* The page you are on is marked in the menu — and marked by the same
   attribute a screen reader announces, not by a class chosen by eye. */
.nav-links a[aria-current="page"], .menu a[aria-current="page"] { color: var(--text); }
.nav-side { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.nav-side .btn {
  height: 32px;
  font-size: 12px;
  padding: 0 12px;
  /* The hit area, which is not the pill. 32px is the header's own height and
     the pill is what the mock-up draws; a finger needs 44. So the button
     carries an invisible 44px box centred on it (`::before` below) instead of
     growing — the burger beside it does the same thing with a 44px box and a
     negative margin, and it can, because nothing is drawn on the burger. Here
     something is: `height: 32px` with `box-sizing: border-box` means padding
     cannot make the hit area bigger either, it only squeezes the label. */
  position: relative;
}
/* 44×32 of button, 44×44 of target, nothing drawn and nothing moved: the
   overlay is out of flow, so it is not a flex item of the button and the
   `gap: 8px` inside it never sees it. It reaches 6px into the row's own 12px
   of padding, where nothing else lives; the burger is the next control along
   and `.nav-side`'s 10px gap keeps the two apart. */
.nav-side .btn::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
}
/* Two identical primary buttons on the first screen of a phone is one call to
   action asked twice and answered once. Where the hero has its own — the home
   page and `/auto-refill`, which say so with `data-hero-cta` on the header —
   the header's copy steps back to the secondary style below 720px, the width
   at which the two share a screen. Above it the header button is the only one
   in view at the top of the page, and it keeps the accent.
   `site/tests/nav.test.js` holds the attribute to the pages whose hero really
   carries a `.btn-primary`. */
.nav[data-hero-cta] .nav-side .btn-primary {
  background: var(--surface);
  border-color: var(--line);
  color: var(--text);
}
.nav[data-hero-cta] .nav-side .btn-primary:hover {
  background: var(--hover);
  color: var(--text);
}
.nav-signin { display: none; font-size: 13px; color: var(--muted); }
.nav-signin:hover { color: var(--text); }

/* The sign-in link is written twice, because the header is two headers.
 *
 * The rule above hides it and the ≥720px block brings it back — it is a
 * wide-screen affordance, and on a phone the row holds the logo, «Get
 * started» and the burger. So the phone's copy lives in the menu, and it
 * needs saying here: a copy dropped into the menu inherits `display: none`
 * from the class that marks it and is invisible on exactly the width the menu
 * exists for. That is what shipped. Every call to action on the site opens the
 * sign-up form, `.nav-signin` is the one link that means sign in, and below
 * 720px nobody could see it: a customer on a phone was offered a second
 * account and no way into the one they had, on every page at once.
 *
 * The colour is stated rather than left to `.menu a` winning it by one point
 * of specificity. `--muted` in a column of rows drawn in `--text` reads as a
 * row that is switched off, and a rule that holds by a tie-break stops holding
 * the day somebody writes `.nav-side .nav-signin`. */
.menu .nav-signin { display: block; color: var(--text); }

/* The bare header: 404 carries no `.nav-links` and no burger, so it has no
   menu to put the link in. It is given its own link at every width rather than
   a menu of its own — four links to elsewhere on a page whose whole message is
   «there is nothing at this address» is navigation nobody came for, and the
   two links it already has are the two that answer the situation. The row has
   the room in all eleven locales; `site/tests/nav.test.js` does that
   arithmetic rather than leaving it as a claim.

   `.nav-bare .nav-signin { display: inline }` stood here until 2026-09-19. It
   dressed the stripped header the 404 used to ship, and that header is gone —
   no page carries the class, and a selector aimed at nothing reads like a
   variant somebody might still use. */

.health {
  display: none;
  align-items: center;
  gap: 7px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim);
}
.health .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); flex: none; }
.health[data-state="degraded"] .dot { background: var(--warn); }
.health[data-state="down"] .dot { background: var(--danger); }
.health[data-state="unknown"] .dot { background: var(--neutral); }
/* The chip is the header's «status» item since wave 2 of the site review: a
   link to the status page, not a caption beside one. Being an anchor it
   would take `a:hover`'s accent, which is the colour of a link in the page's
   copy and of nothing in the header; the row's links hover to `--text`, and
   so does this. */
.nav-side .health:hover { color: var(--text); }

.burger {
  /* The glyph is drawn with `stroke="currentColor"`, like every other icon:
     the colour lives here, in a token, rather than as a copied value in the
     markup of three pages. */
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44×44, and nothing on screen moves for it: the button has no background
     and no border, so its box is invisible and only the 20px glyph inside it
     is drawn. It was 32×32 and it is the only way into the navigation on a
     phone. The negative block margin gives the row back the 12px the height
     took — 44 less 12 is the 32 the «Get started» button beside it is, so the
     header keeps the height it had. Nothing else lives in the 6px above and
     below, and the button is at the right edge, so the taller hit area
     overlaps no other control. */
  width: 44px;
  height: 44px;
  margin-block: -6px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.menu {
  padding: 8px 0 16px;
  display: flex;
  flex-direction: column;
  border-bottom: 1px solid var(--line);
}
.menu[hidden] { display: none; }
.menu a {
  color: var(--text);
  padding: 12px 0;
  border-bottom: 1px solid var(--lineS);
  font-size: 15px;
}
/* The status row of the menu: the same link as the header's chip, drawn as
   one of the rows — the rule under it, the row's own padding — with the dot
   and the mono caption that say it is live rather than a destination typed
   by hand. `.menu a` wins the colour and the size from `.health` by one point
   of specificity, so both are restated here; `--muted` rather than the chip's
   `--dim` because a row two shades down from its neighbours reads as one
   that cannot be pressed (the sign-in row learned that first). The floor is
   the finger's: 12px mono on a 1.5 line is 18px, and 18 + 24 of padding is
   two short of 44. */
.menu .health {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  font-size: 12px;
  color: var(--muted);
}
.menu .health:hover { color: var(--text); }

/* The language switch inside the phone menu.
 *
 * The rule is here before the markup is: the line that adds the switch to the
 * menu adds `<nav class="lang-switch">` to it and nothing else, and without
 * this the eleven names would inherit `.menu a` and come out as eleven full
 * rows, each with its own rule under it — a menu of five destinations
 * followed by eleven of the same page. A wrapping row of chips says what they
 * are: one choice with eleven answers, not eleven more places to go.
 *
 * `.menu a` is overridden rather than tweaked: its `padding`, its
 * `border-bottom` and its `font-size` are all written for a row. */
.menu .lang-switch { padding: 14px 0 2px; gap: 8px; }
.menu .lang-switch a {
  padding: 0 12px;
  border: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface2);
  color: var(--text);
  font-size: 15px;
}
.menu .lang-switch a[aria-current="true"] {
  border-color: var(--accent);
  background: var(--accentSoft);
}

/* ── 1 · Hero ──────────────────────────────────────────────────────────── */

.hero { padding-top: 40px; padding-bottom: 32px; display: grid; gap: 24px; align-items: center; }
/* The same floor `.calc-copy` sets, for the same reason and after the same
   defect. A grid item is floored at its own min-content width by default and
   the track is floored at the item, so one unbreakable thing inside the hero
   sets the width of the whole page: on `/auto-refill` it is the `pre.code`
   JSON sample, whose `white-space: pre` made the document 418px wide at 390
   and cut that page's own primary CTA off at the right. The `pre` has its own
   `overflow-x: auto` and scrolls inside its card, which is what it was meant
   to do. Not scoped to a breakpoint: `grid-template-columns: 7fr 5fr` at
   1024 is `minmax(auto, 7fr)`, so the same floor is there too. */
.hero > * { min-width: 0; }
.hero-copy { display: flex; flex-direction: column; gap: 18px; }
/* Sized for the board's two-line headline — «USDT transfers on TRON / at a
   fraction of the fee.» — at 56px on a 1280 screen; the 64px the shorter
   headline before it was set at puts the same words on three lines. */
.hero h1 {
  font-size: clamp(32px, 4.4vw, 56px);
  line-height: 1.08;
  font-weight: 700;
  letter-spacing: -.03em;
  text-wrap: balance;
}
.hero-lede { color: var(--muted); font-size: clamp(15px, 1.4vw, 18px); line-height: 1.55; text-wrap: pretty; }
.hero-actions { display: flex; flex-direction: column; gap: 10px; }
.hero-actions .btn { width: 100%; }

.hero-card {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.bar-row { display: flex; flex-direction: column; gap: 8px; }
.bar-head { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; }
.bar { height: 14px; background: var(--lineS); overflow: hidden; }
.bar-fill { height: 100%; background: var(--barBurn); }
.bar-fill-accent { background: var(--accent); }
.hero-foot {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  justify-content: space-between;
  border-top: 1px solid var(--lineS);
  padding-top: 16px;
}
/* The other unbreakable run of F-01. The foot of the hero card is a pair of
   figures with their captions, and `.money` is `nowrap` — right for a figure,
   wrong for the line it sits in once the line is narrower than the pair. Same
   trade as `.calc-amount .money` and `.calc-blend`: the tabular figures stay,
   the break falls between the amount and the words around it. From 720px up
   the pair has room and `nowrap` comes back. */
.hero-foot .money { white-space: normal; }
/* The four pieces of small type in the hero card, which were four `style=`
   attributes on the markup until 2026-09-19.
   An inline `font-size` is a rule no stylesheet can reach: no media query
   touches it, so `@media (max-width: 719.98px)` could not floor it at 12px on
   a phone, and no test that reads the sheet could see it at all — the census in
   `layout.test.js` counted the floor's own selectors and reported two elements
   left under 12px on the home page while there were four. And the markup is
   generated into eleven copies, so the attribute had eleven copies too.
   `.hero-terms` is the only one under the floor; it is floored below. */
.bar-label { font-size: 13px; }
.bar-label-burn { color: var(--muted); }
.hero-saving { font-size: 13px; color: var(--ok); }
.hero-terms { font-size: 11px; color: var(--dim); }
.hero-card-head { display: flex; align-items: center; gap: 8px; }
.hero-card-head .zone { margin-left: auto; }

/* ── Stat strip ────────────────────────────────────────────────────────── */

.stats {
  margin: 24px 0 40px;
  border-top: 1px solid var(--line);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.stats > div {
  padding: 20px 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  border-right: 1px solid var(--lineS);
}
/* The left column sits flush with the edge of the page and the right one has
   no rule on its right: otherwise the grid does not line up vertically with
   the rest of the blocks. */
.stats > div:nth-child(odd) { padding-left: 0; }
.stats > div:nth-child(even) { border-right: none; }
.stats .money { font-size: 22px; }
/* The facts strip under the hero. «about a second» at 22px is wider than a
   phone's half-width cell, so the figures may break here — on a desktop the
   cells are wide enough that none does. The count of suppliers is a number
   followed by a translated word in one `.money` run: the number is the live
   hook, and the word is `[data-suppliers-word]` — whichever of its plural
   forms the number beside it takes, redrawn with the number. */
.facts .money, .facts .money > span { white-space: normal; }

/* ── 2 · What it costs ─────────────────────────────────────────────────── */

/* Three cards: the price of one transfer, the month, the market. One column
   on a phone, three from 720px. The card links are 44px tall targets — the
   wave-0 rule for anything a finger has to hit. */
.cost-cards { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
.cost-card { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
.cost-figure { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 8px; }
.cost-big { font-size: 28px; color: var(--accent); letter-spacing: -.02em; }
.cost-unit { font-family: var(--mono); font-size: 15px; color: var(--muted); }
.cost-title { font-size: 17px; font-weight: 600; letter-spacing: -.02em; text-wrap: pretty; }
.cost-title.money { font-size: 22px; font-weight: 500; white-space: normal; }
.cost-text, .cost-note { font-size: 13px; color: var(--muted); text-wrap: pretty; }
.cost-link, .product-link, .trust-link {
  font-size: 13px;
  margin-top: auto;
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.leases-line { margin: 0; font-size: 13px; color: var(--muted); text-wrap: pretty; }

/* ── 2 · Calculator ────────────────────────────────────────────────────── */

.calc { padding-bottom: 40px; }
.calc-card { padding: 20px; display: grid; gap: 20px; align-items: center; }
/* `min-width: 0` is the whole reason the card stays inside itself. A grid item
   is floored at its own min-content by default, and the track is floored at the
   item: the mix table below made `.calc-copy` 368px wide in Spanish, the single
   column of the phone layout followed it, and the card's contents ended 101px
   past its own border at 320px. `overflow-x: clip` on the body means there is
   no scrollbar to get them back — the weekend column simply could not be
   reached. With the floor gone the track is the card's width and the content
   fits into it. */
.calc-copy { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.calc-hint { color: var(--muted); font-size: 13px; max-width: 44ch; }
.calc-control { display: flex; flex-direction: column; gap: 10px; }
.calc-control-head { display: flex; justify-content: space-between; align-items: baseline; }
.calc-control-head .money { font-size: 20px; }
.calc-scale { display: flex; justify-content: space-between; font-size: 11px; color: var(--dim); font-family: var(--mono); }
/* The answer, as a thing of its own rather than as text that happens to be on
   the right. It is what the block is for, and until it had a surface under it
   it read as a caption to the form beside it — worse on a phone, where the
   single column put three lines of totals after a screenful of controls with
   nothing to say the questions had ended. */
.calc-out {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 16px 18px;
  border: 1px solid var(--lineS);
  border-radius: var(--radius);
  background: var(--surface2);
}
.calc-line {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--lineS);
  font-size: 13px;
}
.calc-line-label { color: var(--muted); }
/* Dollars go under the TRX figure, not beside it: on a narrow screen the two
   amounts would otherwise share one column's width with the label. Both they
   and the rate line disappear when the rate is unknown — the script empties
   them, and `:empty` takes the space back rather than leaving a gap where a
   number used to be. */
.calc-amount { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; text-align: right; }
/* And the amount wraps rather than pushing the line out of the card. `.money`
   is `nowrap`, which is right for the figure and wrong for the unit beside it:
   «204,750 TRX / Monat» is one unbreakable 194px run, and next to its own label
   that is 274px of a 246px line at 320px — the amount ended up outside the card
   and the number was clipped mid-word. The break can only fall between the
   figure and the unit, both of which are their own spans; there is no break
   opportunity inside «204,750». Same trade as `.calc-blend` below, and `.money`
   keeps the tabular figures either way. */
.calc-amount .money { white-space: normal; }
/* Dollars again, under a total rather than beside an amount: same floor, same
   colour, same reason as `.usd-eq` above. */
.calc-usd { color: var(--muted); font-size: 13px; font-family: var(--mono); }
.calc-usd:empty { display: none; }
/* The rate under a set of amounts: the calculator's totals on the two pages
   that have one, the top-up rates on `/auto-refill`. Two names for one line
   because the mock-ups name them separately, and one rule because they are
   the same line — small, sitting under the figures it converts. `:empty`
   takes the space back when the tariff answers without a rate.

   «1 trx = $0.3373» is a dollar figure too, and it is the one every other
   dollar figure on the page is derived from, so it takes the same floor and
   the same colour as `.usd-eq`. It was 11px in `--dim`. */
.calc-fx, .rate-fx { padding-top: 10px; font-size: 13px; color: var(--muted); text-align: right; }
.calc-fx:empty, .rate-fx:empty { display: none; }
.calc-total { border-bottom: none; padding-top: 14px; }
.calc-total .calc-line-label { color: var(--text); font-weight: 600; }
.calc-total .money { font-size: 22px; color: var(--ok); letter-spacing: -.02em; }
/* The percentage rides with the figure it qualifies, a size down. Inline
   `style` did that before, which put a type decision in the markup of two
   pages and eleven locales apiece. */
.calc-pct { font-size: 13px; }
.calc-burn { color: var(--danger); font-size: 17px; }
.calc-ours { font-size: 17px; }

/* The price of one transfer at the mix set below — the figure both totals
   above are built from. A line of prose around a number rather than digits
   alone, so `.money` keeps the tabular figures but its `nowrap` comes off:
   the sentence is longer in German and in Vietnamese, and the card is not. */
.calc-blend {
  padding-top: 12px;
  font-size: 12px;
  color: var(--muted);
  text-align: right;
  white-space: normal;
}

/* ── Traffic mix ───────────────────────────────────────────────────────── */

/* When the transfers happen: one question with five answers, the mix it comes
   to, and — behind the last of the five — the shares by zone and by type of
   day it used to open with. The zone chips, the hours and the sun price in
   there repeat the tariff table on /pricing on purpose: it is the same
   schedule, and `applySchedule` writes into both in one pass. */
.calc-mix {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 14px;
  border-top: 1px solid var(--lineS);
}

/* ── When the transfers happen ─────────────────────────────────────────── */

/* Five answers to one question, drawn as chips over the browser's own radio
   buttons: the arrow keys, the focus ring and the announcement come with the
   control, and a `div` with a click handler owes all three back. Only the box
   is hidden — the input still takes focus, and `:focus-visible` puts the ring
   on the label standing in for it. */
.whens { display: flex; flex-wrap: wrap; gap: 8px; }
.when-radio { position: absolute; width: 1px; height: 1px; opacity: 0; margin: 0; }
.when-chip {
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface2);
  color: var(--muted);
  font-size: 13px;
  line-height: 1.2;
  cursor: pointer;
  /* 44px of chip on a phone, from 29.6. The chip *is* the target here — the
     radio behind it is 1×1 by design — so the box has to grow rather than be
     padded invisibly, and a chip is one of the few controls where growing it
     is also what it should look like on a phone. The desktop size comes back
     at 720px. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.when-chip:hover { border-color: var(--accent); color: var(--text); }
.when-radio:checked + .when-chip {
  border-color: var(--accent);
  background: var(--accentSoft);
  color: var(--text);
}
.when-radio:focus-visible + .when-chip { outline: 2px solid var(--accent); outline-offset: 2px; }

/* What the chosen profile comes to, in the zones the month is priced by. A
   bar rather than three more numbers: shares are a composition, and a
   composition is one glance at a strip against three readings of a table. The
   figures stand under it all the same — the blended price beside the totals
   has to be checkable against something, and a coloured strip cannot be
   checked. The three colours are the tints the zone chips already carry. */
.mix-shape { position: relative; height: 10px; margin: 4px 0 8px; }
.mix-track {
  display: flex;
  height: 100%;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--lineS);
}
.mix-seg { height: 100%; }

/* One grip per boundary between neighbouring zones, laid over the bar.
   Native range inputs, so the arrow keys, the focus ring and the announcement
   are the browser's; the label and the spoken value are written by `paintMix`
   from the zone names the schedule gave, because a grip named in the markup
   would keep the name a re-cut schedule had dropped.

   The input covers the whole bar and takes no pointer events; only its thumb
   does. That is what lets two of them lie on top of each other and on the
   coloured track without any of the three swallowing the others' clicks. */
/* The hit area here is the thumb and only the thumb, which is what
   `pointer-events` above decides — so growing the input to 44px would grow
   what a ruler measures and not what a finger can hit. It is the thumb that
   is 44px tall and 24px wide; the input and its track follow the height so the
   taller thumb is laid out and not clipped, and the track is invisible anyway
   (`background: none` — the coloured bar is `.mix-track`, a `div` behind it).

   24 and not 10, which is what it was until 2026-09-18 and the last pointer
   target on this site under WCAG 2.2 SC 2.5.8. Measured, not reasoned about:
   `document.elementFromPoint` swept across the bar in Chrome handed back the
   input over 10.75px of width at 320, 390 and 1280 — a 10px thumb with the
   browser's own slop around it. It is now ≥ 24 at all three.

   The grip is still drawn 10×22. A thumb cannot have a child and cannot be
   given a pseudo-element, so the two rectangles that were a 2px `--bg` border
   around a `--text` fill are two centred background layers instead: 6×18 of
   `--text` over 10×22 of `--bg`, both `background-position: center`, so
   widening the thumb moves neither of them. The only thing lost in the
   translation is the 2px corner radius, at 6px wide.

   The light layer is written first and that is not a matter of taste. CSS
   paints background layers front to back, so the **first** one is the one on
   top. Written the way it reads — the dark block, then the fill on it — the
   10×22 of `--bg` covers the 6×18 of `--text` entirely and the grip is a solid
   dark rectangle. That is what shipped until 2026-09-18, and it took a pixel
   probe to see: a screenshot row across the grip at 390 was ten pixels of
   rgb(9,12,11) with no `--text` pixel on it at all, while every guard was
   green. The row now reads `--bg` 2px, `--text` 6px, `--bg` 2px, and
   `layout.test.js` holds the image list and the size list together — the sizes
   alone never said which colour was which, which is how the two came apart.

   What a wider thumb would move is where the grip is drawn for a given value —
   see `.mix-grip` below, where the input is shifted and widened so that it does
   not.

   The 44px column reaches 17px above and below the 10px bar, over the legend
   under it and the label over it. Both are text, neither is a control, and
   the input around the thumbs still takes no pointer events at all. */
.mix-grip {
  position: absolute;
  /* Half a thumb past each end of the bar, and a whole thumb wider than it.
     A range input's thumb travels between `width/2` of each end of its own
     track, so a track exactly as wide as the bar draws the boundary that far
     inside it — measured in Chrome at 390 and 1280, on a track spanning the
     bar: +11.5px at 0 % and −12.5px at 100 %, with the 24px thumb. Shifting
     the input back half a thumb and widening it by a whole one puts the travel
     on the bar's own ends instead: 0 at both, and 0 at every value between.

     The two lengths are the thumb's width and half of it. They are literals
     rather than a shared custom property, because a custom property that fails
     to reach a UA thumb pseudo-element fails silently into the browser's own
     default width; `layout.test.js` holds the three to each other instead. */
  left: -12px;
  top: 50%;
  transform: translateY(-50%);
  width: calc(100% + 24px);
  height: 44px;
  margin: 0;
  -webkit-appearance: none;
  appearance: none;
  background: none;
  pointer-events: none;
}
.mix-grip::-webkit-slider-runnable-track { height: 44px; background: none; }
.mix-grip::-moz-range-track { height: 44px; background: none; }
.mix-grip::-moz-range-progress { background: none; }
.mix-grip::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  pointer-events: auto;
  width: 24px;
  height: 44px;
  border: 0;
  background-color: transparent;
  background-image: linear-gradient(var(--text), var(--text)), linear-gradient(var(--bg), var(--bg));
  background-size: 6px 18px, 10px 22px;
  background-position: center, center;
  background-repeat: no-repeat;
  cursor: ew-resize;
}
.mix-grip::-moz-range-thumb {
  pointer-events: auto;
  width: 24px;
  height: 44px;
  border: 0;
  background-color: transparent;
  background-image: linear-gradient(var(--text), var(--text)), linear-gradient(var(--bg), var(--bg));
  background-size: 6px 18px, 10px 22px;
  background-position: center, center;
  background-repeat: no-repeat;
  cursor: ew-resize;
}
.mix-grip:focus { outline: none; }
.mix-grip:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--accent); outline-offset: 2px; }
.mix-grip:focus-visible::-moz-range-thumb { outline: 2px solid var(--accent); outline-offset: 2px; }
.mix-grip[hidden] { display: none; }
/* Who the transfer goes to, in the same visual language as the zones: the
   cheap half in the accent, the dear one in the colour this site uses for
   burning. Twice the energy is twice the burn and twice our price, so the
   second segment is the one that costs. */
.mix-seg-held { background: var(--accent); }
.mix-seg-fresh { background: var(--danger); }
.mix-seg-offpeak { background: var(--info); }
.mix-seg-standard { background: var(--neutral); }
.mix-seg-peak { background: var(--warn); }
/* The keys are one item of this line and the hours are the other, rather than
   four things wrapping together. Under one flat row the caption took whatever
   room was left on the first line and the third zone dropped below it — in
   German the legend read «Schwachlast · Mittellast · 00:00 – 24:00 utc» with
   «Hochlast» on a line of its own, which puts the window between two zones it
   is not about. */
.mix-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 14px;
  font-size: 12px;
  color: var(--muted);
}
.mix-keys { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; }
.mix-key { display: inline-flex; align-items: baseline; gap: 6px; }
/* An empty box has no baseline of its own, so it is centred against the text
   rather than sat on the line the text sits on. */
.mix-dot { width: 8px; height: 8px; border-radius: 50%; flex: none; align-self: center; }
.mix-key .money { color: var(--text); }
/* The hours the profile covers, in the units the zone rows print. At the end
   of the line where there is room, under the keys where there is not. */
.mix-when { margin-left: auto; color: var(--dim); }

/* The exact shares, for the reader who has them — the same table it always
   was, now behind «my own mix». Its eight fields are what needed rethinking:
   as the first thing the block asked, it was a form nobody can fill in from
   memory standing between a visitor and a price. */
.mix-exact {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 12px;
  border-top: 1px solid var(--lineS);
}
.mix-exact[hidden] { display: none; }

/* The caption takes a line of its own, so the two fields under it line up
   with the fields of every zone block below. Inline, it pushed «weekends»
   onto a second line by itself on a phone. */
.mix-days { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 16px; }
.mix-days .cap { flex-basis: 100%; }
.mix-field { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); }

/* One share field. The spin buttons are taken off: they cost a third of the
   column on a phone, and the ↑/↓ keys a `type=number` answers to stay. */
.mix-input {
  width: 46px;
  padding: 4px 6px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface2);
  color: var(--text);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  text-align: right;
  -moz-appearance: textfield;
  appearance: textfield;
}
.mix-input::-webkit-outer-spin-button,
.mix-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* A share the page would have to rewrite before it could price it. `weightOf`
   clamps anything above a hundred and floors a fraction, and it does so
   silently: type 300 into a column and the mix becomes an equal third while the
   field still reads 300 — the page prices one thing and shows another. The
   marker is the browser's own `:invalid`, which `min`/`max`/`step` on the input
   already decide and screen readers already announce, so nothing here has to be
   kept in step with the script by hand. It marks, it does not correct: what the
   reader typed stays on the screen. */
.mix-input:invalid {
  border-color: var(--danger);
  background: var(--dangerSoft);
  color: var(--danger);
}

/* A block per zone, where a table used to be.

   The table was two number columns with a word over each — and a word needs
   98px before the zone gets any width at all. The calculator's own column is
   312px at 768 and 301px on a phone, so what was left for the zone was 85px:
   the hours broke inside a range («12:00 –» / «14:00»), the price fell to a
   line of its own, and the name sat alone across a gap from its own fields.
   Every rule that used to be here — fixed layout, an 80px floor, headers
   breaking mid-word — was fighting for that 85px.

   Nothing is held in a column now. Each field carries its own word, the head
   of the block carries the name, the hours and the price, and both lines wrap
   like text. */
.mix-zones { display: flex; flex-direction: column; gap: 12px; }
.mix-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--lineS);
}
.mix-zones .mix-row:last-child { padding-bottom: 0; border-bottom: none; }
.mix-row-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 10px;
  font-size: 13px;
}
.mix-fields { display: flex; flex-wrap: wrap; gap: 8px 16px; }
.mix-hours, .mix-price { font-family: var(--mono); font-size: 11px; color: var(--dim); }
/* The hours wrap where they must; the price never does — «24» on one line
   and «sun» on the next is two facts where there is one. */
.mix-price { white-space: nowrap; }

.mix-note { font-size: 11px; color: var(--dim); }

/* Every zone at one price: the profiles are then a control that cannot move
   the total, and a reader who taps through them and sees nothing change
   concludes the calculator is broken. It is not — it is the autopricer
   holding the whole schedule at one figure, which it does for hours at a
   time. The line says so; `recalc` shows it and hides it, and it lives in the
   totals panel rather than under the profiles because that is where the
   numbers that did not move are. Set narrow and to the right, under the
   blended price it explains — that price is the one figure every profile
   shares. */
.calc-flat {
  padding-top: 8px;
  max-width: 46ch;
  margin-left: auto;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
  text-align: right;
}
.calc-flat[hidden] { display: none; }

/* The slider. The native input is styled rather than replaced: the keyboard
   and the announcement come for free, and a div with a pointer-event handler
   owes both of them back. */
.slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  /* 44px of input, 10px of drawn track. The whole box of a range input takes
     the pointer, so the height *is* the hit area — it was 22px, which
     lighthouse fails even against the 24px floor, and this is the control the
     calculator exists to be dragged by. Not scoped to a breakpoint: a 44px box
     with a 10px track in it looks the same at every width, and the desktop
     reading is the one lighthouse complained about.
   *
   * Things do move, and the note that used to stand here said they did not —
   * «nothing moves … both are drawn exactly where they were». Measured in
   * Chrome against the 22px box this replaced:
   *
   *   · the track is 10px and is laid out centred in the input's box, so a box
   *     of 44 instead of 22 puts its top 17px in rather than 6px — the track is
   *     drawn 11px lower than it was;
   *   · everything after the control goes down by the 22px the box gained. On
   *     `/` at 390px the footer moved 9590.9 → 9612.9 and the document 10530 →
   *     10552; on `/pricing` 5449.3 → 5471.3 and 6389 → 6411; at 1280 the same
   *     22px. Each range input on a page pays it once.
   *
   * The behaviour is kept — the operator's phone mock-up draws these 44px tall,
   * and a hit area the size of a fingertip is the whole reason the box is not
   * the track. What is corrected is the claim. */
  height: 44px;
  margin: 0;
  background: transparent;
  cursor: pointer;
}
/* Same bar and same handle as the two below it. The block asks three questions
   in a row — how many, to whom, and when — and the first one used to be asked
   by a different instrument: a 6px hairline with a round accent dot, over two
   10px bars carrying rectangular white grips. Three controls of one block that
   look like two kinds of control read as two kinds of thing.

   What stays different is the one thing that differs: this bar fills up to the
   value, because a volume has a low end and a high end. A composition has
   neither — its bar is coloured all the way across and the grips divide it. */
.slider::-webkit-slider-runnable-track {
  height: 10px;
  border-radius: var(--radius);
  background: linear-gradient(to right, var(--accent) var(--fill, 50%), var(--lineS) var(--fill, 50%));
}
.slider::-moz-range-track { height: 10px; border-radius: var(--radius); background: var(--lineS); }
.slider::-moz-range-progress { height: 10px; border-radius: var(--radius); background: var(--accent); }
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 10px;
  height: 22px;
  /* Centred on a 10px track: half the difference of the two heights. */
  margin-top: -6px;
  border: 2px solid var(--bg);
  border-radius: var(--radius);
  background: var(--text);
  cursor: ew-resize;
}
.slider::-moz-range-thumb {
  width: 10px;
  height: 22px;
  border: 2px solid var(--bg);
  border-radius: var(--radius);
  background: var(--text);
  cursor: ew-resize;
}
.slider:focus { outline: none; }
.slider:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--accent); outline-offset: 2px; }
.slider:focus-visible::-moz-range-thumb { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ── 3 · How it works ──────────────────────────────────────────────────── */

.section { padding-bottom: 40px; display: flex; flex-direction: column; gap: 14px; }
.section-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 16px; }
.section-head-note { font-size: 13px; color: var(--dim); }
.section-head-link { font-size: 13px; white-space: nowrap; }

.steps { list-style: none; margin: 0; padding: 0; grid-template-columns: minmax(0, 1fr); }
.steps > li {
  padding: 16px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.step-no { font-family: var(--mono); font-size: 11px; color: var(--accent); }
/* The four calls of the quickstart, stacked beside the code panel: the same
   step pieces, no tile frame — the frame is the code panel's. */
.calls { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.calls > li { display: flex; flex-direction: column; gap: 6px; }
.quickstart-links { display: flex; flex-wrap: wrap; gap: 0 24px; margin: 0; }
.quickstart-links a { font-size: 13px; display: inline-flex; align-items: center; min-height: 44px; }
.step-title { font-size: 15px; font-weight: 500; }
.step-text { font-size: 13px; color: var(--muted); text-wrap: pretty; }

/* ── 4 · Built for volume ──────────────────────────────────────────────── */

/* Six product cards: one column on a phone, two from 560px, three from
   720px. The heading line carries the icon in the accent and the title in
   the text colour, the way the step and trust heads do. */
.products { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; }
.product { padding: 16px; display: flex; flex-direction: column; gap: 10px; }
.product-head { display: flex; align-items: center; gap: 10px; color: var(--accent); }
.product-head .product-title { color: var(--text); }
.product-title { font-size: 15px; font-weight: 500; }
.product-text { font-size: 13px; color: var(--muted); text-wrap: pretty; }

/* ── 5 · No code ───────────────────────────────────────────────────────── */

.nocode-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 20px; }
.nocode-note { font-size: 13px; color: var(--muted); }

/* ── Auto-refill (`/auto-refill`) ──────────────────────────────────────── */

.feature {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
  margin-bottom: 40px;
}
.feature-lede { color: var(--muted); font-size: 14px; max-width: 50ch; text-wrap: pretty; }
.feature-list { display: flex; flex-direction: column; gap: 10px; font-size: 14px; }
.feature-actions { display: flex; flex-wrap: wrap; gap: 12px; padding-top: 6px; }

/* ── 6 · Prices ────────────────────────────────────────────────────────── */

/* Each column's cards are their own height rather than one height for all.
 *
 * This said `align-items: stretch`, and gave as its reason that a ragged
 * bottom edge reads as unfinished work. That was true while a grid of two
 * cards was the end of the block. Stretching does not even out what is in the
 * two columns — it pours air into whichever card has nothing to fill its
 * neighbour's height with: 407px of it in the right-hand card at 1280 with the
 * notes on the left, and, once the notes moved down and the right column
 * became the taller one, 261px in the left-hand card on a page whose API is
 * unreachable and whose table is shorter for it.
 *
 * The flush bottom edge is now drawn by `.pricing-foot`, the strip of notes
 * spanning both columns underneath. It closes the block, and the cards end
 * where their content ends. */
.pricing-grid { display: grid; gap: 14px; align-items: start; }
.pricing-grid > .card { display: flex; flex-direction: column; }

/* `/pricing`, where the zone table is the page rather than a column of it.
 *
 * On the home page the grid is right: the table and the price chart are two
 * halves of one statement and 7fr/5fr is how they were drawn. On `/pricing`
 * the chart has a section of its own further down, and the 5fr held two
 * supporting blocks — the Auto-refill rates and the "no subscription" notice —
 * against a six-column table that needs about 900px to lay flat and was being
 * given 644 at every desktop width from 1024 to 1920.
 *
 * Widening the track was not open: 842 of 1108 is 76% of the row, which leaves
 * the rate card 266px. So the table takes the row and the two supporting
 * blocks sit under it, side by side. `.pricing-grid.pricing-grid-lead` rather
 * than an override late in the file: two classes outrank one wherever either
 * is written, and the 1024 block below cannot quietly win it back.
 */
.pricing-grid.pricing-grid-lead { grid-template-columns: minmax(0, 1fr); }
/* Under a table this wide the notes would run to a 1100px line of 12px type.
   Capped at a measure that can be read.

   The cap is on the paragraphs and not on `.table-foot` around them: the foot
   has a background of its own, and capping the block left it ending two thirds
   of the way across the card with the card's own colour beside it — a seam
   down the middle of a panel. The cap does nothing on the home page, where the
   card is narrower than 96ch anyway. */
.pricing-grid-lead .table-note { max-width: 96ch; }
.table-foot {
  padding: 12px 16px;
  background: var(--surface2);
  border-top: 1px solid var(--lineS);
  font-size: 12px;
  color: var(--dim);
}

/* The home page's notes, under both of its columns.
 *
 * They explain the pair rather than the left-hand half of it: one of them is
 * about the table and the chart reading the same prices two different ways.
 * They sat inside the table's card all the same, pulling it 255px down, and
 * `align-items` above used to stretch both cards to the taller of the two —
 * which left the right-hand card 54% air at 1280.
 *
 * `grid-column: 1 / -1` rather than a block after the grid: the gap above them
 * is then the same `gap` that separates the columns, and it changes at 1024
 * along with it rather than needing a rule of its own. A full border rather
 * than just the `border-top` `.table-foot` draws above: inside a card the card
 * drew the other three sides, and out here there is no card. The rule sits
 * after `.table-foot` — the two selectors weigh the same, so source order is
 * what decides. */
.pricing-foot {
  grid-column: 1 / -1;
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
/* The same measure `/pricing` sets: under a column 1130px wide a 12px
   paragraph otherwise runs a line the full width of the grid. */
.pricing-foot .table-note { max-width: 96ch; }
/* The last paragraph's bottom margin used to add to the block's own padding.
   Inside the card the Auto-refill sentence stood after it and it went unseen;
   now the paragraph is the last thing in the block. */
.pricing-foot .table-note:last-child { margin-bottom: 0; }
/* What the numbers in the table are. Its own paragraph rather than a clause in
   the burn note: this one is the difference between a live price and a figure
   about yesterday, and it is the sentence a visitor is owed. */
.table-note { margin: 0 0 8px; }
/* A qualifier under a figure in a table cell: which moment the number is
   about. Its own line, because a showcase figure and the time it belongs to
   read as one claim or as none. */
.cell-note {
  display: block;
  margin-top: 2px;
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--dim);
}
/* The second line of the "per energy unit" header. `th` is nowrap for the
   headers that are one line; this one is the line under them. */
.th-basis {
  display: block;
  margin-top: 3px;
  white-space: normal;
  text-transform: none;
  letter-spacing: 0;
  font-size: 10px;
  color: var(--muted);
}
.chart-card { padding: 20px; display: flex; flex-direction: column; gap: 14px; }
/* Seven leases and three ranges do not fit beside a caption on a phone, and a
   switch that runs off the edge of a card is a switch a reader cannot reach:
   the header wraps instead. It has nothing to wrap on the cards that carry one
   group of three. */
.chart-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.chart-ranges { margin-left: auto; display: flex; gap: 4px; }
/* Buttons, not labels: the range really does switch. A button that does
   nothing lies silently — which is why these used to be `span`s with
   `aria-hidden`, and why `chart.js` has taken their place. */
.chart-ranges button {
  font-family: var(--mono);
  font-size: 11px;
  padding: 2px 8px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--dim);
  cursor: pointer;
  /* 44×44 on a phone, from 35.8×18 and 29.2×18 — the smallest controls on the
     site. The compact chip comes back at 720px: the recorded browser heights
     in `layout.test.js` were measured at 1024 and 1280 with this card at its
     approved size, and the strip on `/market` is seven of these chips in a
     wrapping row, where a taller chip is a taller row rather than a bigger
     target for nothing. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  /* And the 24px floor the desktop size goes back under — 35.8×18 and 29.2×18
     at 1280. The pressed chip is drawn with a background, so the box may not
     grow; the target is an out-of-flow overlay instead. */
  position: relative;
}
.chart-ranges button::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 100%;
  min-width: 24px;
  height: 24px;
  transform: translateY(-50%);
}
.chart-ranges button:hover { color: var(--text); }
.chart-ranges button[aria-pressed="true"] { background: var(--accentSoft); color: var(--accent); }
.chart svg { width: 100%; height: auto; display: block; }
/* `position: relative` because the hover reading is placed inside this box:
   `chart.js` writes `left`/`top` on `.chart-tip` in the plot's own pixels
   (`Chart.place`), so the plot is what those pixels have to be measured from.
   It stood as `style="position:relative"` in the markup of three pages — a
   declaration that decides where a script puts something, written in the one
   place this stylesheet cannot see it and the locale copies repeat it.

   The plot is `tabindex="0"` in the markup and the arrow keys move the reading
   across it (`chart.js`, `follow`). Its focus ring is the site's own, from the
   bare `:focus-visible` rule at the top of this sheet: a tab stop that shows
   nothing is a tab stop a sighted keyboard reader loses. The 2px offset is
   drawn inside the card's own 20px of padding, so nothing clips it. */
.chart-plot { min-width: 0; position: relative; }

/* The hover reading. `chart.js` fills it, shows it and places it; what it
   looks like is here.
 *
 * It borrowed `.notice` for a revision, which is a flex row built for a line
 * of prose with an icon beside it — `display: flex` over a box whose whole
 * content is one text node of three lines joined by newlines. The reading is
 * figures throughout (an hour, a price per unit, a sum in TRX and dollars), so
 * it is set in the mono face every other figure on this site is set in. */
.chart-tip {
  position: absolute;
  left: 0;
  top: 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface2);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 13px;
  /* The three lines are one text node with newlines in it: a live region is
     announced as what it now says, and three boxes are three interruptions
     for one reading. */
  white-space: pre-line;
  /* Never under the pointer that asked for it. A tooltip that takes the
     pointer takes `pointerleave` from the plot with it, and the reading then
     flickers as it is read. */
  pointer-events: none;
}
/* A lease has been pressed and its history has not arrived. The picture is
   still real prices of a real product — the one the reader has just left — so
   it is dimmed rather than cleared: an empty card for three seconds says less
   than a faded one, and the caption beside it names the lease being fetched.
   `chart.js` writes and removes the attribute; the wait ends when the answer
   is drawn or when the chip springs back. */
.chart-plot[data-chart-waiting] { opacity: .45; }

/* The chart's colours live here rather than in SVG attributes: there is one
   palette per page, and the chart changes with it rather than apart from it. */
.chart-grid { stroke: var(--lineS); stroke-width: 1; }
.chart-line { stroke: var(--accent); stroke-width: 2; vector-effect: non-scaling-stroke; }
/* The second line is a muted tone rather than a second accent: a chart has
   one accent, otherwise both lines are "the main one" and there is nothing to
   read. The dashed stroke is taken by the forecast. */
.chart-line-alt { stroke: var(--muted); stroke-width: 2; vector-effect: non-scaling-stroke; }
.chart-forecast { stroke: var(--dim); stroke-width: 2; stroke-dasharray: 5 5; vector-effect: non-scaling-stroke; }
.chart-band { fill: var(--dim); opacity: .06; }
/* The axis type: the sun figures down the left, the dates along the bottom and
   the word over the forecast. It gets an ink of its own, `--axis`, and that is
   the one place on the site where a quiet label is not the palette's quietest
   ink.

   Two reasons, and neither alone would have bought a token. `--dim` clears
   4.5:1 on all four surface tokens — and this label is not drawn on a surface.
   Behind it are the translucent bands below, which *lighten* the ground they
   sit on, and over the peak band on a `--surface` card `--dim` measured
   **4.497:1**: under the AA floor by three thousandths, on the numbers the
   picture is read by. Lifting `--dim` would move twenty other uses of it that
   have their margin already, so the ink moves here, where the tint is.

   The ink it moved to was `--muted` — which is `.chart-line-alt`, the second
   plotted series, at the same weight in the same picture. An axis drawn in a
   series' colour is a label that reads as data, and on `/pricing` the rates
   chart draws that line and those labels side by side. `--axis` sits between
   the two: it clears every band with room and it is not the colour of anything
   plotted. `site/tests/contrast.test.js` composites each band over each card
   ground, holds the lot at 4.5, and holds this fill apart from every stroke
   the chart draws. */
.chart-label { fill: var(--axis); font-family: var(--mono); font-size: 11px; }

/* Time-of-day zones behind the line, in the colours the zone table uses for
   the same three words: a step in the price is a boundary being crossed, and
   without the bands the staircase has no reason in the picture. The standard
   zone is the ground and is left unpainted — three tints for three zones is a
   pattern, two is a reading. */
.chart-zone { opacity: .5; }
.chart-zone-off-peak { fill: var(--infoSoft); }
.chart-zone-standard { fill: none; }
.chart-zone-peak { fill: var(--warnSoft); }

/* The legend is in the markup rather than drawn by a script: without JS the
   chart is still an outline, and "which line is which" is needed just the
   same. The swatch takes its colour from the line's own class, so there is
   nowhere for the two to drift apart. */
.chart-legend { margin-left: auto; display: flex; flex-wrap: wrap; gap: 4px 12px; }
.chart-key {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim);
}
.chart-key::before {
  content: "";
  width: 14px;
  height: 0;
  border-top: 2px solid currentColor;
}
.chart-key-line { color: var(--accent); }
.chart-key-alt { color: var(--muted); }
/* The legend and the range switch both want to be on the right; when they
   share a header, only the switch goes there. */
.chart-legend + .chart-ranges { margin-left: 12px; }
/* And the same bargain for the lease switch, which is the wider of the two and
   goes first: `margin-left: auto` on both would push them apart and leave the
   pair straddling the middle of the header. It wraps inside itself before the
   header wraps around it — seven chips are a line of their own long before a
   card is narrow. */
.chart-leases { flex-wrap: wrap; }
.chart-leases + .chart-ranges { margin-left: 12px; }
/* The lease chips on /market. The same chips as a chart's — `.chart-ranges`
   paints them — under a class of their own, because they are a different
   control: they sit above a table rather than in a card head, so the
   `margin-left: auto` that pushes a chart's strip to the right edge of its
   head would push these off the note they belong to. `chart-leases` is
   deliberately not on them; that class marks the strips `chart.js` drives, and
   `site/tests/products.test.js` reads every one of them expecting the
   five-minute chip to be the pressed one. This strip ships pressed on «every
   lease», which is not a lease at all. */
.market-leases { margin: 0 0 12px; flex-wrap: wrap; }

.chart-src {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim);
}
.chart-span { color: var(--muted); }

/* ── 8 · Trust ─────────────────────────────────────────────────────────── */

.trust { grid-template-columns: minmax(0, 1fr); }
.trust > div { padding: 20px; display: flex; flex-direction: column; gap: 8px; }
.trust-title { font-size: 14px; font-weight: 500; }
.trust-text { font-size: 13px; color: var(--muted); }
.trust-link { padding-top: 2px; }

/* ── 9 · Quick start ───────────────────────────────────────────────────── */

.quickstart { display: grid; gap: 24px; align-items: start; }
.quickstart-copy { display: flex; flex-direction: column; gap: 14px; }
.quickstart-copy p { color: var(--muted); font-size: 13px; max-width: 50ch; text-wrap: pretty; }
.quickstart-copy code, .inline-mono { font-family: var(--mono); font-size: .95em; }
.quickstart-panel { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.tabs { display: flex; gap: 24px; border-bottom: 1px solid var(--line); }
.tab {
  padding: 12px 0;
  font-size: 13px;
  font-family: var(--sans);
  color: var(--dim);
  background: none;
  border: 0;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  cursor: pointer;
  /* 43px tall and 22–41px wide, so it failed on one axis by a pixel and on the
     other by half. The width is the one that moves: the underline marking the
     selected tab runs the width of the box, so a 44px tab is a 44px underline.
     Restored to its own width at 720px, where the mock-up's proportions are
     what is approved. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  /* And the 24px floor the desktop size goes back under: «curl» is 22.2px wide
     at 1280. The underline that marks the selected tab runs the width of the
     box, so widening the box widens the mark — the target is an out-of-flow
     overlay instead, and the underline stays the width it was drawn. The
     `.tabs` row has a 24px gap, so a 24px overlay on a 22.2px tab reaches
     0.9px into it and never touches the neighbour's. */
  position: relative;
}
.tab::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100%;
  min-width: 24px;
  transform: translateX(-50%);
}
.tab[aria-selected="true"] { color: var(--text); border-bottom-color: var(--accent); }
.tab-panel[hidden] { display: none; }

/* ── 10 · FAQ ──────────────────────────────────────────────────────────── */

.faq { grid-template-columns: minmax(0, 1fr); }
.faq > div { padding: 14px 16px; display: flex; flex-direction: column; gap: 6px; }
.faq-q { font-size: 14px; font-weight: 500; }
.faq-a { font-size: 13px; color: var(--muted); text-wrap: pretty; }

/* ── 11 · Final CTA ────────────────────────────────────────────────────── */

.cta { border-top: 1px solid var(--line); background: var(--surface); }
.cta-inner {
  padding-top: 40px;
  padding-bottom: 40px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  text-align: center;
}
.cta h2 { font-size: clamp(24px, 2.6vw, 32px); font-weight: 600; letter-spacing: -.02em; text-wrap: pretty; }
.cta-sub { font-size: 14px; color: var(--muted); }
.cta-fine { font-size: 12px; color: var(--dim); }
.cta .btn { align-self: stretch; }
/* Two buttons in the band: stacked on a phone, side by side from 560px. */
.cta-actions { display: flex; flex-direction: column; gap: 10px; align-self: stretch; }

/* ── 12 · Footer ───────────────────────────────────────────────────────── */

.footer { border-top: 1px solid var(--line); background: var(--bg); }
/* Four columns by audience — Product, Developers, Help, Legal — under one
   brand line, on every page of the site (wave 2 of the site review,
   2026-09-21; `site/tests/chrome.test.js` pins the four captions and their
   order). The brand line spans the grid at every width; the columns are one
   under another on a phone, two by two from 560px, and the four side by side
   from 1024px, where the longest label of the ten locales («Политика
   конфиденциальности», 27 characters) still fits a quarter of the row. At
   720–1023px four would be 144px each and that label would wrap; two is the
   honest answer there. */
.footer-grid {
  padding-top: 32px;
  padding-bottom: 32px;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 20px;
  font-size: 13px;
}
.footer-brand { display: flex; flex-direction: column; gap: 12px; grid-column: 1 / -1; }
.footer-tagline { font-size: 12px; color: var(--dim); max-width: 26ch; }
.footer-col { display: flex; flex-direction: column; gap: 8px; }
/* 169×19.5 at 390px, eight rows deep: the smallest targets left on a phone
   once the burger and the header button had theirs.
 *
 * The row really grows here rather than carrying an invisible box, and that is
 * the whole reason this rule is not the `::before` trick the header button
 * uses: the rows are 27.5px apart (19.5 and an 8px gap), so a 44px box centred
 * on each would overlap both its neighbours by 8px — and two targets that
 * overlap are a reader hitting the link above the one they aimed at, which is
 * worse than the small target. So the phone's footer is taller, by 24.5px a
 * row. It is the last block of a page thirteen screens long.
 *
 * Above 720px the row goes back to what it was and takes the invisible box
 * instead (`::before`, in the block at the bottom): 24px inside a 27.5px pitch
 * fits between the neighbours, so the desktop footer is not moved by a pixel
 * and still clears the 24×24 floor of WCAG 2.2 SC 2.5.8. */
.footer-col a {
  color: var(--muted);
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  position: relative;
}
.footer-col a:hover { color: var(--text); }
.footer-bottom { border-top: 1px solid var(--lineS); }
/* Two bands, not three things sharing a line. All three used to sit in one
   row under `space-between`, and eleven language names are wider than the two
   short lines beside them: the copyright went to one edge, the domains to the
   other, and the languages wrapped onto a line of their own hugging the left.
   Nothing was broken and nothing looked deliberate either — the layout was
   whatever was left over after the wrap.

   So the wrap is the layout now. The languages take their own band, the
   copyright and the domains take the one under it, and a hairline says which
   is which. In the markup the nav comes first, where it is drawn: a reader
   tabbing through the footer meets the links in the order they are on the
   screen, which is the whole reason this is markup and not `order`. */
.footer-bottom-row {
  padding-top: 16px;
  padding-bottom: 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  font-size: 12px;
  color: var(--dim);
}
.footer-legal {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  justify-content: space-between;
  border-top: 1px solid var(--lineS);
  padding-top: 14px;
}
/* The operator, in small print: the size and ink the row already draws it in,
   stated here so they stop depending on the row. `.footer-bottom-row` is sized
   for the language switch, and a row made smaller for that would have taken
   the company line under the 12px floor with it, on every screen
   (`layout.test.js` holds it at 12). Not a highlight — the owner, 2026-09-18. */
.company-line { font-size: 12px; color: var(--dim); }
.footer-domains { font-family: var(--mono); }
/* Languages of the page. Every name is written in its own language, so the
   line is set in the body font rather than the mono one — Devanagari and
   Vietnamese have no mono face here and would fall back mid-line. The current
   language is marked by `aria-current`, the same signal a screen reader
   reads, not by a class of its own. */
.lang-switch { display: flex; flex-wrap: wrap; gap: 6px 14px; }
.lang-switch a {
  color: var(--dim);
  /* The switch is set in the reader's own system font, and the reason is
     weight rather than taste. The single word «Русский» is the only Cyrillic
     on an English, German or Spanish page, and asking IBM Plex for it pulls
     `IBMPlexSans-Regular-Cyrillic.woff2` — 20 576 bytes — onto all 17 pages
     that carry the switch, in every language. Fonts are already 102–176 KB of
     a 177–307 KB page and this is the one slice nothing on the page needs.
     «Türkçe» and «Tiếng Việt» reach into the Latin2 and Latin3 subsets the
     same way. A system face has all of them and costs nothing.

     The Russian pages are unaffected: their body text asks for the Cyrillic
     subset anyway, and this one line of links is the only thing set in
     something else.

     Eleven names in eleven scripts, each 15px and each at least 44×44, which
     is what a finger needs — they measured 18px tall, and one of them («हिन्दी»)
     22px wide. */
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
}
.lang-switch a:hover { color: var(--text); }
.lang-switch a[aria-current="true"] { color: var(--text); }

/* ── /pricing and /auto-refill pages ───────────────────────────────────── */

/* The heading of an inner page. Not the home page's hero: that one sells,
   this one explains, and there is no illustration to the right of it. */
.page-head { padding-top: 32px; padding-bottom: 24px; display: flex; flex-direction: column; gap: 12px; }
.page-head h1 {
  font-size: clamp(28px, 4vw, 40px);
  line-height: 1.1;
  font-weight: 600;
  letter-spacing: -.025em;
  text-wrap: balance;
}
.page-lede { color: var(--muted); max-width: 64ch; text-wrap: pretty; }

/* An inner page's hero: the heading is more modest than the home page's —
   there it is the whole screen, here an explanation follows it. */
.hero-even h1 { font-size: clamp(28px, 4vw, 40px); font-weight: 600; letter-spacing: -.025em; }

/* A column of blocks: the pricing sidebar, the second column's copy on
   /auto-refill. */
.stack { display: flex; flex-direction: column; gap: 14px; }
.stack-lg { gap: 24px; }

/* A band on a surface background — the same device `.feature` uses on the
   home page. */
.band { border-top: 1px solid var(--line); background: var(--surface); }
.band-inner { padding-top: 32px; padding-bottom: 32px; display: flex; flex-direction: column; gap: 16px; }
.band-split { padding-top: 32px; padding-bottom: 32px; display: grid; gap: 24px; align-items: center; }
.band .card, .band .tiles > * { background: var(--bg); }

/* The maintenance rates: a row of "for what" — "how much". */
.rates { display: flex; flex-direction: column; }
.rate-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 4px 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--lineS);
  font-size: 13px;
}
.rate-row:last-child { border-bottom: none; }
.rate-row .money { font-size: 17px; }
.rate-row-label { color: var(--muted); }
.rate-note { font-size: 12px; color: var(--dim); text-wrap: pretty; }

/* The five points of the rule: the same tiles in a frame the home page's
   steps use. */
.points { grid-template-columns: minmax(0, 1fr); }
.points > div { padding: 20px; display: flex; flex-direction: column; gap: 10px; }
.point-no { font-family: var(--mono); font-size: 11px; color: var(--accent); }
.point-title { font-size: 15px; font-weight: 500; }
.point-text { font-size: 13px; color: var(--muted); text-wrap: pretty; }
.point-rates { justify-content: center; gap: 8px; }

/* A quote about the pain. The same `.notice`, in yellow: this is somebody
   else's line rather than a footnote of ours, and the two should not look
   alike. */
.quote {
  align-self: flex-start;
  border-color: var(--warnSoft);
  background: var(--warnSoft);
  color: var(--warn);
}

.rule-card { padding: 20px; display: flex; flex-direction: column; gap: 12px; }
.rule-foot { font-size: 12px; color: var(--dim); }

.budget-card { padding: 20px; display: flex; flex-direction: column; gap: 14px; }
.budget-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 4px 12px; }
.budget-reset { font-size: 13px; color: var(--dim); }
.budget-total { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px; }
.budget-total .money { font-size: 24px; letter-spacing: -.02em; }
.budget-of { font-size: 13px; color: var(--dim); }
.meter { height: 8px; background: var(--surface2); overflow: hidden; }
.meter-fill { height: 100%; background: var(--accent); }
.budget-rows { display: flex; flex-direction: column; }
.budget-foot { font-family: var(--mono); font-size: 11px; color: var(--dim); }

/* ── /market ───────────────────────────────────────────────────────────────
 *
 * The energy market as we measure it: one row a vendor, ours among them, and
 * six columns of one captured answer, with our own price stated once over the
 * table as a figure card. Everything here is prefixed `market-` and nothing
 * above is touched — the table borrows `.card`, `.scroll-x`, `.num`, `.zone`
 * and `.table-foot` from the pricing page, and this block is only what those
 * cannot already say.
 *
 * Two states a row can be in, and neither is carried by colour (hard rule 6 of
 * `docs/design-handoff.md`). A reading that is behind is dimmed *and* says how
 * far behind it is, in the same cell — the age is the statement and the tone
 * is only the emphasis. A rate we had to convert from a rate published for
 * another unit carries the word for it beside the price, with the reason in
 * the tooltip; take the colour away and both still read.
 */

/* The line over the heading: a dot that says the page is measured rather than
   quoted, and the words that say the same thing without it.

   The dot ships neutral. It is lit only by `market.js`, and only on the path
   where the table is drawn from a live answer — the markup that ships is a
   snapshot, and a lit dot over it is the page contradicting its own caption.
   The words carry the statement in either state (hard rule 6). */
.market-eyebrow {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim);
}
.market-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--dim); flex: none; }
.market-dot-live { background: var(--accent); }

/* The heading and the figure card beside it. One column on a phone, where the
   card is a paragraph's worth of numbers under the sentence that introduces
   them; two from 720px up, which is the width at which the card stops
   squeezing the lede's measure. `.page-head` is already a column flex, so the
   grid is set here and only here. */
.market-head { display: grid; gap: 24px; align-items: center; }
.market-headline { display: flex; flex-direction: column; gap: 16px; }

/* The card itself: our price, and what it comes to against the three things a
   reader is deciding between. A surface and a line rather than a tinted panel
   — the figure is the emphasis and the frame only holds it. */
.market-figure {
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding: 28px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}
.market-figure-price { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.market-figure-sun { font-family: var(--mono); font-size: 56px; line-height: 1; color: var(--accent); }
.market-figure-unit { font-family: var(--mono); font-size: 15px; color: var(--muted); }

/* The three comparisons, as one strip of three cells: the 1px gap is the
   background showing through, which is how a table rule is drawn without a
   border on every cell. */
.market-compare {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}
.market-compare > div {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 16px;
  background: var(--surface2);
}
/* The same tone rule the «vs burn» column follows, and one more: an em dash is
   not good news either. A comparison that cannot be computed — no published
   vendor to be cheaper than — painted in the success colour is a green dash,
   which says «we win» about nothing at all. `.market-over` is set on the
   figure by both programs, from the sign the formatter already decided. */
.market-figure-num { font-family: var(--mono); font-size: 20px; color: var(--ok); }
.market-figure-num.market-over { color: var(--muted); }
.market-figure-foot { font-size: 13px; color: var(--muted); }

/* The sentence above the table: what all the numbers in it are prices *of*. */
.market-note { font-size: 13px; color: var(--muted); max-width: 96ch; text-wrap: pretty; }

/* The prose under the table. `.page-lede` is the one paragraph under an h1;
   these are ordinary running text and need a measure of their own. */
.market-prose { color: var(--muted); max-width: 78ch; text-wrap: pretty; }

.market-card { overflow: hidden; }

/* Six columns of translated headers, so they may break: the zone table's
   defect, avoided rather than repeated (`site/tests/layout.test.js` explains
   what an unbreakable header did to a 644px card). On a phone the padding
   comes in — 16px a side of six columns is most of the screen given to
   margins, and what gets pushed off the edge is the price. */
.market-table th { white-space: normal; }
.market-table th,
.market-table td { padding-left: 10px; padding-right: 10px; }

/* The vendor cell is the row's header — `<th scope="row">`, so a screen reader
   reading «31.00» four columns in still says whose price it is. It has to be
   undone as *type*, though: the sheet's `th` is the column-header style, and
   left as it was the vendor names came out mono, uppercase, tracked out and on
   the header's own background, which is a heading down the middle of the body.
   Everything below is the `td` rule, said again for a cell that is not one. */
.market-table tbody th {
  background: none;
  border-bottom: 1px solid var(--lineS);
  padding-top: 11px;
  padding-bottom: 11px;
  vertical-align: middle;
  font-family: var(--sans);
  font-size: 13px;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text);
}
.market-table tbody tr:last-child th { border-bottom: none; }
/* A row a vendor, however many the feed lists — and the fifteen-pixel rule of
   the site's own `td` made the table taller than a laptop screen. Eleven, said
   here rather than in `td`: this is the one table on the site long enough for
   the difference to matter, and how long it is is the market's business and
   not this file's. */
.market-table tbody td { padding-top: 11px; padding-bottom: 11px; }
.market-rank { color: var(--dim); }
.market-price { color: var(--text); }
.market-vs { color: var(--ok); }
/* Every rate in this column but one is cheaper than burning the energy, and the
   success tone is the right thing to say about it. A vendor selling *above* the
   burn is the exception, and painted green the tone says the opposite of the
   figure it is painting. Neutral there: the number and its `+` carry the whole
   statement, and the colour only stops implying good news. */
.market-vs.market-over { color: var(--muted); }
/* Every vendor's rate over our own. Muted throughout, whichever way it points:
   the figure is a comparison with the page's own seller, and painting the
   cheaper ones in the success tone would be the page marking its own
   homework. The sign carries the whole statement. */
.market-ratio { color: var(--muted); }
.market-when { font-family: var(--mono); font-size: 11px; color: var(--muted); }

/* The vendor cell: the name, and beside it the ways that vendor can be bought
   from. They were three bordered chips stacked under the name and are one run
   of muted mono text now — the chips were the widest thing in the column and
   the loudest thing on the page after the prices, which is the wrong emphasis
   for the least important field of the row.

   `flex-wrap` is what keeps a phone honest: the run drops under the name where
   the column is narrow, so the vendor column's floor stays the wider of the
   two rather than their sum. `site/tests/market.test.js` measures it that way,
   and that is true exactly while this wraps. */
.market-vendor { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; }
.market-name { font-weight: 500; white-space: nowrap; }
.market-channels { font-family: var(--mono); font-size: 11px; color: var(--dim); white-space: nowrap; }
.market-channels:empty { display: none; }

/* A price we converted rather than read. Its own line under the figure: a word
   tacked onto the end of a number in a right-aligned column reads as part of
   the number, and — because there is no space between them in the markup — it
   would also make «59.90computed» one unbreakable run and the column that wide.
   `display: table` is what puts it on a line of its own *and* shrink-wraps it;
   a plain `block` would stretch its border across the whole column. The auto
   left margin keeps it under the figure in a right-aligned cell.

   `site/tests/market.test.js` measures this column on a phone taking the mark
   as a floor rather than as something added beside the price — which is true
   exactly while this rule keeps it on its own line.

   The lease label is the same chip for a different fact: a price quoted on a
   longer lease than the table's (an hour standing in a five-minute table,
   `docs/api-spec.md` §2.3a). One rule for both, so a row carrying the two
   stacks two chips of one size under its figure, and the phone measurement
   above covers the label by the same arithmetic. */
.market-mark,
.market-lease {
  display: table;
  margin: 3px 0 0 auto;
  padding: 1px 6px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--dim);
  white-space: nowrap;
}
/* A reading that is behind. The tone is on the age and on nothing else — the
   row is not tinted, because a tinted row says «wrong» about numbers that are
   merely older, and the age itself is already on the screen saying so. */
.market-stale > .market-when > .market-ago { color: var(--dim); }

/* Our own row. The name in it is the redundant half of the marking: take the
   tint away and «nrg.market» in the vendor column still says whose row it is. */
/* The row header is spelled out through `.market-table tbody` so it outweighs
   the type reset above it — a shorter selector loses to that rule and our own
   row comes out of the tint half-way. */
.market-ours > td,
.market-table tbody .market-ours > th { background: var(--surface2); }
.market-ours > .market-price { color: var(--accent); }

/* The table and the card while `market.js` is asking, and only then.
 *
 * The rule does nothing on its own: `market.js` adds `.market-awaiting` on boot
 * and takes it off once the answer lands, the endpoint refuses, or its own
 * bound runs out. A sheet that hid the rows by itself would hide them from the
 * reader whose JavaScript never ran and from the crawler, which is the half of
 * the page this table ships for.
 *
 * `visibility`, not `display` and not opacity: the rows keep their boxes, so
 * nothing under the table moves when they come back, and there is no fade to
 * watch. No spinner and no skeleton — the wait is a third of a second, and a
 * placeholder that long is a flicker with extra steps.
 *
 * No exception, and our own row least of all. It had one for a revision, on the
 * ground that its three figures are `data-tariff` hooks `site.js` keeps
 * current — but `site.js` is fetching the tariff at that same moment, so for
 * the length of the wait the exemption left the shipped price standing alone on
 * a table of headers, in the largest type on the page. The ruling
 * (2026-09-07) is that no wrong number is shown at all, and headers for a third
 * of a second are what that costs. */
.market-awaiting { visibility: hidden; }

/* The caption under the table — where the page says whether these are live
   prices, and the only line on it allowed to say «live». */
.market-status { font-family: var(--mono); }

/* ── 404 ───────────────────────────────────────────────────────────────── */

.oops {
  min-height: 60vh;
  padding-top: 64px;
  padding-bottom: 64px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 18px;
  max-width: 46ch;
}
.oops h1 { font-size: clamp(28px, 4vw, 44px); font-weight: 700; letter-spacing: -.03em; }
.oops p { color: var(--muted); }
.oops-actions { display: flex; flex-wrap: wrap; gap: 12px; }

/* ══ ≥560px: two columns where the content holds them ═══════════════════ */

@media (min-width: 560px) {
  .hero-actions { flex-direction: row; }
  .hero-actions .btn { width: auto; }
  .products { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .trust { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .faq { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .points { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cta .btn { align-self: center; }
  .cta-actions { flex-direction: row; justify-content: center; align-self: center; }
  .footer-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ══ ≥720px: the header opens up, tiles become cards ════════════════════ */

@media (min-width: 720px) {
  .wrap { padding: 0 24px; }
  .zone-table th,
  .zone-table td { padding-left: 16px; padding-right: 16px; }
  /* The wide padding comes back where there is room for it — see the narrow
     rule in the `/market` block above. */
  .market-table th,
  .market-table td { padding-left: 16px; padding-right: 16px; }
  /* The heading and the figure card, side by side. Below this width they stack
     and the card reads as the summary of the sentence above it. */
  .market-head { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 48px; }
  .nav-links { display: flex; }
  .nav-signin { display: inline; }
  .burger { display: none; }
  .menu { display: none !important; }

  /* ── Above the phone ──────────────────────────────────────────────────
   *
   * 720px is where this sheet stops being a phone: the burger goes, the link
   * row comes back, and the tiles become cards. The declarations made for a
   * finger come off with it.
   *
   * One of them is the figure that may break rather than be cut (F-02,
   * measured at 320/360/390): above this width the hero has the room, and a
   * figure that wrapped on a wide screen would be a defect of its own.
   * `min-width: 0` on the hero items is NOT here — that floor is wrong at
   * every width, 7fr at 1024 included. `.btn` is not here either and never
   * was load-bearing: reverted one declaration at a time against the
   * fourteen pages at three widths, `.hero-foot .money` carried 25 of the 42
   * readings and `.hero > * { min-width: 0 }` the other 3; `.btn` carried
   * none, while `height: 44px; line-height: 1` means a label that ever wrapped
   * to three lines would spill out of the button it is in. So it is `nowrap`
   * at every width now, in the base rule.
   *
   * The rest are the 44px minimums. A pointer is not a finger, and the sizes
   * above this width are the approved ones from the mock-ups — the chart
   * card's is also the size the browser heights recorded in
   * `site/tests/layout.test.js` were measured at. What stays 44px at every
   * width is the two sliders: their box is invisible, so nothing is paid for
   * it, and 22px is what lighthouse fails on the desktop reading.
   *
   * What does NOT come off with them is the floor underneath: WCAG 2.2 SC
   * 2.5.8 asks 24×24 of any pointer, and reverting to the drawn size put
   * these four back at 18px tall (the switch links and the range buttons),
   * 22.2px wide (the narrowest quick-start tab) and 19.5px tall (a footer
   * link) — measured in Chrome at 1280. So the drawn size comes back and the
   * target does not go with it. Two devices, and which one is used depends on
   * what is drawn on the control:
   *
   *   · nothing drawn (the switch links are bare text) → padding out to 24
   *     and the same margin back in. An inline flex item's outer box is
   *     unchanged, so the row and its gaps stay where they were;
   *   · something drawn (the range button's pressed pill, the tab's underline,
   *     the footer link's row pitch) → an out-of-flow `::before` centred on
   *     the control. It paints nothing and moves nothing, and it is not a
   *     flex item, so a `gap` inside the control never sees it. */
  .hero-foot .money { white-space: nowrap; }
  /* 40.8×18 … 98.9×18 at 1280, «हिन्दी» the narrowest at 22.2. 3px and 1px of
     padding put the box at 24×24.2 at its smallest; the equal negative margin
     hands the space back, so the eleven names sit exactly where they sat and
     the 14px column gap between them is still 14px of white. */
  .lang-switch a {
    display: inline;
    min-width: 0;
    min-height: 0;
    padding: 3px 1px;
    margin: -3px -1px;
  }
  /* 119.7×29.6 at 1280 and never narrower than its own word: over the floor
     already. It is stated rather than left at 0 so that a shorter label in
     some future locale cannot quietly take it under. */
  .when-chip { display: inline; min-height: 24px; }
  .tab { display: inline-block; min-width: 0; min-height: 0; }
  .chart-ranges button { display: inline-block; min-width: 0; min-height: 0; }
  /* The footer column goes back to a 27.5px pitch and takes the 24px box
     instead — see the note on `.footer-col a` above for why the phone cannot
     have the box and the desktop cannot have the taller row. */
  .footer-col a { min-height: 0; }
  .footer-col a::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    height: 24px;
    transform: translateY(-50%);
  }
  /* The header's own call to action is the only one in view at the top of a
     wide page — the hero's is below the fold on the widths this block covers
     — so it takes the accent back. */
  .nav[data-hero-cta] .nav-side .btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accentInk);
  }
  .nav[data-hero-cta] .nav-side .btn-primary:hover {
    background: var(--accentH);
    border-color: var(--accentH);
    color: var(--accentInk);
  }

  .hero { padding-top: 64px; padding-bottom: 24px; }
  .hero-copy { gap: 24px; }
  .stats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .stats > div,
  .stats > div:nth-child(odd) { padding: 24px; }
  .stats > div:nth-child(even) { border-right: 1px solid var(--lineS); }
  .stats > div:first-child { padding-left: 0; }
  .stats > div:last-child { border-right: none; }

  .section { padding-bottom: 56px; gap: 20px; }
  .section-head-link { margin-left: auto; }
  .h2 { font-size: 24px; }
  .calc { padding-bottom: 56px; }
  .calc-card { padding: 32px; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; }

  .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .steps-three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .steps > li { padding: 24px; }
  .step-title { font-size: 17px; }
  .calls { gap: 18px; }

  .cost-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; }
  .cost-card { padding: 24px; gap: 12px; }
  .cost-big { font-size: 32px; }

  .products { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; }
  .product { padding: 24px; gap: 14px; }
  .product-title { font-size: 17px; }

  .feature-lede { font-size: 15px; }

  /* Six tiles, 3×2, on `/pricing`. The stretched tile is gone: a row of two
     with a hole on the right read as "something failed to arrive here". The
     home page's four trust tiles are two by two here and a row of four from
     1024px — three and one would leave the same hole. */
  .trust { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .trust-four { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  .quickstart { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; }
  .code { padding: 16px; font-size: 12px; }

  .faq > div { padding: 20px; gap: 8px; }
  .faq-q { font-size: 15px; }

  .page-head { padding-top: 56px; padding-bottom: 40px; }
  .band-inner { padding-top: 56px; padding-bottom: 56px; gap: 20px; }
  .band-split { padding-top: 56px; padding-bottom: 56px; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 48px; }
  .points { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .points > div { padding: 24px; }
  .rule-card, .budget-card { padding: 24px; }

  /* The two blocks that used to be the right-hand column of `/pricing`, now
     a row of two under the table that took it. `align-items: start` so the
     short notice does not stretch to the height of the rate card beside it. */
  .pricing-aside {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
  }

  .cta-inner { padding-top: 64px; padding-bottom: 64px; gap: 18px; }
  .cta .btn { align-self: center; }

  .footer-grid { padding-top: 48px; padding-bottom: 40px; gap: 32px; }
  .footer-col { gap: 10px; }
}

/* ══ ≥1024px: the desktop mock-up layout ════════════════════════════════ */

@media (min-width: 1024px) {
  .ticker-row { gap: 8px 28px; letter-spacing: .12em; }
  .nav-row { padding-top: 14px; padding-bottom: 14px; gap: 24px; }
  .nav-side { gap: 14px; }
  .nav-side .btn { height: 36px; font-size: 13px; padding: 0 16px; }
  .nav-links { gap: 24px; font-size: 14px; }
  .nav-signin { font-size: 14px; }
  .logo { font-size: 15px; }
  .health { display: inline-flex; }
  .footer-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }

  .hero { padding-top: 96px; padding-bottom: 24px; grid-template-columns: 7fr 5fr; gap: 64px; }
  .hero-copy { gap: 28px; }
  .hero h1 { max-width: 20ch; line-height: 1.04; }
  .hero-lede { max-width: 46ch; }
  .hero-actions .btn { height: 48px; font-size: 15px; font-weight: 600; padding: 0 22px; }
  .hero-card { padding: 28px; gap: 24px; }
  .hero-card .bar-head .money { font-size: 17px; }
  .hero-card .bar-head .money.lead { font-size: 24px; }

  .stats { margin: 40px 0 64px; }
  .section { padding-bottom: 72px; }
  .calc { padding-bottom: 72px; }
  .calc-card { gap: 48px; }
  .calc-line { padding: 14px 0; }
  .calc-out { padding: 22px 24px; }
  .calc-burn, .calc-ours { font-size: 17px; }
  .calc-total .money { font-size: 26px; }

  .pricing-grid { grid-template-columns: 7fr 5fr; gap: 24px; }
  .feature { margin-bottom: 0; }
  .trust-four { grid-template-columns: repeat(4, minmax(0, 1fr)); }

  /* An inner page's hero: two equal columns rather than the home page's
     7fr/5fr — what stands on the right is a block of substance, not an
     illustration card. */
  .hero-even { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 48px; }
  .hero-even h1 { max-width: 18ch; }
}

@media (min-width: 1280px) {
  .ticker-src { flex-basis: auto; margin-left: auto; }
}

/* ══ The phone ══════════════════════════════════════════════════════════════
 *
 * The one block in this sheet that narrows rather than widens, and it is the
 * complement of the 720px breakpoint above. Last, so it settles the ties:
 * every selector below is the same selector as a base rule, so what decides
 * between them is source order.
 *
 * `max-width: 719.98px` and not `(width < 720px)`. Range syntax is the exact
 * complement of `min-width` and reads better, and it is dropped whole by
 * Safari before 16.4 — a browser that does not understand the condition does
 * not understand the block, so every rule in it is gone at once. What was in
 * here is the 12px floor: on those phones the site would have gone back to
 * drawing its numeric detail at 10 and 11px, which is the one thing this block
 * exists to prevent, and nothing anywhere would have said so. The 0.02px is
 * the sheet's idiom for «just under», and the rest of the sheet uses
 * `min-width` alone.
 *
 * Why a query at all, in a sheet written mobile-first: these are *phone* values
 * for rules whose base value is what the mock-ups approved for the desktop.
 * Raising the base and restoring it above 720 would say the same thing twice,
 * in two places, for thirty rules.
 */
@media (max-width: 719.98px) {
  /* ── A 12px floor on every text style the sheet draws smaller ─────────────
   *
   * Measured at 390px on the served pages: 68 visible text elements under 12px
   * on the home page, 57 on `/pricing`, 48 on `/market` — mostly 11px, some
   * 10px — and what is set in them is the numeric detail a buyer decides on:
   * every table header, every caption, the chart's axis figures, the vendor's
   * channels, the mark under a converted price. 11px on a 390px screen is
   * where that detail is *least* readable while carrying the most. (Those
   * three counts are the pages with the API unreachable, which is the state a
   * local run renders; the audit counted 90/79/50 against production, where
   * the scripts draw more of the same classes.)
   *
   * 12px is the figure the audit asked for and no WCAG success criterion names
   * a size at all — what the standard says is that text must survive being
   * resized, which is a different promise. One point is also all the tables
   * can give away for free: they scroll inside `.scroll-x` at this width by
   * design, so width here is not the constraint it is at a desktop size. What
   * the floor may not do is push the price off the first screen, and
   * `site/tests/layout.test.js` and `site/tests/market.test.js` both compute
   * that at the phone's own type.
   *
   * The list is not a taste: it is every rule of the base sheet under 12px,
   * and `layout.test.js` derives that set from the sheet and fails naming any
   * rule the floor has missed — so an 11px rule added tomorrow is red on the
   * day it lands rather than the next time somebody measures a phone. */
  .zone,
  .zone-now,
  .zone-window,
  .zone-basis,
  .cap,
  .code,
  th,
  .lease-src,
  .ticker-row,
  .health,
  .calc-scale,
  .mix-hours,
  .mix-price,
  .mix-note,
  .step-no,
  .cell-note,
  .th-basis,
  .chart-ranges button,
  .chart-label,
  .chart-key,
  .chart-src,
  .point-no,
  .budget-foot,
  .market-eyebrow,
  .market-when,
  .market-channels,
  .market-mark,
  .market-lease,
  .hero-terms { font-size: 12px; }
  /* Set relative to its surrounding prose rather than absolutely — 12px is the
     floor under it, not a size replacing it. */
  .quickstart-copy code,
  .inline-mono { font-size: max(12px, .95em); }

}

/* ══ /market: the two columns that carry the argument, in the first screen ═══
 *
 * Six columns of the ranking table measure 489px inside a 356px frame at 390,
 * and the two the reader came for — «vs nrg.market» and «Observed» — begin at
 * 306px and 405px, so both are off the frame until the table is swiped
 * sideways. The frame scrolls and says so; the order of what it shows first is
 * what was wrong.
 *
 * Two columns go, and they are the two that are *derived* from what stays.
 * `#` is the row's position in a table already sorted by price, which the
 * order of the rows says over again; `vs burn` is each price against a
 * protocol constant, which the figure card above the table states in full.
 * `vs nrg.market` is neither: it is the comparison this page exists to make,
 * and it cannot be read off anything else on the screen.
 *
 * Its own query, and narrower than the floor's. This rule sat in the 720px
 * block with the type floor, so it dropped two columns over the whole band
 * from 544 to 719 — where all six fit with room to spare and nothing scrolls
 * at all. Measured in headless Chrome on the served page with the rule off:
 * the six columns want 510px, the frame is the viewport less 34, so they fit
 * from 544 up (510/510 at 544, 685/685 at 719) and overflow at 543 (510 into
 * 509). 543.98 is that edge; `site/tests/market.test.js` pins it and pins that
 * the floor keeps 720.
 *
 * By position, header and body together, so the header and its column cannot
 * come apart — hiding the cells by class and the headers by index is two
 * mechanisms that agree only until the generator reorders its columns.
 * Which columns those positions *are* is pinned in
 * `site/tests/market.test.js`, against the page's own `data-i18n` keys. */
@media (max-width: 543.98px) {
  .market-table tr > :nth-child(1),
  .market-table tr > :nth-child(4) { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
}

/* ══ Forced colours ═════════════════════════════════════════════════════════
 *
 * Windows high contrast, and anybody else who has told the browser to use
 * their palette instead of ours. Backgrounds become the system canvas and
 * `box-shadow` is dropped outright, so anything said here in either of those
 * is not said there at all.
 *
 * There used to be something here that mattered: the 2px left rule marking a
 * row of the zone table as a realised average, drawn as a `box-shadow` and so
 * redrawn as a border for this mode. The rule is gone (30.08, see the comment
 * beside `tr[data-averaged]`), and with it the only thing on this site that
 * said something load-bearing in a property forced colours drop. What marks
 * such a row now is its «avg» token, the window line under its hours and its
 * `aria-describedby` — a word, a sentence and a sentence, all of which this
 * mode keeps by construction.
 */
@media (forced-colors: active) {
  /* The scroll shadows are gradients and are gone here: nothing is lost that
     was not decoration, but the frame should not be left with four empty
     background slots on a repaint. */
  .scroll-x { background: none; }
}
