/* ==========================================================================
   mcprush — views.css  (page-level compositions)
   ========================================================================== */

/* =============== shared: server card ====================================== */
.scard {
  display: flex; flex-direction: column; gap: 12px; padding: 16px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r3);
  box-shadow: var(--sh1); transition: border-color .16s, box-shadow .16s, transform .16s;
  position: relative; min-width: 0;
}
.scard:hover { border-color: var(--line-2); box-shadow: var(--sh2); transform: translateY(-2px); }
.scard-top { display: flex; gap: 12px; align-items: flex-start; }
/* AND THE LEVEL BETWEEN THEM. With the products at h3 the page went h1 → h3:
   no ranking cost, but a level that skips is a level a screen reader announces
   as missing. The line above the grid already said what the section holds —
   "21 servers · 12 from verified publishers, 6 with a free tier", counted over
   that exact result set — so it is that line rather than a heading invented
   for the outline or hidden from the reader. It keeps its own type: the class
   carries the size, and the element carries the meaning. */
/* :where() SO THAT `.small` STILL WINS. Written as a plain class this rule
   sat later in the cascade than core.css's `.small` at equal specificity and
   took the line from 13.5px to 15px — measured on the live page, and a heading
   is meant to change the document rather than the design. At zero specificity
   it still beats the browser's own h2 defaults, which is the whole job, and
   loses to every class the element already carries. */
:where(.cat-count-h) { font-size: inherit; }
/* TWO RULES, BECAUSE THEY ARE LOSING TO DIFFERENT THINGS. The size has to lose
   to `.small` (0,1,0) and so is written at zero specificity above. The weight
   and the tracking have to BEAT core.css's `h1, h2, h3, h4` (0,0,1) — which
   turned this line 600 and tightened it the moment it stopped being a span —
   so they are a plain class. Measured either way: 13.5px / 400, exactly what
   the line was before it became a heading. */
.cat-count-h { font-weight: inherit; letter-spacing: inherit; line-height: inherit; margin: 0; }

/* ИМЯ КАРТОЧКИ — ДВЕ СТРОКИ, ДАЛЬШЕ МНОГОТОЧИЕ.
   Имя приходит от автора, и оно бывает не именем, а объявлением: у одного
   импортированного скилла заголовок в четыре строки капсом занял столько же
   места, сколько вся остальная карточка, и ряд поехал. Ограничение стоит
   здесь, а не в разметке: карточку рисуют три разных места (каталог, полка
   на главной, профиль автора), и правило должно быть одно.
   `display: flex` заменён на `-webkit-box` ради самого обрезания; галочка
   проверенного автора рядом с именем остаётся строчным элементом. */
.scard-name { font-size: 15px; font-weight: 600; letter-spacing: -.02em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere; }
.scard-name > * { vertical-align: middle; }
.scard-pub { font-size: 12.5px; color: var(--ink-3); display: flex; align-items: center; gap: 4px; margin-top: 1px; }
.scard-desc { font-size: 13.5px; line-height: 1.5; color: var(--ink-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.scard-meta { display: flex; align-items: center; gap: 12px; font-size: 12px; color: var(--ink-3); font-family: var(--mono); flex-wrap: wrap; }
.scard-meta b { color: var(--ink-2); font-weight: 500; }
.scard-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-top: 12px; border-top: 1px solid var(--line); margin-top: auto; }
.scard-price { font: 500 12.5px/1 var(--mono); color: var(--ink); }
.scard-price.free { color: var(--ink-2); }
.scard-cb { position: absolute; top: 12px; right: 12px; opacity: 0; transition: .12s; }
.scard:hover .scard-cb, .scard-cb:has(input:checked) { opacity: 1; }
.scard.picked { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }

/* =============== HOME ===================================================== */
.hero { padding: 56px 0 34px; position: relative; }
.hero-grid { display: grid; grid-template-columns: 1.04fr .96fr; gap: 56px; align-items: start; }
.hero h1 { margin: 18px 0 18px; }
.hero h1 em { font-style: normal; color: var(--accent); }
.hero .lead { max-width: 52ch; }
.hero-search { margin-top: 26px; }
.big-field {
  display: flex; align-items: center; gap: 12px; height: 54px; padding: 0 8px 0 16px;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: 10px;
  box-shadow: var(--sh1); transition: border-color .14s, box-shadow .14s;
}
.big-field:focus-within { border-color: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
.big-field > [data-icon] { color: var(--ink-3); flex: none; }
.big-field > [data-icon] svg { width: 19px; height: 19px; }
/* height:100% — the box is a <form>, not a <label>, so a tap on the padding
   above or below the text does not reach the input unless the input is there */
.big-field input { width: 100%; height: 100%; font-size: 15.5px; }
.suggests { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; align-items: center; }
.suggests .sg {
  font-size: 12.5px; padding: 5px 9px; border: 1px solid var(--line); border-radius: var(--r1);
  background: var(--surface); color: var(--ink-2); transition: .12s;
}
.suggests .sg:hover { border-color: var(--ink-3); color: var(--ink); }

/* hero side: install demo */
.demo { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r3); box-shadow: var(--sh2); overflow: hidden; }
.demo-head { display: flex; align-items: center; justify-content: space-between; padding: 11px 14px; border-bottom: 1px solid var(--line); }
.demo-clients { display: flex; gap: 2px; padding: 8px 10px; border-bottom: 1px solid var(--line); overflow-x: auto; scrollbar-width: none; background: var(--surface-2); }
.demo-clients::-webkit-scrollbar { display: none; }
.demo-clients button {
  padding: 5px 9px; border-radius: 5px; font-size: 12.5px; color: var(--ink-2); white-space: nowrap; font-weight: 500;
}
.demo-clients button:hover { color: var(--ink); }
.demo-clients button.on { background: var(--surface); color: var(--ink); box-shadow: var(--sh1); border: 1px solid var(--line); padding: 4px 8px; }
.demo-body { padding: 14px; }
.demo-rows { padding: 0 14px 14px; display: grid; gap: 8px; }
.demo-row { display: flex; align-items: center; justify-content: space-between; font-size: 12.5px; padding: 7px 0; border-top: 1px dashed var(--line); }
.demo-row:first-child { border-top: 0; }
.demo-row span { color: var(--ink-3); font-family: var(--mono); font-size: 11.5px; text-transform: uppercase; letter-spacing: .1em; }

/* trust stat bar */
.statbar { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface); overflow: hidden; }
.statbar > div { padding: 18px 20px; border-left: 1px solid var(--line); }
.statbar > div:first-child { border-left: 0; }
.statbar .v { font: 600 26px/1 var(--mono); letter-spacing: -.04em; }
.statbar .k { font-size: 12.5px; color: var(--ink-3); margin-top: 7px; }
.statbar .d { font-size: 11.5px; color: var(--accent); font-family: var(--mono); margin-top: 4px; }

/* categories */
.catgrid { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; }
.catcard { display: flex; align-items: center; gap: 12px; padding: 14px; border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface); transition: .14s; }
.catcard:hover { border-color: var(--ink-3); transform: translateY(-2px); box-shadow: var(--sh1); }
.catcard .ic { width: 32px; height: 32px; border-radius: 8px; display: grid; place-items: center; background: var(--surface-2); border: 1px solid var(--line); color: var(--ink); flex: none; }
.catcard .ic svg { width: 16px; height: 16px; }
/* the name and the count are two lines, not a run-on: they were both inline
   spans, so "Code & Repos" and "412 servers" collided with no space between
   them at every width */
.catcard > span { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.catcard .n { display: block; font-size: 14px; font-weight: 550; letter-spacing: -.01em; overflow-wrap: anywhere; }
.catcard .c { display: block; font: 400 11.5px/1 var(--mono); color: var(--ink-3); }

/* dark panel (earn / security) */
.panel { background: var(--panel); color: var(--panel-ink); border-radius: 18px; padding: 48px; position: relative; overflow: hidden; }
.panel::before {
  content: ''; position: absolute; inset: 0; pointer-events: none; opacity: .5;
  background-image: linear-gradient(var(--panel-line) 1px, transparent 1px), linear-gradient(90deg, var(--panel-line) 1px, transparent 1px);
  background-size: 56px 56px; mask-image: radial-gradient(120% 90% at 85% 0%, #000 0%, transparent 70%);
}
.panel > * { position: relative; }
.panel .spec { color: var(--panel-ink2); }
.panel .muted { color: var(--panel-ink2); }
.panel-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 44px; align-items: center; }
.paylist { display: grid; gap: 1px; background: var(--panel-line); border: 1px solid var(--panel-line); border-radius: var(--r2); overflow: hidden; }
.payrow { display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px; background: var(--panel); }
.payrow .k { font: 500 12px/1 var(--mono); text-transform: uppercase; letter-spacing: .1em; color: var(--panel-accent); min-width: 96px; padding-top: 2px; }
.payrow .t { font-size: 13.5px; color: var(--panel-ink); }
.payrow .t i { display: block; font-style: normal; color: var(--panel-ink2); font-size: 12.5px; margin-top: 3px; }

/* earnings calculator */
.calc { background: var(--panel-2); border: 1px solid var(--panel-line); border-radius: var(--r3); padding: 22px; }
.calc-row { margin-bottom: 18px; }
.calc-row .lbl { display: flex; justify-content: space-between; align-items: baseline; font-size: 13px; color: var(--panel-ink2); margin-bottom: 9px; }
.calc-row .lbl b { color: var(--panel-ink); font-family: var(--mono); font-size: 14px; font-weight: 500; }
/* The panel is dark in every scheme, so these three were written as literals
   back when there was one accent to be literal about — which is why the
   calculator kept the emerald after the scheme went lime. --panel-accent is
   the token for exactly this: the accent as it appears INSIDE the dark panel,
   declared by every scheme. */
.calc input[type="range"]::-webkit-slider-runnable-track { background: var(--panel-line); }
.calc input[type="range"]::-webkit-slider-thumb { background: var(--panel); border-color: var(--panel-accent); }
.calc-out { border-top: 1px solid var(--panel-line); padding-top: 16px; margin-top: 4px; }
.calc-line { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; font-size: 13.5px; color: var(--panel-ink2); }
.calc-line b { font-family: var(--mono); color: var(--panel-ink); font-weight: 500; }
.calc-total { display: flex; justify-content: space-between; align-items: baseline; padding-top: 12px; margin-top: 8px; border-top: 1px dashed var(--panel-line); }
.calc-total .v { font: 600 30px/1 var(--mono); letter-spacing: -.04em; color: var(--panel-accent); }

/* feature rows (security) */
.frow { display: flex; gap: 14px; padding: 18px 0; border-top: 1px solid var(--line); }
.frow:first-child { border-top: 0; }
.frow .ic { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; background: var(--accent-soft); border: 1px solid var(--accent-line); color: var(--accent); flex: none; }
.frow .ic svg { width: 15px; height: 15px; }
/* THE OUTLINE HAD TO SKIP A LEVEL TO GET HERE. These rows sit under a section
   headed <h2>, so their own headings are the third level and were written as
   the fourth — a jump a screen reader announces as a missing section. The tag
   changed and the look did not: h4's own size is 1em, which is what this now
   states for both, so the two are the same box. */
.frow h4, .frow h3 { margin-bottom: 5px; font-size: 1em; letter-spacing: -.012em; }
.frow p { font-size: 13.5px; color: var(--ink-2); }

/* stacks */
.stack-card { display: flex; flex-direction: column; gap: 14px; padding: 18px; border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface); transition: .16s; }
.stack-card:hover { border-color: var(--line-2); box-shadow: var(--sh2); }
.stack-items { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r2); overflow: hidden; }
.stack-item { display: flex; align-items: center; gap: 9px; padding: 9px 11px; background: var(--surface); font-size: 13px; }
.stack-item .tile { width: 22px; height: 22px; font-size: 10px; border-radius: 6px; }
.stack-item .p { margin-left: auto; font: 400 11.5px/1 var(--mono); color: var(--ink-3); }

/* clients strip */
.clients { display: flex; flex-wrap: wrap; gap: 8px; }
.client-chip {
  display: flex; align-items: center; gap: 8px; padding: 8px 12px; border: 1px solid var(--line);
  border-radius: var(--r1); background: var(--surface); font-size: 13.5px; font-weight: 500;
}
.client-chip .tile { width: 20px; height: 20px; font-size: 10px; border-radius: 5px; }

/* quotes */
.quote { padding: 20px; border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface); }
.quote p { font-size: 14.5px; line-height: 1.6; letter-spacing: -.01em; }
.quote .who { display: flex; align-items: center; gap: 10px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.quote .av { width: 28px; height: 28px; border-radius: 50%; background: var(--surface-3); display: grid; place-items: center; font: 600 11px/1 var(--mono); color: var(--ink-2); flex: none; }

/* faq */
.faq { border-top: 1px solid var(--line); }
.faq-item { border-bottom: 1px solid var(--line); }
.faq-q { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 17px 0; text-align: left; font-size: 15.5px; font-weight: 550; letter-spacing: -.015em; }
.faq-q svg { width: 16px; height: 16px; color: var(--ink-3); transition: transform .18s; flex: none; }
.faq-item.on .faq-q svg { transform: rotate(45deg); }
.faq-a { display: none; padding: 0 40px 18px 0; font-size: 14px; color: var(--ink-2); line-height: 1.6; max-width: 88ch; }
.faq-item.on .faq-a { display: block; }

/* final cta */
.cta { text-align: center; padding: 64px 24px; border: 1px solid var(--line); border-radius: 18px; background: var(--surface); }

/* =============== CATALOG ================================================== */
.cat-layout { display: grid; grid-template-columns: 244px minmax(0,1fr); gap: 28px; align-items: start; padding: 26px 0 60px; }
.filters { position: sticky; top: calc(var(--nav-h) + 16px); }
.filters-head { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.filters-head .btn-sm { height: 24px; padding: 0 6px; font-size: 12px; }
/* three controls that only exist once the rail is a sheet */
.filters-x, .filters-done, .filters-clear-btm, .cat-filter-btn { display: none; }
.fscroll { max-height: 214px; overflow: auto; padding-right: 4px; }
.fgroup { border-bottom: 1px solid var(--line); padding: 14px 0; }
.fgroup:first-child { padding-top: 0; }
.fgroup > h5, .fgroup > .fgroup-t { font: 500 11px/1 var(--mono); text-transform: uppercase; letter-spacing: .11em; color: var(--ink-3); margin-bottom: 10px; display: flex; justify-content: space-between; }
.fgroup .cnt { color: var(--ink-3); font-size: 11px; }
.check .cnt { margin-left: auto; font: 400 11px/1 var(--mono); color: var(--ink-3); }
/* the same number, one element further out: in the two groups whose values
   are also places the count is an <a> and lives beside the label rather than
   inside it, so that a click on it opens the page instead of ticking the box.
   It has to be indistinguishable from the other counts until it is hovered. */
.check-row > .cnt-go { font: 400 11px/1 var(--mono); color: var(--ink-3); flex: none; }
/* the toolbar is sticky under the header, so an anchor has to clear both */
/* the toolbar is sticky under the header on a desk, so an anchor has to clear
   both. Below 620px it becomes display:contents and .cat-tools takes over as
   the sticky bar at a different height — reserving the desktop figure there
   dropped every anchor 20px lower than it needed to be. */
@media (min-width: 621px) {
  :root:has(.cat-toolbar) { --scroll-pad: calc(var(--nav-h) + 58px + 16px); }
}
@media (max-width: 620px) {
  /* measured: the tools bar is 61px on one line at 375 and 414 */
  :root:has(.cat-tools) { --scroll-pad: calc(var(--nav-h) + 71px); }
}
.cat-toolbar {
  position: sticky; top: var(--nav-h); z-index: 20; display: flex; align-items: center; gap: 10px;
  padding: 12px 0; background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: blur(8px); border-bottom: 1px solid var(--line); margin-bottom: 16px; flex-wrap: wrap;
}
/* flex:none on the desktop rail so the search field, not the controls, is what
   gives way as the window narrows; the .grow spacer inside it collapses to
   nothing until the phone layout stretches the row to the full width */
.cat-tools { display: flex; align-items: center; gap: 10px; flex: none; min-width: 0; }
.cat-filter-n {
  display: inline-grid; place-items: center; min-width: 19px; height: 19px; padding: 0 5px;
  border-radius: 5px; background: var(--accent); color: #fff;
  font: 600 11px/1 var(--mono);
}
[data-theme="dark"] .cat-filter-n { color: #06180F; }
.chips { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
.chip {
  display: inline-flex; align-items: center; gap: 6px; height: 26px; padding: 0 6px 0 9px;
  border: 1px solid var(--accent-line); background: var(--accent-soft); color: var(--accent);
  border-radius: var(--r1); font-size: 12.5px; font-weight: 500;
}
.chip button { display: grid; place-items: center; width: 16px; height: 16px; border-radius: 3px; color: inherit; opacity: .7; }
.chip button:hover { opacity: 1; background: rgba(0,0,0,.06); }
.results-grid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 14px; }
.results-tbl { border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface); overflow: hidden; }
.results-tbl .nm { display: flex; align-items: center; gap: 10px; }
.results-tbl .tile { width: 28px; height: 28px; font-size: 12px; border-radius: 7px; }
/* the ticked row, as a class rather than an inline colour — see catalog.js */
.results-tbl .tbl tbody tr.picked > td { background: var(--accent-soft); }

/* Shared utility: a table too wide for a phone scrolls inside this box and
   pins its first column, so the row never loses the name that identifies it.
   Put it on the div that wraps a .tbl and give the table a min-width. */
.tbl-scroll { overflow-x: auto; }
.empty { text-align: center; padding: 72px 20px; border: 1px dashed var(--line-2); border-radius: var(--r3); }
.tray {
  position: fixed; left: 50%; bottom: 20px; transform: translate(-50%, 130%); z-index: 90;
  display: flex; align-items: center; gap: 14px; padding: 10px 12px 10px 16px;
  background: var(--panel); color: var(--panel-ink); border-radius: 11px; box-shadow: var(--sh3);
  transition: transform .26s cubic-bezier(.2,.8,.3,1), opacity .2s; max-width: calc(100% - 32px);
  opacity: 0; visibility: hidden;
}
.tray.on { transform: translate(-50%, 0); opacity: 1; visibility: visible; }
.tray .items { display: flex; gap: 6px; }
.tray .ti { display: flex; align-items: center; gap: 6px; padding: 5px 8px; background: var(--panel-2); border: 1px solid var(--panel-line); border-radius: 6px; font-size: 12.5px; }
.tray .ti button { color: var(--panel-ink2); display: grid; place-items: center; }
.tray .ti button:hover { color: var(--panel-ink); }
/* The backdrop centres what it holds, in both directions.

   It used to be `display: block` with 6vh of padding, which put every dialog
   6vh from the top and whatever was left underneath — a short confirm sat high
   with a third of the screen empty below it, and the taller a dialog got the
   less it looked deliberate. Flex plus `margin: auto` on the child is the
   version that centres AND still scrolls: `align-items: center` clips the top
   of anything taller than the viewport, auto margins collapse to zero instead.

   The dim is .58 rather than .4 because a modal is a stop, not a hint — at .4
   the page behind kept competing for the eye, especially on the light schemes
   where the canvas is near-white. */
.cmp-back { position: fixed; inset: 0; z-index: 210; background: rgba(12,13,16,.58); display: none; padding: 6vh 16px; overflow-y: auto; }
.cmp-back.on { display: flex; }
.cmp { max-width: 900px; margin: auto; background: var(--surface); border-radius: var(--r3); border: 1px solid var(--line-2); box-shadow: var(--sh3); overflow: hidden; }

/* =============== SERVER PAGE ============================================== */
.crumbs { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--ink-3); padding: 16px 0; }
.crumbs a:hover { color: var(--ink); }
.crumbs svg { width: 13px; height: 13px; }
.srv-hero { display: flex; gap: 18px; align-items: flex-start; padding-bottom: 22px; }
.srv-hero h1 { font-size: 30px; letter-spacing: -.032em; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.srv-layout { display: grid; grid-template-columns: minmax(0,1fr) 336px; gap: 32px; align-items: start; padding-bottom: 64px; }
.aside { position: sticky; top: calc(var(--nav-h) + 16px); display: grid; gap: 14px; }
.kv { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0; border-top: 1px dashed var(--line); font-size: 13px; }
.kv:first-child { border-top: 0; }
.kv .k { color: var(--ink-3); flex: 0 0 auto; }
/* ДЛИННОЕ ЗНАЧЕНИЕ ЛОМАЕТСЯ, А НЕ РАСПИРАЕТ КАРТОЧКУ. Отпечаток поверхности на
   вкладке Security — шестьдесят четыре знака в моноширинном без единого пробела,
   и строка выезжала за правый край карточки на любой ширине уже колонки. Правило
   про перенос стояло только в мобильной ветке core.css; здесь оно общее: ключ
   держит свою ширину, значение переносится по буквам. */
.kv .v { font-family: var(--mono); font-size: 12.5px; text-align: right; min-width: 0; overflow-wrap: anywhere; }
.metrics { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); border: 1px solid var(--line); border-radius: var(--r2); overflow: hidden; background: var(--surface); }
.metrics > div { padding: 13px 14px; border-left: 1px solid var(--line); }
.metrics > div:first-child { border-left: 0; }
.metrics .v { font: 600 19px/1 var(--mono); letter-spacing: -.03em; }
.metrics .k { font: 400 11px/1.35 var(--mono); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-3); margin-top: 7px; }
/* trust.html adds a third line to each cell — a sentence under the label. It
   is prose, not a figure, so it takes the body face at the size the small print
   uses everywhere else. */
.metrics .d { font-size: 12.5px; line-height: 1.5; color: var(--ink-2); margin-top: 6px; }
/* And the three lines line up ACROSS the row. Without this, a label that wraps
   to two lines in one cell pushes its sentence a line below the sentence beside
   it, which on a phone — where the grid is 2×2 and the labels are longest — is
   the difference between a table and four loose paragraphs. */
@supports (grid-template-rows: subgrid) {
  .metrics { grid-template-rows: auto auto auto; }
  .metrics > div { display: grid; grid-template-rows: subgrid; grid-row: span 3; align-content: start; }
}
.tool { border-bottom: 1px solid var(--line); }
.tool-h { width: 100%; display: flex; align-items: center; gap: 12px; padding: 13px 4px; text-align: left; }
.tool-h .nm { font: 500 13.5px/1 var(--mono); letter-spacing: -.01em; }
.tool-h .ds { font-size: 13px; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
.tool-h .ch { width: 15px; height: 15px; color: var(--ink-3); transition: transform .18s; flex: none; }
.tool.on .tool-h .ch { transform: rotate(180deg); }
.tool-b { display: none; padding: 4px 4px 20px; }
.tool.on .tool-b { display: block; }
.schema { border: 1px solid var(--line); border-radius: var(--r2); overflow: hidden; }
.schema th { background: var(--surface-2); }
.plan { border: 1px solid var(--line); border-radius: var(--r3); padding: 18px; background: var(--surface); display: flex; flex-direction: column; gap: 14px; }
.plan.best { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
/* the unit wraps as one thing or not at all: "/ 1,000 successful calls"
   breaking after "successful" left the word "calls" alone under the figure */
.plan .price { font: 600 30px/1 var(--mono); letter-spacing: -.04em;
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 3px 7px; }
/* letter-spacing is not part of the `font` shorthand, so this inherited the
   −.04em set for the 30px figure above it — at 13px that is half a pixel a
   character and the words ran together. Reset, not re-tightened. */
.plan .price small { font: 400 13px/1 var(--sans); letter-spacing: normal; color: var(--ink-3); }
.plan ul { display: grid; gap: 9px; font-size: 13.5px; color: var(--ink-2); }
.plan li { display: flex; gap: 8px; align-items: flex-start; }
.plan li svg { width: 14px; height: 14px; color: var(--accent); flex: none; margin-top: 3px; }
.scopes { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r2); overflow: hidden; }
.scope { display: flex; gap: 12px; align-items: center; padding: 12px 14px; background: var(--surface); font-size: 13.5px; }
.scope .ic { width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center; flex: none; border: 1px solid var(--line); background: var(--surface-2); }
.scope .ic svg { width: 14px; height: 14px; }
.scope.read .ic { color: var(--ink-2); }
.scope.write .ic { color: var(--warn); background: var(--warn-soft); border-color: var(--warn-line); }
.scope.net .ic { color: var(--info); background: var(--info-soft); }
.ver { display: grid; grid-template-columns: 120px minmax(0,1fr); gap: 20px; padding: 18px 0; border-top: 1px solid var(--line); }
.ver:first-child { border-top: 0; }
.ver .vt { font: 500 13px/1 var(--mono); }
.ver .vd { font-size: 11.5px; color: var(--ink-3); font-family: var(--mono); margin-top: 6px; }
.ver ul { display: grid; gap: 6px; font-size: 13.5px; color: var(--ink-2); margin-top: 8px; }
.ver li { display: flex; gap: 8px; }
.ver li::before { content: '—'; color: var(--ink-3); }
.rev { padding: 18px 0; border-top: 1px solid var(--line); }
.rev-head { display: flex; align-items: center; gap: 10px; margin-bottom: 9px; flex-wrap: wrap; }
.rev-reply { margin: 12px 0 0 34px; padding: 12px 14px; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r2); font-size: 13.5px; color: var(--ink-2); }
.ratebar { display: grid; grid-template-columns: 14px 1fr 36px; align-items: center; gap: 9px; font-size: 12px; color: var(--ink-3); font-family: var(--mono); }

/* =============== LIBRARY (saved + lists) ================================== */
/* padding-block only — a `padding` shorthand here would wipe the horizontal
   gutter that .wrap-wide sets and push the page wider than the header */
.lib-layout { padding-top: 8px; padding-bottom: 60px; }
.list-card {
  display: flex; flex-direction: column; gap: 13px; padding: 18px;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--r3);
  box-shadow: var(--sh1); transition: border-color .16s, box-shadow .16s, transform .16s;
  min-width: 0;
}
.list-card:hover { border-color: var(--line-2); box-shadow: var(--sh2); transform: translateY(-2px); }
.list-card h3 { font-size: 16px; letter-spacing: -.02em; }
.list-card p { font-size: 13.5px; color: var(--ink-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.list-tiles { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.list-tiles .tile { width: 28px; height: 28px; font-size: 10.5px; border-radius: 8px; }
.list-tiles .more { margin-left: 3px; font: 400 12px/1 var(--mono); color: var(--ink-3); }
.list-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding-top: 13px; border-top: 1px solid var(--line); margin-top: auto; }
.new-list {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  min-height: 180px; padding: 18px; border: 1px dashed var(--line-2); border-radius: var(--r3);
  background: transparent; color: var(--ink-2); transition: .16s; text-align: center;
}
.new-list:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
.new-list .ic { width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center; border: 1px solid var(--line-2); background: var(--surface); }
.link-a[data-back]:hover svg { transform: translateX(-2px); }
.lib-title-input {
  font-size: 30px; font-weight: 600; letter-spacing: -.032em; width: 100%;
  border-bottom: 1px solid transparent; padding: 2px 0; border-radius: 0;
}
.lib-title-input:hover { border-bottom-color: var(--line); }
.lib-title-input:focus { outline: none; border-bottom-color: var(--accent); }
.lib-desc-input {
  font-size: 15px; line-height: 1.5; color: var(--ink-2); width: 100%; margin-top: 8px;
  border-bottom: 1px solid transparent; padding: 2px 0;
  font-family: inherit; resize: none; overflow: hidden; display: block;
}
.lib-desc-input:hover { border-bottom-color: var(--line); }
.lib-desc-input:focus { outline: none; border-bottom-color: var(--accent); color: var(--ink); }
/* the one destructive control on the page, kept at the foot and away from the
   buttons a reader presses by habit */
.lib-danger {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 34px; padding-top: 18px; border-top: 1px solid var(--line);
}
.lib-danger .btn { color: var(--risk); border-color: var(--risk-line); }
.lib-danger .btn:hover { background: var(--risk-soft); border-color: var(--risk); }
.pick-list { max-height: 52vh; overflow-y: auto; padding: 8px; }
.pick-row { display: flex; align-items: center; gap: 11px; padding: 9px 12px; border-radius: var(--r1); cursor: pointer; }
.pick-row:hover { background: var(--surface-2); }
.pick-row .tile { width: 28px; height: 28px; font-size: 11px; border-radius: 7px; }
.pick-row input {
  appearance: none; width: 15px; height: 15px; flex: none;
  border: 1px solid var(--line-2); border-radius: 4px; background: var(--surface); transition: .12s;
}
.pick-row input:checked {
  border-color: var(--accent);
  background: var(--accent) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%230C1116' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m3.5 8.5 3 3 6-6.5'/%3E%3C/svg%3E") center/11px no-repeat;
}

/* =============== PUBLISHER ================================================ */
.pub-layout { display: grid; grid-template-columns: 212px minmax(0,1fr); gap: 28px; align-items: start; padding: 22px 0 60px; }
/* sticky has to sit on the grid item, not on the nav inside it: with
   align-items:start the aside is exactly as tall as its content, so a sticky
   child has no room to travel and never appears to stick at all */
.pub-layout > aside { position: sticky; top: calc(var(--nav-h) + 16px); align-self: start; }
.rail { display: grid; gap: 2px; }
.rail a {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--r1);
  font-size: 13.5px; color: var(--ink-2); transition: .12s;
}
.rail a svg { width: 15px; height: 15px; color: var(--ink-3); }
.rail a:hover { background: var(--surface-2); color: var(--ink); }
.rail a.on { background: var(--surface); border: 1px solid var(--line); box-shadow: var(--sh1); color: var(--ink); font-weight: 550; padding: 7px 9px; }
.rail a.on svg { color: var(--accent); }
.rail .badge { margin-left: auto; }
.rail-sec { font: 500 10.5px/1 var(--mono); text-transform: uppercase; letter-spacing: .12em; color: var(--ink-3); padding: 16px 10px 7px; }
.kpis { display: grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 12px; }
.kpi { padding: 15px 16px; border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface); }
.kpi .k { font: 400 11px/1 var(--mono); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-3); }
.kpi .v { font: 600 25px/1 var(--mono); letter-spacing: -.04em; margin: 11px 0 8px; }
.kpi .d { font-size: 12px; display: flex; align-items: center; gap: 5px; color: var(--ink-3); }
.kpi .d b { font-weight: 500; font-family: var(--mono); }
/* On a phone a KPI is 166px wide. At .1em the mono label wraps to two lines on
   anything longer than "Calls · 30 days", which is most of them, and the cards
   in a row end up different heights for no reason a reader can see. And .d as a
   flex row splits a sentence into two wrapping columns — "46 can write" beside
   "149 reach the network" reads as two facts stacked badly rather than one
   line. Both are only a problem at this width. */
@media (max-width: 620px) {
  .kpi { padding: 13px 14px; }
  .kpi .k { font-size: 10.5px; letter-spacing: .05em; }
  .kpi .v { font-size: 22px; margin: 9px 0 7px; }
  .kpi .d { display: block; font-size: 11.5px; line-height: 1.45; }
  .kpi .d > * + * { margin-left: 4px; }
}
.kpi .d.up b { color: var(--accent); }
.kpi .d.down b { color: var(--risk); }
.chart-wrap { position: relative; }
.chart-tip {
  position: absolute; pointer-events: none; opacity: 0; transition: opacity .12s;
  background: var(--panel); color: var(--panel-ink); border-radius: 7px; padding: 8px 10px;
  font-size: 12px; box-shadow: var(--sh2); white-space: nowrap; z-index: 5;
}
.chart-tip b { font-family: var(--mono); }
.bars { display: flex; align-items: flex-end; gap: 7px; height: 196px; }
.bars .b { flex: 1; height: 100%; display: flex; flex-direction: column; justify-content: flex-end; cursor: pointer; }
.bars .b span { display: block; background: var(--accent); transition: opacity .15s; }
.bars .b .s1 { border-radius: 0 0 3px 3px; }
.bars .b .s2 { opacity: .3; border-radius: 3px 3px 0 0; }
/* the third pricing model, between the two: solid at the base is what recurs,
   faint on top is what varies, and a one-time licence is neither */
.bars .b .s3 { opacity: .58; }
.bars .b:hover span { opacity: .78; }
.bars .b:hover .s2 { opacity: .45; }
.bars .b:hover .s3 { opacity: .7; }
.bars-x { display: flex; gap: 7px; margin-top: 9px; }
.bars-x span { flex: 1; text-align: center; font: 400 10.5px/1 var(--mono); color: var(--ink-3); }
.legend { display: flex; gap: 16px; align-items: center; font-size: 12.5px; color: var(--ink-2); }
.legend i { width: 10px; height: 10px; border-radius: 2px; background: var(--accent); display: inline-block; margin-right: 6px; vertical-align: -1px; }
.legend .l2 i { opacity: .3; }
.legend .l3 i { opacity: .58; }
.funnel { display: grid; gap: 10px; }
/* the rows need air between them: five 26px bars stacked with no gap read as
   one striped block rather than as five measurements */
.fstep { display: grid; grid-template-columns: 150px minmax(0,1fr) 90px; gap: 14px; align-items: center; font-size: 13.5px; }
.fstep + .fstep { margin-top: 8px; }
.fstep .fb { height: 26px; border-radius: 5px; background: var(--accent-soft); border: 1px solid var(--accent-line); position: relative; overflow: hidden; }
.fstep .fb i { position: absolute; inset: 0 auto 0 0; background: var(--accent); opacity: .16; }
.fstep .fv { font-family: var(--mono); font-size: 13px; text-align: right; }
.alert { display: flex; gap: 11px; padding: 13px 0; border-top: 1px solid var(--line); font-size: 13.5px; }
.alert:first-child { border-top: 0; }
.alert .ic { width: 26px; height: 26px; border-radius: 7px; display: grid; place-items: center; flex: none; }
.alert .ic svg { width: 14px; height: 14px; }
.alert.warn .ic { background: var(--warn-soft); border: 1px solid var(--warn-line); color: var(--warn); }
.alert.risk .ic { background: var(--risk-soft); border: 1px solid var(--risk-line); color: var(--risk); }
.alert.ok .ic { background: var(--accent-soft); border: 1px solid var(--accent-line); color: var(--accent); }
.alert .t { color: var(--ink); }
.alert .s { color: var(--ink-3); font-size: 12.5px; margin-top: 2px; }
.split { display: grid; grid-template-columns: 1.5fr 1fr; gap: 16px; align-items: start; }
.payout-row { display: flex; justify-content: space-between; padding: 9px 0; border-top: 1px dashed var(--line); font-size: 13.5px; }
.payout-row:first-child { border-top: 0; }
.payout-row b { font-family: var(--mono); font-weight: 500; }
.payout-total { display: flex; justify-content: space-between; align-items: baseline; margin-top: 12px; padding-top: 14px; border-top: 1px solid var(--line-2); }
.payout-total .v { font: 600 27px/1 var(--mono); letter-spacing: -.04em; color: var(--accent); }
.rollout { display: flex; align-items: center; gap: 12px; }
.rollout .track { flex: 1; height: 6px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.rollout .track i { display: block; height: 100%; background: var(--accent); }
.wizard { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--r2); overflow: hidden; }
/* min-width:0 — a grid item defaults to min-width:auto, so the track cannot
   shrink below the .code block's pre-formatted min-content line and the step
   overflowed a clipped .wizard. With it, .code scrolls inside its own box. */
/* THE BADGE SITS BESIDE THE TITLE AND INDENTS NOTHING ELSE.
   As a flex row the 22px badge plus its gap pushed every card, field and
   button 36px to the right of the column they belong to — on the pricing step
   that is three plan cards losing a tenth of their width to a number. The grid
   keeps the badge in its own column on the first row and lets the body span
   both, so the step's content starts where the panel does. Same rule the
   narrow layout in studio.css has always used; it is simply not a phone-only
   problem. */
.wstep {
  display: grid; grid-template-columns: 22px minmax(0, 1fr);
  column-gap: 14px; row-gap: 0; align-items: start;
  padding: 20px; background: var(--surface); min-width: 0;
}
/* the body starts a little below the two title lines rather than against them */
.wstep.on > .grow > *:not(.t):not(.s) { margin-top: 4px; }
.wstep > .grow { display: contents; }
.wstep .t, .wstep .s { grid-column: 2; }
/* everything after the two title lines is the body, and it gets the full width */
.wstep > .grow > *:not(.t):not(.s) { grid-column: 1 / -1; }
/* a step that is not open is one line, so the badge centres on the title */
.wstep:not(.on) { align-items: center; }
.wstep .n { width: 22px; height: 22px; border-radius: 6px; background: var(--ink); color: var(--paper); display: grid; place-items: center; font: 600 11px/1 var(--mono); flex: none; }
.wstep.done .n { background: var(--accent); }
.wstep .t { font-size: 14px; font-weight: 550; }
.wstep .s { font-size: 13px; color: var(--ink-2); margin-top: 3px; }

/* =============== responsive ============================================== */
@media (max-width: 1180px) {
  .results-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .srv-layout { grid-template-columns: minmax(0,1fr) 300px; gap: 24px; }
}
@media (max-width: 1000px) {
  .hero-grid { grid-template-columns: 1fr; gap: 36px; }
  .panel-grid { grid-template-columns: 1fr; gap: 30px; }
  .catgrid { grid-template-columns: repeat(3, minmax(0,1fr)); }
  .cat-layout, .pub-layout { grid-template-columns: 1fr; }
  .filters, .rail, .aside { position: static; }
  .rail { grid-auto-flow: column; grid-auto-columns: max-content; overflow-x: auto; padding-bottom: 8px; }
  .rail-sec { display: none; }
  .srv-layout { grid-template-columns: 1fr; }
  .kpis { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .split { grid-template-columns: 1fr; }

  /* ---- the filter sheet -------------------------------------------------
     A 244px rail stacked above the results puts sixty controls between the
     reader and the first server — 1,250px of them, or a screen and a half of
     scrolling on a phone. So the rail leaves the flow entirely and becomes a
     sheet over the results, opened by one button in the toolbar that carries
     the number of facets currently set.

     Gated on body[data-filter-sheet] because .filters is shared with
     skills.html and stacks.html, which have no trigger or close button yet:
     without the attribute they keep the stacked rail and nothing changes. */
  body[data-filter-sheet] .cat-filter-btn { display: inline-flex; }
  body[data-filter-sheet] .filters-x,
  body[data-filter-sheet] .filters-done { display: inline-flex; }
  body[data-filter-sheet] .filters-clear-btm { display: inline-flex; margin: 18px 0 4px; }
  body[data-filter-sheet] .filters-clear-top { display: none; }
  /* It comes in from the left, the edge it lives on. On a desk the filters are
     a rail down the left-hand side; sliding the same panel in from that side
     says it is that rail arriving rather than a new screen, and the direction
     is the one the reader's own back-swipe undoes. It stops short of the right
     edge so the results stay visible behind it — the panel is a drawer over the
     list, not a replacement for it. */
  body[data-filter-sheet] .filters {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 205;
    width: min(88vw, 380px);
    display: flex; flex-direction: column;
    background: var(--surface); border-right: 1px solid var(--line-2);
    box-shadow: var(--sh3);
    transform: translateX(-100%); visibility: hidden;
    /* visibility flips at 0s but only after the slide finishes on the way out,
       so the sheet is never keyboard-reachable while it is off screen and never
       invisible while it is sliding in */
    transition: transform .24s cubic-bezier(.2,.8,.3,1), visibility 0s linear .24s;
  }
  body[data-filter-sheet].filters-on .filters {
    transform: none; visibility: visible;
    transition: transform .24s cubic-bezier(.2,.8,.3,1), visibility 0s;
  }
  /* the page behind it dims and takes the tap that closes it — a drawer with
     nothing over the rest of the screen reads as a layout that broke */
  body[data-filter-sheet].filters-on::after {
    content: ''; position: fixed; inset: 0; z-index: 204;
    background: rgba(12,13,16,.5); backdrop-filter: blur(2px);
  }
  body[data-filter-sheet] .filters-head {
    flex: none; margin: 0; min-height: 60px; padding: 8px 16px;
    border-bottom: 1px solid var(--line);
    background: var(--surface);
  }
  /* the sheet's bar has room for full-size controls; the rail's 24px ones do not
     belong on a phone. "Show 39" is the one action the whole sheet exists to
     end on, so it is a full 44 rather than the 40 a secondary button takes. */
  body[data-filter-sheet] .filters-head .btn-sm { height: 44px; padding: 0 14px; font-size: 13.5px; }
  body[data-filter-sheet] .filters-body {
    flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain;
    padding: 4px 16px 24px;
  }
  /* a capped scroller inside a scrolling sheet is a trap — the reader flicks
     the category list and the sheet stays put */
  body[data-filter-sheet] .fscroll { max-height: none; overflow: visible; padding-right: 0; }
  @media (prefers-reduced-motion: reduce) {
    body[data-filter-sheet] .filters,
    body[data-filter-sheet].filters-on .filters { transition: none; }
  }
}

@media (max-width: 860px) {
  /* Ten columns will not fit a phone, and the same rows are already available
     as cards one control away — so rather than invent a third representation
     the table keeps its shape, scrolls inside its own box, and pins the two
     columns that say WHICH server a row is: the compare tick and the name.
     Price, latency, installs and rating slide underneath them. */
  .results-tbl { overflow-x: auto; overflow-y: hidden; }
  .results-tbl .tbl th:nth-child(1), .results-tbl .tbl td:nth-child(1),
  .results-tbl .tbl th:nth-child(2), .results-tbl .tbl td:nth-child(2) {
    position: sticky; z-index: 2; background: var(--surface);
  }
  .results-tbl .tbl th:nth-child(1), .results-tbl .tbl td:nth-child(1) {
    left: 0; width: 44px; min-width: 44px; padding-left: 12px; padding-right: 4px;
  }
  .results-tbl .tbl th:nth-child(2), .results-tbl .tbl td:nth-child(2) {
    /* a hairline, not a shadow: it is the only thing telling the reader that
       the columns to its right have slid underneath */
    left: 44px; box-shadow: 1px 0 0 var(--line-2);
  }
  .results-tbl .tbl tbody tr.picked > td:nth-child(-n+2) { background: var(--accent-soft); }
  .results-tbl .tbl-hover tbody tr:hover > td:nth-child(-n+2) { background: var(--surface-2); }

  /* the same trick for every other wide table on the site */
  .tbl-scroll > .tbl th:first-child, .tbl-scroll > .tbl td:first-child {
    position: sticky; left: 0; z-index: 2;
    background: var(--surface); box-shadow: 1px 0 0 var(--line-2);
  }
  .tbl-scroll > .tbl-hover tbody tr:hover > td:first-child { background: var(--surface-2); }
}

@media (max-width: 720px) {
  .statbar, .metrics { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .statbar > div:nth-child(3), .metrics > div:nth-child(3) { border-left: 0; }
  .statbar > div:nth-child(n+3), .metrics > div:nth-child(n+3) { border-top: 1px solid var(--line); }
  .catgrid { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .results-grid { grid-template-columns: 1fr; }
  .panel { padding: 28px 20px; }
  .fstep { grid-template-columns: 100px minmax(0,1fr) 70px; font-size: 12.5px; }
  .ver { grid-template-columns: 1fr; gap: 8px; }
  .srv-hero { flex-direction: column; }
}

/* ---------- the phone ----------------------------------------------------- */
@media (max-width: 620px) {
  /* the field goes on its own line and the four controls become the one thing
     that stays under the header while the results scroll: 60px of nav plus a
     64px bar, not 60 + 109 */
  body[data-filter-sheet] .cat-toolbar {
    /* display:contents removes the toolbar's own box, so the search field and
       the tools row become children of the results column — a sticky element
       needs a containing block taller than itself to have anywhere to travel */
    display: contents;
  }
  body[data-filter-sheet] .cat-q { margin-bottom: 10px; }
  body[data-filter-sheet] .cat-tools {
    position: sticky; top: var(--nav-h); z-index: 20;
    padding: 10px 0; margin-bottom: 12px; flex: 1 1 auto;
    background: color-mix(in srgb, var(--paper) 94%, transparent);
    backdrop-filter: saturate(1.4) blur(8px);
    border-bottom: 1px solid var(--line);
  }
  /* Three controls, and at 320px they do not fit on one line. Shrinking them
     all was the old answer and it broke two of them: the segmented view switch
     came out 18.7px wide — narrower than the icon inside it — and the sort
     field came out 74px, which reads "Sort:" and never says what the sort IS.
     So nothing in this row shrinks below its content now. The filter button
     and the view switch hold their size, the sort field holds a floor wide
     enough for its longest label, and when the three no longer fit the row
     wraps instead: two lines at 320 and 360, one line from 390 up. */
  body[data-filter-sheet] .cat-tools { flex-wrap: wrap; }
  /* the spacer is what was costing the second line. On a desk it pushes the
     view switch to the right edge; here the row is 343px wide and the three
     controls need 190 of it plus whatever the sort field takes — the 44px the
     spacer claimed was the difference between one line and two, and two lines
     of sticky bar is 111px of a 812px screen before a single result. */
  body[data-filter-sheet] .cat-tools .grow { display: none; }
  body[data-filter-sheet] .cat-tools .select { flex: 1 1 140px; min-width: 140px; }
  body[data-filter-sheet] .cat-filter-btn,
  body[data-filter-sheet] .cat-tools .seg { flex: none; }
  .seg-l { display: none; }
  .cat-layout { padding-top: 14px; padding-bottom: 40px; }

  /* the compare tray is the one fixed bar on this page; it has to clear the
     role switcher rather than share the corner with it */
  .tray { left: 16px; right: 16px; transform: translateY(150%); max-width: none; padding: 10px 12px; }
  .tray.on { transform: none; }
  .tray .btn-sm { height: 36px; }

  /* Modals become full-height sheets. Scoped with :has() so only a backdrop
     holding a .cmp panel changes — dashboard.html puts a .cs panel in the same
     backdrop and owns its own phone layout. */
  .cmp-back:has(> .cmp) { padding: 0; }
  .cmp-back:has(> .cmp) > .cmp {
    /* !important beats the inline max-width some pages set on the panel */
    max-width: none !important;
    /* an exact viewport height, not a minimum: with min-height the forty rows
       of the picker pushed its Done button 2,700px down the sheet */
    height: 100dvh; border-radius: 0; border: 0;
    display: flex; flex-direction: column;
  }
  /* whatever sits between the bar and the footer is the scrolling part */
  .cmp-back:has(> .cmp) > .cmp > .tbl-scroll,
  .cmp-back:has(> .cmp) .pick-list { flex: 1 1 0; min-height: 0; overflow: auto; }
  /* the head is the sheet's bar: title left, close right, always reachable */
  .cmp-back:has(> .cmp) > .cmp > .card-head {
    position: sticky; top: 0; z-index: 3; background: var(--surface);
    min-height: 60px; padding: 8px 16px;
  }
  /* the footer bar clears the role switcher, which is fixed over this corner */
  .cmp-back:has(> .cmp) > .cmp > .card-foot {
    margin-top: auto; padding-bottom: 60px; border-radius: 0;
  }
  .cmp-back:has(> .cmp) .pick-list { max-height: none; }

  /* HOME ------------------------------------------------------------------ */
  .hero { padding: 30px 0 24px; }
  .hero-grid { gap: 28px; }
  .hero h1 { margin: 14px 0 14px; }
  /* the button loses its label and keeps its icon; that returns ~70px to the
     input, which is the difference between reading the placeholder and not */
  .big-field { height: 52px; padding: 0 6px 0 14px; }
  .big-field > [data-icon] { display: none; }   /* the button carries the glass now */
  .big-field input { padding-left: 0; }
  .big-field .btn { width: 42px; padding: 0; flex: none; }
  /* the chips are sized in index.html's own phone block, which is the only
     page that has them and which loads after this file */
  .demo-clients { padding: 6px 8px; }
  /* 11px, not 10: at 10 the client tabs came out 39.4px tall, which is a miss
     by six tenths of a pixel and still a miss */
  .demo-clients button { padding: 11px 10px; }
  .demo-clients button.on { padding: 10px 9px; }

  /* The card's whole surface opens the listing. The name link was a 23px slice
     of it, which is the smallest target on the busiest page; everything else
     the card can do — publisher, category, save, list, install — is lifted
     above the stretched area so it still gets its own tap. */
  .scard-name::after { content: ''; position: absolute; inset: 0; }
  .scard-pub a, .scard-foot, .scard-meta a { position: relative; z-index: 1; }
  /* the compare tick is already absolute — giving it position:relative here
     would drop it back into the flow and add 44px to the top of every card.
     The box stays 20px; what grows is the label around it, from a 20px sliver
     to a 44px square centred on the same spot, so the tick is hit where it
     looks rather than only where it draws. */
  .scard-cb { z-index: 1; top: 2px; right: 2px; width: 44px; height: 44px;
    padding: 0; align-items: center; justify-content: center; }
  /* and the row underneath it stops running beneath the tick: a long name used
     to be drawn under a transparent checkbox, so its last word opened the
     comparison rather than the listing */
  .scard:has(.scard-cb) .scard-top { padding-right: 46px; }
  .statbar { grid-template-columns: 1fr 1fr; }
  .statbar > div { padding: 14px 15px; }
  .statbar .v { font-size: 22px; }
  /* ---- decks scroll sideways ----------------------------------------------
     A short, curated set of cards — four trending servers, three stacks, three
     quotes, the "related" strip at the foot of a listing — is the one layout a
     phone does better sideways. Stacked, four cards are four screens for one
     idea; as a rail the reader sees the first card whole and the second card's
     edge tells them there are more, and the section costs one card's height.

     The :not(:has(> :nth-child(5))) is what separates a deck from a result
     list: a catalogue renders dozens of the same card into a grid, and a rail
     there would hide the results behind a swipe. Result containers are excluded
     by id as well, so a filter that happens to return three servers still reads
     as a list of results. */
  section .grid:has(> .scard):not(:has(> :nth-child(5))):not(#results *),
  section .grid:has(> .kcard):not(:has(> :nth-child(5))):not(#results *),
  section .grid:has(> .stack-card):not(:has(> :nth-child(5))):not(#results *),
  section .grid:has(> .quote):not(:has(> :nth-child(5))):not(#results *) {
    display: flex; flex-wrap: nowrap; overflow-x: auto;
    gap: 10px; margin-inline: -16px; padding-inline: 16px; padding-bottom: 4px;
    scroll-snap-type: x proximity; scrollbar-width: none; -ms-overflow-style: none;
  }
  section .grid:has(> .scard)::-webkit-scrollbar,
  section .grid:has(> .kcard)::-webkit-scrollbar,
  section .grid:has(> .stack-card)::-webkit-scrollbar,
  section .grid:has(> .quote)::-webkit-scrollbar { display: none; }
  section .grid:has(> .scard):not(:has(> :nth-child(5))):not(#results *) > *,
  section .grid:has(> .kcard):not(:has(> :nth-child(5))):not(#results *) > *,
  section .grid:has(> .stack-card):not(:has(> :nth-child(5))):not(#results *) > *,
  section .grid:has(> .quote):not(:has(> :nth-child(5))):not(#results *) > * {
    flex: 0 0 min(80vw, 320px); scroll-snap-align: start;
  }

  /* Two columns of 169px, and a 14-letter category name in a 44px-indented
     cell broke mid-word ("Communicatio / n"). The tile comes in, the padding
     with it, and the name is allowed to wrap only between words — a category
     is a label, and a label that hyphenates itself reads as a rendering fault. */
  .catgrid { gap: 8px; }
  .catcard { padding: 10px 11px; gap: 9px; }
  .catcard .ic { width: 28px; height: 28px; border-radius: 7px; }
  .catcard .ic svg { width: 15px; height: 15px; }
  .catcard .n { font-size: 13.5px; overflow-wrap: normal; }
  .catcard .c { font-size: 11.5px; }
  .panel { padding: 18px 14px; border-radius: 14px; }
  /* the same rule the dashboard's KPI figures follow: a 26px mono number on a
     phone column is a headline competing with the headline */
  .statbar .v { font-size: 22px; }
  .statbar .k { font-size: 12px; margin-top: 5px; }
  /* The model and what it means stay on one line. Stacking them was the old
     phone rule and it made each of the five rows two lines plus padding — 640px
     of a panel whose point is "any model works, we run the billing". The label
     is mono and short, the sentence is what varies, so the label holds a fixed
     92px column and the sentence wraps under itself rather than under the
     label. */
  .payrow { align-items: baseline; gap: 10px; padding: 9px 12px; }
  .payrow .k { min-width: 0; flex: none; width: 92px; padding-top: 0; font-size: 11.5px; letter-spacing: .08em; }
  .payrow .t { font-size: 13px; min-width: 0; }
  /* the second line of each row explains a plan the publisher will configure in
     the studio; on a phone the model's name and its one-line meaning are the
     whole point of the list */
  .payrow .t i { display: none; }
  .calc { padding: 18px 16px; }
  .frow { padding: 15px 0; }
  .faq-q { padding: 15px 0; font-size: 15px; }
  .faq-a { padding-right: 0; }
  .cta { padding: 34px 18px; }
  .quote { padding: 17px; }
  .demo-body, .demo-rows { padding-inline: 12px; }
  .stack-row { padding: 14px 14px; }

  /* SERVER / SHARED -------------------------------------------------------- */
  .srv-hero h1 { font-size: 25px; }
  .tool-h { padding: 14px 4px; }
  .tool-h .ds { display: none; }   /* the summary re-appears inside the panel */
  .metrics > div { padding: 12px 13px; }
  .scope { padding: 13px 12px; }

  /* LIBRARY ---------------------------------------------------------------- */
  .lib-title-input { font-size: 25px; }
  .pick-row { padding: 11px 10px; min-height: 52px; }

  /* PUBLISHER -------------------------------------------------------------- */
  .pub-cta .btn { flex: 1 1 0; }
}

/* the compare tick on a card is revealed by :hover, which a touch screen never
   sends — without this the control simply does not exist on a phone */
@media (hover: none) {
  .scard-cb { opacity: 1; }
}

/* ==========================================================================
   publisher profile (publisher.html)
   Not to be confused with studio.html — that is the publisher's own
   workspace. This is the public page a reader lands on from a "by …" link.
   ========================================================================== */
.pub-head { border-bottom: 1px solid var(--line); padding: 10px 0 26px; margin-bottom: 4px; }
.pub-id { display: block; }
/* the mark sits on the name's line rather than in a column of its own — a
   leading avatar column would indent the bio and the meta row away from the
   left edge that the breadcrumb and the stat tiles share */
.pub-idrow { display: flex; align-items: center; gap: 14px; }
.pub-cta { display: flex; gap: 8px; flex: none; }
.pub-head .verified { margin-left: 8px; vertical-align: -1px; }

.pstats {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  border: 1px solid var(--line); border-radius: var(--r2);
  background: var(--surface); margin-top: 26px; overflow: hidden;
}
.pstat { padding: 15px 18px; border-left: 1px solid var(--line); }
.pstat:first-child { border-left: 0; }
.pstat-v { font-size: 25px; letter-spacing: -.03em; line-height: 1.1; }
.pstat-l { font-size: 13px; color: var(--ink-2); margin-top: 5px; }
.pstat-h { font-size: 12px; color: var(--ink-3); margin-top: 3px; }

.pub-facts {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0;
  border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface);
  overflow: hidden;
}
.pfact { padding: 18px; border-left: 1px solid var(--line); }
.pfact:first-child { border-left: 0; }
.pfact-v { font-size: 15.5px; font-weight: 600; letter-spacing: -.012em; margin: 10px 0 6px; }
.pub-link { color: inherit; text-decoration: none; }
.pub-link:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }

@media (max-width: 900px) {
  .pub-idrow { flex-wrap: wrap; }
  .pub-cta { width: 100%; }
  .pstats, .pub-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pstat:nth-child(3), .pfact:nth-child(3) { border-left: 0; }
  .pstat:nth-child(n+3), .pfact:nth-child(n+3) { border-top: 1px solid var(--line); }
}
@media (max-width: 560px) {
  /* The four stats are a value, a label and a hint — short enough to stay two
     across, which keeps the block at ~210px instead of the ~420px a single
     column costs. The three facts each carry a sentence, so they do stack. */
  .pub-facts { grid-template-columns: 1fr; }
  .pfact { border-left: 0; border-top: 1px solid var(--line); }
  .pfact:first-child { border-top: 0; }
  .pstat { padding: 13px 14px; }
  .pstat-v { font-size: 21px; }
  .pstat-l { font-size: 12.5px; }
}

/* =============== SKILLS ON THE OTHER SURFACES =============================
   The skills catalogue carries its own stylesheet. These are the three places
   a skill turns up inside a page that is not about skills: the home band, the
   server rail and the library. Nothing here is a second design language —
   the only thing that changes is the hue of the spec square. */
.spec-skill::before { background: var(--skill); }

/* home: what a skill is, in three facts. Hairlines rather than a boxed strip,
   because the statbar at the top of the page is already a boxed strip and two
   of them in one scroll reads as a template. */
.skillfacts {
  display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  margin-bottom: 22px;
}
.skillfacts > div { min-width: 0; padding: 16px 20px; border-left: 1px solid var(--line); }
.skillfacts > div:first-child { padding-left: 0; border-left: 0; }
.skillfacts p { font-size: 13.5px; line-height: 1.55; color: var(--ink-2); margin-top: 9px; }

/* home: a pack is a set of skills, so it borrows the stack card wholesale and
   only swaps what the right-hand column of each row means — context, not price */
.packcard .stack-item .p { color: var(--skill); }
.packcard .stack-item > span:not(.p):not(.tile) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* server page rail: the skills that call this server. Rows sit flush in the
   card the way the client tabs do, so the hairlines line up with card-head. */
.srv-skills { display: grid; gap: 1px; background: var(--line); }
.srv-skills > a { display: flex; align-items: center; gap: 10px; min-width: 0; padding: 10px 14px; background: var(--surface); }
.srv-skills > a:hover { background: var(--surface-2); }
.srv-skills .tile { width: 26px; height: 26px; border-radius: 7px; font-size: 11px; }
.srv-skills .t { min-width: 0; flex: 1 1 auto; }
.srv-skills .n {
  display: block; font-size: 13.5px; font-weight: 550; letter-spacing: -.012em; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.srv-skills .s {
  display: block; font: 400 11.5px/1.3 var(--mono); color: var(--ink-3); margin-top: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.srv-skills .p { flex: none; font: 400 11.5px/1 var(--mono); color: var(--ink-2); }

/* library: two kinds of saved thing, told apart by a heading rather than by a
   different card, since the cards already differ */
.lib-group + .lib-group { margin-top: 26px; }
.lib-group-h {
  display: flex; align-items: center; gap: 10px;
  padding-bottom: 10px; margin-bottom: 14px; border-bottom: 1px solid var(--line);
}
.lib-group-h .c { margin-left: auto; font: 400 11.5px/1 var(--mono); color: var(--ink-3); }

@media (max-width: 900px) {
  .skillfacts { grid-template-columns: 1fr; }
  .skillfacts > div { padding: 14px 0; border-left: 0; border-top: 1px solid var(--line); }
  .skillfacts > div:first-child { border-top: 0; }
}

/* =============== STUDIO: publish chooser, manage view, skill editor ======= */
.pick-card { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; text-align: left; }
.pick-card .tile { width: 32px; height: 32px; border-radius: 8px; }
.pick-card p { margin: 0; }
.pick-card:hover { border-color: var(--line-2); }
/* The wizard's pricing models are a radio group drawn as cards, so the chosen
   one has to look chosen: the fields underneath change with it, and without a
   marked card the reader cannot tell which model those fields belong to. */
.pick-card.on {
  border-color: var(--accent); background: var(--accent-soft);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.pick-card.on .h4 { color: var(--accent-ink, var(--ink)); }
.pick-card.on .muted { color: var(--ink-2); }

.mg-row { display: flex; gap: 16px; align-items: flex-start; padding: 15px 18px; border-top: 1px solid var(--line); }
.mg-row:first-child { border-top: 0; }
.mg-k { width: 150px; flex: none; font: 500 11px/1.4 var(--mono); text-transform: uppercase; letter-spacing: .1em; color: var(--ink-3); padding-top: 3px; }
.mg-v { font-size: 13.5px; }

.sk-new { display: grid; grid-template-columns: minmax(0,1fr) 320px; gap: 16px; align-items: start; }
/* both columns default to min-width:auto, so the textarea's intrinsic width
   pushes the grid wider than the page instead of scrolling inside its own box */
.sk-new > *, .sb > * { min-width: 0; }
/* an auto-sized grid track grows to its widest item's min-content, so a long
   mono trigger phrase in the preview card pushed the whole column past the
   page; the explicit minmax(0,1fr) makes the cards clip instead */
.sk-new-side { display: grid; grid-template-columns: minmax(0, 1fr); gap: 14px; position: sticky; top: calc(var(--nav-h) + 16px); }
.sk-new-side > * { min-width: 0; }
.sk-new-side .kcard-trig { overflow: hidden; text-overflow: ellipsis; }
.sk-new-md {
  width: 100%; max-width: 100%; resize: vertical; padding: 14px;
  border: 1px solid var(--line-2); border-radius: var(--r2);
  background: var(--surface-2); color: var(--ink);
  font: 400 12.5px/1.65 var(--mono); tab-size: 2;
}
.sk-new-md:focus { outline: 0; border-color: var(--skill); box-shadow: 0 0 0 3px var(--skill-soft); }
.sk-new-grid { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; }
/* a stated value where a control would be: same height and rhythm as the field
   beside it, so the row reads as one line rather than as a broken input */
.sk-new-fact {
  display: flex; align-items: center; min-height: 34px; padding: 0 10px;
  border: 1px dashed var(--line-2); border-radius: var(--r1);
  background: var(--surface-2); color: var(--ink-2);
  font-size: 12.5px; letter-spacing: -.01em;
  /* the label it replaces was long enough to push the grid column wider than
     the card; this one wraps instead of overflowing */
  min-width: 0; overflow-wrap: anywhere;
}
/* :not(.check) is load-bearing. This rule is for a field label — a word above
   the box it names — and a tick box is the one label where the box comes
   FIRST and the words sit beside it. Without the exclusion every .check inside
   the editor was drawn as a 15px square with its name underneath: two lines
   and 47px tall where core.css draws it as one line and 24px, and a column of
   them read as a list of headings rather than as a set of choices.

   The phone block in studio.css used to patch this back with a flex rule; the
   patch is gone now that the rule no longer breaks it. */
/* align-content, not align-items: each label is itself a grid, and the cell it
   sits in is as tall as the tallest label in the row. Left to stretch, a label
   with two children (Category: title + select) spread them over the height of a
   label with three (Name: title + field + the slug line), so the select sat a
   row lower than the input beside it. Packed to the top, the two fields line
   up and the extra line simply hangs below. */
.sk-new-grid label:not(.check), .sk-new label:not(.check) {
  display: grid; gap: 6px; align-content: start;
  font-size: 13px; color: var(--ink-2);
}
.sk-new .input {
  height: 34px; padding: 0 10px; width: 100%;
  border: 1px solid var(--line-2); border-radius: var(--r1);
  background: var(--surface); color: var(--ink); font-size: 13.5px;
}
.sk-new .input:focus { outline: 0; border-color: var(--skill); box-shadow: 0 0 0 3px var(--skill-soft); }
/* the tick list used a 2px row gap where every other stack on the step uses
   the 12–14px rhythm, so it read as one dense block glued to the label above */
.sk-new-needs { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 6px 20px; margin-top: 4px; }
.sk-new-meter { height: 8px; border-radius: 4px; background: var(--surface-3); overflow: hidden; margin-top: 12px; }
.sk-new-meter span { display: block; height: 100%; background: var(--skill); transition: width .18s; }
.sk-new-meter span.warn { background: var(--warn); }
.sk-new-meter span.heavy { background: var(--risk); }
.sk-new-check { display: flex; gap: 8px; align-items: flex-start; padding: 7px 0; font-size: 13px; color: var(--ink-2); border-top: 1px dashed var(--line); }
.sk-new-check:first-child { border-top: 0; }
.sk-new-check svg { width: 15px; height: 15px; flex: none; margin-top: 1px; color: var(--accent); }
.sk-new-check.bad { color: var(--risk); }
/* a field the wizard refused to move past: the ring is on the input itself, so
   it survives the .in-money wrapper that some of them sit inside */
.input.bad, .sk-new .input.bad {
  border-color: var(--risk);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--risk) 22%, transparent);
}
.input.bad::placeholder { color: var(--risk); opacity: .65; }
.in-money:has(> .input.bad) { border-color: transparent; }
.sk-new-check.bad svg { color: var(--risk); }
.sk-new-steps { display: grid; gap: 8px; margin-top: 12px; padding-left: 18px; font-size: 13px; color: var(--ink-2); list-style: decimal; }

/* ---- the visual editor ----------------------------------------------------
   The one field on the publishing form that is a piece of writing rather than
   a value. It is a box with a rule under a toolbar, not a card: it sits in the
   same column as the fields above it and has to read as one of them, so the
   border, the radius and the focus ring are the field's, not a panel's. */
.rte {
  border: 1px solid var(--line-2); border-radius: var(--r1);
  background: var(--surface); margin-top: 6px; overflow: hidden;
}
.rte:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.rte-bar {
  display: flex; gap: 2px; padding: 5px 6px;
  border-bottom: 1px solid var(--line); background: var(--surface-2);
}
.rte-b {
  width: 28px; height: 26px; display: grid; place-items: center;
  border: 0; border-radius: var(--r1); background: none; color: var(--ink-2);
  cursor: pointer; padding: 0;
}
.rte-b svg { width: 15px; height: 15px; }
.rte-b:hover { background: var(--surface-3); color: var(--ink); }
.rte-b:active { background: var(--accent-soft); color: var(--accent); }
.rte-b:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.rte-body {
  padding: 12px 12px 14px; min-height: 148px; max-height: 380px; overflow-y: auto;
  font-size: 13.5px; line-height: 1.62; color: var(--ink); outline: 0;
}
.rte-body > :first-child { margin-top: 0; }
.rte-body p { margin: 0 0 10px; }
.rte-body p:last-child { margin-bottom: 0; }
.rte-body ul, .rte-body ol { margin: 0 0 10px; padding-left: 20px; display: grid; gap: 5px; }
.rte-body ul { list-style: disc; }
.rte-body ol { list-style: decimal; }
.rte-body code {
  font: 500 12.5px/1.4 var(--mono); background: var(--surface-2);
  border: 1px solid var(--line); border-radius: 5px; padding: 1px 5px;
}
.rte-body a { color: var(--accent); text-decoration: underline; text-underline-offset: 2px; }
/* the placeholder: an editor left empty says what belongs in it, the way the
   fields above it do */
.rte-body:empty::before,
.rte-body:has(> p:only-child > br:only-child)::before {
  content: attr(data-ph); color: var(--ink-3); pointer-events: none; display: block;
}
.rte-body:has(> p:only-child > br:only-child) > p { display: none; }
@media (max-width: 720px) { .rte-body { min-height: 120px; } }

/* the same frame around a document rather than around prose: SKILL.md keeps
   its monospace field and its resize handle, and only borrows the toolbar */
.rte-doc { border-radius: var(--r2); background: var(--surface-2); }
.rte-doc:focus-within { border-color: var(--skill); box-shadow: 0 0 0 3px var(--skill-soft); }
.rte-doc > .rte-bar { background: var(--surface-3); }
.rte-doc > .sk-new-md {
  display: block; border: 0; border-radius: 0 0 var(--r2) var(--r2);
  background: none; min-height: 220px;
}
.rte-doc > .sk-new-md:focus { outline: 0; box-shadow: none; }

/* ---- the skill bundle: a folder, picked or dropped ------------------------
   A skill is SKILL.md plus whatever it reads on demand, so the panel has to
   show a tree rather than a single field. The row says the one thing a buyer
   is charged for: whether a file sits in the context window from the moment
   the skill loads, or is only opened when the procedure asks for it. */
.sk-drop {
  border: 1px dashed var(--line-2); border-radius: var(--r2); background: var(--surface-2);
  padding: 22px 18px; text-align: center; transition: .14s;
}
.sk-drop.over { border-color: var(--skill); background: var(--skill-soft); }
.sk-drop b { display: block; margin-top: 8px; font-size: 14px; }
.sk-drop p { margin: 6px auto 0; max-width: 58ch; }
.sk-drop-ic { display: inline-grid; place-items: center; width: 34px; height: 34px; border-radius: var(--r1);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink-2); }
.sk-drop-ic svg { width: 17px; height: 17px; }

.sk-tree { margin-top: 12px; border: 1px solid var(--line); border-radius: var(--r2); overflow: hidden; }
.sk-file {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px 8px 12px; border-top: 1px solid var(--line); background: var(--surface);
}
.sk-file:first-child { border-top: 0; }
.sk-file .p { flex: 1 1 auto; min-width: 0; font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sk-file .sz { flex: none; font-size: 11.5px; color: var(--ink-3); }
.sk-file .badge { flex: none; }
.sk-file.bad { background: var(--risk-soft); }
.sk-file .btn-icon { flex: none; width: 26px; height: 26px; }
.sk-file .btn-icon svg { width: 12px; height: 12px; }

/* ---- the screen a submission ends on -------------------------------------- */
/* The submission receipt. Centred rather than left-aligned: it is the only
   thing on the page once the wizard is gone, and a 74ch column pinned to the
   left of a 1,600px screen looks like a layout that lost half its content. */
/* The submission receipt. Left-aligned with everything else in the panel and
   measured rather than centred: centring a 74ch card inside a 1,100px column
   left 280px of nothing on either side, which reads as a page that lost half
   its content. 92ch keeps the prose readable without the gutters. */
.pub-done { max-width: 92ch; padding: 8px 0 4px; }
/* the wizard's slab of hairlines is for six steps; the receipt replaces it */
.wizard-done { display: block; background: none; border: 0; border-radius: 0; overflow: visible; }
/* kept for the skill flow's own success screen, which still uses it */
.pub-done-ic {
  display: inline-grid; place-items: center; width: 40px; height: 40px; margin-bottom: 14px;
  border-radius: var(--r2); background: var(--accent-soft); color: var(--accent);
  border: 1px solid var(--accent-line);
}
.pub-done-ic svg { width: 21px; height: 21px; }
/* A grid rather than a wrapping flex row. With flex, a second row of facts had
   no rule above it — six facts wrapped 4 + 2 and the last two floated under a
   gap, which is what made a submitted listing look broken at desktop widths.
   Here the gap IS the border, so cells are separated in both directions
   whatever the count. */
.pub-done-kv {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px; margin-top: 20px; background: var(--line);
  border: 1px solid var(--line); border-radius: var(--r2); overflow: hidden;
}
.pub-done-kv > div { min-width: 0; padding: 12px 14px; background: var(--surface); }
.pub-done-kv b { display: block; margin-top: 5px; font-size: 14px; }
.pub-done-steps { list-style: none; margin-top: 12px; display: grid; }
.pub-done-steps li { display: flex; gap: 14px; padding: 13px 0; border-top: 1px dashed var(--line); }
.pub-done-steps li:first-child { border-top: 0; }
.pub-done-steps .w { flex: none; width: 84px; font-size: 11.5px; color: var(--ink-3); padding-top: 2px; }
.pub-done-steps b { font-size: 13.5px; }
.pub-done-acts { gap: 8px; margin-top: 22px; }
@media (max-width: 620px) {
  .pub-done-kv { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
  .pub-done-steps li { flex-direction: column; gap: 4px; }
  .pub-done-steps .w { width: auto; }
  .pub-done-acts .btn { min-height: 38px; }
}

@media (max-width: 1080px) {
  .sk-new { grid-template-columns: 1fr; }
  .sk-new-side { position: static; }
  .mg-k { width: 110px; }
}
@media (max-width: 620px) {
  .sk-new-grid, .sk-new-needs { grid-template-columns: 1fr; }
  .mg-row { flex-wrap: wrap; }
}

/* ---------- studio: sandbox console --------------------------------------- */
.sb { display: grid; grid-template-columns: minmax(0,1fr) 320px; gap: 16px; align-items: start; }
.sb-log { max-height: 620px; overflow-y: auto; }
.sb-entry { padding: 14px 18px; border-top: 1px solid var(--line); }
.sb-entry:first-child { border-top: 0; }
.sb-entry.bad { background: var(--risk-soft); }
.sb-h { display: flex; align-items: center; gap: 9px; margin-bottom: 10px; font-size: 13px; }
.sb-two { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 10px; }

/* ---- the arguments a tool declares ---------------------------------------
   A tool's input schema is the contract a buyer's agent fills, so the console
   fills the same fields: name, type, whether it is required, and one line of
   what it is for. The JSON tab is the escape hatch, not the front door. */
.sb-arg {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 260px);
  gap: 12px 16px; align-items: start;
  padding: 12px 0; border-top: 1px dashed var(--line);
}
.sb-arg:first-child { border-top: 0; padding-top: 4px; }
.sb-arg-k b { font-size: 13px; }
.sb-arg-k p { margin-top: 4px; max-width: 60ch; }
.sb-arg-t {
  font-size: 10.5px; padding: 1px 5px; margin-left: 6px;
  border: 1px solid var(--line); border-radius: 4px; background: var(--surface-2); color: var(--ink-3);
}
.sb-arg-k .badge, .sb-arg-k .dim { margin-left: 6px; }
.sb-arg-v { display: grid; gap: 6px; }
.sb-arg-v .input, .sb-arg-v .select { width: 100%; }
.sb-arr { min-height: 54px; padding: 8px 10px; font: 400 12.5px/1.5 var(--mono); resize: vertical; }
@media (max-width: 720px) {
  .sb-arg { grid-template-columns: minmax(0, 1fr); gap: 8px; }
}
.sb-two pre, .sb-entry pre { margin: 0; max-height: 260px; overflow: auto; font-size: 12px; line-height: 1.6; }
@media (max-width: 1080px) { .sb { grid-template-columns: 1fr; } .sb-two { grid-template-columns: 1fr; } }

/* ---------- home: stacks as rows -----------------------------------------
   Three tall cards spelling out every member took a full screen to say
   "bundles exist". A row states the idea; stack.html carries the detail. */
.stack-rows { display: grid; gap: 0; border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface); overflow: hidden; }
.stack-row {
  display: flex; align-items: center; gap: 18px; padding: 15px 18px;
  border-top: 1px solid var(--line); color: inherit; transition: background .14s;
}
.stack-row:first-child { border-top: 0; }
.stack-row:hover { background: var(--surface-2); }
.stack-row p { margin: 0; }
.stack-row-marks { display: flex; gap: 4px; flex: none; }
.stack-row-marks .tile { width: 26px; height: 26px; font-size: 10.5px; border-radius: 7px; }
.stack-row-marks .tile.sk { background: var(--skill-soft); border-color: var(--skill-line); color: var(--skill); }
.stack-row-cost { flex: none; width: 84px; text-align: right; font-size: 14px; letter-spacing: -.02em; }
.stack-row-cost small { font-size: 11px; color: var(--ink-3); }
@media (max-width: 860px) {
  .stack-row { flex-wrap: wrap; gap: 10px; }
  .stack-row-cost { width: auto; text-align: left; }
}

/* ---------- studio: the support inbox -------------------------------------
   A list of tickets, and one conversation at a time behind a click. The row is
   a button so the whole strip is the target, not a link buried in it. */
.inbox-item {
  display: flex; align-items: center; gap: 14px; width: 100%; text-align: left;
  padding: 14px 18px; border-top: 1px solid var(--line);
  background: none; cursor: pointer; transition: background .14s;
}
.inbox-item:first-child { border-top: 0; }
.inbox-item:hover { background: var(--surface-2); }
.inbox-age { flex: none; }
.inbox-chev { display: grid; place-items: center; flex: none; color: var(--ink-3); }
.inbox-chev svg { width: 15px; height: 15px; }

.inbox-thread { padding: 18px; }
.inbox-msg { margin-top: 16px; padding: 14px 16px; border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface-2); }
.inbox-msg.mine { background: var(--accent-soft); border-color: var(--accent-line); }
.inbox-msg p { font-size: 13.5px; line-height: 1.6; color: var(--ink-2); margin-top: 9px; }
.inbox-who { display: flex; align-items: center; gap: 9px; font-size: 13px; }
.inbox-who .tile { width: 24px; height: 24px; font-size: 10px; border-radius: 6px; }
.inbox-row { align-items: flex-start; }
.inbox-diag {
  margin-top: 10px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface-2);
}
.inbox-diag .kv { padding: 5px 0; font-size: 12.5px; }
.inbox-reply { margin-top: 12px; }
.inbox-reply .sk-new-md { font: 400 13px/1.6 var(--sans); background: var(--surface); }
@media (max-width: 720px) {
  .inbox-row { flex-wrap: wrap; }
  .inbox-row > div[style*="text-align:right"] { text-align: left !important; width: 100%; }
}

/* ---------- writing a review ----------------------------------------------
   The page claims reviews are earned; the composer is where that claim is
   either honoured or exposed, so its gated states are as designed as the form. */
.rw {
  border: 1px solid var(--line); border-radius: var(--r3); background: var(--surface);
  padding: 18px; margin-bottom: 18px;
}
.rw-note { background: var(--surface-2); }
.rw-done { border-color: var(--accent-line); background: var(--accent-soft); }
.rw-stars { display: flex; gap: 2px; }
.rw-star {
  display: grid; place-items: center; width: 30px; height: 30px;
  background: none; cursor: pointer; color: var(--line-2); transition: color .12s, transform .08s;
}
.rw-star svg { width: 21px; height: 21px; }
.rw-star:hover { transform: translateY(-1px); }
.rw-star.on { color: var(--warn); }
.rw-in {
  height: 38px; width: 100%; padding: 0 12px;
  border: 1px solid var(--line-2); border-radius: var(--r1);
  background: var(--surface); color: var(--ink); font-size: 14px;
}
.rw-body {
  width: 100%; margin-top: 10px; padding: 12px; resize: vertical;
  border: 1px solid var(--line-2); border-radius: var(--r1);
  background: var(--surface); color: var(--ink);
  font: 400 13.5px/1.6 var(--sans);
}
.rw-in:focus, .rw-body:focus { outline: 0; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.rw-facts { margin-top: 14px; padding: 12px 14px; border: 1px dashed var(--line-2); border-radius: var(--r2); }
.rw-bar { height: 6px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.rw-bar span { display: block; height: 100%; background: var(--accent); }
@media (max-width: 620px) { .rw { padding: 14px; } }

/* ---------- library on a phone --------------------------------------------
   The lede explains what a list is and both header buttons act on lists, so
   above the Saved tab they were most of a screen of furniture in front of the
   first saved card. They stay at the top on a desktop, where there is room. */
@media (max-width: 720px) {
  .lib-lede, .lib-head-acts { display: none; }
  body[data-lib-tab="lists"] .lib-lede,
  body[data-lib-tab="lists"] .lib-head-acts { display: flex; }
  body[data-lib-tab="lists"] .lib-lede { display: block; }
}

/* ---------- the prose pages -----------------------------------------------
   Terms and Privacy read as one document, so their cards, tables and
   paragraphs share a single measure — but the measure starts at the page
   gutter like every other page rather than floating in the middle of a 1400px
   container, which made those two look like a different site. */
.legal-col { max-width: none; }
/* A CAPPED COLUMN SITS IN THE MIDDLE, NOT AGAINST THE LEFT EDGE. Terms and
   Privacy have no cap and fill the container, so auto margins do nothing to
   them and the rule above still decides their measure. /claim is the one that
   caps itself — a short form at 820px — and without this it hung off the left
   of a 1400px container with half a screen of nothing beside it. */
.legal-col { margin-inline: auto; }
/* the prose still needs a measure, but it belongs to the paragraph rather than
   to the page: cards, tables and grids fill the content block, and only the
   running text inside them stops at a readable line */
.legal-col p, .legal-col li { max-width: 92ch; }

/* ---------- the long-copy block at the foot of the home page --------------
   Collapsed by default: it exists for a reader who arrived from a search for
   "MCP server", and it must not push the product down the page for everyone
   who did not. The fade is decorative and hidden once the block is open. */
.seo { padding-bottom: 8px; }
.seo-box { position: relative; padding-top: 26px; border-top: 1px solid var(--line); }
.seo-box > .h3 { letter-spacing: -.022em; }
.seo-body {
  position: relative; overflow: hidden;
  max-height: 132px; margin-top: 14px;
  transition: max-height .32s cubic-bezier(.2,.8,.3,1);
}
.seo-box.on .seo-body { max-height: 2600px; }
/* runs the full content width rather than to a measure: this block exists to
   be read whole, and the surrounding page is 1400px */
.seo-body p { font-size: 14px; line-height: 1.7; color: var(--ink-2); margin-bottom: 14px; }
.seo-body h3 { margin: 22px 0 8px; color: var(--ink); }
.seo-body b { color: var(--ink); font-weight: 600; }
.seo-fade {
  position: absolute; left: 0; right: 0; bottom: 44px; height: 64px;
  /* a fade needs a gradient; it is the one place in this design where the
     alternative — a hard cut mid-sentence — reads worse */
  background: linear-gradient(to bottom, transparent, var(--paper));
  pointer-events: none;
}
.seo-box.on .seo-fade { display: none; }
.seo-more { margin-top: 14px; }
.seo-box.on .seo-more svg { transform: rotate(180deg); }
.seo-more svg { transition: transform .2s; }
@media (max-width: 620px) { .seo-body { max-height: 168px; } }

/* ---------- the same block at the foot of a catalogue landing -------------
   The rules above are not scoped to the home page, so a landing gets the
   identical block by using the identical classes: one collapsible in this
   build, not two that have to be kept looking alike. Only the two things that
   differ here are written down.

   It lives INSIDE the results column rather than across the page, because it
   is the answer to the question this column is a list for, and a full-width
   band under a 244px rail would read as a new section of the site.

   :empty RATHER THAN A hidden ATTRIBUTE. The section ships in the markup on
   every catalogue page and carries copy on the few landings that have any;
   the script writes what goes in it and nothing else, so there is no second
   piece of state — an attribute — that could disagree with the contents. An
   empty section then costs the page nothing at all: `.seo` has padding and a
   `.seo-box` has a border-top, and 8px of ruled dead space under the pager on
   every unwritten landing is exactly the kind of thing nobody would find. */
.cat-seo { margin-top: 34px; }
.cat-seo:empty { display: none; }


/* =============== Stripe elements, shared by both dashboards ===============
   The buyer adds a card and the publisher connects an account through the same
   kind of surface: a region this document does not own. Drawn once here so the
   two workspaces cannot drift apart on what a Stripe frame looks like. */
/* the region Stripe owns: drawn as a mount point rather than as fields, because
   in the live product the inputs inside it belong to Stripe's iframe and this
   document never sees what is typed there */
.db-el {
  border: 1px solid var(--line-2); border-radius: var(--r2);
  background: var(--surface-2); padding: 12px 13px;
}
.db-el-h { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.db-el-lock { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--ink-3); }
.db-el-lock svg { width: 12px; height: 12px; }
.db-el-row { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 8px; }
.db-el-row .db-el-f.wide { grid-column: 1 / -1; }
/* one row per field, drawn the way Stripe stacks them: label above, the input
   itself replaced by an inert bar because in the live product that box belongs
   to Stripe's frame and this document cannot read or write it */
.db-el-f {
  display: grid; gap: 6px; margin-bottom: 8px;
  padding: 8px 11px 10px; border: 1px solid var(--line); border-radius: var(--r1); background: var(--surface);
  position: relative;
}
.db-el-f .k { font-size: 11.5px; color: var(--ink-3); }
.db-el-ph {
  height: 10px; border-radius: 3px;
  background: repeating-linear-gradient(90deg, var(--line-2) 0 8px, transparent 8px 15px);
  opacity: .7;
}
.db-el-f.sel::after {
  content: ''; position: absolute; right: 12px; bottom: 13px;
  width: 7px; height: 7px; border-right: 1.4px solid var(--ink-3); border-bottom: 1.4px solid var(--ink-3);
  transform: rotate(45deg); opacity: .7;
}
.db-el-brands { position: absolute; right: 10px; bottom: 8px; display: flex; gap: 4px; }
/* 8px was not a size, it was an apology. These stand in for the card marks
   Stripe draws in the live frame, and a reader who cannot tell VISA from MC
   cannot tell whether their card is accepted. */
.db-el-brands i {
  font: 600 10px/15px var(--sans); font-style: normal; letter-spacing: .04em;
  padding: 0 5px; border: 1px solid var(--line); border-radius: 3px;
  background: var(--surface-2); color: var(--ink-3);
}
.db-el p { margin-top: 8px; }

/* our own fields, beside Stripe's */
.db-form { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 10px; }
.db-form label:not(.check) { display: grid; gap: 5px; font-size: 12.5px; color: var(--ink-2); min-width: 0; }
.db-form label.wide { grid-column: 1 / -1; }
.db-in {
  height: 34px; padding: 0 10px; width: 100%;
  border: 1px solid var(--line-2); border-radius: var(--r1);
  background: var(--surface); color: var(--ink); font: 400 13px/1 var(--sans);
}
.db-in:focus { outline: 0; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
@media (max-width: 560px) { .db-form { grid-template-columns: 1fr; } }



/* the band that marks where a Stripe embedded component begins: everything
   under it is Stripe's frame in the live product, and the reader is entitled
   to know which fields leave our origin */
.db-el-band {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  padding: 9px 12px; margin-bottom: 10px;
  border: 1px solid var(--line-2); border-radius: var(--r1) var(--r1) 0 0;
  background: var(--surface-2);
}
.db-el-band .db-el-lock { font-size: 12px; color: var(--ink-2); }

/* =============== PROMOTED =================================================
   A paid slot is a position in the ordinary list, not a shelf of its own: the
   reader compares like with like, and one quiet tag says money was involved.
   The tag is a button — it is the only affordance that can answer "why am I
   seeing this", and a labelled ad has to be able to. */
.scard.promoted, .kcard.promoted { position: relative; border-color: var(--line-2); }
.promo-tag {
  position: absolute; top: 10px; right: 10px; z-index: 1;
  font: 500 10px/1 var(--mono); text-transform: uppercase; letter-spacing: .1em;
  padding: 4px 6px; border-radius: 4px; cursor: help;
  border: 1px solid var(--line-2); background: var(--surface-2); color: var(--ink-3);
}
.promo-tag:hover { color: var(--ink-2); border-color: var(--ink-3); }
/* the table view has no corner to pin it to, so it sits beside the name */
.promo-tag.inline { position: static; margin-left: 6px; vertical-align: 1px; padding: 3px 5px; }
/* the compare checkbox sits top-left, so the tag never collides with it */
.scard.promoted .scard-top, .kcard.promoted .kcard-top { padding-right: 78px; }
/* On a phone the disclosure leaves the corner, where it was a 70x20 sliver
   sharing 44px of card with the compare tick. It becomes the first line of the
   card instead — but a line, not a block: at 40px tall it read as the loudest
   thing on a listing it is only meant to annotate, which is exactly backwards
   for a paid slot. 24px in 10.5px mono is the same tag the desktop draws, moved
   somewhere it collides with nothing, and the name it used to crowd still gets
   its 78px back. */
@media (max-width: 620px) {
  .scard.promoted > .promo-tag, .kcard.promoted > .promo-tag {
    position: static; align-self: flex-start;
    display: inline-flex; align-items: center;
    min-height: 24px; padding: 0 7px; font-size: 10.5px; margin-bottom: 2px;
  }
  /* the 78px reserve went with it. A card that also carries a tick still keeps
     the tick's own reserve, stated here rather than left to the source order of
     two rules of equal weight; the kcard's is skills' 34px, and it wins on its
     own account because skills.css loads after this file. */
  .scard.promoted .scard-top, .kcard.promoted .kcard-top { padding-right: 0; }
  .scard.promoted:has(.scard-cb) .scard-top { padding-right: 46px; }
}



/* ---------- the Installation tab on a listing -----------------------------
   A client picker that shows what it is picking: the mark, the name, and the
   panel below changes with it. Deliberately not a segmented control — twelve
   clients do not fit in one, and the ones a reader wants are rarely first. */
.inst-pick { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.inst-btn {
  display: inline-flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 12px 0 6px;
  border: 1px solid var(--line); border-radius: var(--r1);
  background: var(--surface); color: var(--ink-2);
  font: 500 13px/1 var(--sans); cursor: pointer; transition: .12s;
}
.inst-btn:hover { border-color: var(--line-2); color: var(--ink); }
.inst-btn .tile {
  width: 24px; height: 24px; border-radius: 5px; font-size: 9.5px; flex: none;
}
.inst-btn.on { background: var(--ink); border-color: var(--ink); color: var(--paper); }
.inst-btn.on .tile { background: var(--paper); color: var(--ink); }
@media (max-width: 620px) {
  .inst-btn { height: 36px; flex: 1 1 auto; justify-content: flex-start; }
  /* КОМАНДА ПЕРЕНОСИТСЯ, А НЕ ЕЗДИТ ВБОК. Кнопка копирования лежит в .code
     абсолютом, а .code — сам контейнер прокрутки: стоит увести команду вбок,
     и кнопка уезжает вместе с содержимым на середину текста. У восьми из
     двенадцати клиентов здесь многострочный JSON/TOML, то есть это обычный
     случай, а не длинная команда изредка. На странице листинга тот же приём
     уже применён (server-page.css), здесь его не было. */
  #mgPanel .code { white-space: pre-wrap; overflow-wrap: anywhere; overflow-x: visible; }
}

/* the two blocks a publish receipt ends on: what is still outstanding, and how
   long the human review takes. Both are statements of fact rather than
   decorations, so they read as rows rather than as banners. */
.pub-hold { margin-top: 20px; border-color: var(--warn-line); background: var(--warn-soft); }
.pub-mod {
  display: flex; gap: 12px; align-items: flex-start;
  margin-top: 20px; padding: 14px 16px;
  border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface-2);
}
.pub-mod .ic { flex: none; color: var(--ink-2); margin-top: 1px; }
.pub-mod .ic svg { width: 16px; height: 16px; }
.pub-mod b { font-size: 13.5px; }
.pub-mod p { max-width: 76ch; }

/* =============== the 12px floor ===========================================
   Last in the file on purpose. This is the composition-level half of the rule
   core.css states for .spec, .badge and the table head: on a phone nothing
   that carries a fact renders under 12px. Several of the selectors below are
   defined further up this file with a `font:` shorthand, so a block placed
   beside them would lose on source order; here it wins on every one of them
   without needing weight it has not earned.

   Two-letter monograms — .tile, .quote .av, .nav-acct-av — are deliberately
   not in this list. They are marks sized to their box, not words a reader
   parses, and growing them means growing every square they sit in. */
@media (max-width: 620px) {
  .statbar .d, .metrics .k, .kpi .k, .demo-row span, .catcard .c,
  .stack-item .p, .srv-skills .s, .srv-skills .p, .ver .vd, .bars-x span,
  .sk-file .sz, .db-el-lock, .db-el-f .k, .db-el-brands i,
  .fgroup > h5, .fgroup > .fgroup-t, .fgroup .cnt, .check .cnt, .check-row > .cnt-go, .cat-filter-n, .lib-group-h .c,
  .pub-done-steps .w, .stack-row-cost small, .sb-arg-t, .mg-k,
  .rail-sec { font-size: 12px; }
  .db-el-brands i { line-height: 17px; }
}

/* A price field that says what it is asking for. The label above says "per
   month" or "per 1,000 calls" but never what unit the number is in, and the
   answer — dollars — was only visible after the listing was published. The
   suffix sits inside the field's own border so it reads as part of the input
   rather than as a word next to it. */
.in-money { position: relative; display: inline-flex; align-items: center; }
.in-money input { width: 132px; padding-right: 46px; }
.in-money i {
  position: absolute; right: 10px; pointer-events: none;
  font: 500 11.5px/1 var(--mono); font-style: normal; letter-spacing: .04em;
  color: var(--ink-3);
}

/* Where the server comes from. Five sources do not fit a segmented control at
   any width worth having — and a segment says "a setting", where this is the
   first real decision of the wizard. Tiles, each carrying the shape of the
   thing it wants, so the answer to "what do I paste" is on the button rather
   than in the hint under the field. */
/* minmax(0, 1fr) and not 1fr: the example line is one unbreakable token, and a
   plain 1fr track takes its automatic minimum from that token — the columns
   came out uneven and 25px wider than a 375 phone even with the span set to
   ellipsis, because the track had already been sized by text that never
   wrapped. Zero minimum lets the ellipsis do its job. */
.wz-src { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; margin: 12px 0 0; }
.wz-src button {
  display: block; text-align: left; padding: 10px 12px; border-radius: 9px;
  border: 1px solid var(--line); background: var(--surface); transition: .12s;
}
.wz-src button:hover { border-color: var(--line-2); background: var(--surface-2); }
.wz-src button b { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink); }
.wz-src button span {
  display: block; margin-top: 3px; font-size: 11.5px; color: var(--ink-3);
  /* the example is the widest part of the tile and the least important: it
     truncates rather than setting the column width */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.wz-src button.on { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; background: var(--surface); }
.wz-src button.on b { color: var(--accent); }
/* Two columns hold down to a 375-wide phone: the tile is 136px there, which
   fits the longest label with room over, and five tiles in one column is
   360px of scrolling before the first field. One column only below that. */
@media (max-width: 700px) { .wz-src { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 330px) { .wz-src { grid-template-columns: minmax(0, 1fr); } }

/* The private-repository grant. It is a card and not a checkbox because what
   is being agreed to is a list of scopes, and a checkbox cannot carry one. */
.wz-gh.on { border-color: var(--line-2); background: var(--surface-2); }
.wz-gh-tick {
  flex: none; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  background: var(--accent); color: #fff;
}
.wz-gh-tick svg { width: 12px; height: 12px; }

/* The scopes the App asks for. A .kv right-aligns its value, which is right for
   "$8 a month" and wrong for a sentence: the three rows came out ragged down
   the middle with a word hanging off the end. Left-aligned against a fixed
   label column, so the three permissions read as a list rather than a table. */
.wz-scopes { margin-top: 10px; border-top: 1px solid var(--line); }
.wz-scopes > div {
  display: grid; grid-template-columns: 132px 1fr; gap: 12px;
  padding: 8px 0; border-bottom: 1px solid var(--line);
}
.wz-scopes b { font-size: 12.5px; font-weight: 500; color: var(--ink-3); }
.wz-scopes span { font-size: 13px; color: var(--ink-2); }
@media (max-width: 560px) {
  .wz-scopes > div { grid-template-columns: 1fr; gap: 2px; }
}

/* the address the listing will live at, under the field that decides it */
.wz-slug { display: block; margin-top: 6px; }
.wz-slug b { color: var(--ink-2); }

/* Reading a source. A bar plus the phases it is actually moving through: a
   spinner says "wait", a list says what for — and when it stops on one line
   for three seconds, that line is the answer to why. */
.wz-read { border-color: var(--line-2); }
.wz-bar { height: 4px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
.wz-bar i { display: block; height: 100%; background: var(--accent); border-radius: 3px;
  transition: width .5s cubic-bezier(.4, 0, .2, 1); }
.wz-phase { display: flex; gap: 10px; padding: 7px 0; opacity: .45; }
.wz-phase.done, .wz-phase.now, .wz-phase.todo { opacity: 1; }
.wz-phase b { font-size: 13.5px; }
.wz-phase p { margin: 0; }
.wz-dot {
  flex: none; width: 16px; height: 16px; margin-top: 2px; border-radius: 50%;
  border: 1px solid var(--line-2); display: grid; place-items: center;
  color: #fff; background: var(--surface);
}
.wz-phase.done .wz-dot { background: var(--accent); border-color: var(--accent); }
.wz-phase.done .wz-dot svg { width: 10px; height: 10px; }
/* A ROW THAT IS WORK, NOT A ROW THAT IS FINISHED. The same markup draws the
   read phases, where a filled accent dot means "this happened", and the
   connect step's list of things the publisher still has to build. Those were
   drawn `done`, so three solid accent dots sat above three instructions
   nobody had carried out yet. `todo` reads at full strength and keeps the
   ring hollow, with the number of the step where the tick would be. */
.wz-phase.todo .wz-dot {
  background: var(--surface); border-color: var(--line-2); color: var(--ink-2);
  font: 600 10px/1 var(--mono);
}
/* the one that is running pulses rather than spins: a spinner beside four
   static rows reads as the whole card loading */
.wz-phase.now .wz-dot { border-color: var(--accent); animation: wzPulse 1.1s ease-in-out infinite; }
/* the ring is the accent at a third, mixed rather than written out, so the
   pulse follows the scheme the same way the dot it comes off does */
@keyframes wzPulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 34%, transparent); }
  60% { box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .wz-phase.now .wz-dot { animation: none; background: var(--accent-soft); }
  .wz-bar i { transition: none; }
}

/* A listing being wound down. Dimmed rather than hidden: buyers who already
   run it come looking for exactly this card, and a catalogue that quietly
   drops a listing teaches people it might drop theirs. */
.scard.deprecated .scard-name, .kcard.deprecated .kcard-name { color: var(--ink-2); }
.scard-dep {
  margin: 8px 0 0; padding: 7px 9px; border-radius: var(--r1);
  background: var(--warn-soft); border: 1px solid var(--warn-line);
  font-size: 12.5px; color: var(--warn);
}
.scard-dep a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

/* The free-listing band on for-publishers: four facts on one line, so the
   section that says "this costs nothing" does not itself cost a screen. */
.fp-free-facts {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-top: 18px;
}
.fp-free-facts > div {
  display: grid; gap: 4px; padding: 14px 16px;
  border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface);
}
.fp-free-facts b { font-size: 17px; letter-spacing: -0.01em; }
.fp-free-facts span { font-size: 12.5px; color: var(--ink-3); line-height: 1.45; }
@media (max-width: 860px) { .fp-free-facts { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 460px) { .fp-free-facts { grid-template-columns: 1fr; } }

/* ---------- 404 -----------------------------------------------------------
   A wrong address is the one page nobody designed for and everybody sees. It
   gets a centred column and a real anchor at the top, because the alternative
   — a left-aligned paragraph in a 1400px bar — reads as a server error rather
   than as a page somebody wrote. */
.nf { max-width: 680px; margin-inline: auto; text-align: center; padding: 56px 0 72px; }
.nf-mark {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  font-family: var(--mono); font-size: 13px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--line); border-radius: 999px;
  padding: 7px 16px; background: var(--surface);
}
.nf-code {
  margin: 26px 0 0; font-size: clamp(64px, 12vw, 108px); line-height: 0.9;
  letter-spacing: -0.045em; font-weight: 600; color: var(--ink);
}
.nf-code span { color: var(--line-2); }
.nf h1 { margin-top: 18px; }
.nf .lead { margin: 12px auto 0; max-width: 56ch; }
.nf-card { text-align: left; margin-top: 30px; }
.nf-asked {
  display: flex; align-items: center; gap: 10px; justify-content: center; flex-wrap: wrap;
  margin-top: 22px; font-size: 13px; color: var(--ink-3);
}
.nf-asked code {
  font-family: var(--mono); font-size: 12.5px; color: var(--ink-2);
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r1);
  padding: 5px 9px; word-break: break-all;
}
.nf-sugg { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-top: 14px; }
.nf-sugg a {
  display: flex; align-items: center; gap: 11px; text-align: left;
  padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--r2);
  background: var(--surface); color: var(--ink); transition: border-color .14s, background .14s;
}
.nf-sugg a:hover { border-color: var(--line-2); background: var(--surface-2); }
.nf-sugg .n { display: block; font-weight: 500; font-size: 14px; }
.nf-sugg .i { display: block; font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }
.nf-acts { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; margin-top: 28px; }
.nf-foot { margin-top: 26px; font-size: 13px; color: var(--ink-3); max-width: 62ch; margin-inline: auto; }
@media (max-width: 620px) { .nf-sugg { grid-template-columns: minmax(0, 1fr); } }

/* the claim picker's rows — the console has .wrow for this and the shop did
   not, which is how a list of listings ended up stacked in a column */
.cl-row {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 13px 16px; border-top: 1px solid var(--line);
}
.cl-row:first-of-type { border-top: 0; }

/* ---------- pagination ----------------------------------------------------
   One control, both catalogues, drawn by MMPager in app.js. It sits under the
   results rather than above them, because a reader reaches it by finishing the
   page — and it repeats nothing the count line above already says except the
   range, which is the one fact only the pager knows.

   The number strip is a fixed set of slots (first, last, current ± 1, gaps),
   so the row does not reflow as you walk through it. Under 620px the strip is
   replaced by "3 / 12" between the two steps: nine tap targets of 30px do not
   fit a 358px column, and on a phone the reader is stepping rather than
   jumping anyway. */
.pager {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  margin-top: 26px; padding-top: 18px; border-top: 1px solid var(--line);
}
.pager-note { font-size: 13px; color: var(--ink-3); }
.pager-note b { font-weight: 550; color: var(--ink-2); font-family: var(--mono); font-size: 12.5px; }
.pager-row { display: flex; align-items: center; gap: 6px; }
.pager-nums { display: flex; align-items: center; gap: 2px; }
.pager-of { display: none; font: 500 13px/1 var(--mono); color: var(--ink-2); padding: 0 4px; }

/* EVERY SEAT IN THE STRIP IS AN ADDRESS NOW, and an anchor is inline where a
   button is inline-block — so the two declarations that were free on a button
   are stated here. Both selectors carry them, so a pager made of links and one
   made of buttons are the same control on screen. */
.pager-n, .pager-step { text-decoration: none; }
.pager-n {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 32px; height: 32px; padding: 0 8px;
  border: 1px solid transparent; border-radius: var(--r1);
  font: 500 13px/1 var(--mono); color: var(--ink-2);
  transition: background .12s, border-color .12s, color .12s;
}
.pager-n:hover { background: var(--surface-2); color: var(--ink); }
.pager-n.on {
  background: var(--accent-soft); border-color: var(--accent-line);
  color: var(--accent); font-weight: 600;
}
.pager-gap { min-width: 20px; text-align: center; font-size: 13px; color: var(--ink-3); }

.pager-step {
  display: inline-flex; align-items: center; gap: 5px;
  height: 32px; padding: 0 10px;
  border: 1px solid var(--line-2); border-radius: var(--r1);
  background: var(--surface); font-size: 13px; font-weight: 500; color: var(--ink);
  transition: border-color .12s, background .12s;
}
.pager-step:hover:not(:disabled) { border-color: var(--ink-3); }
/* disabled rather than hidden: the row keeps its width at both ends, so the
   strip does not shift sideways when you reach the first or the last page */
.pager-step:disabled { color: var(--ink-3); border-color: var(--line); cursor: default; }
.pager-step svg { width: 13px; height: 13px; }

@media (max-width: 620px) {
  .pager { justify-content: center; gap: 10px; }
  .pager-note { order: 2; width: 100%; text-align: center; }
  .pager-nums { display: none; }
  .pager-of { display: block; }
  .pager-row { width: 100%; justify-content: space-between; }
  .pager-step { height: 38px; padding: 0 14px; }
}


/* ---------- the founder's quote (about.html) --------------------------------
   A pull-quote, not a testimonial card: no photograph, no border box, no
   quotation-mark graphic the size of a fist. What marks it as speech is the
   rule down its left edge and the size — 23px against the 15px the page runs
   at — and what makes it a person is the line under it. It sits inside the
   header block, so it inherits the page's measure and needs no width of its
   own beyond a reading one.

   The opening quotation mark is drawn rather than typed, hanging in the
   margin: typed, it pushes the first word out of alignment with every other
   line on the page, which is the one thing a pull-quote must not do. */
/* the about header: one column at the page's own measure */
.ab-head { padding-bottom: 8px; }

/* The quote keeps a reading measure of its own: the sections around it are
   tables and cards that want the full 1400px, and a 23px line running that
   width is a line nobody finishes. */
.fq {
  margin: 4px 0 8px;
  padding: 6px 0 6px 26px;
  border-left: 2px solid var(--accent);
  max-width: 74ch;
  position: relative;
}
.fq::before {
  content: '\201C';
  position: absolute; left: 10px; top: -6px;
  font: 600 40px/1 var(--sans); color: var(--accent-line);
  pointer-events: none;
}
.fq-q {
  margin: 0;
  font-size: 23px; line-height: 1.45; letter-spacing: -.022em;
  font-weight: 400; color: var(--ink);
}
.fq-q em { font-style: italic; }
.fq-by {
  display: flex; align-items: center; gap: 11px;
  margin-top: 18px;
}
.fq-by > span { display: grid; }
.fq-by b { font-size: 14px; font-weight: 550; letter-spacing: -.012em; }
.fq-role {
  font: 400 11.5px/1.5 var(--mono); text-transform: uppercase; letter-spacing: .1em;
  color: var(--ink-3);
}
/* the initials tile: the same object the header chip uses, so a reader who has
   seen the account chip recognises what it is */
.fq-av {
  flex: none; width: 34px; height: 34px; border-radius: 9px;
  display: grid; place-items: center;
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  /* sans, not mono: the mono capital I carries crossbars, and "IH" set in it
     reads as "TH" at 12px */
  color: var(--ink); font: 600 12.5px/1 var(--sans); letter-spacing: .01em;
}

@media (max-width: 1000px) {
  /* stacked again, so it needs the air a second block wants */
  .fq { margin-top: 24px; max-width: 74ch; }
}

@media (max-width: 720px) {
  /* 23px over a 375px measure is four words a line; 19px is six, which is
     where a quote stops reading as a poster */
  .fq { margin-top: 20px; padding-left: 18px; }
  .fq::before { left: 6px; font-size: 32px; }
  .fq-q { font-size: 19px; line-height: 1.5; }
}


/* ==========================================================================
   «НЕТ ДАННЫХ» — СНОВА ТИРЕ, И БЕЛОЕ, распоряжение владельца от 14 авг 2026,
   отменяющее его же от 13-го. Тогда в слотах цифр (четыре метрики над
   описанием листинга, счётчики в профиле издателя) тире заменили словами «no
   data» мелким серым. На живой витрине это оказалось хуже: рядом с цифрами
   стоял серый обрывок фразы, и ряд метрик читался как наполовину сломанный.
   Тире в цвете самой цифры держит ряд ровным и говорит то же самое.

   Классы остались на месте — их ставит тот же признак `none` из API, — но
   теперь они не меняют ни размер, ни цвет, только гасят межбуквенный интервал,
   от которого тире съезжает. Специфичность 0,3,0 — выше, чем у `.metrics .v`
   здесь и в lang-v2, поэтому порядок подключения по-прежнему ничего не решает.
   ========================================================================== */
.metrics .v.v-none,
.pstat-v .pstat-none {
  letter-spacing: 0;
  color: var(--ink);
}

/* ==========================================================================
   ЛЕСТНИЦА ТАРИФОВ В КАРТОЧКЕ PRICING (боковая колонка страницы листинга).
   Распоряжение владельца от 13 авг 2026: показывать в этом блоке те тарифы
   подписки, которые задал автор, а не одну самую дешёвую цифру.

   Строка = имя тарифа и его лимит слева, цена справа. Активный тариф
   покупателя обведён акцентом; «popular» — тот же самый дешёвый платный,
   который уже помечен на карточках во вкладке Pricing, чтобы две поверхности
   не спорили. Кнопка внизу открывает вкладку, а не новую страницу.
   ========================================================================== */
.srv-tiers { display: flex; flex-direction: column; gap: 8px; }

.srv-tier {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; text-align: left; font: inherit; color: inherit;
  background: none; cursor: pointer;
  border: 1px solid var(--line); border-radius: var(--r2); padding: 11px 13px;
  transition: border-color .12s ease, background-color .12s ease, transform .12s ease;
}
/* the row is a button: it has to look like one under the cursor, or a reader
   never finds out that pressing it subscribes */
/* --surface-2 is the token the rest of the build uses for a raised row, and it
   is defined in both themes; a hand-mixed rgba would be right in one of them */
.srv-tier:hover { border-color: var(--ink-3); background: var(--surface-2); }
.srv-tier:active { transform: translateY(1px); }
.srv-tier:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.srv-tier.on { border-color: var(--accent); }
.srv-tier.on:hover { border-color: var(--accent); }

.srv-tier-n {
  display: flex; align-items: center; gap: 7px;
  font-size: 14px; font-weight: 600; color: var(--ink); line-height: 1.25;
}
.srv-tier-a { margin-top: 3px; font-size: 12.5px; color: var(--ink-3); }

.srv-tier-tag {
  font: 500 10.5px/1 var(--sans); letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-3); border: 1px solid var(--line); border-radius: 999px; padding: 3px 6px;
}
.srv-tier-tag.on { color: var(--accent); border-color: var(--accent); }

.srv-tier-p {
  font: 600 15px/1 var(--mono); color: var(--ink); white-space: nowrap;
  letter-spacing: -.02em;
}
.srv-tier-u { font-size: 12px; font-weight: 500; color: var(--ink-3); margin-left: 1px; }

.srv-tier-all {
  margin-top: 2px; width: 100%; background: none; border: 0; cursor: pointer;
  font: 500 13px/1 var(--sans); color: var(--ink-2); padding: 8px 0;
}
.srv-tier-all:hover { color: var(--ink); text-decoration: underline; }

/* ==========================================================================
   /why-us — короткая страница и таблица на галочках.

   Первая сборка была стеной прозы: пятнадцать строк, в каждой ячейке абзац,
   и читать это было нечем. Здесь ячейка — галочка, короткое значение или
   точка «не опубликовано», а лайм расставлен так, как он расставлен на всём
   сайте: им отмечено то, что ЕСТЬ, и ничего больше. Ни одного лаймового
   заголовка, ни одной лаймовой заливки во всю карточку — акцент перестаёт
   быть акцентом ровно в тот момент, когда его становится много.
   ========================================================================== */
.wu-tbl { min-width: 980px; font-size: 13px; }
.wu-tbl th[scope="row"] {
  position: sticky; left: 0; z-index: 1; background: var(--surface);
  min-width: 152px; max-width: 172px; text-align: left; vertical-align: middle;
  font-weight: 500; color: var(--ink-2); line-height: 1.35; white-space: nowrap;
}
.wu-tbl td { text-align: center; vertical-align: middle; white-space: nowrap; }
.wu-tbl thead th { white-space: nowrap; text-align: center; font-size: 12.5px; }
.wu-tbl thead th:first-child { text-align: left; }

/* наша колонка: подсветка полосой, а не заливкой строки */
.wu-tbl .wu-us { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.wu-tbl thead .wu-us { color: var(--ink); font-weight: 700; box-shadow: inset 0 -2px 0 var(--accent); }

.wu-y { color: var(--accent); font-size: 16px; font-weight: 700; line-height: 1; }
.wu-n { color: var(--ink-3); opacity: .5; }
.wu-x { color: var(--risk); font-size: 15px; font-weight: 600; line-height: 1; opacity: .75; }
.wu-v { font: 500 12.5px/1.3 var(--mono); color: var(--ink-2); white-space: nowrap; }
.wu-us .wu-v { color: var(--ink); }

/* группа строк: подпись раздела внутри таблицы */
.wu-tbl .wu-g th {
  position: static; background: var(--surface-2); color: var(--ink);
  font: 600 11.5px/1 var(--sans); letter-spacing: .08em; text-transform: uppercase;
  padding-top: 12px; padding-bottom: 12px;
}

/* четыре тезиса наверху: лаймовая засечка слева, как у цитаты */
.wu-claim { border-left: 2px solid var(--accent); }
.wu-claim b { color: var(--ink); }

/* «что мы не делаем» — тот же приём, но приглушённым цветом, чтобы раздел
   читался как оговорка, а не как второй список достоинств */

.wu-kicker { color: var(--accent); }

/* ---- /why-us: блоки, собранные из деталей, которые на сайте уже есть -------
   Ничего нового не изобретается: полоса цифр — та же .metrics, что над
   описанием листинга; четыре тезиса — та же .paylist, что на главной у блока
   для паблишеров; вопросы — та же .faq, только на <details>, потому что на
   этой странице нет своего скрипта и раскрытие должно работать без него.
   Лайм расставлен точечно: кикер, галочка, засечка слева у тезиса. */
.wu-metrics .v { font-size: 26px; }
.wu-metrics .k { text-transform: none; letter-spacing: 0; }



/* <details> вместо кнопки: та же разметка классов, тот же вид, без скрипта */
.wu-faq .faq-item { border-bottom: 1px solid var(--line); }
.wu-faq summary.faq-q { cursor: pointer; list-style: none; }
.wu-faq summary.faq-q::-webkit-details-marker { display: none; }
.wu-faq summary.faq-q::after {
  content: "+"; color: var(--ink-3); font: 400 20px/1 var(--sans); flex: none;
}
.wu-faq details[open] summary.faq-q::after { content: "–"; color: var(--accent); }
.wu-faq details[open] summary.faq-q { color: var(--ink); }
.wu-faq .faq-a { display: block; }
.wu-faq summary.faq-q [data-icon] { display: none; }

.wu-cta { background: var(--surface); border-color: var(--line); padding: 46px 24px; }
.wu-cta .h3 { color: var(--ink); }

/* ---- /why-us: три секции, три разные формы ---------------------------------
   Одинаковая сетка из карточек три раза подряд читается как один длинный
   список, в котором ничего не выделено. Поэтому: проверки — лента ступеней в
   одной рамке, что видно до установки — плотный чек-лист в две колонки,
   способы поставки — три полосы с шапкой, из которых подсвечена одна. */
.wu-flow { padding: 4px 0; }
.wu-lane { display: flex; gap: 14px; padding: 13px 18px; border-bottom: 1px solid var(--line); }
.wu-lane:last-child { border-bottom: 0; }
.wu-lane b { display: block; font-size: 14px; color: var(--ink); margin-bottom: 3px; }
.wu-lane .small { display: block; }
.wu-num {
  flex: none; width: 30px; font: 600 11.5px/1.6 var(--mono); letter-spacing: .1em;
  color: var(--ink-3); padding-top: 2px;
}
.wu-num-on { color: var(--accent); font-size: 15px; }
.wu-lane-end { background: color-mix(in srgb, var(--accent) 6%, transparent); }
.wu-lane-end b { color: var(--accent); }

.wu-checks { display: grid; grid-template-columns: 1fr 1fr; gap: 14px 22px; }
@media (max-width: 760px) { .wu-checks { grid-template-columns: 1fr; } }
.wu-check { display: flex; gap: 10px; align-items: flex-start; }
.wu-check b { display: block; font-size: 13.5px; color: var(--ink); margin-bottom: 2px; }
.wu-check .small { display: block; }

.wu-ships { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
@media (max-width: 900px) { .wu-ships { grid-template-columns: 1fr; } }
.wu-shipcard { border: 1px solid var(--line); border-radius: var(--r2); background: var(--surface); overflow: hidden; }
.wu-shipcard.on { border-color: var(--accent); }
.wu-shiphead {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--line); background: var(--surface-2);
  font-size: 14.5px; color: var(--ink);
}
.wu-shipcard.on .wu-shiphead { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.wu-shipwhere { padding: 10px 16px 0; font-size: 12px; color: var(--ink-3); letter-spacing: .02em; }
.wu-shipcard p { padding: 0 16px 16px; }


/* ---- /why-us на телефоне ---------------------------------------------------
   На 375px из таблицы видно полторы колонки: подпись строки занимает 152px, а
   ячейка — 200. Здесь всё сжимается так, чтобы рядом с подписью помещались три
   площадки, а не одна: подпись переносится в две строки, ячейка живёт по
   содержимому, шрифт мельче. Ширину таблицы это уменьшает с 980 до 620, то
   есть свайпов становится вдвое меньше. */
.wu-swipe { display: none; }

@media (max-width: 760px) {
  .wu-swipe { display: block; }

  .wu-tbl { min-width: 600px; font-size: 12px; }
  /* подпись строки — фиксированный рельс: она не едет вместе с колонками, и
     значит её текст должен быть заметно мельче, иначе рельс съедает треть
     экрана и площадок рядом остаётся полторы */
  .wu-tbl th[scope="row"] {
    min-width: 88px; max-width: 96px; white-space: normal;
    font-size: 10px; line-height: 1.2; letter-spacing: .01em;
    padding-right: 7px; padding-left: 9px; text-transform: none;
  }
  .wu-tbl th, .wu-tbl td { padding-left: 7px; padding-right: 7px; }
  .wu-tbl td { min-width: 64px; }
  .wu-tbl thead th { font-size: 11px; }
  .wu-tbl .wu-v { font-size: 10.5px; white-space: normal; line-height: 1.3; }
  .wu-tbl .wu-g th { font-size: 10.5px; padding-top: 9px; padding-bottom: 9px; }

  /* ступени и полосы поставки уже в одну колонку — им остаётся только воздух */
  .wu-lane { padding: 11px 14px; gap: 10px; }
  .wu-num { width: 24px; }
  .wu-checks { gap: 12px; }
  .wu-shiphead { padding: 10px 13px; font-size: 13.5px; }
  .wu-shipwhere { padding: 9px 13px 0; }
  .wu-shipcard p { padding: 0 13px 13px; }

  /* пять тезисов: подпись слева на своей строке, а не в 96px колонке */
  
  .wu-metrics .v { font-size: 22px; }
  .wu-cta { padding: 32px 16px; }
}

/* ---- /why-us: таблица на телефоне ------------------------------------------
   Тридцать одна строка и семь площадок в 375px — это горизонтальный скролл, и
   вопрос только в том, понятно ли по нему, куда ехать и где ты сейчас. Три
   вещи делают его читаемым: колонка с подписью стоит на месте и отделена
   тенью, шапка липнет к верху при прокрутке вниз, а колонки встают по одной
   благодаря scroll-snap — палец отпускается, и столбец не остаётся разрезанным
   пополам. Справа лежит градиент: он и есть подсказка, что там ещё есть. */
.wu-wrap { position: relative; }
/* СКРОЛЛ ЗАДАЁТСЯ ЗДЕСЬ, А НЕ УНАСЛЕДОВАН. Обёртка носила класс .db-scroll,
   который описан в dashboard.css — а он на этой странице не подключён, и
   overflow-x оставался visible: таблица шириной 633px просто вылезала за
   карточку, страница её обрезала, и правые колонки были недостижимы ни
   пальцем, ни колесом. Теперь прокрутка принадлежит самому блоку. */
.wu-scroll {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity; scroll-padding-left: 96px;
  overscroll-behavior-x: contain;
}
.wu-tbl td { scroll-snap-align: start; }

@media (max-width: 760px) {
  .wu-wrap::after {
    content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 26px;
    pointer-events: none; border-radius: 0 var(--r2) var(--r2) 0;
    background: linear-gradient(to right, transparent, var(--surface));
  }

  /* липкая шапка: при 31 строке иначе не видно, чья это колонка */
  .wu-tbl thead th {
    position: sticky; top: 0; z-index: 2; background: var(--surface-2);
  }
  .wu-tbl thead th:first-child { left: 0; z-index: 3; }
  .wu-tbl thead .wu-us { background: color-mix(in srgb, var(--accent) 14%, var(--surface-2)); }

  /* подпись строки — рельс: тень отделяет её от того, что под ней проезжает */
  .wu-tbl th[scope="row"] { box-shadow: 6px 0 10px -8px rgba(0, 0, 0, .75); }
  .wu-tbl .wu-g th { box-shadow: none; }

  .wu-tbl tbody tr { height: 46px; }
  .wu-swipe { display: flex; align-items: center; gap: 6px; }
  .wu-swipe svg { width: 13px; height: 13px; color: var(--accent); }
}

/* полоса прокрутки, которую рисуем сами: системная на iOS появляется только во
   время движения пальца и ложится поверх содержимого */
.hsb { position: relative; height: 3px; margin: 10px 12px 2px; border-radius: 2px;
  background: var(--line); overflow: hidden; }
.hsb i { position: absolute; top: 0; bottom: 0; left: 0; width: 30%;
  background: var(--accent); border-radius: 2px; transition: left .06s linear; }
@media (min-width: 761px) { .hsb { display: none; } }

/* ==========================================================================
   /about — проза во всю ширину контента.
   Страница была собрана из одиннадцати карточек и полосы счётчиков; владелец
   попросил оставить текст и цитату. Ширина — контейнерная, поэтому читаемость
   держится не колонкой, а воздухом: заголовки крупные и с большими отступами,
   абзацы редкие, списки там, где перечисление честнее прозы.
   ========================================================================== */
.ab-prose p { margin-top: 16px; font-size: 16px; line-height: 1.75; color: var(--ink-2); max-width: 104ch; }
.ab-prose p.lead { font-size: 19px; line-height: 1.65; color: var(--ink); margin-top: 0; max-width: 104ch; }
.ab-prose h2 { margin: 46px 0 4px; color: var(--ink); }
.ab-prose h3 { margin: 30px 0 2px; color: var(--ink); }
.ab-prose h2 + h3 { margin-top: 20px; }
.ab-prose b { color: var(--ink); font-weight: 600; }

.ab-list { margin: 14px 0 0; padding: 0; list-style: none; max-width: 104ch; }
.ab-list li {
  position: relative; padding-left: 22px; margin-top: 10px;
  font-size: 16px; line-height: 1.7; color: var(--ink-2);
}
.ab-list li::before {
  content: ""; position: absolute; left: 4px; top: 11px;
  width: 6px; height: 6px; border-radius: 2px; background: var(--accent);
}

/* ---- /about: колонка текста и полка с цифрами ------------------------------
   Текст во всю ширину контента читался тяжело — строка на 1400px не читается
   ни в одной типографике. Колонка забирает две трети, справа встают четыре
   счётчика, посчитанные на сервере при сборке страницы. На узком экране полка
   уезжает под текст, а не жмёт его. */
.ab-grid { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 44px; align-items: start; }
@media (max-width: 1000px) { .ab-grid { grid-template-columns: 1fr; gap: 28px; } }

.ab-side { position: sticky; top: calc(var(--nav-h, 60px) + 22px); }
@media (max-width: 1000px) { .ab-side { position: static; } }

.ab-stats { display: grid; gap: 1px; background: var(--line); border: 1px solid var(--line);
  border-radius: var(--r2); overflow: hidden; }
.ab-stat { display: flex; align-items: center; gap: 11px; padding: 14px 16px; background: var(--surface); }
/* иконка — единственное цветное пятно в полке: она отвечает на «что это за
   число» быстрее подписи, а лайм здесь работает как маркер, а не как заливка */
.ab-ic { display: inline-flex; color: var(--accent); flex: none; }
.ab-ic svg { width: 17px; height: 17px; }
.ab-stat .v { font: 600 23px/1 var(--sans); letter-spacing: -.03em; color: var(--ink); min-width: 56px; }
.ab-stat .k { font: 500 12.5px/1.3 var(--mono); letter-spacing: .04em; color: var(--ink-3); }

/* строка прозы держится в читаемой мере даже внутри колонки */
.ab-prose p, .ab-list { max-width: 78ch; }

/* ссылка на профиль автора цитаты: тихая, пока на неё не навели — подпись под
   цитатой не должна соревноваться с самой цитатой */
.fq-in { display: inline-flex; align-items: center; margin-left: 10px; color: var(--ink-3);
  transition: color .14s ease; }
.fq-in:hover { color: var(--accent); }
.fq-in svg { display: block; }

/* ==========================================================================
   МОБИЛЬНАЯ ПАЧКА, распоряжения владельца от 17 авг 2026.

   Шесть мест, где на телефоне ломалось чтение, а не логика. Все правила здесь,
   одним блоком: разложить их по шести файлам значило бы шесть мест, где
   следующий человек будет искать, почему на 390px иначе.

   КАЖДЫЙ СЕЛЕКТОР СВЕРЕН С ОТДАННОЙ РАЗМЕТКОЙ. Первая версия этого блока была
   написана по памяти — `.pubcard`, `.co-row`, `.db-first`, `.srv-tabs` — и ни
   одного такого класса на сайте нет: правила выглядели сделанной работой и не
   делали ничего. Имена ниже взяты из HTML и из checkout.css.
   ========================================================================== */
@media (max-width: 720px) {

  /* 1. ХЛЕБНЫЕ КРОШКИ НЕ РАЗЪЕЗЖАЮТСЯ. Длинный идентификатор листинга
     (`ai-smithery-saidsef-mcp-github-pr-issue-analyser`) рвал строку посреди
     слова, а разделители уезжали вниз. Полоса скроллится по горизонтали одной
     строкой, последний рунг обрезается многоточием — читатель и так стоит на
     нём. */
  .crumbs {
    display: flex; flex-wrap: nowrap; align-items: center;
    overflow-x: auto; scrollbar-width: none; -ms-overflow-style: none;
    padding-block: 12px;
  }
  .crumbs::-webkit-scrollbar { display: none; }
  .crumbs > * { flex: 0 0 auto; }
  .crumbs > :last-child {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* 2. АВАТАР ПАБЛИШЕРА СЛЕВА ОТ ИМЕНИ. Блок `#publisher` — это `.card-body
     .row` с плиткой и `.grow` рядом; на узком экране `.row` заворачивался, и
     плитка оказывалась над именем: три строки на то, что читается одной.
     Здесь он не заворачивается вовсе. */
  #publisher .card-body.row { flex-wrap: nowrap; align-items: flex-start; }
  #publisher .card-body.row > .tile { flex: none; }
  #publisher .card-body.row > .grow { min-width: 0; }

  /* 3. ПОЛОСА ВКЛАДОК НЕ ТАЩИТ СТРАНИЦУ И НЕ ЛИПНЕТ К ЗАГОЛОВКУ. `.tabs` с
     горизонтальной прокруткой перехватывала и вертикальный жест: палец вверх —
     страница стоит. `touch-action: pan-x` отдаёт вертикаль странице. */
  .tabs {
    overscroll-behavior-x: contain; touch-action: pan-x;
    margin-top: 14px; margin-bottom: 14px;
  }

  /* 4. ЧЕКАУТ: ЦЕНА И ГАЛОЧКА ВСЕГДА СПРАВА. `.co-item` — flex с `flex-wrap:
     wrap` (checkout.css:78), и на длинной второй строке («MCP server ·
     SUBSCRIPTION · Team · 5,000 calls/mo») цена с чекбоксом сваливались вниз,
     а на короткой («Agent skill · ONE-TIME») — нет. На телефоне перенос
     выключен: название сжимается, цена держит свою колонку. */
  .co-item { flex-wrap: nowrap; }
  .co-item-main { min-width: 0; }
  .co-item-m { flex: none; }

  /* 5. ОТСТУП НАД МЕНЮ КАБИНЕТА: «Overview» начинался вплотную под шапкой. */
  .db-rail-out, .rail { margin-top: 14px; }

  /* 6. EARNINGS: ДАТЫ И «Export CSV» В ДВЕ КОЛОНКИ. Три контрола в ряд на
     390px превращались в лестницу с разной шириной ступеней. */
  .er-filter .row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-items: end; }
  .er-filter .er-date { min-width: 0; }
  .er-filter #erCsv { grid-column: span 2; width: 100%; }
  .er-filter .grow { display: none; }
}

/* ---------- где опубликован прямой листинг --------------------------------
   Значок и слово рядом с автором в шапке. Иконка выровнена по базовой линии
   подписи, а не по её коробке: у svg нет базовой линии, и без сдвига марка
   висит на пару пикселей выше букв рядом. */
.src-link { display: inline-flex; align-items: center; gap: 5px; }
.src-link svg { width: 13px; height: 13px; flex: none; position: relative; top: .5px; }
.src-link:hover { color: var(--ink); }
.src-link:hover span { text-decoration: underline; text-underline-offset: 2px; }

/* ---------- playground ------------------------------------------------------
   Список инструментов слева, вызов справа — та же раскладка, что у вкладки
   Installation, чтобы читателю не пришлось учить вторую. На телефоне колонки
   складываются: выбор инструмента это один тап, и он не должен стоить
   горизонтальной прокрутки. */
.pg { display: grid; grid-template-columns: 210px minmax(0, 1fr); gap: 18px; align-items: start; }
.pg-t {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; padding: 8px 10px; margin-bottom: 4px;
  border: 1px solid transparent; border-radius: var(--r1);
  color: var(--ink-2); font-size: 13px; text-align: left;
}
.pg-t:hover { background: var(--surface-2); color: var(--ink); }
.pg-t.on { background: var(--surface-2); border-color: var(--line-2); color: var(--ink); }
.pg-t .n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pg-t .r { font-size: 11px; color: var(--ink-3); flex: none; }
.pg-params {
  display: grid; gap: 8px; padding: 12px; border: 1px solid var(--line); border-radius: var(--r1);
  background: var(--surface-2); font-size: 12.5px;
}
/* SELECT БЕРЁТ ШИРИНУ ОТ САМОГО ДЛИННОГО ПУНКТА и не умеет сжиматься: имя
   инструмента принимается длиной до 80 символов, и на 360px контрол вылезал
   за карточку вместе со стрелкой. max-width везде, ширина во всю строку —
   только на телефоне: на десктопе он и должен быть по содержимому. */
.pg-run select.select, .pg-list select.select { max-width: 100%; }
@media (max-width: 620px) { .pg-run select.select { width: 100%; } }
.pg-args {
  width: 100%; padding: 10px 12px; border: 1px solid var(--line-2); border-radius: var(--r1);
  background: var(--surface); color: var(--ink); font-size: 12.5px; line-height: 1.5;
  resize: vertical;
}
/* в этой коробке лежит ЧУЖОЙ текст: отказ издательского сервера с адресом,
   идентификатором вызова или замаскированным ключом внутри. Переносить его
   некуда, если не разрешить перенос где угодно */
.pg-out { margin-top: 14px; padding: 12px 14px; border-radius: var(--r1); border: 1px solid var(--line); overflow-wrap: anywhere; }
.pg-out.ok { border-color: var(--accent-line); background: var(--accent-soft); }
.pg-out.bad { border-color: var(--risk-line); background: var(--risk-soft); }
.pg-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
@media (max-width: 760px) {
  .pg { grid-template-columns: 1fr; }
  .pg-list { display: flex; flex-wrap: wrap; gap: 6px; }
  .pg-list .spec { width: 100%; }
  .pg-t { width: auto; margin: 0; border-color: var(--line); }
  /* ДЛИННОЕ ИМЯ ИНСТРУМЕНТА ЗАДАВАЛО ШИРИНУ ВСЕЙ ПЕСОЧНИЦЫ. Многоточие на
     .pg-t .n тут не помогает: оно рисует обрезку, но min-content элемента
     остаётся прежним, а схлопнутая колонка меряется именно по нему —
     замерено 479.8px в 360px экране. Перенос где угодно уменьшает min-content
     сам, и заодно читатель получает имя целиком, а не «extract_structu…». */
  .pg-t .n, .pg-run .card-head .spec { overflow-wrap: anywhere; white-space: normal; }
}
/* КНОПКА ЗАПУСКА НА ТЕЛЕФОНЕ — ВО ВСЮ СТРОКУ. Она стоит одна под текстовым
   полем во всю ширину, и 58px «Run it» под 313px аргументами читается как
   что-то второстепенное — а это единственное действие на панели. Где кнопок
   две (запуск и сброс примера), строку они делят. */
@media (max-width: 620px) {
  #dpgRun, #pgRun, #pgReset { flex: 1 1 auto; }
}

/* ---------- ключи покупателя (кабинет → In use) ----------------------------
   Строка переменной читается сверху вниз: что это, зачем, куда вводить. Поле
   и кнопки в одной строке, на телефоне — в столбик, потому что вставка ключа
   с телефона это уже неудобно и сужать поле нельзя. */
.env-g + .env-g { margin-top: 18px; padding-top: 18px; border-top: 1px solid var(--line); }
.env-v { padding: 12px 0; border-top: 1px dashed var(--line); }
.env-v:first-of-type { border-top: 0; }
.env-v.need .env-t .mono { color: var(--ink); }
.env-t { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* ИМЯ ПЕРЕМЕННОЙ ОБЪЯВЛЯЕТ ИЗДАТЕЛЬ, и NOTION_INTERNAL_INTEGRATION_TOKEN —
   это нормальное имя. Подчёркивания не дают браузеру места для переноса, а
   flex-элемент без min-width не сжимается ниже собственного min-content: одна
   строка расширяла карточку. `anywhere`, а не `break-word`: только первый
   уменьшает min-content, то есть только он разрешает элементу сжаться. */
.env-t .mono {
  overflow-wrap: anywhere; min-width: 0;
  /* 15px МОНОШИРИННЫМ РЯДОМ С 13.5px ОБЫЧНЫМ читается заметно крупнее, чем
     число обещает: у моноширинного шире и знак, и межбуквенное. Ключ — это
     подпись строки, а не заголовок раздела, и весом он выделяется лучше, чем
     кеглем. Владелец 24 авг 2026: «названия variables сделай чуть меньше». */
  font-size: 13.5px; font-weight: 500;
}
.env-n { margin: 4px 0 9px; max-width: 74ch; }
.env-in { display: flex; gap: 8px; align-items: center; }
.env-in .db-in { flex: 1 1 auto; min-width: 0; }
.env-say { margin-top: 7px; }
@media (max-width: 620px) {
  .env-in { flex-wrap: wrap; }
  .env-in .db-in { flex: 1 1 100%; }
  /* поле уходит на всю ширину и кнопка остаётся одна на строке — 53px «Save»
     под 313px полем это промах пальцем на ровном месте. Одна кнопка занимает
     строку целиком. */
  .env-in .btn { flex: 1 1 0; min-width: 0; }
  /* А ГДЕ КНОПОК ДВЕ — ПОПОЛАМ ИХ ДЕЛИТЬ НЕЛЬЗЯ: вторая всегда второстепенная
     и нарисована без рамки (Clear, Cancel), и растянутая на полстроки надпись
     без рамки выглядит как оборванная кнопка. Тянется первая — настоящее
     действие, вторая остаётся по содержимому. Владелец 24 авг 2026. */
  .env-in:has(.btn + .btn) .btn-quiet { flex: 0 0 auto; }
}

/* ---------- что входит в тариф, над песочницей ------------------------------
   Отдельная плашка, а не строка текста: владелец просил видимую рамку и
   заметную кнопку. Акцентная рамка и подложка — те же, что у карточки цены на
   странице листинга, поэтому «это про деньги» читается до чтения слов. */
.pg-banner {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-bottom: 16px; padding: 14px 16px;
  border: 1px solid var(--warn-line); border-radius: var(--r2);
  background: var(--warn-soft);
}
.pg-banner .t { font-weight: 550; font-size: 14px; color: var(--ink); }
.pg-banner .s { font-size: 13px; color: var(--ink-2); margin-top: 3px; max-width: 76ch; }
.pg-banner .grow { flex: 1 1 320px; min-width: 0; }
.pg-banner.is-ok { border-color: var(--accent-line); background: var(--accent-soft); }
@media (max-width: 620px) {
  .pg-banner .btn { width: 100%; }
}

/* ---------- «ключ не введён» — полоса над всеми панелями кабинета ----------
   Не карточка: карточка стоит в потоке одной панели, а это состояние аккаунта.
   Красная рамка и подложка риска — то же, чем кабинет уже помечает деньги,
   которые уходят зря; здесь ровно та же природа: за сервер платят, а он не
   отвечает. */
.db-envbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 16px; padding: 13px 15px;
  border: 1px solid var(--risk-line); border-radius: var(--r2);
  background: var(--risk-soft);
}
.db-envbar .ic { color: var(--risk); flex: none; display: inline-flex; }
.db-envbar .ic svg { width: 17px; height: 17px; }
.db-envbar .t { font-weight: 550; font-size: 14px; color: var(--ink); }
.db-envbar .s { font-size: 13px; color: var(--ink-2); margin-top: 3px; max-width: 78ch; }
.db-envbar .grow { flex: 1 1 300px; min-width: 0; }
@media (max-width: 620px) { .db-envbar .btn { width: 100%; } }

/* ---------- песочница под замком -------------------------------------------
   Блок остаётся на месте и остаётся читаемым по форме — видно, что тут список
   инструментов и поле аргументов, — но пользоваться им нельзя, пока не введены
   переменные. Размытие в 3px делает текст нечитаемым и оставляет узнаваемым
   силуэт: это не «скрыто», это «пока недоступно».

   pointer-events на нижнем слое сняты: под размытием остаются настоящие поля,
   и без этого в них можно было бы попасть табом с клавиатуры — форма, которая
   принимает ввод и ничего с ним не делает, хуже отключённой. */
.pg-lock { position: relative; }
.pg-lock-under {
  filter: blur(3px); opacity: .55; pointer-events: none; user-select: none;
}
.pg-lock-over {
  position: absolute; inset: 0; display: grid; place-items: center; padding: 16px;
}
.pg-lock-box {
  /* min(), а не 460px: коробка — элемент grid с place-items:center, её
     автоминимум равен min-content, и одно длинное имя ключа внутри раздвигало
     её шире карточки, которую она закрывает */
  max-width: min(460px, 100%); min-width: 0; text-align: center;
  padding: 18px 20px; border: 1px solid var(--line-2); border-radius: var(--r2);
  background: var(--surface); box-shadow: 0 8px 30px rgba(0,0,0,.28);
}
.pg-lock-box .ic { display: inline-flex; color: var(--ink-3); margin-bottom: 8px; }
.pg-lock-box .ic svg { width: 20px; height: 20px; }
.pg-lock-box .t { font-weight: 550; font-size: 14.5px; color: var(--ink); }
.pg-lock-box .s { font-size: 13px; color: var(--ink-2); margin: 6px 0 14px; }
.pg-lock-box .mono { color: var(--ink); }
/* и в самой фразе, и в именах ключей: оба текста приходят снаружи — ключи
   объявляет издатель, имя листинга тоже его */
.pg-lock-box .s, .pg-lock-box .s .mono { overflow-wrap: anywhere; }
/* НА ТЕЛЕФОНЕ ЗАМОК ЧИТАЕТСЯ, А НЕ ЦЕНТРИРУЕТСЯ. Коробка накрывает
   размытую панель абсолютом, и на 360px её текст в шесть центрированных строк
   ровно вырастал до высоты самой панели (замерено: 266 из 267) — ещё одна
   строка, и накладка вылезла бы за пределы того, что закрывает. Текст влево,
   поля меньше, кнопка во всю ширину, а сама накладка прижата к верху и
   прокручивается, если всё-таки не поместилась. */
@media (max-width: 620px) {
  .pg-lock-over { align-items: start; padding: 12px; overflow-y: auto; }
  .pg-lock-box { text-align: left; padding: 14px 15px; }
  .pg-lock-box .s { margin: 6px 0 12px; }
  .pg-lock-box .btn { width: 100%; }
}
/* полоса «не введён ключ» молчит там, где о том же говорит замок */
.db-envbar.is-quiet, #envAlert.is-quiet { display: none; }

/* заполненная переменная выглядит заполненной */
.env-mask { color: var(--ink-2); letter-spacing: .06em; background: var(--surface-2); cursor: default; }
.env-ok { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; color: var(--accent); }
.env-ok svg { width: 14px; height: 14px; }
.env-v.is-set .env-t .mono { color: var(--ink-2); }

/* ---------- шаги мастера после покупки -------------------------------------
   Три подписи в ряд, текущая подсвечена, пройденная приглушена галочкой. Не
   прогресс-бар: шагов три, и человеку важнее знать, что осталось, чем какой
   процент пройден. */
.co-steps { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.co-steps span {
  padding: 6px 11px; border: 1px solid var(--line); border-radius: 999px;
  font-size: 12.5px; color: var(--ink-3);
}
.co-steps span.on { border-color: var(--accent-line); background: var(--accent-soft); color: var(--ink); }
.co-steps span.done { color: var(--ink-2); }
.co-steps span.done::before { content: '✓ '; color: var(--accent); }
.co-bad { color: var(--risk); }

/* ВОЗВРАТ К СПИСКУ. Стрелка слева, потому что «назад» читается стрелкой
   раньше, чем словами; ссылка, а не кнопка, потому что это навигация, а не
   действие. На телефоне у неё своя высота: 40px — это пункт, в который
   попадают пальцем, а 17px строки текста — это не цель. */
.in-back {
  display: inline-flex; align-items: center; gap: 6px;
  margin-bottom: 12px; padding: 2px 0;
  font-size: 13px; color: var(--ink-2);
}
.in-back svg { width: 14px; height: 14px; color: var(--ink-3); }
.in-back:hover { color: var(--ink); }
.in-back:hover svg { color: var(--ink); }
@media (max-width: 1000px) {
  .in-back { min-height: 40px; margin-bottom: 6px; font-size: 14px; }
}

/* имя установки над первым блоком её страницы */
.in-id { margin-bottom: 14px; }

/* ПОДТВЕРЖДЕНИЕ ПРЯМО В КАРТОЧКЕ. Лист с диалогом (stripeSheet) живёт внутри
   dashboard.js и наружу не выходит, а переспросить тут есть о чём ровно два
   раза: отмена подписки и удаление. Обе кнопки — в цвете риска, обе рядом с
   тем, что они отменяют. */
.mg-ask {
  margin-top: 12px; padding: 13px 14px;
  border: 1px solid var(--line-2); border-radius: var(--r1); background: var(--surface);
}
.mg-ask p { font-size: 13px; line-height: 1.5; color: var(--ink-2); margin: 0 0 12px; }
.mg-ask .btn-go { color: var(--risk); border-color: var(--risk-line); }
.mg-ask .btn-go:hover { background: var(--risk-soft); border-color: var(--risk); }

