/* Shared layout primitives. Every pack loads this, then its own theme.css.
   Nothing here decides colour: that is the pack's job. */

/* ======================================================================
 * The four families the theme editor offers
 *
 * ⚠⚠ UNTIL 15 OCTOBER 2026 NO WEBFONT WAS EVER LOADED. `Theme.font_display`
 * and `font_body` have offered Plus Jakarta Sans, Manrope, Outfit and Inter
 * since they were written, and no page linked or @font-faced one of them, so
 * the picker was decoration: the named family resolved on a phone only by
 * luck and the FALLBACK was what a customer actually read.
 *
 * ⚠ SELF-HOSTED, NOT LINKED. Owner's decision, 15 Oct. A `<link>` to
 * fonts.googleapis.com puts a third party in front of text rendering on
 * every page of every tenant shop, which on a slow Nigerian mobile
 * connection is where the time goes — and a shop that cannot reach Google
 * is a shop whose text is still waiting. These are served from the tenant's
 * own domain.
 *
 * ⚠ VARIABLE FONTS, ONE FILE EACH. The whole 200-800 (or 100-900) weight
 * range is in the single file, so there is no per-weight request and no
 * synthesised bold. 132KB for all four on disk — and a browser downloads a
 * font only when a rule that uses it MATCHES, so a tenant pays for the one
 * family they chose and nothing for the other three.
 *
 * ⚠ `font-display: swap`. Text is readable in the fallback immediately and
 * reflows when the font lands. The alternative is a customer looking at a
 * blank balance while a font downloads, which is the worse failure on the
 * connection this is built for.
 *
 * ⚠ The `unicode-range` is Google's own latin subset and is NOT decoration:
 * only the latin subset is shipped, so a character outside it falls through
 * to the UI stack and renders, instead of becoming tofu in a font that does
 * not contain it.
 *
 * Replacing a file: refetch the src from the latin block of
 * `https://fonts.googleapis.com/css2?family=<Family>:wght@<range>&display=swap`
 * and send a browser user-agent — the API serves TTF to anything it does
 * not recognise, and a TTF here would quietly triple the download.
 * ==================================================================== */
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url('../fonts/plus-jakarta-sans-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 200 800;
  font-display: swap;
  src: url('../fonts/manrope-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Outfit';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/outfit-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ⚠ STILL STATED ON EVERY RULE THAT SETS A FAMILY, even now the fonts are
   real. A `font-family` whose only entry is an absent family is not
   "ignored": it computes to the browser's default, which is a SERIF.
   Measured on tetragit.com on 15 October 2026 — the live landing headings
   rendered at exactly the `serif` width, Times New Roman on a fintech shop,
   because four Meridian rules said `var(--t-font-display)` and stopped
   there. A font file can fail to load; the stack is what is read then. */
:root {
  --ui-stack: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
              'Helvetica Neue', Arial, sans-serif;
}

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

/* An author display rule beats the UA stylesheet, so without this every
   hidden panel renders at once. */
[hidden] { display: none !important; }

/* ⚠⚠ A CONTROL THAT IS A REAL <button> ARRIVES WEARING THE BROWSER'S OWN
   BUTTON. Kobopay's `.icon-btn` sets a size, a radius and a hover background
   and never resets the resting one, so every icon button on that pack — the
   mobile burger, hide-balance on the dashboard and on the wallet — rendered
   as a grey `buttonface` rectangle inside a violet app bar. Seen only by
   opening the page; the markup, the CSS and every test were all correct.

   ⚠ Plain `.icon-btn`, NOT `button.icon-btn`. A type selector would raise
   the specificity above the packs' own rules and Techsub, which deliberately
   gives its icon buttons a border and a white face, would lose them. At this
   specificity a pack's `theme.css` loads later and wins, which is the order
   that lets this be a floor rather than an override. */
.icon-btn {
  appearance: none; -webkit-appearance: none;
  background: none; border: 0; padding: 0;
  font: inherit; color: inherit; cursor: pointer;
}

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

body {
  margin: 0;
  font-family: var(--t-font-body), var(--ui-stack);
  background: var(--surface);
  color: var(--ink);
  line-height: 1.55;
  min-height: 100vh;
}

h1, h2, h3, h4 {
  font-family: var(--t-font-display), var(--ui-stack);
  line-height: 1.2;
  margin: 0 0 .5rem;
  letter-spacing: -0.015em;
}

a { color: inherit; }

/* Both default to min-width:auto, so a child refuses to shrink and pushes the
   page wider than the viewport. This is the single most common cause of
   horizontal overflow on a phone. */
.row > *, .grid > *, .stack > * { min-width: 0; }

.wrap { width: 100%; max-width: 1180px; margin-inline: auto; padding-inline: 24px; }

/* Never a `padding` shorthand on an element that also carries .wrap: the
   shorthand's zero sides silently wipe the inline padding above. */
.section { padding-block: 48px; }

.row { display: flex; gap: 16px; align-items: center; }
.row--between { justify-content: space-between; }
.row--wrap { flex-wrap: wrap; }
.stack { display: flex; flex-direction: column; gap: 16px; }

.grid { display: grid; gap: 16px; }
.grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }

.muted { color: var(--muted); }
.small { font-size: .875rem; }
.tiny  { font-size: .78rem; }
.strong { font-weight: 650; }
.right { text-align: right; }
.mono { font-variant-numeric: tabular-nums; }

.scroll-x { overflow-x: auto; }

@media (max-width: 900px) {
  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
  .wrap { padding-inline: 16px; }
  .grid--2, .grid--3, .grid--4 { grid-template-columns: minmax(0, 1fr); }
}
