/* ==========================================================================
   The showcase portal's stylesheet.
   --------------------------------------------------------------------------
   Ported from the mockup the owner approved for the one-page portal
   (docs/dev/portal-spa-template.md, decision 1): its palette, its metrics, its
   class names and its composition, section for section.

   Two deliberate departures from that file, both recorded as decisions:

     * the body font is Inter and not 'Instrument Sans' (decision 10). The
       template shipped Instrument Sans in latin and latin-ext only, so every
       Cyrillic string fell back to a system font while the Latin characters
       beside it kept the webfont — the finding T-4 of the template review.
       Inter is already in this image, covers both scripts and is the stack the
       portal has always used. JetBrains Mono stays for the monospaced labels
       and ships with its Cyrillic subsets.
     * the rules are not scoped under #app/#cart-root/#detail-root as the
       template scopes them. The portal renders one document and this stylesheet
       is the whole of it; the scoping existed to keep the template's markup out
       of the surrounding page it was pasted into, and there is no surrounding
       page here.

   Everything a page loads ships in this image: the URLs below are rewritten at
   build time to the digest-carrying names the asset route answers
   (internal/webui/assets.go), and a plain relative url() would be a 404.
   ========================================================================== */

@font-face {
  font-family: 'Inter';
  src: url('/assets/InterVariable.693b77d4f32ee9b8.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

/* JetBrains Mono, in the four subsets the template carried, each with the
   unicode-range that decides whether the browser fetches it at all: a page in
   Russian downloads the Cyrillic file and the Latin one, and neither of the two
   extended files. The ranges are the template's own (which are Google Fonts'),
   carried over unchanged. */
@font-face {
  font-family: 'JetBrains Mono';
  src: url('/assets/JetBrainsMono-latin.83c005d49d8a6a50.woff2') format('woff2');
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url('/assets/JetBrainsMono-latin-ext.db5ff4db83e58042.woff2') format('woff2');
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url('/assets/JetBrainsMono-cyrillic.e17cfd15fb96909d.woff2') format('woff2');
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

@font-face {
  font-family: 'JetBrains Mono';
  src: url('/assets/JetBrainsMono-cyrillic-ext.62213be8a78b42f1.woff2') format('woff2');
  font-weight: 400 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* --------------------------------------------------------------------------
   The palette and the metrics of the mockup.

   The four translucent properties at the end are new with the second theme and
   are the same colours as before it: the header's blur, the focus ring of the
   range thumb, the tap highlight and the overlay scrim were written out as
   rgba() at the place they were used, which made them the four palette colours
   a theme could not repaint. Their v1 values here are the literals that stood
   there, unchanged.

   What stays written out are the two DROP SHADOWS (the suggestion list, the
   range thumb) and the white ring of that thumb: a shadow is near-black at 10%
   and 25% under either palette, and the ring is --card in both, so tokenising
   them would add three properties that decide nothing.

   What is NOT a colour — the monospaced stack, the header height, the safe
   area — stays out of the theming: a theme here is a variant of the PALETTE and
   nothing else (sitepolicy.Theme, docs/dev/portal-spa-template.md).
   -------------------------------------------------------------------------- */
:root {
  --paper: #fbfaf8;
  --card: #ffffff;
  --ink: #16160f;
  --teal: #0f6e6b;
  --teal-dark: #0a4f4d;
  --amber: #a8631a;
  --line: #e7e3db;
  --line-soft: #f0ede6;
  --muted: #55524a;
  --dim: #8a8579;
  --dark-text: #f6f4ef;
  --dark-muted: #b5b0a4;
  --dark-line: #2c2c22;
  --dark-dim: #7c7768;
  --dark-link: #4ecac5;
  --dark-link-hover: #7ee0db;
  --paper-blur: rgba(251, 250, 248, 0.92);
  --ring: rgba(15, 110, 107, 0.25);
  --tap: rgba(15, 110, 107, 0.12);
  --scrim: rgba(22, 22, 15, 0.45);
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --hdr: 68px;
  --safe-bottom: env(safe-area-inset-bottom, 0px);
}

/* --------------------------------------------------------------------------
   The second palette: "AskeSIM Home v2", the owner's indigo scheme, switched on
   from the admin panel (settings.KeyWebTheme) and rendered by the server as
   data-theme="v2" on <html>.

   It overrides the COLOURS of :root above and nothing else — every declaration
   here is one of those custom properties, and the typefaces, the radii and the
   metrics of the approved mockup are shared by both themes. That is the owner's
   own decision, recorded in docs/dev/portal-spa-template.md: a colour variant,
   not a second design.

   html[data-theme="v2"] outranks :root by specificity, so the order of the two
   blocks decides nothing; they are adjacent so that a colour changed in one is
   read beside its counterpart in the other.
   -------------------------------------------------------------------------- */
html[data-theme='v2'] {
  --paper: #f8f9fb;
  --card: #ffffff;
  --ink: #1b2130;
  --teal: #5b6abf;
  --teal-dark: #4a59b0;
  --amber: #b45309;
  --line: #e5e7ec;
  --line-soft: #eef0f3;
  --muted: #4b5563;
  --dim: #6b7280;
  --dark-text: #e2e5ec;
  --dark-muted: #8b92a5;
  --dark-line: rgba(255, 255, 255, 0.06);
  --dark-dim: #8b92a5;
  --dark-link: #a8b3ec;
  --dark-link-hover: #c8cff5;
  --paper-blur: rgba(248, 249, 251, 0.92);
  --ring: rgba(91, 106, 191, 0.1);
  --tap: rgba(91, 106, 191, 0.12);
  --scrim: rgba(27, 33, 48, 0.45);
}

/* BOTH themes are light, as the approved mockup is. Declaring it stops a
   browser in dark mode from recolouring the form controls of a light page. */
html {
  color-scheme: light;
  -webkit-text-size-adjust: 100%;
}

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Arial, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-wrap: pretty;
  overflow-wrap: break-word;
  -webkit-tap-highlight-color: var(--tap);
}

h1,
h2,
h3 {
  margin: 0;
  line-height: 1.2;
}

p {
  margin: 0;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

img {
  max-width: 100%;
  height: auto;
}

a {
  color: var(--teal);
  text-decoration: none;
}

a:hover {
  color: var(--teal-dark);
  text-decoration: underline;
}

button {
  font-family: inherit;
  touch-action: manipulation;
}

input {
  font-family: inherit;
}

/* One visible focus ring for every interactive element, on the keyboard only.
   It is never removed: a portal with no visible focus cannot be operated
   without a pointer. */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

[hidden] {
  display: none !important;
}

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

/* --------------------------------------------------------------------------
   The skip link — the first element of <body>, out of sight until focused.
   Hidden BY CLIPPING and not by display:none or visibility:hidden: those two
   take an element out of the tab order as well as off the screen, so the link
   could never receive the focus that reveals it.
   -------------------------------------------------------------------------- */
.skip-link {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link:focus {
  position: fixed;
  top: calc(8px + env(safe-area-inset-top, 0px));
  left: calc(8px + env(safe-area-inset-left, 0px));
  z-index: 80;
  width: auto;
  height: auto;
  margin: 0;
  padding: 12px 16px;
  overflow: visible;
  clip: auto;
  clip-path: none;
  background: var(--card);
  border: 1px solid var(--teal);
  border-radius: 9px;
  color: var(--teal);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Header
   -------------------------------------------------------------------------- */
.hdr {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--paper-blur);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line);
}

.hdr-in {
  max-width: 1180px;
  margin: 0 auto;
  padding: 12px clamp(16px, 4vw, 32px);
  display: flex;
  align-items: center;
  gap: clamp(12px, 2vw, 32px);
  flex-wrap: nowrap;
}

.logo {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 19px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
  flex: none;
}

.logo:hover {
  color: var(--ink);
  text-decoration: none;
}

/* The SECTION menu, by its own class and not as `.hdr nav`.

   The descendant selector was the bug: the language switcher's menu is a <nav>
   inside this same header (layout.templ), so `.hdr nav` (0,1,1) matched it and
   outranked its own `.lang-menu` (0,1,0). The switcher opened onto a menu laid
   out as a header row instead of a dropdown — and below 560px, where the rule
   further down hides the section menu, onto a menu with display:none: the
   control opened and nothing appeared.

   No vertical padding of its own: the links inside it are 44px tall, which is
   what sets the height of the whole bar (--hdr = 44 + the 12px of padding the
   row carries above and below). The section anchors clear that height by their
   own scroll-margin-top, so the two numbers have to agree. */
.nav-main {
  display: flex;
  align-items: center;
  gap: clamp(14px, 2vw, 24px);
  font-size: 14px;
  flex-wrap: nowrap;
  min-width: 0;
  white-space: nowrap;
}

.nav-main a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--muted);
  flex: none;
}

.hdr-right {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  flex: none;
}

/* The way into the cabinet (layout.templ).

   It is selected by a CLASS although the element also carries an id, and the
   reason is a test rather than taste: a selector written `#account-link` reads
   as the colour `#acc` to anything scanning this file for literal colours, and
   one of those scanners is the guard that keeps every colour inside the two
   palettes (assets_test). The id stays what it is — the CLIENT's handle on the
   element, which rewrites the word once it has read the marker cookie — and the
   class is what the stylesheet knows it by, exactly like .hdr-cta beside it.

   44px tall like every other control of this bar (the owner's decision of batch
   22, which refused to trade touch targets for width): it is a target a thumb
   hits on a phone, and the height of the bar is set by the tallest of them.

   It is on EVERY breakpoint, unlike the menu and the action below: those two
   are reachable otherwise — the catalogue is in the footer, the picker is the
   section right under the first screen — while this link is the only way into
   the cabinet from anywhere on the site. So what a narrow screen buys is
   spacing, not this element (see the 440px block). */
.hdr-account {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 14px;
  color: var(--muted);
  flex: none;
  white-space: nowrap;
}

@media (max-width: 720px) {
  .nav-wide {
    display: none;
  }
}

/* Below 560px the header is the brand, the cart and the language, and nothing
   else — because nothing else FITS. At 320px the row has about 288px to work
   with, and the brand, the globe and their gaps take 150 of them: the menu item,
   the action and the cart pill are three claims on the remaining 140, and any
   two of them together overflow the viewport, which is a horizontally scrolling
   page on every screen of that width.

   What is dropped is chosen by what else the page offers. The menu's remaining
   item and the action are both reachable otherwise — the catalogue is linked
   from the footer of every page, and the picker is the section right under the
   first screen, which is where a visitor arrives by scrolling one screen (the
   hero's own button to it was removed by the owner's decision of batch 30) —
   while the cart pill is the ONLY way to open the drawer, and it is drawn only
   when there is something in it.

   The selector names the SECTION menu by its class: written as `.hdr nav` it
   also hid the language switcher's own menu, which is the one control this
   breakpoint leaves in the bar. */
@media (max-width: 560px) {
  .nav-main,
  .hdr-cta {
    display: none;
  }
}

.cart-pill {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--mono);
  font-size: 12px;
  padding: 11px 14px;
  border: 1px solid var(--teal);
  background: var(--paper);
  color: var(--teal);
  border-radius: 999px;
  white-space: nowrap;
  cursor: pointer;
}

.cart-pill:hover {
  background: var(--teal);
  color: var(--paper);
}

.hdr-cta {
  font-size: 14px;
  font-weight: 600;
  padding: 12px 16px;
  background: var(--ink);
  color: var(--paper);
  border-radius: 8px;
  white-space: nowrap;
}

.hdr-cta:hover {
  background: var(--teal);
  color: var(--paper);
  text-decoration: none;
}

/* The language switcher is not in the mockup and cannot be left out: the portal
   serves two locales and each page declares the other as an alternate.

   It is a <details> holding a globe and the two links (layout.templ), which is
   what makes it fit a phone's header: two locale codes side by side cost the
   width of a control that is pressed once in a session. It discloses natively,
   so the CSS is a menu's appearance and nothing else — no state here belongs to
   a script, and the switcher works with none running.

   The summary is 44px in both directions (the whole header is sized by it) and
   the menu it opens is absolutely positioned inside the switcher, so opening it
   moves nothing in the bar. */
.lang-switcher {
  position: relative;
  flex: none;
}

.lang-summary {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 44px;
  min-height: 44px;
  padding: 0 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 12px;
  cursor: pointer;
  /* The disclosure triangle is replaced by the globe beside the locale, in both
     engines that draw one. */
  list-style: none;
}

.lang-summary::-webkit-details-marker {
  display: none;
}

.lang-summary:hover,
.lang-switcher[open] .lang-summary {
  border-color: var(--teal);
  color: var(--teal);
}

.lang-globe {
  flex: none;
}

.lang-now {
  line-height: 1;
}

.lang-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 30;
  display: grid;
  min-width: 128px;
  padding: 4px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(22, 22, 15, 0.1);
}

.lang-link {
  display: flex;
  align-items: center;
  min-height: 44px;
  padding: 0 12px;
  border-radius: 7px;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--muted);
}

.lang-link:hover {
  background: var(--line-soft);
  color: var(--ink);
  text-decoration: none;
}

.lang-link.active {
  color: var(--teal);
  font-weight: 600;
}

/* The narrow bar, and it is spacing alone.

   Below 560px the row is the brand, the cart pill, the way into the cabinet and
   the language switcher. Four elements, three gaps and the row's own padding —
   and at 320px, with something in the cart, that came to three pixels MORE than
   the viewport: the document scrolled sideways, which inside a webview is a page
   that slides under a thumb with its right edge off the screen. It was found by
   the e2e suite at exactly that width (e2e/tests/mobile.spec.ts) and only with a
   pill in the bar, because the pill is drawn by the client and only when the
   cart holds something.

   WHAT IS NOT DONE ABOUT IT, and both are decisions of earlier batches rather
   than taste. Nothing further is HIDDEN: the rule above may drop the menu item
   and the action because the page offers both a second time, and the cabinet
   link has no such double — hiding it would leave a phone with no way in at all.
   And no touch target is made smaller: 44px is the floor the owner refused to
   trade away in batch 22, so every min-height and min-width here is untouched.

   What is left is the space BETWEEN things and the SIZE of the type, and that is
   what this block takes: 6px off each side of the row, 6px off each gap, three
   points off the brand, two off the cabinet link, 4px and one point off the
   pill, and 4px off each side of the language control.

   The numbers are MEASURED and not estimated — Chromium at 320px in the e2e
   container, with a pill in the bar — and they are chosen against the widest
   state this row can be in rather than against the one the suite caught. That
   state is the RUSSIAN cabinet word ("Кабинет", which the client writes in place
   of "Войти" once it has read the marker cookie) beside a pill counting a
   two-digit cart: the four controls and their gaps then measure 292px inside a
   300px content box — eight pixels to spare with both 10px paddings intact —
   against 320 and change before this block, which is the overflow. The state the
   suite actually caught, the English word with one item in the cart, has some
   thirty pixels to spare. Fitting that one alone would have left the Russian one
   over the edge with nothing testing it.

   The breakpoint is 440px and not 320: at 400 the same row already stands within
   a few pixels of the edge, and a rule that engaged only where the overflow had
   already begun would be a fix for one phone and a defect on the next one up. */
@media (max-width: 440px) {
  .hdr-in {
    padding-left: 10px;
    padding-right: 10px;
    gap: 6px;
  }

  /* The brand, three points down. It is the widest fixed element of the row
     after the pill, and it is the one whose reading does not depend on its
     size: nobody parses a word mark, they recognise it. */
  .logo {
    font-size: 16px;
  }

  .hdr-right {
    gap: 6px;
  }

  /* Horizontal padding and type size only: the pill's own min-height keeps it a
     44px target, and its text is the client's (cart_title + the count). */
  .cart-pill {
    font-size: 11px;
    padding-left: 10px;
    padding-right: 10px;
  }

  /* Same rule, same reason: min-width and min-height stay at 44px, so what
     changes is the space around the globe and the locale beside it — never
     whether the locale is shown, which is what the closed switcher says. */
  .lang-summary {
    gap: 4px;
    padding: 0 6px;
  }

  .hdr-account {
    font-size: 12px;
  }
}

/* --------------------------------------------------------------------------
   Section shells
   -------------------------------------------------------------------------- */
.wrap {
  max-width: 1180px;
  margin: 0 auto;
}

/* The sticky header is 68px tall (--hdr: the 44px of its tallest control plus
   its own 12px of padding above and below), so a section scrolled to by its
   fragment stops 14px below it instead of under it. */
.sec {
  scroll-margin-top: calc(var(--hdr, 68px) + 14px);
}

/* The gutters of a section that has no padding of its own. The front page's
   hero pads itself under .hero and its other two sections under their ids,
   which outrank this by specificity; what is left is the catalogue page, whose
   one section is plain .wrap.sec — it used to be #all of the front page and the
   rule that padded it went with the id.

   Without this the catalogue's cards touch both edges of a phone.

   :not(.hero) rather than a specificity race: the hero is a class now, and a
   bare .hero would lose its own padding to the two classes of this selector on
   every element carrying both. Each section is matched by exactly one padding
   rule instead. */
.wrap.sec:not(.hero) {
  padding: clamp(32px, 6vw, 56px) clamp(16px, 4vw, 32px) 24px;
}

.eyebrow {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: 20px;
}

.lbl {
  display: block;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

/* The one sentence a visitor without JavaScript is owed: the catalogue below is
   whole, the picker above it is not. */
.noscript {
  display: block;
  max-width: 1180px;
  margin: 0 auto;
  padding: 12px clamp(16px, 4vw, 32px);
  font-size: 14px;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   Hero and the waiting list
   -------------------------------------------------------------------------- */
/* The hero is a CLASS and not the id #top, and that is the repair of a leak
   rather than a preference: #top is the anchor the footer's "back to the top"
   points at, and every scrollable document of this portal carries it — the
   catalogue page included. While these two rules hung on the id, the catalogue
   was laid out by the hero's own grid (three columns on a wide screen) and its
   heading was printed at the hero's 34–60px. The id is now a pure anchor with
   no declaration of its own anywhere in this file. */
.hero {
  padding: clamp(40px, 8vw, 88px) clamp(16px, 4vw, 32px) 56px;
  display: grid;
  /* min(…, 100%) and not the bare minimum on every auto-fit grid of this file:
     at 320px the gutters leave 288px, a track declared 300px wide overflows it,
     and the whole page scrolls sideways. The minimum is what the track wants,
     never more than the row it sits in. */
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: clamp(28px, 5vw, 64px);
  align-items: start;
}

.hero h1 {
  font-size: clamp(34px, 7vw, 60px);
  line-height: 1.03;
  letter-spacing: -0.035em;
  font-weight: 700;
  margin: 0 0 20px;
}

.cta-main {
  font-size: 16px;
  font-weight: 600;
  padding: 14px 24px;
  background: var(--teal);
  color: var(--paper);
  border-radius: 10px;
}

.cta-main:hover {
  background: var(--teal-dark);
  color: var(--paper);
  text-decoration: none;
}

.aside-card {
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 14px;
  padding: clamp(20px, 4vw, 28px);
}

.aside-card .eyebrow {
  font-size: 11px;
  color: var(--amber);
  margin-bottom: 14px;
}

.aside-card h2 {
  font-size: clamp(19px, 2.6vw, 22px);
  line-height: 1.25;
  letter-spacing: -0.02em;
  font-weight: 600;
  margin: 0 0 10px;
}

.aside-card p {
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted);
  margin: 0 0 20px;
}

/* The lists inside a card, which today is the guides and only the guides
   (guide.templ: a block prints a <ul> for its items and an <ol> for its steps).

   The reset at the top of this file takes the markers off EVERY ul and ol,
   because on this site a list is normally a layout — the navigation, the footer,
   the rows of a card. Under it a sequence of steps rendered as nothing but
   several paragraphs: no numbers, no bullets, no inset, and a reader told to
   open a settings screen was not shown the order to do it in, which is the
   entire reason steps are a field of their own beside the items. The reset
   itself is left alone: it is what those three layouts depend on.

   The markers come back here and nowhere wider. .aside-card is also the waiting
   list of the front page and the signed-out half of the cabinet; neither holds a
   list of any kind, so no other screen changes.

   The measures are the card's own: the 15px and the muted colour of .aside-card
   p, so a marker is neither bigger nor darker than the item beside it, and 10px
   between items against the 20px between paragraphs — the list's own 10px under
   the last item adds up to exactly that 20px again. */
.aside-card ul,
.aside-card ol {
  margin: 0 0 10px;
  padding-left: 22px;
  font-size: 15px;
  color: var(--muted);
}

.aside-card ul {
  list-style: disc;
}

.aside-card ol {
  list-style: decimal;
}

.aside-card li > p {
  margin-bottom: 10px;
}

.wl-form {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}

.wl-form input {
  flex: 1;
  min-width: 180px;
  font-size: 16px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--paper);
  color: var(--ink);
}

.wl-form input:focus {
  outline: 2px solid var(--teal);
  outline-offset: 1px;
}

.wl-form button {
  min-height: 44px;
  font-size: 15px;
  font-weight: 600;
  padding: 12px 18px;
  background: var(--ink);
  color: var(--paper);
  border: 0;
  border-radius: 9px;
  cursor: pointer;
}

.wl-form button:hover {
  background: var(--teal);
}

.wl-done {
  font-size: 15px;
  font-weight: 500;
  color: var(--teal);
  padding: 12px 14px;
  background: var(--paper);
  border: 1px solid var(--teal);
  border-radius: 9px;
}

/* The failure the mockup has no state for: the form is a real submission now,
   and a request the API refused or never answered has to say so instead of
   pretending the address was taken. */
.wl-error {
  margin-top: 12px;
  font-size: 14px;
  color: var(--amber);
}

/* --------------------------------------------------------------------------
   The picker
   -------------------------------------------------------------------------- */
#find {
  padding: 24px clamp(16px, 4vw, 32px) 40px;
}

/* overflow VISIBLE, and it is load-bearing: the suggestions of the country
   field are positioned under it and reach past the bottom of the top section, so
   a card that clipped its children would cut the dropdown off. The rounded
   bottom corners are then the result's own (.find-res below), because they are
   no longer clipped by the card. */
.find-card {
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 16px;
  overflow: visible;
}

.find-top {
  padding: clamp(20px, 4vw, 32px) clamp(16px, 4vw, 32px) 30px;
  border-bottom: 1px solid var(--line-soft);
  display: grid;
  gap: 28px;
}

.find-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: clamp(24px, 4vw, 32px);
  align-items: start;
}

.q-wrap {
  position: relative;
}

.q-wrap .lbl {
  margin-bottom: 10px;
}

.q-wrap input {
  width: 100%;
  font-size: 17px;
  font-weight: 500;
  padding: 13px 15px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--paper);
}

/* The two states of the ONE box under the country field: what a query matched
   (.suggest) and the whole published directory to look through (.browse). They
   share this rule rather than a copy of it — where the box hangs and how tall it
   may grow inside a webview is one decision, and a second copy of it is the copy
   that stays behind. */
.suggest,
.browse {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 6px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 12px 32px rgba(22, 22, 15, 0.1);
  z-index: 10;
  overflow-y: auto;
  /* dvh, with vh left behind as the FALLBACK and not the other way round: inside
     the Telegram and Instagram webviews the address bar and the toolbar are part
     of the large viewport that vh measures, so a list sized in vh is taller than
     the room it has. A browser that does not know dvh keeps the vh above it. */
  max-height: 46vh;
  max-height: 46dvh;
  -webkit-overflow-scrolling: touch;
}

.sug-item {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 14px;
  cursor: pointer;
  border-bottom: 1px solid var(--line-soft);
}

/* The row under the pointer, and the row the field's own arrows are standing on
   — one mark for both, because they are one state: the option that Enter would
   take. The keyboard half is aria-selected and not a class of ours, so what a
   screen reader is told and what a sighted visitor sees can never be two
   different rows (assets/app.js, moveSuggestion). */
.sug-item:hover,
.sug-item[aria-selected='true'] {
  background: var(--line-soft);
}

/* Three rows at most and no vendor code on any of them (the owner's decision
   5.1), so the row is the name and the price and nothing competes with them for
   the width of a phone. */
.sug-name {
  font-size: 15px;
  font-weight: 500;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sug-from {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--teal);
}

/* The heading of a group of the browse list. It sticks to the top of the box
   while its own group is scrolled through: the list is two hundred rows inside
   46dvh, and a visitor a screenful in has otherwise nothing telling them whether
   they are still among the countries or already among the packages. */
.browse-head {
  position: sticky;
  top: 0;
  padding: 10px 14px;
  background: var(--card);
  border-bottom: 1px solid var(--line-soft);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

/* A row of that list, at the same 52px the suggestions are: it is the same tap
   with the same thumb, and this is the list where a mistap costs a request. The
   button is stripped back to the row it draws — a browser's own button font,
   border and centring have no place in a list. */
.browse-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 52px;
  padding: 14px;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--line-soft);
}

.browse-item:hover {
  background: var(--line-soft);
}

.browse-name {
  font-size: 15px;
  font-weight: 500;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

.browse-from {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--teal);
}

.sl-head {
  display: flex;
  align-items: baseline;
  gap: 4px 10px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}

/* The value wraps onto its own line rather than being cut when the label beside
   it is long: Russian inflects the noun after the number, so "7 дней" and
   "30 дней" are not the same width, and a phone is where that shows. */
.sl-val {
  margin-left: auto;
  min-width: 0;
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
}

/* 44px tall and not the 28 the thumb needs: the slider is the one control of
   the picker that is dragged rather than tapped, and a 28px band is a target a
   thumb misses. The track and the thumb are drawn in the middle of it by the
   pseudo-elements below, so the height is hit area and nothing else. */
.sl {
  width: 100%;
  height: 44px;
  cursor: pointer;
  margin: 0;
  touch-action: pan-y;
}

input[type='range'] {
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
}

input[type='range']::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: 2px;
  background: var(--line);
}

input[type='range']::-moz-range-track {
  height: 4px;
  border-radius: 2px;
  background: var(--line);
}

input[type='range']::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  margin-top: -8px;
  border-radius: 50%;
  background: var(--teal);
  border: 3px solid #ffffff;
  box-shadow: 0 1px 4px rgba(22, 22, 15, 0.25);
  cursor: grab;
}

input[type='range']::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--teal);
  border: 3px solid #ffffff;
  cursor: grab;
}

input[type='range']:focus-visible::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px var(--ring);
}

/* The ticks under a slider. They are a POINTER SHORTCUT over a control that is
   already there — the slider itself is the labelled, keyboard-operable one, and
   the strip is aria-hidden — which is why their hit area is 44px TALL but only
   as wide as their own label plus its padding: a scale of six ticks at 44px each
   needs 264px of the 288 a 320px screen has, and equal-width targets would
   overlap their neighbours. An overlapping 44px target is worse than a small one
   beside a full-width slider. */
.ticks {
  position: relative;
  height: 44px;
  margin-top: 2px;
}

.tick {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  padding: 0 8px;
  color: var(--muted);
  font-weight: 400;
}

.tick.on {
  color: var(--ink);
  font-weight: 500;
}

/* The picker on a phone, at the breakpoint this file already calls one: 620px is
   where .plans collapses to a single column.

   What it takes out is WHITESPACE, and only whitespace: the picker's top was
   435px tall on a 390x844 screen, which is more than half the viewport spent
   before the first price. The paddings, the grid gaps and the label spacing give
   back about 10% of that; the value beside a slider drops 19px -> 17px, which is
   the one type size here that is decoration rather than a target.

   What it deliberately does NOT touch: .sl, .ticks and .tick. Their 44px are hit
   area for a control that is dragged, argued in the comments above them, and a
   phone is exactly where shrinking them would be felt. The country field keeps
   font-size 17px for a reason that is not taste either — iOS Safari zooms the
   page when a field below 16px takes focus, and a zoomed page is a picker the
   visitor then has to scroll sideways. */
@media (max-width: 620px) {
  #find {
    padding: 8px 16px 28px;
  }

  .find-top {
    padding: 8px 16px 10px;
    gap: 8px;
  }

  .find-row {
    gap: 8px;
  }

  .q-wrap .lbl {
    margin-bottom: 2px;
  }

  .q-wrap input {
    padding: 9px 14px;
  }

  .sl-head {
    margin-bottom: 0;
  }

  /* A FIFTH OFF THE HEIGHT OF THIS BLOCK, and the owner asked for it in those
     words (2026-08-21): on a real phone, where the browser's own bars take a
     hundred pixels the emulator does not, the picker plus the prices it found
     still did not fit. 333px -> 265px, and every pixel of it comes from two
     places.

     THE TICK STRIPS, and this reverses what the comment above them argues for
     the desktop. Their 44px were hit area for a POINTER SHORTCUT: the strip is
     aria-hidden, and the control it duplicates — the slider itself, labelled and
     operable from a keyboard — is right above it and keeps every pixel of its
     own target. A shortcut is what a screen this tight spends first.

     THE DRAG BAND goes 44 -> 40, and no further. It is the one control of this
     picker that is dragged rather than tapped, the 20px thumb sits in the middle
     of it, and 40px is still a band a thumb finds. The rest is whitespace:
     paddings, the gaps of the grid, the space under a label. */
  .sl {
    height: 40px;
  }

  .ticks {
    height: 14px;
    margin-top: 0;
  }

  /* At this height the strip is a LEGEND and no longer a target: it says which
     stops the slider has and which one it is on, and the tapping is done on the
     slider above it, which keeps its 40px band. That is the honest reading of
     what these already were — aria-hidden duplicates of a labelled, keyboard
     operable control — and it is what buys the third price its place on the
     screen. Pressing one still works; nothing is asked to. */
  .tick {
    min-height: 14px;
    font-size: 10px;
    padding: 0 6px;
  }

  /* The live value of a slider, one size down: it is read at arm's length off a
     control the thumb is already on, not looked for. */
  .sl-val {
    font-size: 15px;
  }
}

.find-res {
  padding: clamp(20px, 4vw, 28px) clamp(16px, 4vw, 32px) 32px;
  background: var(--paper);
  border-radius: 0 0 15px 15px;
}

.res-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 20px;
}

/* The picker's result line. It is a DIV and not a heading (home.templ: the
   server ships it empty and the browser writes it), so the line-height the
   "h1, h2, h3" rule above used to give it is stated here — without it the node
   would fall back to the body's 1.5 and the line would sit lower. */
.res-head .res-title {
  font-size: 15px;
  font-weight: 600;
  line-height: 1.2;
  margin: 0;
}

/* The line ABOVE the row: which two packages were chosen, on what term, and
   which family the unlimited block opened on. It is empty on the server's render
   and until the client has an answer, and an empty node draws nothing at all
   rather than a gap where a sentence will be. */
.grp-title {
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
}

.grp-title:empty {
  display: none;
}

/* ONE ROW OF THREE CARDS OF ONE SIZE (prototype/home-v2.html): two packages and
   the unlimited block. The columns are EQUAL — 1fr each, with a zero minimum so
   a long note cannot widen one of them — and the row stretches its cards to one
   height, so a card whose note is empty is not shorter than the one beside it.
   Two columns where three no longer fit, one on a phone. */
.plans {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  align-items: stretch;
}

@media (max-width: 900px) {
  .plans {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

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

.plan {
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 13px;
  padding: clamp(14px, 2vw, 20px);
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: relative;
  min-width: 0;
}

.plan.cheapest {
  border-color: var(--teal);
}

/* The badges of one card. A card answers one, two or all three of the picker's
   questions (assets/app.js), so the strip is a wrapping row of spans rather than
   one label, and the colour sits on the BADGE and not on the card: a plan that
   is both the cheapest and the best value carries one of each. */
.plan-tag {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  /* A line high even when it is empty: the three cards are one row of one size,
     and a card that won no badge must not start its price a line above its
     neighbours. */
  min-height: 14px;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
}

.badge-cheapest {
  color: var(--teal);
}

.badge-value {
  color: var(--amber);
}

.badge-traffic {
  color: var(--ink);
}

.plan-price {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

/* Three prices side by side rather than one: the figure scales with the room a
   card actually has, so nothing is cut off at 320px and nothing shrinks on a
   desktop. */
.plan-price b {
  font-size: clamp(24px, 3.4vw, 34px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
}

.plan-price span {
  font-size: 13px;
  color: var(--dim);
}

.plan-vol {
  font-size: 16px;
  font-weight: 500;
  margin-top: 8px;
}

.plan-days {
  font-size: 14px;
  color: var(--muted);
  margin-top: 2px;
}

/* The comment stays on the card and is held to TWO LINES, with an ellipsis for
   whatever an operator wrote past them: the row is three cards of one size, and
   a paragraph in one of them would otherwise set the height of all three. */
.plan-note {
  font-size: 13px;
  line-height: 1.45;
  color: var(--muted);
  border-top: 1px solid var(--line-soft);
  padding-top: 12px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The unlimited block's own switch: one button per family this TERM is sold in,
   the current one pressed. It changes the price and the text of this card alone
   and moves nothing in the row. */
.fam {
  display: flex;
  gap: 4px;
  padding: 3px;
  background: var(--line-soft);
  border-radius: 10px;
}

.fam button {
  flex: 1 1 0;
  min-width: 0;
  min-height: 44px;
  padding: 0 4px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}

.fam .fam-on {
  background: var(--card);
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(22, 22, 15, 0.1);
}

/* What stands in a place of the row where a card cannot: a trip no unlimited
   plan of this destination covers, or a request its packages answer in fewer
   volumes than the row has places for. It is the size of a card and reads as a
   statement rather than as a card that failed to load. */
.empty-note {
  display: flex;
  align-items: center;
  padding: clamp(14px, 2vw, 20px);
  border: 1px dashed var(--line);
  border-radius: 13px;
  font-size: 13px;
  line-height: 1.45;
  color: var(--dim);
}

/* One note standing in TWO places of the row (a destination whose catalogue
   answers the request with no package at all): one statement across both,
   rather than the same sentence printed twice. */
.empty-note.empty-wide {
  grid-column: span 2;
}

/* And back to one place where the row itself is a single column: an item
   spanning two columns of a one-column grid would create a second, implicit
   column and take the row out of the shape the rest of this file gives it. */
@media (max-width: 620px) {
  .empty-note.empty-wide {
    grid-column: auto;
  }
}

.plan-cta {
  margin-top: auto;
  min-height: 44px;
  font-size: 15px;
  font-weight: 600;
  padding: 13px 0;
  border-radius: 9px;
  cursor: pointer;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
}

.plan.cheapest .plan-cta {
  border-color: var(--teal);
  background: var(--teal);
  color: var(--paper);
}

.plan-more {
  font-size: 12px;
  color: var(--dim);
  text-align: center;
  margin-top: -4px;
}

.res-notes {
  margin-top: 18px;
  display: flex;
  align-items: center;
  gap: 4px 20px;
  flex-wrap: wrap;
  font-size: 13px;
  color: var(--dim);
}

.res-notes a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* THE RESULT ON A PHONE, and this is the block the whole screen was rebuilt
   around (the owner's requirement of 2026-08-21): the picker and the tariffs it
   found must stand on ONE screen. They did not — 1304px of card on the 731px a
   360x800 phone leaves under the sticky header — so a visitor saw both sliders
   and a third of the first price, and compared three offers by scrolling.
   Everything below is measured against those 731px.

   A CARD IS A ROW HERE, not a narrow column of the desktop card: the badges
   over the price, the two facts beside it, the cart button on the right. 84px
   instead of 275px, and three of them under the sliders come to about 660px.

   WHAT IS TAKEN OFF, AND WHERE IT WENT: the operator's comment (.plan-note) and
   "tap for the terms" (.plan-more), and they are hidden HERE ONLY. The whole
   card is role=button and opens the plan's own modal, which carries both in
   full (planCard, assets/app.js) — nothing leaves the page, it moves one tap
   away, and it is the tap the card already offered.

   WHAT IS DELIBERATELY NOT TOUCHED: the 44px of .plan-cta, of the family switch
   and of every slider control above them. Hit area was never what put the
   prices off the screen — the third column of facts and the note were — and a
   phone is exactly where shrinking a target is felt.

   It stands after the rules it overrides and not inside the picker's own mobile
   block above, which sits BEFORE .find-res, .plan and .res-notes are declared:
   at equal specificity the later rule wins, and half of this would have been
   overridden by the desktop values it is meant to replace. */
@media (max-width: 620px) {
  .find-res {
    padding: 12px 16px 20px;
  }

  /* TWO SUMMARY LINES STOOD HERE, one under the other, and both said what the
     sliders a finger's width above them already said. .res-head is the terse one
     — "Italy · 5 GB · 10 days", the request itself — and it stays, cut to one
     line so that a long destination cannot fold it. .grp-title is the sentence
     ("Closest to your request: 5 GB and 10 GB for 30 days · unlimited for 10
     days, family by data volume — Essential…"), and on a phone it was already
     being truncated mid-clause, which is a sentence nobody can finish reading.
     It goes; on a wide screen it is untouched and reads whole.

     What it alone carried — WHICH family the unlimited block opened on — is
     stated by the block's own switch, which is on that card. */
  .res-head {
    margin-bottom: 6px;
  }

  .res-head .res-title {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .grp-title {
    display: none;
  }

  .plans {
    gap: 4px;
  }

  .plan {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      'tag cta'
      'facts cta'
      'fam fam';
    align-items: center;
    gap: 2px 8px;
    padding: 7px 12px;
  }

  /* A line high on the desktop because three cards of one row must start their
     prices at one height; here every card is its own row, and a card that won
     no badge should not carry an empty line for one. */
  /* ONE LINE, and the size is what keeps it there. A card can win two badges at
     once — "best price per GB" AND "most data" — and in English those two are
     123px + 65px against the 194px this column has, so at the desktop's 11px
     they folded and cost the card twenty pixels for a second row of two words.
     At 10px with a tighter gap they stand side by side. */
  .plan-tag {
    grid-area: tag;
    min-height: 0;
    font-size: 9.5px;
    gap: 4px 6px;
  }

  /* THE PRICE AND THE TWO FACTS ON ONE LINE, joined by the separator the rest of
     this site joins facts with: "1 490 ₽ · 5 ГБ · 7 дней". Three lines of one
     column each was what made the card 275px tall, and none of the three is a
     sentence — they are a price and two figures.

     wrap is kept as the SAFETY and not as the layout: a four-figure price beside
     a thirty-day term on a 320px screen drops the facts to a second line, which
     costs a line where an overflowing row would cost the button its place. */
  .plan-body {
    grid-area: facts;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 4px;
  }

  /* Two points off the desktop figure, and they are what puts the price, the
     volume and the term on one line at 360px: at 24px the second card's
     "2 190 ₽ · 10 ГБ · 7 дней" is four pixels too wide for the room beside the
     button, and four pixels cost the whole row a second line. */
  .plan-price b {
    font-size: 22px;
  }

  /* The price per gigabyte, and it is the one figure this layout gives up. It
     does not fit on that line beside the price and the two facts, it is derived
     from what is already on the card, and the badge that stands on it ("the best
     value") is right there — the number itself is in the plan's own modal, one
     tap away, where the note and the terms went too. */
  .plan-price span {
    display: none;
  }

  .plan-vol {
    font-size: 14px;
    margin-top: 0;
  }

  .plan-days {
    margin-top: 0;
  }

  /* The separator, and it TRAILS the volume rather than leading the term. Where
     the line does not fit — an unlimited block, whose two facts are words rather
     than figures — the row wraps between them, and a separator written as
     .plan-days::before then opens the second line with a lone dot. Trailing, it
     closes the first line the way a hyphen does.

     The escape carries its own space: a CSS escape swallows ONE space after it
     as its terminator, so the margin below is the gap and not a space. */
  .plan-vol::after {
    content: '\00b7';
    margin-left: 4px;
  }

  /* Narrower than the desktop's full-width button, and every pixel of that is
     given to the line beside it: the button keeps its 44px of height and loses
     only the padding that made the facts wrap. */
  .plan-cta {
    grid-area: cta;
    margin-top: 0;
    padding: 12px 8px;
    font-size: 12px;
  }

  /* The unlimited block's family switch is the full width of the card and keeps
     its own targets: it is a control that is chosen from, not a fact that is
     read. */
  .fam {
    grid-area: fam;
    margin-top: 6px;
  }

  /* The one target of this screen that gives up its 44px, and it gives up four
     of them: the switch is a choice BETWEEN three visible options, each the full
     third of a card wide, not a small control a thumb has to find. */
  .fam button {
    min-height: 40px;
  }

  .plan-note,
  .plan-more {
    display: none;
  }

  .empty-note {
    padding: 12px 14px;
  }

  .res-notes {
    margin-top: 12px;
  }
}

.popular {
  margin-top: 28px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.popular-label {
  font-size: 13px;
  color: var(--dim);
  margin-right: 4px;
}

.popular-all {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 14px;
  font-weight: 500;
  padding: 8px 6px;
}

.pop-chip {
  min-height: 44px;
  font-size: 14px;
  font-weight: 500;
  padding: 12px 16px;
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 999px;
  cursor: pointer;
  color: var(--ink);
}

.pop-chip:hover {
  border-color: var(--teal);
  color: var(--teal);
}

/* The picker's own state node: empty and hidden on the server's render, filled
   by the browser with whichever state it is in. It renders empty on purpose —
   the server does not know which state the client will be in, and "loading…"
   written here would stay on the screen of every visitor with JavaScript off. */
.region-status {
  margin: 12px 0 0;
  font-size: 14px;
  color: var(--muted);
}

/* The placeholders of the picker's three cards. aria-hidden in the markup: a
   placeholder is a drawing of a card that does not exist yet. */
.plan-skeleton {
  min-height: 232px;
  justify-content: flex-start;
  gap: 12px;
}

.skeleton {
  display: block;
  border-radius: 7px;
  background: linear-gradient(90deg, var(--line-soft) 0%, var(--line) 50%, var(--line-soft) 100%);
  background-size: 200% 100%;
  animation: skeleton-shift 1.4s ease-in-out infinite;
}

.skeleton-tag {
  width: 40%;
  height: 11px;
}

.skeleton-price {
  width: 60%;
  height: 34px;
}

.skeleton-line {
  width: 80%;
  height: 14px;
}

.skeleton-note {
  width: 100%;
  height: 42px;
}

@keyframes skeleton-shift {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton {
    animation: none;
  }

  html {
    scroll-behavior: auto;
  }
}

/* --------------------------------------------------------------------------
   The catalogue page (/{locale}/destinations)

   It used to be the section #all of the front page, and the rules below are that
   section's, moved with it. Two things changed with the move and are marked
   where they are: the page's heading is an <h1> and a group's is an <h2> (a page
   has one first-level heading and this one is it), and a row is a LINK with a
   flag rather than a disabled button.
   -------------------------------------------------------------------------- */
.dir-head {
  display: flex;
  align-items: end;
  gap: clamp(20px, 3vw, 32px);
  flex-wrap: wrap;
  margin-bottom: 24px;
}

.dir-head h1 {
  font-size: clamp(26px, 5vw, 34px);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 700;
  margin: 0 0 10px;
}

.dir-head p {
  font-size: 16px;
  line-height: 1.55;
  color: var(--muted);
  margin: 0;
  max-width: 52ch;
}

.dir-search {
  margin-left: auto;
  width: min(300px, 100%);
}

.dir-search input {
  width: 100%;
  font-size: 16px;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--card);
}

.dir-card {
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 16px;
  padding: 8px clamp(14px, 3vw, 28px) 24px;
}

.grp {
  padding-top: 24px;
}

/* The letter a section is filed under, and the whole of its heading: there is
   no count beside it any more, because a figure the client's filter forgot to
   rewrite would be the one thing on the page saying something untrue. */
.grp-letter {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.08em;
  color: var(--dim);
  font-weight: 500;
  margin: 0;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}

/* FOUR columns, declared and not derived: an auto-fit track count changes with
   the width of whatever the grid happens to sit in, and this one sat inside the
   front page's hero grid for a while and laid itself out in one. The two
   breakpoints are the same pair the plan row uses, so the page narrows in one
   rhythm.

   row-gap and not gap alone: the rows are 48px links separated by a hairline,
   and without a gap between the rows the hairline of one row runs into the top
   of the next. */
.grp-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  column-gap: clamp(16px, 3vw, 32px);
  row-gap: 4px;
}

@media (max-width: 900px) {
  .grp-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

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

/* One destination: a LINK to the front page with this destination in the query
   (catalogue.templ), 48px tall so it is a comfortable target on a phone, with
   the flag of the embedded set beside the name. The code is not on it — a
   visitor searches for "Germany", not for "DE" — and it is what the client
   filters on all the same. */
.dir-item {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  min-height: 48px;
  padding: 12px 10px;
  border-radius: 7px;
  border-bottom: 1px solid var(--line-soft);
  color: var(--ink);
}

.dir-item:hover {
  background: var(--paper);
  color: var(--ink);
  text-decoration: none;
}

/* The width and height are attributes on the element as well (catalogue.templ),
   so the row keeps its shape before this stylesheet has been applied: the file
   is a 4:3 SVG with no intrinsic pixel size. */
.dir-flag {
  width: 24px;
  height: 18px;
  flex: none;
  border-radius: 2px;
  object-fit: cover;
  /* A hairline out of the palette and not a literal: a flag on white needs an
     edge, and the second theme has to be able to repaint it. */
  box-shadow: 0 0 0 1px var(--line);
}

.dir-name {
  font-size: 15px;
  min-width: 0;
  color: var(--ink);
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dir-from {
  margin-left: auto;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--teal);
  flex: none;
}

.dir-empty {
  padding: 40px 0 24px;
  font-size: 15px;
  color: var(--muted);
}

.dir-foot {
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.dir-foot span {
  font-size: 13px;
  color: var(--dim);
}

/* The button setStatus puts under a failed region so a visitor can try again
   (assets/app.js). It was the catalogue's "show all" as well until the groups
   stopped being collapsed; the name is kept because the shape is the same and
   it is what the client asks for. */
.btn-expand {
  font-size: 14px;
  font-weight: 600;
  min-height: 44px;
  padding: 10px 18px;
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 9px;
  cursor: pointer;
  color: var(--ink);
  white-space: nowrap;
}

.btn-expand:hover {
  border-color: var(--teal);
  color: var(--teal);
}

/* --------------------------------------------------------------------------
   The page of one country (/{locale}/destinations/{iso})

   Everything on it is rendered by the server (country.templ): the trail, the
   header, five sentences at most, and two TABLES — the metered packages and the
   matrix of the unlimited families across the terms they are sold on.

   THE TABLES ARE TABLES, and the narrow screen is answered by SCROLLING them
   sideways rather than by reflowing them into cards. That is a choice with one
   real alternative and a reason for refusing it: a card reflow prints the column
   name in front of every value, and the only place that name can come from is a
   per-cell attribute in the markup (the data-label technique). country.templ
   carries none, and adding one would repeat all five headings into every row —
   which a screen reader then announces twice, once from the <th> it already
   associates and once from our copy. Scrolling keeps one heading per column, and
   keeps the packages readable DOWN the column, which is the whole reason a
   visitor is looking at a price list at all.

   What scrolling costs is that a row scrolled sideways loses what identifies it,
   and that is repaired rather than accepted: the first column of both tables is
   sticky at the left edge — the volume of a package, the family of an unlimited
   plan — so a price is never read without the row it belongs to named beside it.
   The column headings are NOT made sticky in their turn: the box scrolls
   sideways and the page scrolls down, so a heading row pinned inside that box
   would be pinned to a top that never moves.
   -------------------------------------------------------------------------- */

/* The trail above the heading. Its links are 44px tall like every other target
   of this portal, which is what makes the row as tall as it is: the type is
   small, the touch area is not. */
.crumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 8px;
  font-size: 13px;
  margin-bottom: 4px;
}

.crumb {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--muted);
}

.crumb:hover {
  color: var(--teal);
}

/* The document a visitor is already reading: text and not a link, so it is not
   coloured like one either. */
.crumb-current {
  color: var(--dim);
}

.crumb-current:hover {
  color: var(--dim);
}

.crumb-sep {
  color: var(--dim);
}

.country-head {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

/* The width and height are attributes on the element as well (country.templ),
   so the header keeps its shape before this stylesheet has been applied: the
   file is a 4:3 SVG with no intrinsic pixel size. The hairline is the
   catalogue's, out of the palette rather than a literal — a flag on white needs
   an edge, and the second theme has to be able to repaint it. */
.country-flag {
  width: 48px;
  height: 36px;
  flex: none;
  border-radius: 3px;
  object-fit: cover;
  box-shadow: 0 0 0 1px var(--line);
}

/* min-width: 0 so a long name shrinks the block instead of pushing the badge
   off the row, and a basis of 240px so the badge drops to a line of its own
   before the name is squeezed to nothing. */
.country-title {
  flex: 1 1 240px;
  min-width: 0;
}

.country-title h1 {
  font-size: clamp(26px, 5vw, 34px);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 700;
  margin: 0 0 4px;
}

/* The code and the region: the vendor's own two labels for this page, printed
   in the face this site prints codes in. */
.country-sub {
  font-family: var(--mono);
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--dim);
  margin: 0;
}

.country-region {
  color: var(--muted);
}

/* The cheapest price per gigabyte, the same figure the catalogue's card carries
   for this destination and in the same colour. margin-left: auto puts it at the
   far end of a wide header and nowhere at all once the row wraps. */
.country-from {
  margin-left: auto;
  flex: none;
  align-self: center;
  font-family: var(--mono);
  font-size: 14px;
  color: var(--teal);
  white-space: nowrap;
  padding: 9px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
}

/* The sentences. A measure of about 68 characters rather than the full width of
   the page: a line of text as wide as a 1180px container is one a reader loses
   their place in. */
.country-intro {
  display: grid;
  gap: 8px;
  margin-top: 24px;
  max-width: 68ch;
  font-size: 16px;
  line-height: 1.55;
  color: var(--muted);
}

/* The API did not answer, and the page says so instead of the tables. Amber,
   like the waiting list's own failure line (.wl-error): it is this deployment
   reporting a degradation and not a fact about the country. */
.country-unread {
  color: var(--amber);
}

/* Read, and there is nothing published for this country — a state of the
   showcase and not a failure, so it reads like the rest of the prose. */
.country-empty {
  color: var(--muted);
}

/* The one link out of this page. It is the hero's button, and it needs the two
   declarations the hero's own flex row gave it for free: on this page it is an
   <a> standing on its own, so it has to become a box before its padding means
   anything. The selector is the sibling it actually is (country.templ) rather
   than a second class on the same element. */
.country-intro + .cta-main {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: 24px;
}

/* border-collapse: separate and not collapse, which is what lets the table
   carry the rounded card border every other block of this site has: a collapsed
   border model ignores border-radius. The hairlines are therefore per cell. */
.country-table {
  width: 100%;
  margin-top: 20px;
  border: 1px solid var(--line);
  border-radius: 14px;
  border-collapse: separate;
  border-spacing: 0;
  background: var(--card);
  font-size: 15px;
  text-align: left;
}

.country-table th,
.country-table td {
  padding: 12px clamp(10px, 2vw, 16px);
  border-bottom: 1px solid var(--line-soft);
  /* Figures of one width, so a column of prices lines up on the decimal point
     without the cells being monospaced. */
  font-variant-numeric: tabular-nums;
}

/* The column headings, in the same face and weight as the catalogue's letters:
   they name a column and are not read as content. */
.country-table thead th {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  font-weight: 500;
  color: var(--dim);
  white-space: nowrap;
  border-bottom: 1px solid var(--line);
}

/* The row header of the matrix: the family, which is what a cell in that row is
   about. */
.country-table tbody th {
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}

.country-table tbody td {
  color: var(--muted);
}

/* Every cell of a row that HAS a row header is a price, which is the matrix and
   never the packages table — whose last column is a sentence. That is why the
   monospaced figures are written for this selector and not for every cell. */
.country-table tbody th[scope='row'] ~ td {
  font-family: var(--mono);
  font-size: 14px;
  color: var(--ink);
}

/* The last row draws no hairline: the card's own border is already there, and
   two lines a pixel apart look like a mistake. */
.country-table tbody tr:last-child > * {
  border-bottom: 0;
}

/* What the browser makes pressable on this page (assets/app.js, bindOffers): a
   whole row of the packages table, and a priced cell of the unlimited matrix.
   The server ships neither as a control — a visitor without a script reads a
   table and is offered nothing that does not work — so both rules below describe
   an element only the client ever produces.

   THE TARGET IS THE ROW AND THE CONTROL IS A BUTTON, which is why there are two
   selectors. The pointer aims at the whole row; the keyboard and the screen
   reader get a real <button>, into which the client MOVES the cell's own text.
   It is dressed to look like that text and nothing else: this page gains no
   control a reader can see, and the row it stands in gains no sixth column. */
.country-table [data-offer] {
  cursor: pointer;
}

.country-table [data-offer]:hover {
  background: var(--tap);
}

.offer-open {
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
  /* The narrow layout turns the cells to nowrap so that the TABLE scrolls and
     the document does not; a button that reset that would put a package's volume
     on two lines and widen the page. */
  white-space: inherit;
}

/* Where a press is answered when it opens nothing: a plan the published showcase
   stopped carrying between the render of this page and the press of it, or a
   request that did not arrive (assets/app.js, statusIn). One per table section,
   built by the client and standing in the document from the load — a live region
   the browser has never rendered announces nothing the first time it is filled.

   Zero margin so that the empty node it is for all but that moment is zero pixels
   tall, and the space appears with the sentence. Amber, like the waiting list's
   own failure line and the page's "the aggregate could not be read": it is this
   deployment reporting something, not a fact about the country. */
.country-status {
  margin: 0;
  color: var(--amber);
  font-size: 15px;
}

.country-status:not(:empty) {
  margin-top: 12px;
}

/* Below this width the five columns of the packages table cannot be read side
   by side, so the table itself becomes the scrolling box — the same 620px the
   catalogue's grid drops to one column at, so the page narrows in one rhythm.

   display: block is what makes a <table> scrollable: the rows keep their own
   table layout inside the block, and the block is what overflow applies to.
   nowrap on the cells is the other half — without it the cells would wrap
   instead of overflowing, and there would be nothing to scroll and nothing to
   read either. */
@media (max-width: 620px) {
  .country-table {
    display: block;
    overflow-x: auto;
  }

  .country-table th,
  .country-table td {
    white-space: nowrap;
  }

  /* The first column stays put while the rest scrolls under it: the volume of a
     package and the family of an unlimited plan are what say which row a price
     belongs to, and a price with its row scrolled out of sight states nothing.
     The background is the card's own, because a sticky cell is drawn OVER the
     cells passing beneath it and a transparent one would show both. */
  .country-table thead th:first-child,
  .country-table tbody th[scope='row'],
  .country-table tbody td:first-child {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--card);
    border-right: 1px solid var(--line-soft);
  }
}

/* The headings of a page's own sections. They have no class of their own — they
   are what the sections point at with aria-labelledby (country.templ,
   guide.templ) — so the hook is the id, as with #how above. The last two are
   the onward-links blocks: the country page's and the guides', which sit
   outside .aside-card and would otherwise render at the browser's default
   size while every heading beside them is styled. */
#country-packages,
#country-unlimited,
#country-next,
#guide-more {
  font-size: clamp(19px, 3vw, 22px);
  letter-spacing: -0.02em;
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   The three steps
   -------------------------------------------------------------------------- */
#how {
  padding: clamp(48px, 8vw, 64px) clamp(16px, 4vw, 32px);
}

.how-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: clamp(28px, 5vw, 64px);
  align-items: start;
}

.how-grid h2 {
  font-size: clamp(26px, 5vw, 34px);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 700;
  margin: 0 0 14px;
}

.how-grid > div > p {
  font-size: 16px;
  line-height: 1.55;
  color: var(--muted);
  margin: 0;
}

.steps {
  display: grid;
  gap: 4px;
}

.step {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: 16px;
  padding: 22px 0;
  border-top: 1px solid var(--line);
}

.step-num {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--dim);
  padding-top: 4px;
}

.step h3 {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
  margin: 0 0 6px;
}

.step p {
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted);
  margin: 0;
  max-width: 60ch;
}

/* The three notes under the steps — the ones that used to stand under the
   hero's buttons (.hero-notes, until batch П2). They are the last child of
   .steps, so they are a row of that grid and the 4px gap of it is already
   between them and the last step; the margin adds the 18 that make 22 — the
   step's own vertical padding — so the notes sit one step-rhythm below the
   text above them rather than at the 40px they had under the hero, where
   there was a whole section edge to clear. */
.how-notes {
  margin-top: 18px;
  display: flex;
  gap: 28px;
  font-size: 13px;
  color: var(--dim);
  flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   The dark block and the questions
   -------------------------------------------------------------------------- */
#trust {
  background: var(--ink);
  color: var(--dark-text);
}

/* The links of this band are the ONE place on the site where a link sits on
   --ink, and the accent it is painted with everywhere else does not survive the
   move: --teal on --ink is 3.00:1 in the original palette and 3.26:1 in v2,
   against the 4.5:1 WCAG 1.4.3 asks of 15px text. Both tokens are therefore
   lighter accents of their own palette, and they are declared in both blocks
   above rather than written out here, so a colour changed in one is read beside
   its counterpart in the other.

   The figures, on each palette's own --ink: --dark-link 9.15:1 (v2 7.90:1) and
   --dark-link-hover 11.74:1 (v2 10.48:1) — each above the text the link stands
   in, which is --dark-muted at 8.40:1 and 5.17:1. The global `a` rule is
   untouched: nothing outside this band changes colour. */
#trust a {
  color: var(--dark-link);
}

#trust a:hover {
  color: var(--dark-link-hover);
}

.trust-in {
  max-width: 1180px;
  margin: 0 auto;
  padding: clamp(48px, 8vw, 72px) clamp(16px, 4vw, 32px);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
  gap: clamp(36px, 5vw, 64px);
}

.trust-in h2 {
  font-size: clamp(26px, 5vw, 34px);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 700;
  margin: 0 0 18px;
}

.trust-lede {
  font-size: 16px;
  line-height: 1.6;
  color: var(--dark-muted);
  margin: 0 0 28px;
  max-width: 48ch;
}

.trust-rows {
  display: grid;
  gap: 18px;
}

.trust-row {
  display: grid;
  grid-template-columns: clamp(104px, 22vw, 150px) minmax(0, 1fr);
  gap: clamp(12px, 3vw, 20px);
  padding-bottom: 18px;
  border-bottom: 1px solid var(--dark-line);
}

.trust-row .k {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dark-dim);
  padding-top: 3px;
}

.trust-row .v {
  font-size: 15px;
  line-height: 1.5;
}

.faq-h {
  font-size: 15px;
  font-weight: 600;
  margin: 0 0 20px;
  letter-spacing: -0.01em;
  color: var(--dark-muted);
}

.faq {
  display: grid;
  gap: 2px;
}

/* <details> and not a scripted accordion: it discloses natively, with the
   keyboard, with the script absent, and without a line of JavaScript to
   maintain. That also closes the template review's finding T-7 — its own
   accordion was a <div> no keyboard could open. */
.faq-item {
  border-top: 1px solid var(--dark-line);
  padding: 18px 0;
}

.faq-q {
  display: flex;
  gap: 16px;
  align-items: baseline;
  min-height: 44px;
  cursor: pointer;
  font-size: 17px;
  font-weight: 500;
  letter-spacing: -0.01em;
  list-style: none;
}

.faq-q::-webkit-details-marker {
  display: none;
}

.faq-q::after {
  content: '+';
  margin-left: auto;
  font-family: var(--mono);
  font-size: 15px;
  color: var(--dark-dim);
}

.faq-item[open] .faq-q::after {
  content: '–';
}

.faq-a {
  font-size: 15px;
  line-height: 1.55;
  color: var(--dark-muted);
  margin: 10px 0 0;
  max-width: 52ch;
}

/* --------------------------------------------------------------------------
   Footer
   -------------------------------------------------------------------------- */
.ftr {
  max-width: 1180px;
  margin: 0 auto;
  padding: 40px clamp(16px, 4vw, 32px) calc(64px + var(--safe-bottom));
  display: flex;
  gap: clamp(20px, 3vw, 32px);
  align-items: baseline;
  border-top: 1px solid var(--line);
  flex-wrap: wrap;
}

.ftr-logo {
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.02em;
}

.ftr-note {
  font-size: 13px;
  color: var(--dim);
  max-width: 44ch;
  line-height: 1.5;
}

.ftr-links {
  margin-left: auto;
  display: flex;
  gap: 20px;
  font-size: 13px;
  flex-wrap: wrap;
}

.ftr-links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--muted);
}

/* --------------------------------------------------------------------------
   The storage notice: the strip at the bottom of every page that says what this
   site keeps on the visitor's own device (layout.templ).

   It ships hidden and the BROWSER decides whether to show it, because the answer
   is in that same storage. Its own padding carries the phone's bottom inset
   (--safe-bottom), so the button cannot end up under a home indicator; its
   z-index is below both overlays, so an open drawer covers it rather than
   fighting it for the bottom edge.
   -------------------------------------------------------------------------- */
.storage-note {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  background: var(--card);
  border-top: 1px solid var(--line);
  box-shadow: 0 -8px 24px rgba(22, 22, 15, 0.1);
}

.storage-in {
  max-width: 1180px;
  margin: 0 auto;
  padding: 14px clamp(16px, 4vw, 32px) calc(14px + var(--safe-bottom));
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.storage-text {
  flex: 1 1 240px;
  min-width: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}

.storage-accept {
  flex: none;
  min-height: 44px;
  padding: 11px 24px;
  border: 0;
  border-radius: 9px;
  background: var(--ink);
  color: var(--paper);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}

.storage-accept:hover {
  background: var(--teal);
}

/* --------------------------------------------------------------------------
   The cart drawer and the plan modal.
   Both ship hidden and are opened by the browser; the static texts are rendered
   by the server so they are translated and readable in the markup.
   -------------------------------------------------------------------------- */
.cart-ovl {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--scrim);
  display: flex;
  justify-content: flex-end;
  /* An explicit height, in dvh with vh behind it: with inset:0 alone a webview
     measures the LARGE viewport — the one that includes the bar it is drawing
     over the page — and the drawer's own footer, the total and the button end up
     under it. The height wins over `bottom` when both are set, which is exactly
     what is wanted here. */
  height: 100vh;
  height: 100dvh;
}

.cart-panel {
  width: min(440px, 100%);
  background: var(--paper);
  border-left: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  max-height: 100%;
}

.cart-head {
  padding: clamp(20px, 4vw, 28px) clamp(18px, 4vw, 28px) 20px;
  background: var(--card);
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: baseline;
  gap: 16px;
}

.cart-head h2 {
  font-size: 24px;
  line-height: 1.15;
  letter-spacing: -0.025em;
  font-weight: 700;
  margin: 0 0 6px;
}

.cart-count {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--dim);
}

.cart-close {
  margin-left: auto;
  min-height: 44px;
  font-size: 14px;
  font-weight: 500;
  padding: 10px 14px;
  border: 1px solid var(--line);
  background: var(--paper);
  border-radius: 9px;
  cursor: pointer;
  color: var(--muted);
}

.cart-close:hover {
  border-color: var(--ink);
  color: var(--ink);
}

.cart-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 8px clamp(18px, 4vw, 28px) 20px;
  -webkit-overflow-scrolling: touch;
}

.cart-item {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: start;
  padding: 18px 0;
  border-bottom: 1px solid var(--line);
}

.ci-title {
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
}

.ci-meta {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--dim);
  margin-top: 6px;
}

.ci-rm {
  font-size: 13px;
  margin-top: 6px;
  min-height: 44px;
  padding: 6px 0;
  border: 0;
  background: transparent;
  color: var(--amber);
  cursor: pointer;
  text-decoration: underline;
}

.ci-price {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
  white-space: nowrap;
}

.cart-empty {
  padding: 40px 0;
  font-size: 15px;
  line-height: 1.55;
  color: var(--muted);
}

.cart-foot {
  padding: 20px clamp(18px, 4vw, 28px) calc(24px + var(--safe-bottom));
  background: var(--card);
  border-top: 1px solid var(--line);
}

.cart-total-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 16px;
}

.cart-total-row span:first-child {
  font-size: 15px;
  color: var(--muted);
}

.cart-total {
  margin-left: auto;
  font-size: 30px;
  font-weight: 700;
  letter-spacing: -0.03em;
}

.cart-pay {
  width: 100%;
  font-size: 16px;
  font-weight: 600;
  padding: 15px 0;
  border: 0;
  border-radius: 10px;
  background: var(--line);
  color: var(--dim);
  cursor: not-allowed;
}

.cart-note {
  font-size: 13px;
  line-height: 1.5;
  color: var(--dim);
  margin-top: 12px;
}

.dt-ovl {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: var(--scrim);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: clamp(12px, 5vw, 56px) clamp(10px, 4vw, 24px) calc(clamp(12px, 5vw, 56px) + var(--safe-bottom));
  /* The same dvh as the drawer, and for the same reason: the modal is what
     scrolls, and in a webview a scroller sized by the large viewport hides its
     own last rows under the browser's bar. */
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

.dt-panel {
  width: 100%;
  max-width: 720px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 16px;
  overflow: hidden;
}

.dt-head {
  padding: clamp(20px, 4vw, 28px) clamp(18px, 4vw, 32px) 24px;
  background: var(--card);
  border-bottom: 1px solid var(--line);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr));
  gap: clamp(14px, 3vw, 24px);
  align-items: start;
}

.dt-op {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--teal);
  margin-bottom: 12px;
}

/* The plan modal's title, a DIV for the same reason (components.templ). Every
   declaration the heading rules used to supply — size, weight, line-height,
   margin — is already stated here, so the tag change costs it nothing. */
.dt-head .dt-h {
  font-size: clamp(23px, 4vw, 30px);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 700;
  margin: 0 0 8px;
}

.dt-sub {
  font-size: 15px;
  color: var(--muted);
}

.dt-priceside {
  text-align: right;
}

.dt-price {
  font-size: clamp(28px, 5vw, 40px);
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1;
}

.dt-pergb {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--dim);
  margin-top: 6px;
}

.dt-body {
  padding: 24px clamp(18px, 4vw, 32px) 28px;
}

.spec {
  display: grid;
  grid-template-columns: clamp(104px, 26vw, 190px) minmax(0, 1fr);
  gap: clamp(12px, 3vw, 20px);
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}

.spec .k {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
  padding-top: 3px;
}

.spec .v {
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  min-width: 0;
}

/* The coverage row's value is a BUTTON that folds the list of destinations out
   under itself — under itself and not in a second dialog, which would put one
   focus trap inside another. */
.cov-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--teal);
  font-size: 15px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.cov-btn::after {
  content: '▾';
  font-size: 11px;
}

.cov-btn.open::after {
  content: '▴';
}

/* The list scrolls INSIDE this box, both ways: down, because a global plan
   covers a hundred destinations and the modal must not become a page of them;
   and sideways, because a long destination name on a 320px screen may not be
   allowed to widen the document. */
.cov-wrap {
  margin: 4px 0 14px;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: auto;
  max-height: 46vh;
  max-height: 46dvh;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

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

table.cov th {
  position: sticky;
  top: 0;
  background: var(--line-soft);
  padding: 9px 12px;
  text-align: left;
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--dim);
}

table.cov td {
  padding: 9px 12px;
  border-top: 1px solid var(--line-soft);
  vertical-align: middle;
}

table.cov td.cov-flag {
  width: 34px;
}

table.cov td.cov-code {
  width: 68px;
  font-family: var(--mono);
  color: var(--muted);
}

.flag {
  display: block;
  width: 22px;
  height: 16px;
  border-radius: 2px;
  object-fit: cover;
  box-shadow: 0 0 0 1px var(--line);
}

.dt-notice {
  margin-top: 24px;
  padding: 18px 20px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 11px;
}

.dt-notice .k {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: 8px;
}

.dt-notice .v {
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted);
}

.dt-actions {
  margin-top: 22px;
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
}

.dt-add {
  font-size: 15px;
  font-weight: 600;
  padding: 13px 22px;
  background: var(--teal);
  color: var(--paper);
  border: 0;
  border-radius: 10px;
  cursor: pointer;
}

.dt-add:hover {
  background: var(--teal-dark);
}

.dt-back {
  font-size: 15px;
  font-weight: 500;
  padding: 13px 20px;
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 10px;
  cursor: pointer;
  color: var(--ink);
}

.dt-back:hover {
  border-color: var(--ink);
}

.dt-cmp {
  font-size: 13px;
  color: var(--dim);
  margin-left: auto;
  flex: 1 1 100%;
  order: 3;
}

/* THE PLAN'S OWN CARD ON A PHONE (the owner's requirement of 2026-08-21): it
   must be SMALLER THAN THE SCREEN, not a document the screen is a window onto.

   What changes is WHERE THE SCROLLING HAPPENS. On a wide screen the overlay is
   the scroller and the panel is as tall as what it says — there is room for
   both. On a phone the panel is taller than the viewport every time, and an
   overlay-sized scroller means the price and the title leave the screen the
   moment a visitor looks at the coverage, and "add to cart" is somewhere below
   the fold with nothing saying how far. So the PANEL is held to the height of
   the overlay, its head stays put, its body scrolls inside it, and the two
   buttons stick to the bottom of that scroller — the modal is then a card of a
   known size with a known way out of it, which is what a dialog is.

   It reaches the account's own dialogs too (.acc-stepup, .acc-reveal reuse this
   frame), and it should: a dialog that cannot be seen whole on the screen it
   opened on is the same defect wherever it stands.

   The type comes down with it — the title from 23-30px to 20, the price from
   28-40 to 26 — and the head keeps the price BESIDE the name instead of under
   it: at this width the auto-fit grid folds to one column, and a right-aligned
   price on a line of its own costs a whole row for one figure. */
@media (max-width: 620px) {
  .dt-ovl {
    padding: 10px 10px calc(10px + var(--safe-bottom));
    overflow: hidden;
  }

  .dt-panel {
    max-height: 100%;
    display: flex;
    flex-direction: column;
  }

  .dt-head {
    flex: 0 0 auto;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 10px;
    padding: 14px 16px;
  }

  .dt-head .dt-h {
    font-size: 20px;
  }

  .dt-sub {
    font-size: 13px;
  }

  .dt-price {
    font-size: 26px;
  }

  .dt-pergb {
    margin-top: 2px;
  }

  /* The one scroller of the dialog. overscroll-behavior keeps a flick at its end
     from scrolling the page behind the modal, exactly as the overlay's own rule
     does on a wide screen. */
  .dt-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior: contain;
    padding: 14px 16px 0;
  }

  .spec {
    padding: 10px 0;
    gap: 4px 12px;
  }

  .dt-notice .v,
  .spec .v {
    font-size: 14px;
  }

  /* The way out of the dialog, kept on the screen whatever the body is scrolled
     to. -1px of bottom offset is not a nicety: at exactly 0 a sub-pixel viewport
     height leaves a hairline of scrolled content under the strip. */
  .dt-actions {
    position: sticky;
    bottom: -1px;
    margin-top: 14px;
    padding: 12px 0 14px;
    background: var(--paper);
    border-top: 1px solid var(--line-soft);
  }

  .dt-add,
  .dt-back {
    font-size: 14px;
    padding: 12px 16px;
  }
}

/* --------------------------------------------------------------------------
   The error pages, which are the only documents of this service that are not
   the front page.
   -------------------------------------------------------------------------- */
.error-page {
  max-width: 1180px;
  margin: 0 auto;
  padding: clamp(48px, 10vw, 96px) clamp(16px, 4vw, 32px);
  display: grid;
  gap: 20px;
  justify-items: start;
}

.page-title {
  font-size: clamp(26px, 5vw, 34px);
  letter-spacing: -0.03em;
  font-weight: 700;
}

.error-text {
  font-size: 16px;
  line-height: 1.55;
  color: var(--muted);
  max-width: 52ch;
}

.button {
  font-size: 16px;
  font-weight: 600;
  padding: 14px 24px;
  background: var(--teal);
  color: var(--paper);
  border-radius: 10px;
}

.button:hover {
  background: var(--teal-dark);
  color: var(--paper);
  text-decoration: none;
}

/* --------------------------------------------------------------------------
   The cabinet (/{locale}/account)

   Selected by CLASSES throughout, and never by the ids the client resolves
   these nodes through (lookUpAccount, app.js). A selector written `#acc-…`
   reads as the colour `#acc` to the guard that keeps every colour of this file
   inside the two palettes (assets_test) — the same reason the header's way
   into the cabinet is styled as .hdr-account. Everything below paints out of
   the custom properties alone, so the second theme repaints the page with no
   rule of its own.

   The SIGNED-OUT half is a card: .aside-card and NOT the .find-card it used to
   be. .find-card is the shell of the picker and carries no padding on purpose:
   its children (.find-top, .find-res) pad themselves, and the rounded corners
   belong to the result because the shell must not clip the country dropdown.
   Prose put straight inside one therefore has no inset at all — which is what
   the cabinet had: headings over the top border, text on both edges, buttons
   hanging off the bottom. .aside-card is the portal's plain card, padding
   included, with its h2 and its p already styled.

   The SIGNED-IN half is not cards at all any more (batch 26, Variant 3): one
   identity strip, one row of tabs, and one panel of the three on the screen at
   a time. Three cards down the page were ~2200px of scroll on a phone for
   content that fits a screenful — and stacking cards is what made that height,
   not the content. What replaces them carries no frame of its own: the strip
   is the only bordered surface, and each panel is a list on the page's own
   paper.
   -------------------------------------------------------------------------- */

/* One column of 720px inside the 1180px every other page uses, and that is the
   shape of what this page holds rather than a taste: an address, a list of
   devices and a reminder of the cart are all short, and across the full width
   each becomes a band of whitespace with one sentence in the corner. Two
   classes so the width outranks .wrap by specificity instead of by order. */
.wrap.acc-sec {
  max-width: 720px;
}

/* The heading of the catalogue page, because this is the same kind of page:
   a title over a stack, not a hero. */
.acc-sec h1 {
  font-size: clamp(26px, 5vw, 34px);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-weight: 700;
  margin: 0 0 20px;
}

/* Tightened here and only here: these cards carry one line of prose each over
   the region they introduce, while the 20px of .aside-card is spacing under a
   paragraph of the front page. */
.acc-sec .aside-card p {
  margin-bottom: 12px;
}

/* The label of a field, on the line above it.

   .wl-form is a flex ROW — the waiting list of the front page is one input and
   one button — so a <label> inside it becomes a third item beside the input
   rather than a name over it. Full width plus the row's own flex-wrap puts it
   back on its own line. Nothing outside the cabinet is touched: the front
   page's form names its field with aria-label and has no .lbl in it. */
.wl-form .lbl {
  width: 100%;
  margin-bottom: 2px;
}

/* Signing in is two steps and one card. The code cannot be typed before the
   address has been sent, so the second step is ruled off from the first
   instead of being given a card of its own that would stand empty. */
.acc-code-box {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--line-soft);
}

.acc-list .ci-title {
  font-size: 15px;
}

/* The last row closes on the card's own padding: a separator under it would be
   a line with nothing beneath it. :last-of-type and not :last-child, because
   the cart list ends with the link back to the picker (renderAccountCart,
   app.js) and the last ROW is the div before it. */
.acc-list .cart-item:last-of-type {
  border-bottom: 0;
  padding-bottom: 4px;
}

.acc-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 16px;
}

/* The secondary button of this portal, spelled as the drawer's own close: 44px
   of touch target, an outline and no fill. Ending other devices and signing
   out are not what a visitor came here to do, and a filled button would read
   as if they were. */
.acc-btn {
  min-height: 44px;
  font-size: 14px;
  font-weight: 500;
  padding: 10px 14px;
  border: 1px solid var(--line);
  background: var(--paper);
  border-radius: 9px;
  cursor: pointer;
  color: var(--muted);
}

.acc-btn:hover {
  border-color: var(--ink);
  color: var(--ink);
}

/* The step-up dialog is one field wide; the 720px of .dt-panel is the width of
   the plan modal's table. */
.acc-stepup .dt-panel {
  max-width: 440px;
}

.acc-stepup h2 {
  font-size: clamp(20px, 3vw, 24px);
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 700;
  margin: 0 0 8px;
}

/* Direct children of the body only: the client retypes a refused code through
   a p.wl-error INSIDE the form (stepUpRetype, app.js), and a bare `.acc-stepup
   p` would out-rank .wl-error and repaint the one alarming sentence of this
   dialog in the colour of its prose. */
.acc-stepup .dt-body > p {
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted);
  margin: 0 0 16px;
}

.acc-stepup .acc-btn {
  margin-top: 12px;
}

/* --------------------------------------------------------------------------
   The identity strip: whose session this is, and the two things that act on it.

   It is the ONE bordered surface of the signed-in half, and that is deliberate:
   what stands under it — the tabs and their panel — is a list on the page's own
   paper, so a second card would frame a frame.
   -------------------------------------------------------------------------- */
.acc-idbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  background: var(--card);
  border-radius: 14px;
  margin-bottom: 14px;
}

/* The initial of the address, drawn by the client into an empty circle. --paper
   and not white: the letter sits on --teal in both palettes, and the page's own
   background colour is what reads as "not ink" under either. */
.acc-avatar {
  flex: none;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--teal);
  color: var(--paper);
  font-size: 14px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* min-width: 0 is what lets the address ellipsis instead of pushing the two
   buttons off a narrow screen: a flex item's default minimum is its content. */
.acc-idmain {
  flex: 1;
  min-width: 0;
}

.acc-idemail {
  font-size: 14.5px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.acc-idstatus {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  color: var(--dim);
  margin-top: 2px;
}

.acc-live-dot {
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--teal);
}

/* The strip's own buttons: smaller than .acc-btn because they sit inside a
   14px-padded row, and still 40px of touch target. */
.acc-idbtn {
  flex: none;
  min-height: 40px;
  font-size: 13px;
  font-weight: 500;
  padding: 8px 12px;
  border: 1px solid var(--line);
  background: var(--paper);
  border-radius: 8px;
  cursor: pointer;
  color: var(--muted);
}

.acc-idbtn:hover {
  border-color: var(--ink);
  color: var(--ink);
}

/* The storage disclosure is an icon and therefore a square; it follows
   .acc-idbtn in this file so its padding wins by order rather than by
   !important. */
.acc-info {
  width: 40px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
}

/* What the strip's icon opens: the same sentence the layout's strip carries,
   in the place a visitor asked for it. It is in the normal flow and not a
   floating layer — the strip at the foot of the document is the fixed one, and
   two overlapping surfaces saying the same thing is what this replaces. */
.acc-storage-pop {
  margin: -4px 0 14px;
  padding: 12px 14px;
  background: var(--line-soft);
  border-radius: 10px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}

.acc-storage-accept {
  margin-top: 10px;
  min-height: 40px;
  font-size: 13px;
  font-weight: 600;
  padding: 8px 14px;
  border: 0;
  border-radius: 8px;
  background: var(--ink);
  color: var(--paper);
  cursor: pointer;
}

.acc-storage-accept:hover {
  background: var(--teal);
}

/* --------------------------------------------------------------------------
   The tabs.

   The selected one is marked by [aria-selected="true"] and by nothing else, so
   the colour and the underline follow the accessibility tree instead of
   doubling it with a class: a tab that looks selected while it announces itself
   as not is the failure this shape makes unreachable.
   -------------------------------------------------------------------------- */
.acc-tablist {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 16px;
}

/* --------------------------------------------------------------------------
   The two screens and the row that walks them (batch 58)

   The cabinet is "My eSIMs" and "Account", and the row that moves between them
   sits at the FOOT of the section rather than the top: it is the last thing on
   a phone's screen, where a thumb is, and the first row of tabs — the sections
   of the account — stays where a row of tabs belongs, at the head of what it
   divides. Two rows at the top would read as one row of six.

   The screens themselves carry no box of their own: each is a plain region the
   client reveals, and a frame around one would draw a card round the whole
   cabinet.
   -------------------------------------------------------------------------- */
.acc-screen:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* The row is sticky at the bottom of the viewport on a phone, for the reason it
   is at the foot at all: a visitor scrolling a list of SIMs must be able to
   cross to the account without scrolling back. It carries the page's own
   background so the list does not read through it, and a hairline above it so
   the boundary is visible on a long list.

   position: sticky and not fixed: fixed would take it out of the flow and lay
   it over the footer of every page this section shares a document with. */
.acc-nav {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  margin-top: 24px;
  padding: 6px 0 max(6px, env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  background: var(--paper);
}

.acc-navbtn {
  min-height: 48px;
  padding: 10px 12px;
  border: 0;
  border-radius: 10px;
  background: none;
  font-size: 15px;
  font-weight: 600;
  color: var(--dim);
  cursor: pointer;
}

/* The selected one is marked by [aria-selected="true"] and by nothing else, for
   the tabs' own reason: a button that looks selected while it announces itself
   as not is the failure this shape makes unreachable. */
.acc-navbtn[aria-selected='true'] {
  color: var(--ink);
  background: var(--line-soft);
}

.acc-navbtn:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

.acc-tab {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 9px 14px;
  border: 0;
  background: none;
  font-size: 14px;
  font-weight: 600;
  color: var(--dim);
  cursor: pointer;
}

.acc-tab[aria-selected='true'] {
  color: var(--ink);
}

.acc-tab[aria-selected='true']::after {
  content: '';
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: -1px;
  height: 2px;
  background: var(--teal);
}

/* The counters ship EMPTY (the cart is the browser's, the devices are a relay's
   answer), and an empty pill is a grey blob that says nothing — so it has no
   box until there is a figure in it. */
.acc-tab-badge {
  font-family: var(--mono);
  font-size: 11px;
  color: var(--dim);
  background: var(--line-soft);
  padding: 1px 6px;
  border-radius: 999px;
}

.acc-tab-badge:empty {
  display: none;
}

.acc-tab-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--amber);
}

/* A tabpanel is focusable (tabindex="0") so the keyboard reaches its content
   after the tabs. The ring is the page's own focus ring rather than the browser
   default, and it is :focus-visible, so a panel revealed by a mouse press does
   not draw one. */
.acc-panel:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* The heading of a SECTION inside a panel, which since batch 51.1 exists on two
   of them: the Profile panel holds the address and the devices, and the Help
   panel holds the checks and the gap. Everywhere else a panel is one thing and
   carries no heading at all.

   It is deliberately smaller than the page's own h1 and not far above the prose
   under it: this is a divider inside a section, not a second title for the
   page. */
.acc-subhead {
  font-size: 15px;
  font-weight: 700;
  margin: 22px 0 8px;
}

/* The first heading of a panel has nothing above it to be spaced from. */
.acc-panel > .acc-subhead:first-child {
  margin-top: 0;
}

/* The numbered checks of the Help panel. The markers come back exactly the way
   they come back inside a guide card (.aside-card ol above): the reset at the
   top of this file takes them off every list on the site, because a list here is
   normally a layout, and an ordered list rendered without its numbers is a
   sequence of paragraphs with the order — the whole point of it — thrown away. */
.acc-steps {
  margin: 0 0 4px;
  padding-left: 22px;
  list-style: decimal;
  font-size: 15px;
  line-height: 1.5;
  color: var(--muted);
}

.acc-steps li {
  margin-bottom: 8px;
}

/* The one line of prose a panel carries over the region it introduces. */
.acc-note {
  font-size: 14px;
  line-height: 1.5;
  color: var(--muted);
  margin: 0 0 12px;
}

/* A named value on one line: the label on the left, the value the client fills
   on the right. */
.acc-kv {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-soft);
}

.acc-k {
  font-size: 13px;
  color: var(--muted);
}

.acc-v {
  font-size: 15px;
  min-width: 0;
  overflow-wrap: anywhere;
  text-align: right;
}

/* The rows of a panel are tighter than the drawer's: a drawer holds a handful
   of plans on a screen of its own, a panel holds a list under a row of tabs. */
.acc-list .cart-item {
  padding: 14px 0;
  border-bottom: 1px solid var(--line-soft);
}

/* The cart's total, in the drawer's own row (.cart-total-row / .cart-total)
   with the panel's spacing over it. The figure is smaller than the drawer's
   30px for the same reason the rows are tighter. */
.acc-total {
  margin: 6px 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.acc-total .cart-total {
  font-size: 24px;
}

/* --------------------------------------------------------------------------
   The limits: how long a code lives, how many tries are left, when another one
   may be asked for, and the refusal when too many have been.

   Every block here ships hidden and holds its words already translated; the
   figures are the client's (batch 26.3) and the nodes it writes into are empty.
   -------------------------------------------------------------------------- */
.acc-ttl {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 12px 0;
  font-size: 12.5px;
  color: var(--muted);
}

.acc-ttl-bar {
  flex: 1;
  height: 4px;
  border-radius: 2px;
  background: var(--line-soft);
  overflow: hidden;
}

/* The width is a percentage the client sets on every tick; the transition is
   what makes one step a second read as draining rather than as jumping. */
.acc-ttl-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--teal);
  transition: width 1s linear;
}

.acc-ttl-time {
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  white-space: nowrap;
}

.acc-attempts {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 10px 0;
  font-size: 12.5px;
  color: var(--muted);
}

.acc-dots {
  display: flex;
  gap: 5px;
}

/* One pip per attempt the code was issued with; .acc-dot-on is the class the
   client leaves on the ones still available. */
.acc-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--line);
}

.acc-dot-on {
  background: var(--teal);
}

.acc-attempts-count {
  font-variant-numeric: tabular-nums;
}

.acc-resend {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 12px;
}

/* Asking for another code is a link's worth of emphasis, not a button's: the
   button of this step is the one that signs in. Disabled is a real state here —
   it stands for the whole of the cooldown — so it is spelled out rather than
   left to the browser's default grey. */
.acc-linkbtn {
  min-height: 40px;
  padding: 8px 0;
  border: 0;
  background: none;
  font-size: 14px;
  font-weight: 600;
  color: var(--teal);
  cursor: pointer;
}

.acc-linkbtn:hover:not(:disabled) {
  color: var(--teal-dark);
  text-decoration: underline;
}

.acc-linkbtn:disabled {
  color: var(--dim);
  cursor: not-allowed;
  text-decoration: none;
}

.acc-cooldown {
  font-size: 12.5px;
  color: var(--dim);
}

.acc-cooldown-time {
  font-variant-numeric: tabular-nums;
}

/* The refusal. Amber is this portal's warning colour in both palettes, and it
   is carried by the icon and the title alone: a whole panel painted in it would
   read as a failure of the page rather than as a limit that expires. */
.acc-alert {
  display: flex;
  gap: 10px;
  margin-top: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  background: var(--line-soft);
  border-radius: 12px;
}

.acc-alert-icon {
  flex: none;
  color: var(--amber);
  margin-top: 1px;
}

.acc-alert-title {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--amber);
}

.acc-alert-text {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--muted);
  margin-top: 2px;
}

/* tabular-nums so a countdown does not shuffle the words beside it once a
   second. */
.acc-alert-time {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The phone. Four tabs have to fit 390px in both locales — Russian is the longer
   of the two on every one of these words — so the padding and the type come down
   before anything wraps. One of the four carries two marks of its own since batch
   51.1 (the devices' counter and the dot of a running limit, which moved onto the
   profile tab with the section they belong to), which is the tab this rule has to
   hold. */
@media (max-width: 480px) {
  .acc-tab {
    padding: 9px 9px;
    font-size: 13px;
    gap: 5px;
  }

  .acc-tab[aria-selected='true']::after {
    left: 4px;
    right: 4px;
  }

  .acc-idbar {
    gap: 10px;
    padding: 12px;
  }

  .acc-idbtn {
    font-size: 12.5px;
    padding: 8px 10px;
  }
}

/* --------------------------------------------------------------------------
   The purchases (batch C5.5)

   The orders tab is a list of CARDS and not the flat row list the other panels
   are, because one order is several things at once — what it cost, what was in
   it, and the eSIM profiles it produced — and those cannot be read as three
   unrelated rows in one column.
   -------------------------------------------------------------------------- */

/* One order. The frame is the only one in this panel: the rows inside it are
   the same .cart-item every other list of this page draws, and a border on each
   of them under a border round the card would be two frames saying one thing. */
.acc-order {
  padding: 14px;
  margin-bottom: 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
}

.acc-order:last-child {
  margin-bottom: 0;
}

/* The COLLAPSED row of a purchase, and it is a real <button> (batch 51.1): what
   the card opens on is a disclosure, so it is operated by the control the
   browser already gives a keyboard, a screen reader and a finger. The element's
   own chrome is taken off — a button paints a background, a border and centred
   13px text by default — and what is left is the row every list of this site
   states a price in: what was bought on the left, what it cost on the right.

   `text-align: left` is not decoration: without it the two lines of the left
   column centre themselves inside a full-width button. */
.acc-order-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  width: 100%;
  padding: 0 0 10px;
  border: 0;
  border-bottom: 1px solid var(--line-soft);
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

/* A collapsed card has nothing under its row, so the rule that separates the two
   would be a line under the whole card inside the card's own frame. */
.acc-order-head[aria-expanded='false'] {
  padding-bottom: 0;
  border-bottom: 0;
}

.acc-order-head:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

/* The three nodes of that row are SPANS — a button may hold phrasing content, and
   the same row anywhere else on this page is built out of divs — so the layout
   they would have had as blocks is given to them here. min-width lets the left
   column shrink instead of pushing the price out of the row, which a flex item
   with long text does by default. */
.acc-order-head > .acc-order-sum,
.acc-order-head > .ci-price,
.acc-order-sum > .acc-order-title,
.acc-order-sum > .ci-meta {
  display: block;
}

.acc-order-head > .acc-order-sum {
  min-width: 0;
}

/* What the purchase WAS — its destinations and the plans on it — which is what a
   list of past purchases is scanned by. */
.acc-order-title {
  font-size: 15px;
  font-weight: 600;
}

/* The mark of a disclosure: a caret drawn out of two borders, which is the one
   form of it that needs no glyph, no icon font and no text — a character here
   would be a text this file's own language rule forbids, and an inline SVG per
   card would be a picture repeated per purchase. It turns a quarter when the
   card is open. */
.acc-order-caret {
  flex: none;
  align-self: center;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--dim);
  border-bottom: 2px solid var(--dim);
  transform: rotate(45deg);
  transform-origin: 60% 60%;
}

.acc-order-head[aria-expanded='true'] .acc-order-caret {
  transform: rotate(225deg);
}

/* Everything the row does not say, revealed by it: the state, the lines, the
   profiles and — last — the number the purchase is named by. */
.acc-order-body {
  padding-top: 10px;
}

/* The two long values an open card states: the order's public identifier, which
   since batch 51.1 stands INSIDE the card and not on its collapsed row, and the
   ICCID of each profile it produced, which since batch 51.3 is all a purchase
   says about a profile. Neither tells a reader which purchase this is — that is
   what the collapsed row is for — and both are what a person names a thing by
   when they write to support. Monospaced like every other value read out loud,
   and wrapping rather than pushing their own labels off a phone. */
.acc-order-id,
.acc-iccid {
  font-size: 13px;
  overflow-wrap: anywhere;
}

/* The number's own row is the panel's label-and-value pair with no rule under
   it: it is the last line of the card, and a border there would be a second
   frame inside the card's own. */
.acc-order-number {
  border-bottom: 0;
}

/* Where a line was SOLD FOR, at the head of its left column: the flag of a
   place and the word for it, read as one run of text. It needs a box of its own
   because .flag is display:block and sized for a flex or a table cell (the
   coverage table draws it in one), and the left column of a row is a plain
   block — a flag dropped into it would take a line to itself above the name it
   belongs to.

   Built on the catalogue's own row (.dir-item): flex, centred on the text, with
   a gap instead of spaces. It WRAPS because one line can freeze a dozen codes
   onto itself, and a single unbroken row of them would push the price off a
   phone. The top margin .ci-meta carries is dropped: this node leads the column
   and there is nothing above it to be spaced from. */
.ci-places {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin-top: 0;
}

/* One destination: its flag and its name, which never wrap away from each
   other. The image keeps the 22x16 of .flag — one flag size across the site,
   never a third one — and flex: none only stops the row from SHRINKING it,
   which a flex item is otherwise free to do: a dozen destinations on one line
   would squeeze every flag on it into a sliver. */
.ci-place {
  display: flex;
  align-items: center;
  gap: 6px;
}

.ci-place .flag {
  flex: none;
}

/* WHAT IS LEFT on a profile: one scale per assignment the vendor still lists,
   under the row's own facts and above the material (assets/app.js, usageBar).

   The track and its filled part are the code countdown's own bar
   (.acc-ttl-bar/.acc-ttl-fill) laid out as a block instead of a flex cell:
   same 4px, same radius, same two colours, because a scale that measured
   something else on the same page would read as a different kind of thing. The
   WIDTH is the client's and is the only thing it writes here.

   No transition: this bar is drawn once with the list and never ticks, and a
   width animating from zero on every render would read as a figure moving. */
.acc-usage {
  margin-top: 10px;
}

.acc-usage-bar {
  height: 4px;
  border-radius: 2px;
  background: var(--line-soft);
  overflow: hidden;
}

.acc-usage-fill {
  display: block;
  height: 100%;
  background: var(--teal);
}

/* The figures under the track sit against it rather than a line below. */
.acc-usage .ci-meta {
  margin-top: 4px;
}

/* The material of one disclosure, boxed so it reads as something that was
   shown rather than as one more fact of the row. It ships hidden and is filled
   by the request the visitor made (assets/app.js, drawMaterial). */
.acc-material {
  margin-top: 10px;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--line-soft);
}

/* The last row of the box closes on the box's own padding — the last field of
   the DATA form, or the second of the two install links. */
.acc-material .acc-kv:last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

/* The QR itself. WHITE and not the surface behind it, in both palettes and
   whatever the box around it is painted: a symbol read against a tinted
   background is a symbol a camera reads slowly or not at all. The size is
   capped so a code stays a code and does not become a wall on a desktop, and
   image-rendering keeps the modules square when the browser scales it. */
.acc-qr {
  display: block;
  width: 100%;
  max-width: 260px;
  height: auto;
  background: #ffffff;
  border-radius: 6px;
  shape-rendering: crispEdges;
}

/* The dark modules, as the one path the client builds. */
.acc-qr path {
  fill: #000000;
}

/* The material of a disclosure sits under the row of links it was asked from,
   against them rather than a line below. */
.acc-sim > .acc-material {
  margin-top: 8px;
}

/* --------------------------------------------------------------------------
   The eSIMs themselves (batch 51.3)

   The Orders tab is two blocks now: the SIMs a visitor owns, and under them the
   purchases that produced them. A returning customer comes with "how much is
   left", and until this batch the figure was behind a purchase that had to be
   opened and scrolled past — so the SIMs are what the tab now opens on.
   -------------------------------------------------------------------------- */

/* One eSIM. It carries the same frame as a purchase (.acc-order) because the
   two are the same kind of object in a list — a thing of the visitor's own with
   several facts inside it — and a card of a different weight would read as a
   different kind of thing. */
.acc-sim {
  padding: 14px;
  margin-bottom: 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
}

/* The head: what the SIM is on the left, the corner mark on the right. They are
   baseline-aligned rather than centred, because the left half is one or two
   lines tall and the mark is one: centring it against a two-line name puts it
   opposite nothing. */
.acc-sim-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.acc-sim-name {
  min-width: 0;
}

/* What the SIM IS, out of the line that bought it: the destination with its
   flag, then the plan's own title. Both are absent where the profile could not
   be matched to a line — the card is then the word for a profile and the ICCID
   under it, which is the whole point of drawing them separately rather than as
   one heading. */
.acc-sim-name > .ci-title {
  font-size: 16px;
  font-weight: 600;
  margin-top: 4px;
}

/* The ICCID of an unmatched profile, under that word: it is NOT the heading —
   the word above it is — so it steps back to the size of a value that is read
   out rather than scanned. */
.acc-sim-name > .ci-meta {
  font-size: 13px;
  color: var(--muted);
}

/* The mark in the corner (batch 58). It is a word and not a colour: what it says
   is either this portal's own sentence about a SIM nobody has used, or the
   VENDOR's own state printed verbatim, and neither can be guessed from a dot.

   The grey is the default because most of the vendor's words are neither good
   nor bad news; the one that is — his "active" — takes the page's own accent.
   Nothing here is red: a SIM whose state a customer should worry about is
   explained in a sentence further down the card, not by a colour. */
.acc-pill {
  flex: none;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 3px 8px;
  border-radius: 999px;
  color: var(--muted);
  background: var(--line-soft);
}

.acc-pill-on {
  color: var(--teal);
}

/* WHAT IS LEFT, on ONE line: the figure a returning customer opens the cabinet
   for, and the term of the same package beside it. The term used to be a second
   scale of its own, and folding it into this row is most of the third of the
   height this card lost (batch 58).

   They are the two ends of a row and wrap on a narrow phone rather than
   shrinking: a figure cut in half is a figure nobody can read. */
.acc-metrics {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-top: 12px;
}

.acc-left {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

.acc-term {
  font-size: 12.5px;
  color: var(--dim);
}

/* The main action of a SIM that has never been used: the width of the card,
   because it is the one thing that visitor came to do. It is the portal's filled
   button and not .acc-btn's outline — ending a session is not what somebody came
   for, and installing an eSIM they have just bought is.

   It is built as .acc-btn and re-painted here (assets/app.js adds this class
   beside it) rather than being a second kind of button: what it does and how it
   toggles is the same code as the other disclosures. */
.acc-install {
  display: block;
  width: 100%;
  margin-top: 14px;
  border-color: var(--teal);
  background: var(--teal);
  color: var(--paper);
  font-weight: 600;
}

.acc-install:hover:not(:disabled) {
  border-color: var(--teal-dark);
  background: var(--teal-dark);
  color: var(--paper);
}

/* The row of links at the foot of a card: the details disclosure, the QR, and —
   for a SIM that has been used — the install links under their own word.

   ALL THREE ARE ONE CONTROL IN ONE CHROME — the portal's outlined .acc-btn (the
   owner's decision of 2026-08-21) — and until this rule two of them were not:
   the details disclosure was a bare text button while the QR and the reinstall
   link were boxes, so one row of one weight of offer was drawn in two kinds of
   thing. assets/app.js builds every one of them the same way now (revealButton
   takes the class of the control it makes).

   IT IS ONE LINE ON A PHONE, and that is what the sizes below are for. A framed
   button costs its border and its padding on top of the label, so the three
   Russian words plus their chrome need more room than a 360px screen leaves
   inside the card (298px) at any comfortable type size. The type therefore
   TRACKS THE VIEWPORT instead of stepping at a breakpoint: calc() is the width
   the row actually has, divided between the three labels, and the clamp holds it
   between the smallest size worth setting and the 13px of a roomy screen.
   Narrower than that (a 320px phone) the labels ellipsise rather than fold — a
   second row of controls under a card that already carries a full-width filled
   button is the thing this row exists not to be. */
.acc-sim-links {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 5px;
  margin-top: 8px;
}

/* Smaller than the .acc-btn of the panels below, where the same class is a
   button standing alone under a list. Here it is three of them in one row under
   two other kinds of button, and the row is the quietest thing on the card:
   36px of height, the padding of a chip and none of the 14px type. */
.acc-sim-links .acc-btn {
  min-width: 0;
  min-height: 36px;
  padding: 6px 5px;
  border-radius: 8px;
  font-size: clamp(10.5px, calc(4.2vw - 4.6px), 13px);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Everything the card folded away, revealed by the first link of that row. The
   left rule is what says it belongs to the card above it rather than being the
   next thing on the screen. */
.acc-details {
  margin-top: 8px;
  padding-left: 10px;
  border-left: 2px solid var(--line);
}

.acc-details > .ci-meta + .ci-meta {
  margin-top: 2px;
}

/* "Show the data" is the LAST item of that block and the only control in it, so
   it stands off the facts above it. */
.acc-details > .acc-btn {
  margin-top: 10px;
}

/* The FINISHED SIMs, behind one disclosure. It repeats the purchase row's own
   chrome instead of sharing its classes: the two lists live in one panel, and a
   shared class would make every selector over one of them reach into the other
   (assets/app.js says the same from its side). */
.acc-sims {
  margin-bottom: 12px;
}

.acc-sims-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--muted);
  text-align: left;
  cursor: pointer;
}

.acc-sims-head:focus-visible {
  outline: 2px solid var(--teal);
  outline-offset: 2px;
}

.acc-sims-title {
  font-size: 13px;
  font-weight: 700;
}

/* The caret is the purchase card's, drawn out of two borders: no glyph, no icon
   font, no text — a character here would be a text app.js is forbidden to
   write. */
.acc-sims-caret {
  flex: none;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--dim);
  border-bottom: 2px solid var(--dim);
  transform: rotate(45deg);
  transform-origin: 60% 60%;
}

.acc-sims-head[aria-expanded='true'] .acc-sims-caret {
  transform: rotate(225deg);
}

.acc-sims-body {
  padding-top: 4px;
}
