/* Stylesheet for the sub-pages.
 *
 * index.html keeps its own inlined CSS and stays a single self-contained file.
 * These fourteen pages share this one instead, because the alternative is
 * fourteen copies of the same nav rule. Same tokens, same gradient, same 14px
 * radius, same system font stack — extending the incumbent world, not
 * replacing it.
 *
 * One webfont, and only one: Inter Tight, self-hosted from assets/fonts/ so a
 * visitor's browser never calls Google — the same family and the same two
 * subsets (SIL Open Font License) every other castomo product site now ships,
 * castomapi.com included. Nothing else here loads off-site.
 */

/* Same two subsets the sibling sites ship. */
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/inter-tight-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/assets/fonts/inter-tight-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;
}

:root {
  --bg:          #0b0c10;
  --bg-raise:    #101218;
  --lift:        0 18px 50px rgba(0, 0, 0, 0.5);
  --surface:     #14161d;
  --surface-2:   #191c24;
  --ink:         255, 255, 255;
  --border:      rgba(var(--ink), 0.09);
  --border-soft: rgba(var(--ink), 0.06);
  --text:        #f4f5f8;
  --sub:         #b9bcc7;
  --muted:       #878b98;
  /* Red, and measured against this style's own ground rather than copied from
     the next one. #aa0000 is the colour asked for and it is right on the light
     ground, where it reads 7.50:1 — but on #0b0c10 it is 2.52:1, and
     `a { color: var(--accent) }` is every inline link on every page. So the two
     dark styles carry the same hue lifted until it clears what the blue it
     replaces was clearing: 5.40 here, against the old 5.37. */
  --accent:      #ff3535;
  /* The skip link's own ground. White on --accent is 3.61:1 here,
     under the 4.5 that normal text needs, and this is the first thing
     a keyboard user meets on every page. --accent itself is untouched:
     it paints the buttons, the gradient and the kickers, and repainting
     the brand to fix one utility bar is the wrong trade. Light (7.75)
     and contrast (9.51) already pass and simply defer to --accent. */
  --skip-bg: #ef0000;
  --accent-2:    #f16c4f;
  --accent-3:    #d55181;
  --grad:        linear-gradient(100deg, #ff3535 10%, #f16c4f 60%, #d55181 110%);
  --live-grad:   linear-gradient(100deg, #E84520 0%, #FF834F 48%, #FFB07A 100%);
  /* The scrollbar's own gradient. It is --live-grad turned upright, because a
     thumb is tall and thin and a 100deg ramp across eleven pixels is one colour.
     The x- pair is for horizontal bars, where the geometry is the other way
     round. --scroll-thumb is the flat fallback Firefox gets. */
  --scroll-grad:      linear-gradient(180deg, #E84520, #FF834F);
  --scroll-grad-hi:   linear-gradient(180deg, #FF5A30, #FFA061);
  --scroll-grad-x:    linear-gradient(90deg, #E84520, #FF834F);
  --scroll-grad-x-hi: linear-gradient(90deg, #FF5A30, #FFA061);
  --scroll-thumb:     #FF834F;
  /* The kicker's orange, and the same one the wordmark and Live. wear: the
     middle stop of --live-grad in each style. Contrast against this style's
     own ground was measured, not assumed — 8.02, 5.01 and 10.45 to one,
     against the 4.5 that 12.5px bold needs. */
  --accent-warm: #FF834F;
  --good:        #35c07a;
  /* Measured, not copied. index.html and landing-page.html both carry
     --bad: #d03b3b, which reads 4.07:1 on this ground and FAILS the 4.5 that
     13px/600 body text needs — it never showed there because nothing on either
     page reads it. The contact form's error line does read it, and an error
     message is the one string on a form whose entire job is to be noticed. */
  --bad:         #e8635f;   /* 5.94:1 on #0b0c10 */
  --on-accent:   #fff;
  --btn-glow:    rgba(220, 68, 37, 0.35);
  --btn-glow-hi: rgba(220, 68, 37, 0.5);
  --radius:      14px;
  /* The primary button's arrow, as a mask rather than markup. Two reasons it is
     not an <svg> in the page: there are forty-odd call sites, and translate()
     swaps a label's inner HTML wholesale — an icon living inside the button
     would be deleted the first time a language pack had that button's string. */
  --btn-arrow:   url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 12h14M12 6l6 6-6 6'/%3E%3C/svg%3E");
  /* The other two glyphs a button can carry. Which one it gets is decided
     by what the button does, not by a class: see .btn::after below. */
  --btn-more:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9.5 5.5 6.5 6.5-6.5 6.5'/%3E%3C/svg%3E");
  --btn-mail:    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2.8' y='5.4' width='18.4' height='13.2' rx='2.2'/%3E%3Cpath d='m3.6 7.4 8.4 6.2 8.4-6.2'/%3E%3C/svg%3E");
  /* The primary button's two stops, named because the "Most people" badge now
     wears them too. They are NOT --live-grad: white text measures 1.79 to 3.96
     on the dark live gradient and fails on the light one against both white and
     near-black, while these were tuned for white and clear 4.66 to 6.72. */
  --cta-from:    #A83208;
  --cta-to:      #B85A0E;
  /* ONE container, named once. The front page and the sub-pages had drifted
     into two different spellings of it — max-width plus padding here, a
     width: min() there — which put the logo 40px apart on desktop and 4px
     apart on a phone, visibly jumping as you moved between them. The rule
     below is now identical in index.html and assets/pages.css. */
  --wrap-max:    1120px;
  --wrap-gutter: 24px;
  --font:        "Inter Tight", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono:        ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* One scale, used everywhere. Sub-pages are read, not scanned, so the body
     size is a notch up from the front page's. */
  --measure:     64ch;
  --gap:         clamp(3.5rem, 7vw, 5.5rem);

  color-scheme: dark;
}

/* The same three styles the front page offers, and the same reasoning: --ink is
   the channel every hairline and hover tint draws from, so one line per style
   moves all of them. Dark is the default and needs no attribute, which is what
   lets the pre-paint script in the shell stay three lines long. */
:root[data-theme="light"] {
  --bg:          #fbfbfd;
  --bg-raise:    #fff;
  --lift:        0 18px 44px rgba(14, 16, 24, 0.13);
  --surface:     #fff;
  --surface-2:   #f2f3f8;
  --ink:         14, 16, 24;
  --border:      rgba(var(--ink), 0.14);
  --border-soft: rgba(var(--ink), 0.09);
  --text:        #12141b;
  --sub:         #474b59;
  --muted:       #666b7a;
  /* Exactly the value asked for. 7.50:1 on this ground, against the 5.03 the
     blue read — the one style where the colour needs no lifting at all. */
  --accent:      #aa0000;
  --skip-bg: var(--accent);
  --accent-2:    #92240c;
  --accent-3:    #c33f6c;
  /* The middle stop is this style's own --accent-2, not the #6a5cd8 in the line
     that was handed over. That hex is the blue-violet the recolour exists to
     remove, and --grad is not only the 2px edge on .pull where the first stop
     is all you see: it is also the full border around /pricing/'s Pro card,
     where 60% of the ramp is the middle of the top and bottom edges. Restore it
     by putting #6a5cd8 back on the light line — nothing else depends on it. */
  --grad:        linear-gradient(100deg, #aa0000 10%, #92240c 60%, #c33f6c 110%);
  --live-grad:   linear-gradient(100deg, #A82E06 0%, #C2410C 48%, #D1590E 100%);
  /* Darker stops for the light ground — the dark style's orange on white is a
     bright stripe rather than a scrollbar. See the note in the dark palette. */
  --scroll-grad:      linear-gradient(180deg, #A82E06, #C2410C);
  --scroll-grad-hi:   linear-gradient(180deg, #C2410C, #D1590E);
  --scroll-grad-x:    linear-gradient(90deg, #A82E06, #C2410C);
  --scroll-grad-x-hi: linear-gradient(90deg, #C2410C, #D1590E);
  --scroll-thumb:     #C2410C;
  /* The kicker's orange, and the same one the wordmark and Live. wear: the
     middle stop of --live-grad in each style. Contrast against this style's
     own ground was measured, not assumed — 8.02, 5.01 and 10.45 to one,
     against the 4.5 that 12.5px bold needs. */
  --accent-warm: #C2410C;
  --good:        #0a7d2a;
  --bad:         #c02626;   /* 5.73:1 on #fbfbfd */
  --btn-glow:    rgba(58, 44, 38, 0.20);
  --btn-glow-hi: rgba(58, 44, 38, 0.30);
  color-scheme: light;
}

:root[data-theme="contrast"] {
  --bg:          #000;
  --bg-raise:    #08080b;
  --lift:        0 18px 50px rgba(0, 0, 0, 0.85);
  --surface:     #101015;
  --surface-2:   #17171e;
  --ink:         255, 255, 255;
  --border:      rgba(var(--ink), 0.38);
  --border-soft: rgba(var(--ink), 0.24);
  --text:        #fff;
  --sub:         #e8eaf1;
  --muted:       #c3c7d4;
  /* The same red lifted further, for black: 9.91:1, against the 9.96 the blue
     read. The reasoning is in the dark palette at the top of this file. */
  --accent:      #ff9494;
  --skip-bg: var(--accent);
  --accent-2:    #f6a897;
  --accent-3:    #ff9dc0;
  --on-accent:   #05070c;
  --grad:        linear-gradient(100deg, #ff9494 10%, #f6a897 60%, #ff9dc0 110%);
  --live-grad:   linear-gradient(100deg, #FF7A4D 0%, #FFA061 48%, #FFC48F 100%);
  /* Lifted for black, like everything else in this palette. */
  --scroll-grad:      linear-gradient(180deg, #FF7A4D, #FFA061);
  --scroll-grad-hi:   linear-gradient(180deg, #FFA061, #FFC48F);
  --scroll-grad-x:    linear-gradient(90deg, #FF7A4D, #FFA061);
  --scroll-grad-x-hi: linear-gradient(90deg, #FFA061, #FFC48F);
  --scroll-thumb:     #FFA061;
  /* The kicker's orange, and the same one the wordmark and Live. wear: the
     middle stop of --live-grad in each style. Contrast against this style's
     own ground was measured, not assumed — 8.02, 5.01 and 10.45 to one,
     against the 4.5 that 12.5px bold needs. */
  --accent-warm: #FFA061;
  --good:        #4fd97a;
  --bad:         #ff8080;   /* 8.65:1 on #000    */
  --btn-glow:    rgba(255, 131, 79, 0.45);
  --btn-glow-hi: rgba(255, 131, 79, 0.62);
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* Reserved permanently, not just while a popover is locking scroll — otherwise the scrollbar's own
   width appearing and disappearing shifts every fixed-width thing on the page sideways. */
html { scroll-behavior: smooth; scrollbar-gutter: stable; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 17px;
  line-height: 1.62;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
/* This file had NO button reset, so every <button> on every sub-page was
   drawn in the browser's own chrome. It shows worst on the style picker,
   whose buttons set neither a background nor a border and so came out as
   three solid grey boxes — the same control on the front page, which does
   carry this line, is three transparent icons in a pill. Same rule, same
   place in the cascade. */
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 3px; border-radius: 6px; }

.wrap { max-width: var(--wrap-max); margin-inline: auto; padding-inline: var(--wrap-gutter); }

/* Parked ABOVE the page rather than beside it. `left: -9999px` is the usual
   trick and it is wrong in an RTL document: the start edge is the right one, so
   the negative offset lands 9999px inside the scrollable area and every Arabic,
   Persian, Hebrew, Urdu and Yiddish page scrolled sideways by ten thousand
   pixels. Vertical overflow above the viewport creates no scrollbar in either
   direction, and needs no magic number. */
.skip {
  position: absolute; inset-inline-start: 0; top: 0; z-index: 99;
  transform: translateY(-110%);
  padding: 10px 16px; background: var(--skip-bg, var(--accent)); color: var(--on-accent); border-radius: 0 0 8px 0;
}
.skip:focus { transform: none; }

/* ---------- chrome, matched to index.html ---------- */

/* The two sticky heights, named once. .nav-in sets the bar's own height and
   that is now all of it — the hairline under the bar was removed on request,
   and this had to lose the pixel with it or the section strip, which hangs off
   --bar-h, would have floated one pixel clear of the bar it hangs from. The
   strip keeps its own underline and so keeps its odd pixel. */
:root { --bar-h: 60px; --tabs-h: 47px; }
@media (max-width: 900px) { :root { --bar-h: 56px; } }

.nav {
  position: sticky; top: 0; z-index: 50;
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  background: color-mix(in srgb, var(--bg) 82%, transparent);
}
/* The order Maciej asked for, left to right: the mark, the menu, Listen, the
   style picker, and the call to action hard against the right edge. It is set
   here rather than in the markup because the drawer needs the OTHER order —
   links, styles, button, stacked — and Listen has to stay on the phone bar
   rather than go into the drawer with them. */
.nav-in { display: flex; align-items: center; gap: 12px; height: 60px; }
.wordmark {
  display: flex; align-items: center; gap: 9px;
  font-weight: 700; font-size: 17px; letter-spacing: -0.02em; color: var(--text);
  /* The product's name is one word and may never be broken. It contains a
     hyphen, which is a legal break point to a browser, and the header is a flex
     row that squeezes the brand when the nav beside it is wide — so in every
     language whose nav labels run longer than English's, "dash-b" wrapped to
     "dash-" above "b" in the top-left corner. Measured across thirteen
     languages: five wrapped (de, el, fi, hu, ta) at 54px against English's
     27px, and the other eight fit only because their nav happens to be
     narrower, which is luck rather than design.

     flex-shrink is the half that matters. nowrap on its own leaves the element
     shrinkable, so the text stops wrapping and starts being CLIPPED instead —
     the same defect wearing different clothes, and harder to see. */
  white-space: nowrap; flex-shrink: 0;
}
.wordmark:hover { text-decoration: none; }
/* margin-inline-END: auto — everything after the links goes to the far edge in
   one move. An auto margin on the right-hand cluster instead breaks the moment
   Listen is hidden, which it is on every browser that cannot speak. */
.nav-links { display: flex; align-items: center; gap: 6px; margin-inline-start: 10px; margin-inline-end: auto; order: 1; }
.readaloud { order: 2; }
.styles { order: 3; }
.nav .btn-primary { order: 4; }
/* :not(.btn): this rule is for nav LINKS, and the header CTA is a button that
   happens to sit among them. Unscoped it outranks .btn-primary and paints the
   label muted grey. */
.nav-links > a:not(.btn), .nav-group > summary { font-size: 14px; color: var(--sub); transition: color .15s; }

/* The two menus are <details>. Everything below is appearance — the opening,
   the keyboard and the focus order are the element's own. */
.nav-group { position: relative; }
.nav-group > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 6px;
  padding: 7px 11px; border-radius: 9px;
}
.nav-group > summary::-webkit-details-marker { display: none; }
.nav-group > summary::after {
  content: ""; width: 6px; height: 6px; margin-block-start: -3px;
  border-inline-end: 1.6px solid currentColor; border-block-end: 1.6px solid currentColor;
  transform: translateY(-1px) rotate(45deg); transition: transform .18s;
}
.nav-group[open] > summary::after { transform: translateY(2px) rotate(-135deg); }
.nav-group > summary:hover,
.nav-group[open] > summary { color: var(--text); background: rgba(var(--ink), .05); }
/* The group holding the page you are on. Marked, not opened — see site_nav(). */
.nav-group[data-current] > summary { color: var(--text); }
.nav-group[data-current] > summary::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent-warm); margin-inline-end: 2px;
}

.nav-menu {
  position: absolute; inset-inline-start: 0; top: calc(100% + 10px);
  z-index: 60; width: min(52rem, calc(100vw - 2.5rem));
  display: grid; grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
  gap: 2px 6px; padding: 8px;
  background: var(--bg-raise); border: 1px solid var(--border);
  border-radius: 14px; box-shadow: 0 18px 50px rgba(0,0,0,.55);
}
/* The column count comes from the group's own length — see site_nav(). */
.nav-menu[data-cols="2"] { --cols: 2; width: min(36rem, calc(100vw - 2.5rem)); }
.nav-menu[data-cols="3"] { --cols: 3; }
/* The panel hangs ten pixels clear of the bar, which on a menu that opens by
   hover is ten pixels of nothing to cross. This bridges the gap so the pointer
   never leaves the group on the way down to it. */
.nav-menu::before {
  content: ""; position: absolute; inset-inline: 0; top: -11px; height: 11px;
}
/* Two short rows beat one wide panel that runs off the screen: the menus sit
   just right of the mark, so a 52rem panel stops fitting well before the phone
   breakpoint arrives — at 1121px the second menu had four pixels to spare. */
@media (max-width: 1160px) {
  .nav-menu, .nav-menu[data-cols="3"] { --cols: 2; width: min(34rem, calc(100vw - 2.5rem)); }
}
/* Two columns: the icon tile spans both rows, the label and blurb stack beside
   it. The children place themselves — no explicit grid-area anywhere — so the
   generated <a> keeps its markup order for a screen reader. */
.nav-menu a {
  display: grid; grid-template-columns: 2.35rem 1fr;
  column-gap: 11px; align-items: start;
  padding: 9px 11px; border-radius: 9px; color: var(--sub);
}
.nav-menu a:hover { text-decoration: none; background: var(--surface-2); }
.nav-menu a b { display: block; color: var(--text); font-size: 15px; font-weight: 600; }
.nav-menu a span { display: block; color: var(--muted); font-size: 13px; line-height: 1.45; }
.nav-menu a[aria-current="page"] { background: var(--surface-2); }
.nav-menu a[aria-current="page"] b { color: var(--accent-warm); }
/* rem, not em: the tile sits beside a 15px label and a 13px blurb, and an
   em-sized box would come out a different size in each menu row. */
.nav-ico {
  grid-row: 1 / span 2;
  display: grid; place-items: center;
  inline-size: 2.35rem; block-size: 2.35rem;
  border-radius: 10px;
  background: rgba(var(--ink), .05);
  color: var(--muted);
  transition: background .16s, color .16s;
}
.nav-ico svg { inline-size: 1.3rem; block-size: 1.3rem; }
.nav-menu a:hover .nav-ico { background: var(--accent-warm); color: var(--bg); }
.nav-menu a[aria-current="page"] .nav-ico { background: var(--accent-warm); color: var(--bg); }

.nav .btn { padding: 8px 16px; font-size: 14px; }
.nav-top { padding: 7px 11px; border-radius: 9px; }
.nav-top:hover { color: var(--text); text-decoration: none; background: rgba(var(--ink), .05); }

/* ---------- the bar, and the drawer it becomes ----------
 *
 * ONE nav, restyled — not a desktop copy and a mobile copy. On a wide screen
 * the toggle is hidden and .menu-body is the right-hand cluster; on a phone
 * the toggle is the hamburger and .menu-body drops out of the bar as a drawer.
 *
 * The toggle is a checkbox rather than a <details>, and that choice is the
 * whole reason this works. A closed <details> puts its body in a subtree the
 * engine skips for layout: forcing that body visible on a wide screen paints
 * it but never sizes it, so the bar measured zero wide and the menus, the
 * style picker and the call to action fell off the right edge of the page.
 * A checkbox leaves .menu-body an ordinary sibling that is always laid out,
 * and hides it only where we ask it to. Still no JavaScript in the path. */
/* display:none, not just invisible: on a wide screen the hamburger does not
   exist, and a focusable control that operates something nobody can see is a
   Tab stop that appears to do nothing. */
.menu-toggle { display: none; }
.burger { display: none; }
/* `contents`, so on a wide screen the drawer is not a box on the bar — it is
   just its three children sitting on the bar as flex items in their own right,
   which is what lets `order` interleave Listen between them. The phone rules
   below turn it back into a real element. */
.menu-body { display: contents; }

/* Under 900px the menus stop floating and drop into the drawer. A dropdown
   pinned to a 60px bar has nowhere to go on a phone, and 14 links inside one
   is a scroll trap; opened in flow they push the page down, which is what a
   phone can actually show.

   The .nav stays sticky here on purpose: the open drawer is positioned
   against it, so making the bar static would drop the drawer against the
   page instead of under the header. */
@media (max-width: 900px) {
  /* Logo left. Menu and Listen right, both icons only. Everything else —
     the two page menus, the style picker, the call to action — moves inside
     the drawer, because a phone bar that carries all of it carries none of
     it well. */
  .nav-in { flex-wrap: nowrap; height: 56px; gap: 10px; }
  /* Present and focusable, just not visible — the label beside it is what the
     eye gets, and the checkbox is what the keyboard gets. */
  .menu-toggle { display: block; position: absolute; opacity: 0; width: 0; height: 0; margin: 0; }
  .burger {
    display: grid; place-items: center; margin-inline-start: auto;
    width: 38px; height: 38px; border-radius: 10px;
    color: var(--sub); cursor: pointer;
    border: 1px solid var(--border);
    background: rgba(var(--ink), 0.03);
  }
  .burger svg { width: 19px; height: 19px; }
  .burger .burger-x { display: none; }
  /* The checkbox is invisible but still the focusable control, so the ring
     has to be drawn on the label that stands in for it. */
  .menu-toggle:focus-visible + .burger { outline: 2px solid var(--accent-2); outline-offset: 3px; }
  .menu-toggle:checked ~ .burger { color: var(--text); background: rgba(var(--ink), 0.08); }
  .menu-toggle:checked ~ .burger svg:not(.burger-x) { display: none; }
  .menu-toggle:checked ~ .burger .burger-x { display: block; }

  .menu-body { display: none; }
  .menu-toggle:checked ~ .menu-body {
    display: block;
    position: absolute; inset-inline: 0; top: 100%;
    max-height: calc(100svh - 56px); overflow-y: auto;
    padding: 14px clamp(18px, 5vw, 26px) 22px;
    background: var(--bg-raise);
    border-block: 1px solid var(--border-soft);
    box-shadow: var(--lift);
  }
  .menu-body .nav-links { display: grid; gap: 4px; inline-size: 100%; margin-inline-start: 0; }
  /* The current-group dot is an inline ::before, which on a horizontal bar
     costs nothing and in a stacked drawer indents one row out of three. The
     group still reads as current from its colour. */
  .menu-body .nav-group[data-current] > summary::before { display: none; }
  .menu-body .styles { margin-block: 14px 4px; width: max-content; }
  .menu-body .btn-primary { display: flex; margin-block-start: 12px; }

  .nav-group { position: static; }
  .nav-menu {
    position: static; inline-size: 100%; max-width: none;
    margin-block-start: 6px; box-shadow: none; background: transparent;
    border-color: transparent; padding: 0 0 4px 10px;
  }
  /* No floating panel here, so nothing to bridge — and an absolute box with no
     positioned ancestor would land somewhere it can only be in the way. */
  .nav-menu::before { display: none; }
  /* One column in a phone drawer: the panel is in flow here, so a second column
     is two half-width blurbs rather than a wider menu. Written against the same
     attribute selectors used above, because a media query adds no specificity
     and a bare `.nav-menu` would lose to them. */
  .nav-menu, .nav-menu[data-cols="2"], .nav-menu[data-cols="3"] { --cols: 1; width: auto; }

  /* A drawer is a touch surface, not a bar. Fourteen pixels reads well beside a
     wordmark on a wide screen and is small under a thumb, so every row in here
     steps up by 40% and takes the whole width of the drawer: the thing you press
     is the row, not the word inside it. The 48px floor is the tap target — at
     this size the text alone would clear it, but the floor keeps holding if the
     text ever shrinks back.
   Written through .nav-links rather than as a bare .menu-body .nav-top,
   because the base rule for these links is `.nav-links > a:not(.btn)` and
   a :not() counts as a class — the shorter selector loses to it. */
  .menu-body .nav-links > .nav-top,
  .menu-body .nav-group > summary {
    font-size: 19.6px; inline-size: 100%;
    padding: 12px; border-radius: 10px; min-block-size: 48px;
  }
  /* The chevron leaves the label and goes to the far edge, because the row is
     now wide enough that a marker tucked against the word reads as a typo. */
  .menu-body .nav-group > summary::after { margin-inline-start: auto; width: 8px; height: 8px; }
  /* The icon tile grows with the text it sits beside, and the column that holds
     it has to grow with the tile — the template is what sets the tile's width,
     so changing one without the other clips it. */
  .menu-body .nav-menu a {
    grid-template-columns: 2.9rem 1fr; align-items: center;
    padding: 12px; min-block-size: 56px;
  }
  .menu-body .nav-menu a b { font-size: 21px; }
  .menu-body .nav-menu a span { font-size: 15px; }
  .menu-body .nav-ico { inline-size: 2.9rem; block-size: 2.9rem; }
  .menu-body .nav-ico svg { inline-size: 1.6rem; block-size: 1.6rem; }

  /* Icon only, as asked — the word is what the accessible name is for. */
  /* Scoped to .nav-in so it beats the base rule on specificity rather than
     on source order — the base sits later in this file. */
  .nav-in .readaloud { width: 38px; height: 38px; padding: 0; justify-content: center; }
  .nav-in .readaloud .ra-bars { display: none; }
  /* The word is hidden from the eye but deliberately NOT from the
     accessibility tree. display:none removed it from both, which left this
     icon-only button with no accessible name at all on narrow screens --
     exactly the opposite of what the note above intends. Clipping it keeps
     the button named, in whatever language the page is in, without an
     aria-label that would pin the name to English on all 88 of them. */
  .nav-in .readaloud { position: relative; }
  .nav-in .readaloud #ra-label {
    position: absolute; width: 1px; height: 1px; padding: 0;
    margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
  }
  .nav-in .readaloud svg { width: 16px; height: 16px; }
}

/* ---------- the section strip ----------
 *
 * A page in Product or Build carries the rest of its own section as tabs under
 * the header. It is generated from tools/pages.py by site_tabs(), so a page
 * added there joins the strip without anybody editing markup.
 *
 * Sticky, at exactly the bar's height, and one z-index below it: the strip
 * hangs off the header rather than floating over the page, and the phone drawer
 * — which is a child of .nav and therefore in .nav's stacking context — comes
 * down over the top of it rather than behind it.
 */
.tabs {
  position: sticky; top: var(--bar-h); z-index: 40;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--border-soft);
}
/* .wrap AND the scroller, one element: the gutter has to be inside the
   scrollable box or the first tab starts hard against the screen edge on a
   phone. `contain` so a sideways flick through the tabs does not turn into the
   browser's back gesture halfway along. */
.tabs-in {
  display: flex; gap: 2px;
  overflow-x: auto; overscroll-behavior-inline: contain;
  scrollbar-width: none;
}
.tabs-in::-webkit-scrollbar { display: none; }
.tabs-in > a {
  flex: none; white-space: nowrap;
  padding: 12px 11px; font-size: 14px; color: var(--sub);
  /* Explicit, so --tabs-h is a fact rather than an estimate: 20 + 12 + 12 and
     the strip's own 2px border make the 46px that section's scroll-margin and
     the strip's own `top` are both written against — 44, plus this rule's own
     2px marker and the strip's 1px underline, is the 47 that --tabs-h says.
     The phone rule below changes the size of the text, not the height of the
     row. */
  line-height: 20px;
  border-block-end: 2px solid transparent;
  /* So the nudge below leaves a gutter's worth of the previous tab showing,
     which is what says the strip scrolls. */
  scroll-margin-inline: var(--wrap-gutter);
  transition: color .15s, border-color .15s;
}
.tabs-in > a:hover { color: var(--text); text-decoration: none; }
.tabs-in > a[aria-current="page"] {
  color: var(--text); font-weight: 600;
  border-block-end-color: var(--accent-warm);
}
/* Bigger under a thumb, for the same reason the drawer's rows are — and the
   strip is the one piece of navigation on a phone that is always on screen. */
@media (max-width: 900px) {
  .tabs-in > a { padding-inline: 13px; font-size: 15px; }
}

/* ---------- style picker and read-aloud ---------- */
.chrome-tools { display: flex; align-items: center; gap: 6px; }
.styles {
  display: flex; align-items: center; gap: 2px; padding: 3px;
  border: 1px solid var(--border); border-radius: 10px;
  background: rgba(var(--ink), 0.03);
}
.styles button {
  display: grid; place-items: center;
  width: 28px; height: 26px; border-radius: 7px;
  color: var(--muted);
}
.styles button svg { width: 15px; height: 15px; }
.styles button:hover { color: var(--text); background: rgba(var(--ink), 0.07); }
.styles button[aria-pressed="true"] {
  /* Ink against the header, whichever style is showing — see index.html. */
  color: var(--bg); background: var(--text);
}
/* The contrast style's own button says what it does by being the only thing
   on the bar drawn at full strength. */
.styles button[data-style="contrast"][aria-pressed="true"] {
  background: var(--text); color: var(--bg);
}
.readaloud {
  display: inline-flex; align-items: center; gap: 7px;
  height: 32px; padding: 0 11px;
  border: 1px solid var(--border); border-radius: 10px;
  color: var(--sub); font-size: 13px; font-weight: 600;
  background: rgba(var(--ink), 0.03);
}
.readaloud:hover { color: var(--text); border-color: rgba(var(--ink), 0.2); }
.readaloud svg { width: 14px; height: 14px; }
.readaloud .ra-stop { display: none; }
.readaloud[data-state="reading"] { color: var(--text); border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
.readaloud[data-state="reading"] .ra-play { display: none; }
.readaloud[data-state="reading"] .ra-stop { display: block; }
.readaloud[hidden] { display: none; }
/* The bars only move while speech is actually running. */
.ra-bars { display: inline-flex; align-items: flex-end; gap: 2px; height: 12px; }
.ra-bars i { width: 2px; height: 4px; border-radius: 1px; background: var(--accent); }
.readaloud[data-state="reading"] .ra-bars i { animation: ra 900ms ease-in-out infinite; }
.readaloud[data-state="reading"] .ra-bars i:nth-child(2) { animation-delay: 150ms; }
.readaloud[data-state="reading"] .ra-bars i:nth-child(3) { animation-delay: 300ms; }
@keyframes ra { 0%, 100% { height: 4px; } 50% { height: 12px; } }
/* What is being read, marked in place so a listener can follow along. */
.ra-now {
  background: color-mix(in srgb, var(--accent) 20%, transparent);
  border-radius: 4px; box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent);
}

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* The surfaces nobody draws — selection, scrollbar, caret. They ship with
   browser defaults that belong to no design system, and they are the cheapest
   tell that a page was built rather than assembled. */
::selection { background: color-mix(in srgb, var(--accent) 34%, transparent); color: var(--text); }
/* The scrollbar wears the brand gradient. Two implementations, because there is
   no one way to do this: WebKit and Blink take a real background-image on the
   thumb, and Firefox's standard `scrollbar-color` takes two COLOURS and nothing
   else — a gradient there is simply not a value the property accepts. So
   Firefox gets the middle stop, which is the colour the gradient is mostly made
   of, and nobody gets a browser default.

   `background-clip: padding-box` because the 3px border is what insets the
   thumb from the track, and a background-image would otherwise be painted under
   it and show through as a fringe.

   The @supports is load-bearing and was found by measuring rather than by
   reading: setting `scrollbar-color` on html at all makes Blink switch to the
   standard styling path and IGNORE every ::-webkit-scrollbar rule, so the first
   version of this had a flat orange bar at the default 15px width in Chrome and
   the gradient nowhere. The query is true only where ::-webkit-scrollbar is not
   a selector the engine knows — Firefox — so Firefox gets the flat colour and
   everything else keeps the gradient. */
@supports not selector(::-webkit-scrollbar) {
  html { scrollbar-color: var(--scroll-thumb) var(--bg); }
}
::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb {
  background-image: var(--scroll-grad);
  background-clip: padding-box;
  border-radius: 99px; border: 3px solid var(--bg);
}
::-webkit-scrollbar-thumb:hover { background-image: var(--scroll-grad-hi); }
/* A horizontal bar is wide and short, so the same gradient run vertically is
   one flat colour along its whole length. */
::-webkit-scrollbar-thumb:horizontal { background-image: var(--scroll-grad-x); }
::-webkit-scrollbar-thumb:horizontal:hover { background-image: var(--scroll-grad-x-hi); }
::-webkit-scrollbar-corner { background: var(--bg); }
input, textarea { caret-color: var(--accent); }

.btn {
  /* inline-flex, not inline-block: the primary button's arrow is a ::after box
     that has to sit on the label's centre line. Matches index.html's .btn. */
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  /* The same tracking index.html sets. It was missing here, so the one label a
     visitor is meant to read at a glance was set slightly differently on the
     front page and on every other page. */
  font-weight: 600; font-size: 15px; letter-spacing: -0.01em;
  padding: 9px 16px; border-radius: 10px; color: var(--text);
  border: 1px solid var(--border);
}
.btn:hover { text-decoration: none; border-color: rgba(var(--ink), .2); }
/* The gradient never stops moving, and the button leans into the cursor —
   the same rules index.html carries, because a CTA that behaves one way on the
   front page and another on /features/ is two buttons wearing one style. The
   drift is the page's own three stops travelling across a background twice the
   button's width: no new colour, no second animation. The lean is --pull-x/y,
   written by the script below and capped at 6px, on a long ease-out so the
   button arrives after the cursor rather than with it. */
.btn-primary {
  /* Sentence case at the heaviest weight the family has — see the long note in
     index.html. 900 is loaded deliberately; a weight the file does not hold is
     synthesised rather than drawn. */
  font-weight: 900;
  background: linear-gradient(-45deg, var(--cta-from), var(--cta-to), var(--cta-from));
  background-size: 400% 400%;
  background-position: 0% 50%;
  color: #fff; border-color: transparent;
  box-shadow: 0 4px 24px var(--btn-glow);
  animation: gradient 3s ease infinite;
  translate: var(--pull-x, 0) var(--pull-y, 0);
  transition: translate .28s cubic-bezier(0.22, 1, 0.36, 1), box-shadow .7s;
}
@keyframes gradient {
  0%   { background-position: 0% 50%; }
  50%  { background-position: 100% 50%; }
  100% { background-position: 0% 50%; }
}
/* EVERY button carries a glyph, and which one it carries is decided by what the
   button does rather than by a class somebody remembered to add.

   It has to come from CSS. Every one of these labels is tagged for translation
   and translate() swaps an element's inner HTML wholesale, so an <svg> written
   inside a button would be thrown away the first time the page shipped in a
   language — the same trap the generated menu documents.

   Three glyphs, three jobs: the long arrow on the call to action, which leaves
   for the designer and keeps its loop; an envelope on a mailto; and a chevron
   everywhere else, the quieter "there is more of this, one page along".
   `flex: none` because .btn is a flex row and an em-sized box would otherwise
   be shrunk by the label beside it. */
.btn::after {
  content: "";
  flex: none;
  inline-size: .85em; block-size: .85em;
  background: currentColor;
  -webkit-mask: var(--btn-icon, var(--btn-more)) center / contain no-repeat;
  mask: var(--btn-icon, var(--btn-more)) center / contain no-repeat;
  transition: transform .18s cubic-bezier(.22, 1, .36, 1);
}
/* The arrow belongs to a button that leaves for the designer, whatever it is
   wearing: /pricing/'s Free tier sends you there in a ghost button, and a
   chevron on it would have promised another page of this site. */
.btn-primary::after,
.btn[data-app]::after,
.btn[data-app-upgrade]::after,
.btn[data-app-design]::after { --btn-icon: var(--btn-arrow); }
.btn[href^="mailto:"]::after { --btn-icon: var(--btn-mail); }
/* `scale` is applied before `transform`, so both the keyframes' translateX and
   the chevron's lean run in the flipped coordinate space and the glyph moves
   towards the reading edge in either direction. One rule, no mirrored
   keyframes. */
[dir="rtl"] .btn::after { scale: -1 1; }
/* The call to action's arrow loops; a secondary button's chevron only leans. A
   plain nudge reads as a hover state, and that is all a secondary button is
   claiming. */
.btn:not(.btn-primary):hover::after { transform: translateX(2px); }
.btn-primary:hover::after { animation: btn-arrow .62s cubic-bezier(.65, 0, .35, 1); }
@keyframes btn-arrow {
  0%   { transform: translateX(0);     opacity: 1; }
  45%  { transform: translateX(.6em);  opacity: 0; }
  55%  { transform: translateX(-.5em); opacity: 0; }
  100% { transform: translateX(0);     opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .btn-primary { animation: none; translate: none; transition: none; }
  .btn-primary:hover::after { animation: none; }
}
.btn-primary:hover { text-decoration: none; box-shadow: 0 8px 34px var(--btn-glow-hi); }

/* ---------- page head ---------- */

.phead { padding-block: clamp(3.5rem, 9vw, 6rem) 0; }
.crumb {
  font-family: var(--mono); font-size: .75rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--accent-warm); margin: 0 0 1.2rem;
}
/* inherit, not a second copy of the colour: the crumb is one line of text that
   happens to have a link in the middle of it, and the two halves parting
   company is what a repeated literal eventually causes. */
.crumb a { color: inherit; }

h1 {
  font-size: clamp(2.4rem, 6vw, 3.9rem);
  line-height: 1.04; letter-spacing: -.032em; font-weight: 600;
  margin: 0 0 1rem; text-wrap: balance; max-width: 20ch;
}
/* The same device as `Live.` on the front page, so the same gradient. */
h1 em {
  font-style: normal;
  background: var(--live-grad); -webkit-background-clip: text; background-clip: text; color: transparent;
}
.standfirst { font-size: 1.2rem; color: var(--sub); max-width: var(--measure); margin: 0; }

.phead .actions { margin-top: 1.8rem; display: flex; flex-wrap: wrap; gap: .7rem; }

/* ---------- sections ---------- */

/* The header is sticky and 60px tall, so an in-page anchor lands with its
   heading underneath it. The contact page is the first sub-page to link to
   one of its own sections; the rule is on `section` rather than on that
   page so the next one to do it is already right. */
section { margin-top: var(--gap); scroll-margin-top: calc(var(--bar-h) + 16px); }
/* A section page carries a second sticky bar, and an anchor has to clear both.
   Asked of the document rather than set by a class on <body>, because the strip
   is generated and the body is not — a class would be a second place to
   remember, and the thing being described here is simply "is the strip there". */
body:has(.tabs) section { scroll-margin-top: calc(var(--bar-h) + var(--tabs-h) + 16px); }
section:last-of-type { margin-bottom: var(--gap); }

h2 {
  font-size: clamp(1.6rem, 3.4vw, 2.3rem);
  line-height: 1.14; letter-spacing: -.025em; font-weight: 600;
  margin: 0 0 .6rem; text-wrap: balance; max-width: 22ch;
}
h3 { font-size: 1.12rem; font-weight: 650; letter-spacing: -.01em; margin: 0 0 .35rem; }

.kicker {
  font-family: var(--mono); font-size: .72rem; letter-spacing: .15em;
  text-transform: uppercase; color: var(--accent-warm); margin: 0 0 .7rem;
}

p { max-width: var(--measure); margin: 0 0 1rem; color: var(--sub); }
p.lede { color: var(--text); font-size: 1.1rem; }
strong { color: var(--text); font-weight: 600; }
em.hl { font-style: normal; color: var(--text); font-weight: 600; }

ul, ol { max-width: var(--measure); padding-inline-start: 1.2rem; color: var(--sub); }
li { margin-bottom: .5rem; }

code {
  font-family: var(--mono); font-size: .87em;
  background: var(--surface-2); border: 1px solid var(--border-soft);
  padding: .08em .4em; border-radius: 5px; color: var(--sub);
}

/* ---------- cards ---------- */

/* 15.5rem, not the 17rem this used to say. Nothing about the cards changed —
   the container did: unifying .wrap took the sub-pages from 1152 to 1072, and
   four 17rem columns plus three 1rem gaps need 1136. The page would have
   dropped to three columns as a side effect of a header alignment fix, which
   is not what anyone asked for. 15.5rem needs 1040 and keeps the four. */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(15.5rem, 100%), 1fr));
  gap: 1rem;
  margin: 1.8rem 0 0;
}
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.3rem 1.35rem;
}
.card > :last-child { margin-bottom: 0; }

/* The ecosystem grid is six cards, and the page's default four columns would
   wrap them 4 + 2. Three columns fills both rows, which reads as one group
   rather than as a group and a leftover — the reason this rule was written when
   there were five, and the reason it still holds now there are six. */
#castomo .cards { grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr)); }
.card p { font-size: .96rem; }
a.card { display: block; color: inherit; }
a.card:hover { text-decoration: none; border-color: rgba(var(--ink), .2); }
/* The colour is unscoped and the layout is not, deliberately. .more is a span
   inside a card link on most pages and a standalone <a> on /pricing/; colouring
   only the card version left the same class reading blue there, which is the
   split this whole pass exists to remove. The sizing stays on the card variant
   because the pricing links are set at body size on purpose. */
.more { color: var(--accent-warm); }
a.card .more { font-size: .92rem; font-weight: 600; display: inline-block; margin-top: .5rem; }

/* The note under a block of cards, and the one under the pricing list. It had
   no rule at all: a plain paragraph in body colour, hard against the cards above
   it, which made it read as one more line of the last card rather than as a
   remark about all of them. A rule, a hairline and some air say what it is. */
.fine {
  margin-block: 2.6rem 0;
  padding-block-start: 1.5rem;
  border-block-start: 1px solid var(--border-soft);
  max-width: 68ch;
  font-size: .93rem; color: var(--muted);
}

.card .n {
  font-family: var(--mono); font-size: .72rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--accent-warm); display: block; margin-bottom: .5rem;
}

/* A card whose whole job is one number. */
.figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 1px; background: var(--border);
  border: 1px solid var(--border); border-radius: var(--radius);
  overflow: hidden; margin: 1.8rem 0 .7rem;
}
.figure { background: var(--surface); padding: 1.15rem 1.1rem; }
.figure b {
  display: block; font-size: 2rem; line-height: 1; font-weight: 700;
  letter-spacing: -.03em; font-variant-numeric: tabular-nums; color: var(--text);
}
.figure span {
  display: block; margin-top: .45rem; font-family: var(--mono);
  font-size: .7rem; letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}

/* ---------- the accented block, used sparingly ---------- */

.pull {
  border-inline-start: 2px solid transparent;
  border-image: var(--grad) 1;
  padding-inline-start: 1.3rem;
  margin: 2rem 0;
}
.pull p:first-child {
  font-size: clamp(1.3rem, 2.8vw, 1.7rem);
  line-height: 1.28; letter-spacing: -.02em; color: var(--text);
  font-weight: 650; max-width: 30ch;
}
.pull > :last-child { margin-bottom: 0; }

/* ---------- definition rows ---------- */

.rows { border-top: 1px solid var(--border); margin: 1.8rem 0 0; }
.row {
  display: grid; grid-template-columns: minmax(9rem, 15rem) 1fr;
  gap: .5rem 2rem;
  padding: 1.1rem 0; border-bottom: 1px solid var(--border-soft);
}
.row > h3 { margin: 0; }
.row > p { margin: 0; }
@media (max-width: 46rem) { .row { grid-template-columns: 1fr; } }

/* ---------- family list on /widgets and /design ---------- */

.families {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
  gap: .8rem; margin: 1.6rem 0 0;
}
.family {
  background: var(--surface); border: 1px solid var(--border-soft);
  border-radius: 10px; padding: .85rem .95rem;
}
.family b {
  font-size: .82rem; font-family: var(--mono); letter-spacing: .08em;
  text-transform: uppercase; color: var(--accent); font-weight: 600;
  display: flex; justify-content: space-between; gap: .5rem;
}
.family b span { color: var(--muted); font-variant-numeric: tabular-nums; }
.family p {
  margin: .5rem 0 0; font-family: var(--mono); font-size: .76rem;
  line-height: 1.7; color: var(--sub); word-spacing: .16em;
}

/* ---------- tables ---------- */

.scroll { overflow-x: auto; margin: 1.6rem 0; }
table { border-collapse: collapse; width: 100%; min-width: 30rem; font-size: .95rem; }
th, td { text-align: start; padding: .65rem .8rem; border-bottom: 1px solid var(--border-soft); vertical-align: top; color: var(--sub); }
th {
  font-family: var(--mono); font-size: .68rem; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); white-space: nowrap;
  border-bottom: 1px solid var(--border);
}
td strong { color: var(--text); }
td.num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* ---------- closing call to action ---------- */

.closer {
  margin-top: var(--gap);
  background: var(--bg-raise);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: clamp(1.8rem, 4vw, 2.8rem);
  text-align: center;
}
.closer h2 { margin-inline: auto; }
.closer p { margin-inline: auto; }
.closer .actions { margin-top: 1.4rem; display: flex; flex-wrap: wrap; gap: .7rem; justify-content: center; }

/* ---------- footer, matched to index.html ---------- */

/* Every destination the header holds, listed flat. A footer is where somebody
   looks once the menus have closed and they still have not found the page,
   so nothing here opens, hovers or hides. */
/* padding-BLOCK, never the shorthand: these elements are also .wrap, and
   `padding: 56px 0 44px` silently zeroes the 24px inline padding .wrap gives
   them. That is what glued the footer to both edges of a phone. */
.foot-top {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 3.5fr);
  gap: clamp(38px, 5vw, 64px); padding-block: 76px 56px;
}
.foot-brand p {
  margin: 14px 0 20px; max-width: 34ch;
  font-size: 0.93rem; color: var(--sub); text-wrap: pretty;
}
/* Four columns: one per header menu, and a fourth holding Info and Legal
   stacked. Those two are four links and three where each menu above is nine, so
   a column each gave them the same rank as the product and made the footer read
   as five things of equal weight when it is three and a bit. */
.foot-links {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 30px 24px;
}
.foot-col h2 {
  font-size: 0.72rem; font-weight: 700; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 14px;
}
.foot-col ul { list-style: none; display: grid; gap: 9px; }
/* The second heading in a shared column. Margin above rather than a rule or a
   gap on the parent, because only the column that has two of them needs it. */
.foot-col .foot-next { margin-block-start: 26px; }
.foot-col a { color: var(--sub); font-size: 0.92rem; }
.foot-col a:hover { color: var(--text); }
@media (max-width: 900px) {
  .foot-top { grid-template-columns: 1fr; gap: 36px; padding-block: 56px 40px; }
  .foot-links { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px 20px; }
}
@media (max-width: 560px) {
  .foot-links { grid-template-columns: 1fr 1fr; }
}

footer {
  margin-top: var(--gap);
  border-top: 1px solid var(--border-soft);
  padding: 1.6rem 0 4rem;
  position: relative;
}
/* Air around the sign-off, matching the front page: the company notice above
   it is a wall of small grey type and the two ran together. */
.foot-in {
  display: flex; align-items: center; gap: .7rem 1rem; flex-wrap: wrap;
  margin-block-start: 2rem; padding-block-start: 1.6rem;
  border-block-start: 1px solid var(--border-soft);
  color: var(--muted); font-size: .88rem;
}
.foot-right { margin-inline-start: auto; }
.langs {
  max-width: var(--wrap-max); margin-inline: auto; margin-block-end: 1.2rem;
  padding-inline: var(--wrap-gutter);
}
.lang-toggle {
  display: inline-flex; align-items: center; gap: .5rem;
  background: transparent; border: 1px solid var(--border);
  color: var(--sub); border-radius: 9px; padding: .4rem .8rem;
  font: inherit; font-size: .88rem; cursor: pointer;
}
.lang-toggle svg { width: 16px; height: 16px; }
/* [hidden] FIRST, and it is not optional. The attribute is only a UA rule
   setting display:none, so the `display: grid` below outranks it — the list
   sat permanently open on all thirteen sub-pages, 802px of it, while the
   button beside it said aria-expanded="false" and the first click closed
   what was never open. The front page had this rule and the sub-pages did
   not, which is the whole difference between the two. */
.lang-list[hidden] { display: none; }
.lang-list {
  list-style: none; margin: 12px 0 0; padding: 10px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 2px;
  /* Bounded, because 88 languages is a page of its own otherwise. */
  max-height: 320px; overflow-y: auto;
  border: 1px solid var(--border-soft); border-radius: 12px;
  background: var(--surface);
}
.lang-list a {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 10px; border-radius: 7px;
  font-size: 13px; color: var(--sub); text-decoration: none;
}
.lang-list a:hover { background: var(--bg-raise); color: var(--text); text-decoration: none; }
.lang-list a[aria-current="true"] { color: var(--text); background: var(--bg-raise); font-weight: 650; }

/* One fixed slot whether it holds a flag or a code, so the names line up on a
   single edge down every column. Emoji flags are the only zero-request way to
   draw 88 of them, and they are not universal: Windows has no flag glyphs and
   falls back to the two letters, which lands close enough to the code chip
   beside it to read as deliberate. */
.lang-flag {
  /* rem, not em: the code chip sets its own smaller font-size, and an
     em slot would shrink with it — the flags measured 18px wide and the
     codes 12px, so the names sat on two different edges. */
  flex: 0 0 auto; inline-size: 1.4rem; text-align: center;
  font-size: 1.05em; line-height: 1;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif;
}
.lang-flag.is-code {
  font-family: var(--mono); font-size: .68em; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
}
.lang-name { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The button wears the same flag-then-name pair the rows do. */
.lang-current { display: inline-flex; align-items: center; gap: 7px; }

@media (max-width: 40rem) {
  .nav-links { gap: 8px; }
  /* The rule that stood here hid every .nav-links a that was not the button.
     It was written when those links were three, and it now matches all
     thirteen inside the menus as well: on a phone both menus opened empty.
     The 900px block above already stacks the header, so nothing needs hiding. */
  .foot-right { margin-inline-start: 0; }
}

/* ---------- the full-bleed picture, with a line over it ----------
 *
 * The one photograph on a use-case page, and the only place on the sub-pages
 * where anything breaks out of .wrap. It is the emotional half of the argument:
 * the boards below it say what the product does, and this says who it is for.
 *
 * Built here rather than borrowed from index.html's .panel/.shot, which do not
 * exist in this stylesheet — the front page carries its own CSS and always has.
 * Same idea, a third of the rules: no parallax, no LQIP variable, no reveal.
 */
.showpiece {
  position: relative; isolation: isolate;
  /* Full width by being OUTSIDE .wrap in the markup, and by nothing else. The
     first version did the usual `margin-inline: calc(50% - 50vw); width: 100vw`
     and that was wrong here twice over: this figure is already a top-level
     child of <main>, and 100vw counts the scrollbar — so on any page long
     enough to scroll it overflowed by exactly the scrollbar's width and grew a
     horizontal one to prove it. Seen in a screenshot, which is the only way
     that bug is ever seen. */
  /* margin-inline: 0 because this is a <figure>, and the browser's own
     stylesheet gives one 40px on each side — which is what was left over
     after the vw trick came out. */
  margin-inline: 0; margin-block: clamp(3rem, 8vw, 5.5rem) 0;
}
/* At the top of the page it sits straight under the section strip, so it has no
   room to ask for above it. `:first-child` rather than a class, because "is it
   the first thing on the page" is the actual condition and the markup already
   says so. */
.showpiece:first-child { margin-block-start: 0; }
.showpiece:first-child + .phead { padding-block-start: clamp(2.5rem, 6vw, 4rem); }
/* Mid-page, the panel needs room below as well — the .usecase that follows
   brings its own margin-top, but that is row-to-row spacing, not the breath a
   full-bleed photograph asks for. `:not(:first-child)` rather than touching
   the base rule, because the language copies still lead with this figure and
   a bottom margin there would push the .phead down on 77 pages this English
   iteration is not supposed to move. */
.showpiece:not(:first-child) { margin-block-end: clamp(1.4rem, 3vw, 2rem); }
.showpiece {
  display: grid; place-items: center;
  min-block-size: clamp(20rem, 46vw, 34rem);
  overflow: hidden;
}
.showpiece img {
  position: absolute; inset: 0; z-index: -2;
  inline-size: 100%; block-size: 100%;
  object-fit: cover; object-position: var(--focus, 50% 45%);
}
/* Dark enough for white type at any size, and warm rather than grey so the
   picture still looks like the site. Measured per page, not assumed. */
.showpiece::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(to top, rgba(6, 7, 10, .88) 0%, rgba(6, 7, 10, .56) 46%, rgba(6, 7, 10, .74) 100%),
    radial-gradient(120% 90% at 50% 50%, rgba(6, 7, 10, .28), rgba(6, 7, 10, .70));
}
.showpiece-say {
  position: relative; text-align: center;
  padding: clamp(2.5rem, 7vw, 5rem) var(--wrap-gutter);
  /* rem, not ch. `ch` resolves against THIS element's font-size, which is the
     body's 16px, not the 51px of the line inside it — so 30ch came out about
     250px wide and broke "Time to make your fridge door digital." over four
     lines. The measure that matters belongs to the <p>, and it is set there. */
  max-width: 54rem;
}
.showpiece-say p {
  font-size: clamp(1.9rem, 4.6vw, 3.2rem); line-height: 1.08;
  letter-spacing: -.03em; font-weight: 600; color: #fff;
  /* Two lines at most for a catch phrase, and `balance` to decide where. */
  max-width: 17ch; margin-inline: auto;
  margin-block: 0; text-wrap: balance;
}
.showpiece-say small {
  display: block; margin-top: 1.1rem; font-size: 1.02rem; line-height: 1.5;
  color: rgba(255, 255, 255, .84); letter-spacing: 0; font-weight: 400;
  /* Its own measure, and a wider one — it is a sentence, not a headline. The
     ch here is honest because this element sets its own font-size. */
  max-width: 52ch; margin-inline: auto; text-wrap: pretty;
}
/* The credit for whatever the page is showing, generated per page — see
   compose(). It sits with the rest of the footer's small print. */
.credits { font-size: .78rem; color: var(--muted); margin: 1.1rem 0 0; }
.credits a { color: var(--sub); }

/* ---------- use-case pages ----------
 *
 * The picture on these pages is a BOARD, not a photograph, and that is a
 * decision rather than a shortcut. A stock photograph of a family says nothing
 * about what this product does for a family; a board with the child's drawing,
 * the dentist on Tuesday and the school trip money on it says the whole thing
 * in one glance. It also costs no bytes on a site that spent a day getting
 * fast, and it cannot go out of date the way a photograph of somebody's kitchen
 * does.
 *
 * Every tile here is a widget that exists — the names are taken from the
 * catalogue, not invented for the picture. That is the property that makes this
 * an illustration rather than a promise.
 */
.board {
  margin: 0;
  background: var(--bg-raise);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .7rem;
  overflow: hidden;
}
/* The chrome a real board carries, so the thing reads as a screen rather than
   as a diagram: who it belongs to, and who can see it. */
.board-bar {
  display: flex; align-items: center; gap: .5rem;
  padding: 0 .25rem .6rem; font-size: .78rem; color: var(--muted);
}
.board-bar b { color: var(--text); font-weight: 600; letter-spacing: -.01em; }
.board-bar .tag {
  margin-inline-start: auto; font-family: var(--mono); font-size: .62rem;
  letter-spacing: .1em; text-transform: uppercase;
  border: 1px solid var(--border); border-radius: 99px; padding: .15rem .5rem;
}
.board-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent-warm); flex: none; }

.board-grid {
  display: grid; grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: 3.1rem; gap: .38rem;
}
.tile {
  grid-column: span var(--w, 4); grid-row: span var(--h, 1);
  min-inline-size: 0; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center;
  gap: .1rem; padding: .45rem .6rem;
  background: var(--surface); border-radius: 9px;
  border: 1px solid var(--border-soft);
}
.tile b {
  font-size: .82rem; font-weight: 600; letter-spacing: -.01em; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* A tile two rows tall has room for two lines, so a long label there should
   wrap rather than be cut off — which is the only place a sentence ever goes on
   one of these boards. One row stays nowrap: wrapping in 3.1rem just clips
   lower down instead. */
.h2 > b, .h3 > b { white-space: normal; overflow-wrap: anywhere; }
.tile span {
  font-family: var(--mono); font-size: .58rem; letter-spacing: .09em;
  text-transform: uppercase; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.w2 { --w: 2; } .w3 { --w: 3; } .w4 { --w: 4; } .w5 { --w: 5; }
.w6 { --w: 6; } .w7 { --w: 7; } .w8 { --w: 8; } .w9 { --w: 9; } .w12 { --w: 12; }
.h2 { --h: 2; } .h3 { --h: 3; }
/* One tile per board carries the accent, and only one. A board where everything
   is highlighted is a board where nothing is. */
.tile-lead {
  background: linear-gradient(140deg, color-mix(in srgb, var(--accent-warm) 22%, var(--surface)), var(--surface));
  border-color: color-mix(in srgb, var(--accent-warm) 34%, transparent);
}
.tile-lead b { color: var(--text); }
.tile-big b { font-size: 1.5rem; line-height: 1.04; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
/* A big number and its label do not both fit in a one-row tile: 24px of digit
   plus 13px of caption is 39 into 35, and the caption was being clipped by the
   overflow that keeps long titles from breaking the grid. Two rows get the full
   size; one row gets a number that still reads as the loud thing on the tile. */
.tile-big:not(.h2):not(.h3) b { font-size: 1.15rem; }
.tile-quiet { background: rgba(var(--ink), .02); border-style: dashed; }
.tile-quiet b { color: var(--sub); font-weight: 500; }
/* A chart, drawn as bars rather than as an image: five divs and a flexbox say
   "there is a chart here" at this size as well as a picture would. */
/* Label on top, bars filling whatever is left — so the same tile reads as a
   chart whether it is one row tall or two, rather than being drawn at a fixed
   height and squashed in the short one. */
.tile-chart { justify-content: flex-start; gap: .3rem; }
.bars { display: flex; align-items: flex-end; gap: .18rem; flex: 1 1 auto; min-block-size: 1.2rem; }
.bars i {
  flex: 1; border-radius: 2px 2px 0 0;
  background: color-mix(in srgb, var(--accent-warm) 62%, transparent);
  block-size: var(--v, 50%);
}
/* A picture, standing in for a picture. Three shapes and two crayon strokes,
   which at tile size is enough to read as "a child drew this" — and is honest
   in a way a photograph of somebody else's child would not be. */
.tile-art {
  justify-content: flex-end;
  background:
    linear-gradient(72deg, transparent 46%, color-mix(in srgb, var(--accent-warm) 30%, transparent) 46% 49%, transparent 49%),
    linear-gradient(-64deg, transparent 58%, color-mix(in srgb, var(--good) 26%, transparent) 58% 60.5%, transparent 61%),
    radial-gradient(circle at 30% 30%, color-mix(in srgb, var(--accent-3) 44%, transparent) 0 7%, transparent 7.5%),
    radial-gradient(circle at 62% 44%, color-mix(in srgb, var(--accent-warm) 38%, transparent) 0 9%, transparent 9.5%),
    radial-gradient(circle at 78% 24%, color-mix(in srgb, var(--accent-2) 40%, transparent) 0 5%, transparent 5.5%),
    linear-gradient(120deg, color-mix(in srgb, var(--accent-2) 14%, var(--surface)), var(--surface));
}

/* The alternating rows. Picture, then words, then the other way round — and it
   is `direction` on the row rather than `order` on the children, so the reading
   order a screen reader gets never changes no matter which side the board is
   on. */
.usecase {
  display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: clamp(1.4rem, 4vw, 3rem); align-items: center;
  margin-top: clamp(2.6rem, 6vw, 4.4rem);
}
.usecase:nth-of-type(even) { direction: rtl; }
.usecase:nth-of-type(even) > * { direction: ltr; }
.usecase h3 {
  font-size: clamp(1.25rem, 2.6vw, 1.6rem); line-height: 1.16;
  letter-spacing: -.022em; margin: 0 0 .6rem; text-wrap: balance;
}
.usecase p { color: var(--sub); margin: 0 0 .8rem; }
.usecase p:last-child { margin-bottom: 0; }
.usecase .n {
  font-family: var(--mono); font-size: .68rem; letter-spacing: .13em;
  text-transform: uppercase; color: var(--accent-warm); display: block; margin-bottom: .5rem;
}

/* The one big board at the top of the page: the same component, given room. */
.showcase { margin-top: clamp(2rem, 5vw, 3.2rem); }
.showcase .board-grid { grid-auto-rows: 3.6rem; }
.showcase-say { max-width: 46ch; margin: 0 auto clamp(1.4rem, 3vw, 2rem); text-align: center; }
.showcase-say h2 { margin-inline: auto; max-width: 20ch; }
.showcase-say p { margin-inline: auto; }

/* ---------- the preset bento ----------
 *
 * Eight ready-made designs per use-case page, bento-fashion: one hallmark card
 * at double width, seven at single. Every card is an <a> whose href opens the
 * REAL design in the app, and the id it names is gated by
 * tools/check_design_links.py at build and at publish — so a card can only
 * promise a board the library actually holds.
 *
 * The icon is CSS (--pi, a mask on the card) rather than an <svg> in the
 * markup, for the same reason the buttons' glyphs are: translate() swaps an
 * element's inner HTML wholesale, and a markup icon dies the first time a
 * language pack holds that card's string. The chip and the ghost in the corner
 * both read the one --pi the card's i-* class sets. */
.presets { padding-block-start: clamp(2.6rem, 6vw, 4rem); }
.pgrid {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: clamp(.65rem, 1.4vw, .95rem);
  margin-block-start: clamp(1.6rem, 3vw, 2.2rem);
}
.pcard {
  grid-column: span 2;
  position: relative; isolation: isolate; overflow: hidden;
  display: flex; flex-direction: column;
  padding: 1.3rem 1.35rem 1.2rem;
  min-block-size: 12.5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: inherit; text-decoration: none;
  transition: translate .3s cubic-bezier(.22, 1, .36, 1), border-color .3s, box-shadow .3s;
}
.pcard:hover, .pcard:focus-visible {
  translate: 0 -4px; border-color: rgba(var(--ink), .24);
  box-shadow: var(--lift); text-decoration: none;
}
.pcard-hero { grid-column: span 4; }
/* The oversized ghost of the card's own icon, bleeding off the far corner. It
   is decoration with a second job: when the screenshots the cards will one day
   carry arrive, this is the layer they replace. */
.pcard::before {
  content: ""; position: absolute; z-index: -1;
  inset-inline-end: -1.8rem; inset-block-end: -1.8rem;
  inline-size: 8.5rem; block-size: 8.5rem;
  background: rgba(var(--ink), .045);
  -webkit-mask: var(--pi) center / contain no-repeat;
  mask: var(--pi) center / contain no-repeat;
  rotate: -10deg;
  transition: rotate .5s cubic-bezier(.22, 1, .36, 1), scale .5s cubic-bezier(.22, 1, .36, 1),
              background-color .3s;
}
.pcard:hover::before {
  rotate: -2deg; scale: 1.05;
  background: color-mix(in srgb, var(--accent-warm) 14%, transparent);
}
/* A sheen that sweeps once per hover. The transition sits on the hover state
   only, so leaving the card snaps the band back off-canvas invisibly instead
   of dragging it backwards across the surface. */
.pcard::after {
  content: ""; position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(115deg, transparent 32%, rgba(var(--ink), .05) 47%, transparent 62%);
  translate: -120% 0;
}
.pcard:hover::after { translate: 120% 0; transition: translate .8s cubic-bezier(.3, .7, .4, 1); }
.pcard-ic {
  inline-size: 44px; block-size: 44px; flex: none;
  border-radius: 13px; display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent-warm) 13%, transparent);
  transition: scale .3s cubic-bezier(.22, 1, .36, 1), rotate .3s cubic-bezier(.22, 1, .36, 1);
}
.pcard-ic::after {
  content: ""; inline-size: 23px; block-size: 23px;
  background: var(--accent-warm);
  -webkit-mask: var(--pi) center / contain no-repeat;
  mask: var(--pi) center / contain no-repeat;
}
.pcard:hover .pcard-ic { scale: 1.08; rotate: -4deg; }
.pcard b {
  display: block; margin-block: .9rem .3rem;
  font-size: 1.09rem; font-weight: 700; letter-spacing: -.015em; color: var(--text);
}
.pcard p { margin: 0 0 .4rem; font-size: .9rem; line-height: 1.5; color: var(--sub); max-width: 46ch; }
.pcard-hero b { font-size: clamp(1.25rem, 2.4vw, 1.55rem); }
.pcard-hero p { font-size: .98rem; max-width: 52ch; }
/* The invitation, revealed on hover — and always there where hover does not
   exist, because a reward that touch can never reach is not a reward. It holds
   its space at opacity 0, so nothing in the card shifts. */
.pcard-go {
  margin-block-start: auto; padding-block-start: .8rem;
  display: inline-flex; align-items: center; gap: .45rem;
  font-size: .88rem; font-weight: 700; color: var(--accent-warm);
  opacity: 0; translate: 0 7px;
  transition: opacity .25s, translate .3s cubic-bezier(.22, 1, .36, 1);
}
.pcard-go::after {
  content: ""; inline-size: .82em; block-size: .82em; background: currentColor;
  -webkit-mask: var(--btn-arrow) center / contain no-repeat;
  mask: var(--btn-arrow) center / contain no-repeat;
}
.pcard:hover .pcard-go, .pcard:focus-visible .pcard-go { opacity: 1; translate: 0 0; }
[dir="rtl"] .pcard-go::after { scale: -1 1; }
@media (hover: none) { .pcard-go { opacity: 1; translate: none; } }
@media (max-width: 60rem) {
  .pgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pcard { grid-column: span 1; min-block-size: 11rem; }
  .pcard-hero { grid-column: span 2; }
}
@media (max-width: 34rem) {
  .pgrid { grid-template-columns: 1fr; }
  .pcard, .pcard-hero { grid-column: auto; min-block-size: 0; }
}
/* The 52 glyphs the cards draw from — same contract as --btn-arrow at the top
   of this file: a 24×24 stroke drawing carried as a mask, where only the alpha
   matters. Named for what they depict, not for the card that wears them,
   because most are worn by more than one. */
.i-sun      { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='4.2'/%3E%3Cpath d='M12 2.6v2.4M12 19v2.4M2.6 12h2.4M19 12h2.4M5.3 5.3l1.7 1.7M17 17l1.7 1.7M18.7 5.3 17 7M7 17l-1.7 1.7'/%3E%3C/svg%3E"); }
.i-run      { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 9.8v4.4M6 7v10M18 7v10M21.5 9.8v4.4M6 12h12'/%3E%3C/svg%3E"); }
.i-spark    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3c.7 4.8 2.7 6.8 7.5 7.5-4.8.7-6.8 2.7-7.5 7.5-.7-4.8-2.7-6.8-7.5-7.5C9.3 9.8 11.3 7.8 12 3ZM18.5 15.5l.5 1.9 1.9.5-1.9.5-.5 1.9-.5-1.9-1.9-.5 1.9-.5Z'/%3E%3C/svg%3E"); }
.i-pot      { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 10.5h15M6 10.5V16a3.5 3.5 0 0 0 3.5 3.5h5A3.5 3.5 0 0 0 18 16v-5.5M10 7.2C10 6 10 5.4 11 4.5M14.5 7.2c0-1.2 0-1.8-1-2.7'/%3E%3C/svg%3E"); }
.i-coin     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.6'/%3E%3Cpath d='M14.8 9.2A3.3 3.3 0 0 0 9.4 12a3.3 3.3 0 0 0 5.4 2.8M7.8 10.8h4.4M7.8 13.2h4.4'/%3E%3C/svg%3E"); }
.i-rings    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='3.6'/%3E%3Cpath d='M12 4.4a7.6 7.6 0 1 1-7.6 7.6'/%3E%3C/svg%3E"); }
.i-cloud    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.2 18.5h8.6a4.2 4.2 0 0 0 .9-8.3 5.7 5.7 0 0 0-11-1.4 4 4 0 0 0 1.5 9.7Z'/%3E%3C/svg%3E"); }
.i-cal      { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='5' width='17' height='15.5' rx='2.5'/%3E%3Cpath d='M3.5 9.8h17M8 2.8v4.2M16 2.8v4.2'/%3E%3C/svg%3E"); }
.i-pin      { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9.4 3.5h5.2l-.7 5 2.6 2.6c.5.5.2 1.4-.6 1.4H7.5c-.8 0-1.1-.9-.6-1.4l2.6-2.6ZM12 12.5v8'/%3E%3C/svg%3E"); }
.i-brush    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.5 3.5c-4.3.2-8.5 3-11 6.9l3.6 3.6c3.9-2.5 6.7-6.7 7.4-10.5ZM8.4 14.5c-2.6 0-4.4 1.9-4.9 5.5 3.6-.5 5.5-2.3 5.5-4.9'/%3E%3C/svg%3E"); }
.i-photo    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='4.8' width='17' height='14.4' rx='2.5'/%3E%3Cpath d='m6 16.5 4.2-4.7 3.1 3.4 2-2.2 2.7 3'/%3E%3Ccircle cx='9' cy='9' r='1.3'/%3E%3C/svg%3E"); }
.i-route    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='5.8' cy='17.8' r='2.3'/%3E%3Ccircle cx='18.2' cy='6.2' r='2.3'/%3E%3Cpath d='M8.1 17.8H14a3.4 3.4 0 0 0 3.4-3.4V8.5'/%3E%3C/svg%3E"); }
.i-list     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M8.8 6h11.7M8.8 12h11.7M8.8 18h11.7M3.6 6h.9M3.6 12h.9M3.6 18h.9'/%3E%3C/svg%3E"); }
.i-cake     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.5 12h15a1.5 1.5 0 0 1 1.5 1.5v6.5H3v-6.5A1.5 1.5 0 0 1 4.5 12ZM3 16.2c1.5 1.3 3 1.3 4.5 0s3-1.3 4.5 0 3 1.3 4.5 0 3-1.3 4.5 0M12 8.8V12M7.5 9.8V12M16.5 9.8V12M12 6.2v.01M7.5 7.2v.01M16.5 7.2v.01'/%3E%3C/svg%3E"); }
.i-book     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 6.2C10 4.5 7.1 4 4 4.2v13.6c3.1-.2 6 .3 8 2 2-1.7 4.9-2.2 8-2V4.2c-3.1-.2-6 .3-8 2ZM12 6.2v13.6'/%3E%3C/svg%3E"); }
.i-compass  { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.6'/%3E%3Cpath d='m15.7 8.3-2.2 5.2-5.2 2.2 2.2-5.2Z'/%3E%3C/svg%3E"); }
.i-map      { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 4 3.5 5.8v14L9 18l6 2 5.5-1.8v-14L15 6ZM9 4v14M15 6v14'/%3E%3C/svg%3E"); }
.i-dice     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='4' width='16' height='16' rx='3.5'/%3E%3Cpath d='M8.3 8.3v.01M15.7 8.3v.01M12 12v.01M8.3 15.7v.01M15.7 15.7v.01'/%3E%3C/svg%3E"); }
.i-percent  { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='7.4' cy='7.4' r='2.5'/%3E%3Ccircle cx='16.6' cy='16.6' r='2.5'/%3E%3Cpath d='M18.8 5.2 5.2 18.8'/%3E%3C/svg%3E"); }
.i-trophy   { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7.5 4h9v5.5a4.5 4.5 0 0 1-9 0ZM7.5 5.5H4.2V7a3.6 3.6 0 0 0 3.4 3.6M16.5 5.5h3.3V7a3.6 3.6 0 0 1-3.4 3.6M12 14v3M8.5 20.5h7M9.8 17h4.4v3.5H9.8Z'/%3E%3C/svg%3E"); }
.i-film     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='4.5' width='17' height='15' rx='2.5'/%3E%3Cpath d='M7.6 4.5v15M16.4 4.5v15M3.5 9.3h4.1M3.5 14.7h4.1M16.4 9.3h4.1M16.4 14.7h4.1'/%3E%3C/svg%3E"); }
.i-gift     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.2 11.2h15.6v7.4a1.9 1.9 0 0 1-1.9 1.9H6.1a1.9 1.9 0 0 1-1.9-1.9ZM3.4 7.4h17.2v3.8H3.4ZM12 7.4v13.1M12 7.4C10.2 7.4 7.8 6.7 7.8 5s2.9-1.9 4.2 2.4c1.3-4.3 4.2-4.1 4.2-2.4S13.8 7.4 12 7.4Z'/%3E%3C/svg%3E"); }
.i-kanban   { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4.2' y='4.2' width='5.8' height='15.6' rx='1.4'/%3E%3Crect x='14' y='4.2' width='5.8' height='9.6' rx='1.4'/%3E%3C/svg%3E"); }
.i-people   { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='9' cy='8.4' r='3.3'/%3E%3Cpath d='M3.4 19.6c.5-3 2.7-4.8 5.6-4.8s5.1 1.8 5.6 4.8M15.9 5.4a3.3 3.3 0 0 1 0 6M17.4 14.9c1.9.7 3 2.3 3.3 4.7'/%3E%3C/svg%3E"); }
.i-badge    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='3.5' width='16' height='17' rx='2.5'/%3E%3Ccircle cx='12' cy='9.4' r='2.4'/%3E%3Cpath d='M7.5 17.5c.5-2.2 2.3-3.4 4.5-3.4s4 1.2 4.5 3.4'/%3E%3C/svg%3E"); }
.i-pulse    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 12.5h3.8l2.4-6 3.9 11 2.3-5h5.6'/%3E%3C/svg%3E"); }
.i-plane    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 3.5 3 10.8l6.2 2.5L12 21l3.1-6.4ZM9.2 13.3 21 3.5'/%3E%3C/svg%3E"); }
.i-chart    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.6 20.5v-7M9.5 20.5V8M14.4 20.5v-9.5M19.4 20.5V4.5'/%3E%3C/svg%3E"); }
.i-funnel   { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 4.5h16l-6.2 7.6v6.2l-3.6 1.9v-8.1Z'/%3E%3C/svg%3E"); }
.i-gauge    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4.4 18.5a8.6 8.6 0 1 1 15.2 0M12 14.5l3.8-4.3'/%3E%3Ccircle cx='12' cy='15' r='1.3'/%3E%3C/svg%3E"); }
.i-fork     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M7 3.5v5.7a2.5 2.5 0 0 0 5 0V3.5M9.5 3.5v17M16.8 3.5c-1.7 1.5-2.4 3.7-2.4 6.4 0 1.5.9 2.6 2.4 2.6v8'/%3E%3C/svg%3E"); }
.i-briefcase{ --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.5' y='7.4' width='17' height='13.1' rx='2.4'/%3E%3Cpath d='M9 7.4V5.7A1.7 1.7 0 0 1 10.7 4h2.6A1.7 1.7 0 0 1 15 5.7v1.7M3.5 12.4h17'/%3E%3C/svg%3E"); }
.i-wrench   { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.8 6.6a5.1 5.1 0 0 1-6.7 6.1L7 19.8a2.2 2.2 0 0 1-3.1-3.1l7.1-7.1a5.1 5.1 0 0 1 6.1-6.7L13.9 6l.8 3.3L18 10Z'/%3E%3C/svg%3E"); }
.i-target   { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.6'/%3E%3Ccircle cx='12' cy='12' r='4.8'/%3E%3Ccircle cx='12' cy='12' r='1'/%3E%3C/svg%3E"); }
.i-building { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='5' y='3.5' width='14' height='17' rx='1.6'/%3E%3Cpath d='M9 7.4h1.6M13.4 7.4H15M9 11h1.6M13.4 11H15M9 14.6h1.6M13.4 14.6H15M10.4 20.5v-3.2h3.2v3.2'/%3E%3C/svg%3E"); }
.i-palette  { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 3.5a8.5 8.5 0 0 0 0 17c1.5 0 2.1-.9 2.1-1.9 0-1.6-1.5-2-1.5-3.2 0-1 .9-1.8 2.1-1.8h2.4a3.4 3.4 0 0 0 3.4-3.5C20.2 6.3 16.5 3.5 12 3.5Z'/%3E%3Cpath d='M7.5 9.5v.01M11 6.8v.01M15.5 7.5v.01M6.8 13.5v.01'/%3E%3C/svg%3E"); }
.i-key      { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='7.4' cy='15.6' r='4.1'/%3E%3Cpath d='m10.4 12.6 9.4-9.4M16.6 6.4l3 3M13.7 9.3l2.6 2.6'/%3E%3C/svg%3E"); }
.i-star     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m12 3.6 2.6 5.3 5.8.8-4.2 4.1 1 5.8-5.2-2.7-5.2 2.7 1-5.8-4.2-4.1 5.8-.8Z'/%3E%3C/svg%3E"); }
.i-pencil   { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m4 20 .9-4.2L16.4 4.3a2.1 2.1 0 0 1 3 0l.3.3a2.1 2.1 0 0 1 0 3L8.2 19.1ZM14.6 6.1l3.3 3.3'/%3E%3C/svg%3E"); }
.i-timer    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='13.6' r='7'/%3E%3Cpath d='M12 13.6v-4M9.4 2.6h5.2M12 2.6v4'/%3E%3C/svg%3E"); }
.i-globe    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.6'/%3E%3Cpath d='M3.4 12h17.2M12 3.4a13.2 13.2 0 0 1 0 17.2M12 3.4a13.2 13.2 0 0 0 0 17.2'/%3E%3C/svg%3E"); }
.i-inbox    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 5.5h12l2.5 8H15a3 3 0 0 1-6 0H3.5ZM3.5 13.5V18a2 2 0 0 0 2 2h13a2 2 0 0 0 2-2v-4.5'/%3E%3C/svg%3E"); }
.i-clock    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.6'/%3E%3Cpath d='M12 6.6V12l3.4 2'/%3E%3C/svg%3E"); }
.i-help     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='12' cy='12' r='8.6'/%3E%3Cpath d='M9.4 9.3a2.7 2.7 0 0 1 5.2.9c0 1.8-2.5 2.1-2.5 3.8M12 17.2v.01'/%3E%3C/svg%3E"); }
.i-chat     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.5 5.6a1.9 1.9 0 0 0-1.9-1.9H5.4a1.9 1.9 0 0 0-1.9 1.9v8.8a1.9 1.9 0 0 0 1.9 1.9H8v4.2l4.7-4.2h5.9a1.9 1.9 0 0 0 1.9-1.9Z'/%3E%3C/svg%3E"); }
.i-repeat   { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m17 2.8 3.4 3.4L17 9.6M20.4 6.2H8a4.4 4.4 0 0 0-4.4 4.4M7 21.2l-3.4-3.4L7 14.4M3.6 17.8H16a4.4 4.4 0 0 0 4.4-4.4'/%3E%3C/svg%3E"); }
.i-heart    { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 20.3C6.7 16.6 3.6 13.5 3.6 10A4.3 4.3 0 0 1 12 8.5 4.3 4.3 0 0 1 20.4 10c0 3.5-3.1 6.6-8.4 10.3Z'/%3E%3C/svg%3E"); }
.i-flag     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5.5 21V4.2c4-2 9 2 13 0v10.2c-4 2-9-2-13 0'/%3E%3C/svg%3E"); }
.i-doc      { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 3.5h8l5 5v12H6ZM14 3.5v5h5M9.2 12.4h5.6M9.2 15.8h5.6'/%3E%3C/svg%3E"); }
.i-heat     { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5.2 5.2h.01M12 5.2h.01M18.8 5.2h.01M5.2 12h.01M12 12h.01M18.8 12h.01M5.2 18.8h.01M12 18.8h.01M18.8 18.8h.01'/%3E%3C/svg%3E"); }
.i-megaphone{ --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 10.4v3.2a1.6 1.6 0 0 0 1.6 1.6H7l7.5 4.3v-15L7 8.8H5.1a1.6 1.6 0 0 0-1.6 1.6ZM17.8 9.4a3.8 3.8 0 0 1 0 5.2'/%3E%3C/svg%3E"); }
.i-ab       { --pi: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3.4' y='6' width='7.6' height='12' rx='1.8'/%3E%3Crect x='13' y='6' width='7.6' height='12' rx='1.8'/%3E%3Cpath d='m15.6 12 1.2 1.2 2.2-2.4'/%3E%3C/svg%3E"); }

/* The page's own mark, trailing its headline — the same 40px glyph the other six
   product sites put beside an <h1>, at the same size and colour, so a visitor
   moving between them meets one icon system rather than six.

   A PSEUDO-ELEMENT, and that is the whole design. translate() swaps a tagged
   element's inner HTML wholesale, so an <svg> written inside <h1 data-i18n="…">
   renders in English and vanishes in the other 86 languages — the reason this
   file already draws card icons as masks rather than markup (see the note above
   .presets). ::after content is generated, not inner HTML, so it survives
   translation untouched and needs no key, no pack entry and no markup parity.
   It reads the same --pi the .i-* class on the heading sets, so a page picks its
   icon exactly the way a .pcard does, and the 52 glyphs are already there.

   At the END of the headline rather than the start: leading it pushed the first
   word off the optical margin every other page aligns to. aria-hidden is not
   needed — generated content is not in the accessibility tree. */
.head-ico::after {
  content: ""; display: inline-block;
  inline-size: 40px; block-size: 40px;
  margin-inline-start: 10px; vertical-align: -7px;
  background: var(--accent-warm);
  -webkit-mask: var(--pi) center / contain no-repeat;
  mask: var(--pi) center / contain no-repeat;
}
/* Below the phone breakpoint the headline is the page's whole first screen, and
   40px of glyph after a three-line headline is a fourth line that says nothing. */
@media (max-width: 34rem) {
  .head-ico::after { inline-size: 30px; block-size: 30px; vertical-align: -4px; }
}

@media (max-width: 52rem) {
  .usecase, .usecase:nth-of-type(even) { grid-template-columns: 1fr; direction: ltr; }
  .board-grid, .showcase .board-grid { grid-auto-rows: 2.7rem; gap: .3rem; }
  .tile { padding: .38rem .5rem; }
  .tile b { font-size: .76rem; }
  /* Twelve columns on a 343px board is a 78px tile, which is a truncated word.
     The board collapses to halves and wholes instead — the spans are rewritten
     rather than the grid, so a tile that was a quarter becomes a half and a
     tile that was two thirds becomes the row. */
  .w2, .w3, .w4, .w5, .w6, .w7 { --w: 6; }
  .w8, .w9 { --w: 12; }
}
@media (max-width: 26rem) {
  /* And on a small phone only the SHORT tiles stay paired. A board of nothing
     but full-width rows is thirty-six of them, which is a scroll rather than a
     glance — and a glance is the entire claim these pages are making. */
  .w5, .w6, .w7 { --w: 12; }
}

/* ---------- catalogue pages ---------- */

/* /widgets/ reuses .families, but its cards carry a name and nothing else, so
   the name has to stop behaving like a section label and become the content. */
#main .family b { display: block; font-size: .95rem; letter-spacing: 0; text-transform: none; color: var(--text); font-family: var(--font); font-weight: 600; }

.gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(12rem, 100%), 1fr));
  gap: .7rem;
  margin: 1.8rem 0 0;
}
.g-card {
  display: block; color: inherit;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  padding: .8rem .9rem .85rem;
}
.g-card:hover { text-decoration: none; border-color: rgba(var(--ink), .22); }
/* The swatch is the design, in miniature: its own background, its own accent.
   Three colours say more about a design than a name ever will. */
.g-swatch {
  display: block; height: 46px; border-radius: 8px; margin-bottom: .6rem;
  background:
    linear-gradient(90deg, var(--gp) 0 34%, transparent 34%),
    linear-gradient(180deg, var(--gwr) 0 62%, var(--gbg) 62%);
  border: 1px solid var(--border-soft);
}
.g-name { display: block; font-weight: 600; font-size: .93rem; color: var(--text); }
.g-meta { display: block; font-family: var(--mono); font-size: .7rem; color: var(--muted); margin-top: .15rem; }

/* ---------- pricing, carried from index.html ----------

 *

 * The tier cards used to live only in the front page's inline <style>,

 * because pricing only ever appeared there. Moving the section to its own

 * page moved the markup and left the rules behind, and the page shipped as

 * a stack of unstyled text. They belong here now — the sub-pages are where

 * shared components go, and pricing is a shared component the moment it has

 * a page of its own.

 */

.btn-ghost {
  border: 1px solid var(--border); color: var(--sub);
  background: rgba(var(--ink), 0.02);
}

.btn-ghost:hover { color: var(--text); border-color: rgba(var(--ink), 0.2); }

.hero-ctas { display: flex; gap: 14px; justify-content: center; margin-top: 28px; flex-wrap: wrap; }

.lead { margin-top: 16px; max-width: 62ch; color: var(--sub); font-size: 1.06rem; text-wrap: pretty; }

.lead strong { color: var(--text); font-weight: 600; }

.steps {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px;
  margin-top: 54px;
}

.step { border-top: 1px solid var(--border); padding-top: 18px; }

.step .no {
  font-size: 13px; font-weight: 700; color: var(--accent);
  font-variant-numeric: tabular-nums;
}

.step h3 { font-size: 1.08rem; letter-spacing: -0.01em; margin-top: 8px; }

.step p { font-size: 0.94rem; color: var(--sub); margin-top: 7px; }

#circle .hero-ctas { justify-content: flex-start; margin-top: 30px; }

/* Three claims under a hairline — the same device .steps already uses, minus
   the numbers, because these are three true things rather than three stages
   of anything. Deliberately not cards: a box around each one would make them
   look like features again, which is what this section exists to stop. */
.claims { margin-top: 42px; }

.claims .step h3 {
  margin-top: 0; font-size: 1.14rem; font-weight: 700; letter-spacing: -0.018em;
}

.claims .step p { margin-top: 10px; }

/* ---------- features ---------- */
/* ---------- pricing ---------- */
/* The amounts here are a COPY. castomapi-billing's `plans` table is the
   source of truth — an operator edits the price there and Stripe follows,
   and this page has to be edited to match. It is a copy on purpose: the
   page makes no external request, and fetching a catalog to render a
   headline number would trade that for a spinner where the price goes. */
/* Yearly is the number every .tier-cost ships with — the no-JS state and the
   toggle's default agree on purpose, so a visitor without scripts sees the
   real, lower price rather than a monthly figure with no way to change it. */
.billing-switch {
  display: inline-flex; align-items: center; gap: 2px; padding: 3px;
  margin-top: 30px;
  border: 1px solid var(--border); border-radius: 999px;
  background: rgba(var(--ink), 0.03);
}

.billing-opt {
  padding: 9px 18px; border-radius: 999px;
  font-size: 0.86rem; font-weight: 650; color: var(--sub);
}

.billing-opt:hover { color: var(--text); }

.billing-opt[aria-pressed="true"] { background: var(--text); color: var(--bg); }

.price-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px;
  margin-top: 18px; align-items: start;
}

.tier {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--border-soft); border-radius: var(--radius);
  padding: 26px 24px 24px;
  display: flex; flex-direction: column; gap: 18px;
  transition: translate .2s, border-color .2s, background .2s;
}

.tier:hover { translate: 0 -3px; border-color: color-mix(in srgb, var(--accent) 40%, transparent); background: var(--surface-2); }

/* The middle tier carries the gradient border the primary button uses, so
   the recommended plan reads as recommended before a word is read. */
.tier-pick {
  background: linear-gradient(var(--surface), var(--surface)) padding-box,
              var(--grad) border-box;
  border: 1px solid transparent;
}

.tier-pick:hover { background: linear-gradient(var(--surface-2), var(--surface-2)) padding-box, var(--grad) border-box; }

.tier-flag {
  position: absolute; top: -11px; inset-inline-start: 24px;
  font-size: 11.5px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  padding: 3px 10px; border-radius: 99px;
  background: linear-gradient(100deg, var(--cta-from), var(--cta-to)); color: #fff;
}

.tier h3 { font-size: 1.2rem; font-weight: 700; letter-spacing: -0.02em; }

.tier-note { margin-top: 4px; font-size: 13.5px; color: var(--muted); }

.tier-cost { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }

.tier-cost b { font-size: 2.5rem; font-weight: 750; letter-spacing: -0.04em; line-height: 1; }

.tier-cost span { font-size: 14px; color: var(--muted); }

.tier ul { list-style: none; display: grid; gap: 10px; font-size: 14.5px; color: var(--sub); }

.tier li { display: grid; grid-template-columns: 18px 1fr; gap: 9px; align-items: start; }

.tier li b { color: var(--text); font-weight: 650; }

/* A tick and a dash, drawn in CSS: a feature the tier has, and one it does
   not. The dash is deliberately not a crossed-out tick — the free tier is a
   smaller product, not a broken one. */
/* PHYSICAL left, not inline-start, and deliberately so: a tick is a symbol,
   not text, and it does not mirror in a right-to-left script. Drawn from a
   logical property it comes out backwards in the seven RTL languages —
   confirmed in the Arabic build, where the border landed on the right. The
   card's flag above keeps inset-inline-start, because WHERE a badge sits is
   a reading-order question and does flip. */
.tier li::before {
  content: ""; margin-top: 8px;
  width: 11px; height: 6px; border-left: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent); rotate: -45deg;
}

.tier li.no { color: var(--muted); }

.tier li.no::before {
  width: 11px; height: 0; rotate: none; border-bottom: 2px solid var(--border);
  border-left: 0; margin-top: 11px;
}

.tier .btn { width: 100%; margin-top: auto; }

/* An annotation on the price, not a part of it: same baseline as "per
   month", but outlined so a visitor scanning only the numbers still sees it.
   The free tier has none — there is nothing to add tax to. */
.tier-cost .vat {
  align-self: center;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.02em;
  color: var(--sub);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px 8px;
  white-space: nowrap;
}

/* Promoted out of fine print. What a customer is charged differs from the
   number on the tile by the local VAT rate, and burying that under three
   columns is how a €5 plan surprises somebody at €6.15. */
.price-fine {
  margin-top: 20px; padding-top: 16px;
  border-top: 1px solid var(--border-soft);
  font-size: 14px; color: var(--sub); text-wrap: pretty;
}

/* Custom is a different kind of thing from the three above — not a bigger
   plan, a different relationship — so it gets a different shape rather than
   a fourth column. */
.tier-custom {
  margin-top: 18px;
  display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: 30px; align-items: center;
  background:
    radial-gradient(900px 300px at 88% 0%, rgba(144, 133, 233, 0.16), transparent 70%),
    var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 30px;
}

.tier-custom h3 { font-size: 1.45rem; font-weight: 750; letter-spacing: -0.025em; }

.tier-custom .lead { margin-top: 12px; font-size: 1rem; }

/* A smaller relative of .tier-custom — one more thing priced outside the
   three columns, but a single ask-and-answer rather than a whole build, so
   it gets a banner rather than a two-column card with its own numbered
   flow. */
.tier-addon {
  margin-top: 18px;
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 24px; align-items: center;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  padding: 26px 30px;
}

.tier-addon h3 { font-size: 1.15rem; font-weight: 700; letter-spacing: -0.02em; }

.tier-addon .lead { margin-top: 8px; font-size: 0.95rem; }

.tier-addon .hero-ctas { margin-top: 0; }

.flow {
  display: grid; gap: 12px;
  font-size: 14px; color: var(--sub);
}

.flow div { display: grid; grid-template-columns: 26px 1fr; gap: 12px; align-items: start; }

.flow i {
  width: 26px; height: 26px; border-radius: 8px;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--accent-2) 18%, transparent);
  color: color-mix(in srgb, var(--accent-2) 82%, var(--text)); font-size: 12.5px; font-weight: 800; font-style: normal;
}

.flow b { color: var(--text); font-weight: 650; }

.fin .lead { margin-inline: auto; text-align: center; }

.fin .hero-ctas { margin-top: 36px; }

/* ---------- reveal ---------- */
/* Hidden-for-reveal only once JS has stamped html.js — a visitor without
   scripts gets the whole page, just without the choreography. */
.js .reveal { opacity: 0; transform: translateY(22px); transition: opacity .7s ease, transform .7s cubic-bezier(0.16, 1, 0.3, 1); }

.js .reveal.in { opacity: 1; transform: none; }

.reveal[data-d="1"] { transition-delay: .08s; }

.reveal[data-d="2"] { transition-delay: .16s; }

.reveal[data-d="3"] { transition-delay: .24s; }

@media (max-width: 860px) {
  
  .claims { gap: 26px; }
  
  .steps { grid-template-columns: 1fr; gap: 18px; }
}

@media (prefers-reduced-motion: reduce) {
  
  .js .reveal { opacity: 1; transform: none; }
}

@media (max-width: 960px) {
  
  .price-grid { grid-template-columns: 1fr; }

  .tier-custom { grid-template-columns: 1fr; }

  .tier-addon { grid-template-columns: 1fr; }
}

/* The statutory company notice. Generated by build_site.py from
   tools/company.py and identical in all 88 languages — see site_legal().
   Set small and muted on purpose: it is a legal requirement rather than
   something to read, and the page it sits under is not about us. */
.foot-legal {
  max-inline-size: 100%; margin-block: 30px;
  font-size: 12px; line-height: 1.75; color: var(--muted);
  text-wrap: pretty;
}
/* Underlined, not merely tinted. The address sits in a grey paragraph and
   the tint alone came to 1.79:1 against that text, well under the 3:1 that
   makes a link findable without relying on colour vision -- and the global
   `a { text-decoration: none }` above is what had stripped the underline. */
.foot-legal a { color: var(--sub); text-decoration: underline;
                 text-underline-offset: 2px; }
.foot-legal a:hover { color: var(--text); }

/* ---------- the contact form ----------
 *
 * The only form on the site, on the only page that has one. It is styled here
 * rather than inline beside its script because a form is chrome — an input that
 * looks like this site's inputs is worth more than an input that lives next to
 * the code that sends it.
 *
 * Everything below inherits the page's own tokens, so the three styles carry it
 * without a second palette: a field is a --surface on a --bg, and the focus
 * ring is the same one every other focusable thing on this site wears. */
.form { max-width: 40rem; margin-top: 2rem; display: grid; gap: 1.15rem; }

.field { display: grid; gap: .4rem; }

.field label {
  font-size: .92rem; font-weight: 600; color: var(--text);
  display: flex; align-items: baseline; gap: .5rem;
}

.field .opt { font-weight: 400; font-size: .82rem; color: var(--muted); }

/* One rule for three elements. A select that does not match the inputs beside
   it is the tell that a form was assembled rather than designed, and the
   browser's own select is never going to match on its own. */
.field input,
.field select,
.field textarea {
  font: inherit; font-size: .98rem; color: var(--text);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: .7rem .85rem;
  inline-size: 100%; max-inline-size: 100%;
  transition: border-color .15s, background .15s;
}

.field textarea { resize: vertical; min-block-size: 8rem; line-height: 1.55; }

/* appearance:none removes the platform chrome; the caret is drawn back as a
   mask so it takes currentColor and therefore follows the three styles. It is
   inset-inline-end, so it moves to the left edge in Arabic and Hebrew. */
.field select {
  appearance: none; -webkit-appearance: none;
  padding-inline-end: 2.4rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-size: 1.1rem;
  background-position: right .85rem center;
}

[dir="rtl"] .field select { background-position: left .85rem center; }

.field input:hover,
.field select:hover,
.field textarea:hover { border-color: rgba(var(--ink), .22); }

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  outline: 2px solid var(--accent-2); outline-offset: 2px;
  border-color: transparent; background: var(--bg-raise);
}

.hint { margin: 0; font-size: .84rem; color: var(--muted); line-height: 1.5; }

/* The status line doubles as the error message, so it takes a colour from what
   happened rather than staying decoratively neutral. */
#c-status[data-kind="ok"] { color: var(--good); font-weight: 600; }

#c-status[data-kind="bad"] { color: var(--bad); font-weight: 600; }

#c-status a { color: inherit; text-decoration: underline; }

.form-actions { display: flex; align-items: center; flex-wrap: wrap; gap: .9rem 1.2rem; }

.form-actions .hint { flex: 1 1 14rem; }

.btn[disabled] { opacity: .5; cursor: not-allowed; filter: grayscale(.35); }

/* The honeypot. Off-screen ABOVE the page rather than beside it: a negative
   inline offset lands inside the scrollable area in a right-to-left document,
   which is the bug the skip link already had once. Vertical overflow above the
   viewport creates no scrollbar in either direction. */
.trap {
  position: absolute; inline-size: 1px; block-size: 1px;
  overflow: hidden; clip-path: inset(50%);
  inset-block-start: 0; inset-inline-start: 0;
  transform: translateY(-200%);
}

/* ---------- the dashboard mockup ----------
 *
 * A real dash-b board, drawn as skeletons: the widget types, their families
 * and the column spans they default to all come from the app's own registry
 * through tools/build_mockup.py. Only the data is missing, and it is missing
 * on purpose — a fake number on a marketing page is a claim, a skeleton is an
 * invitation.
 *
 * The board sets its OWN --mk-* palette from the design it is wearing, the way
 * the design reel on the front page sets --r*: it is demonstrating the
 * PRODUCT's theming, so it has to keep the design's colours while the site
 * around it follows the visitor's chosen style.
 *
 * assets/mockup.js draws into this. The two are written together and the class
 * names are the contract between them. index.html carries its own copy of both
 * for the from-disk promise, the way it already carries its own chrome — and
 * tools/build_mockup.py writes that copy, so only this stylesheet is by hand.
 */
.mk {
  --mk-p: #9c87ff;
  --mk-c: #c57ef9;
  --mk-bg: #1a1729;
  --mk-wrap: #f0f1ea;
  --mk-row: 78px;
  --mk-ink: 255, 255, 255;
  margin-block-start: clamp(1.6rem, 4vw, 2.6rem);
}
/* A light design has to invert the tile ink, or a skeleton drawn in white on a
   near-white wrap is an empty box. One property, read off the design's own
   `theme` — the same field the gallery card uses. */
.mk[data-mk-theme="light"] { --mk-ink: 12, 14, 20; }

.mk-frame {
  border: 1px solid var(--border);
  border-radius: var(--radius, 14px);
  overflow: hidden;
  background: var(--bg-raise);
}

/* The window chrome, so the thing reads as a screen rather than as a diagram —
   the same argument .board-bar already makes on the use-case pages. */
.mk-bar {
  display: flex; align-items: center; gap: .55rem;
  padding: .6rem .8rem;
  border-block-end: 1px solid var(--border-soft);
  background: rgba(var(--ink), .02);
  font-size: .78rem; color: var(--muted);
}
.mk-dot { inline-size: 8px; block-size: 8px; border-radius: 50%; background: var(--mk-p); flex: none; }
.mk-brand { color: var(--text); font-weight: 600; letter-spacing: -.01em; }
.mk-open {
  margin-inline-start: auto;
  font-size: .76rem; font-weight: 600;
  color: var(--text);
  border: 1px solid var(--border); border-radius: 99px;
  padding: .2rem .7rem;
  white-space: nowrap;
}
.mk-open:hover { border-color: rgba(var(--ink), .28); text-decoration: none; }

/* The board itself, on the design's ground. Twelve tracks, because that is
   what dash-b's canvas is (css/widgets.css: repeat(var(--dashb-columns,12))),
   and a mockup on a different grid would be showing a layout the product
   cannot produce. */
.mk-board {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  grid-auto-rows: var(--mk-row);
  gap: 10px;
  padding: 14px;
  background: var(--mk-bg);
  min-block-size: calc(var(--mk-row) * 2 + 38px);
}

.mk-tile {
  grid-column: span var(--mk-span, 4);
  grid-row: span var(--mk-rows, 1);
  min-inline-size: 0; overflow: hidden;
  display: flex; flex-direction: column; gap: 9px;
  padding: 11px 13px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--mk-wrap) 92%, transparent);
  transition: opacity .45s ease, transform .45s cubic-bezier(.22, 1, .36, 1);
}
/* The starting state of the entrance, removed one forced reflow later. Never
   behind requestAnimationFrame: a frame does not arrive in a hidden pane and
   the board would stay in this state for ever. */
.mk-tile.mk-in { opacity: 0; transform: translateY(10px) scale(.97); }

.mk-head { display: flex; align-items: center; gap: 6px; min-inline-size: 0; }
.mk-ico {
  inline-size: 13px; block-size: 13px; flex: none;
  background-color: var(--mk-p);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
}
/* The widget's own type name, set as a label. It is the real type from the
   registry — `kpi`, `sparkline`, `uptime` — and showing it is the point: this
   is that widget, not a picture of a widget. */
.mk-name {
  font-size: 10.5px; font-weight: 650; letter-spacing: .04em; text-transform: uppercase;
  color: rgba(var(--mk-ink), .62);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------- the skeletons ----------
 *
 * The eight shapes from js/dataloading.js, and the same names: bars, dots,
 * ring, wave, pulse, orbit, grid, sweep. A shape named by the generator is a
 * shape the app really draws while a tile's data is on the way, so what a
 * visitor sees here is what they will see there for the first second.
 */
.mk-skel { flex: 1; min-block-size: 0; display: flex; gap: 6px; }
.mk-skel i {
  display: block; border-radius: 5px;
  background: rgba(var(--mk-ink), .13);
  animation: mk-breathe 1.6s ease-in-out infinite;
}

.mk-skel[data-mk-anim="bars"] { flex-direction: column; justify-content: center; gap: 7px; }
.mk-skel[data-mk-anim="bars"] i { block-size: 7px; inline-size: 100%; }
.mk-skel[data-mk-anim="bars"] i:last-child { inline-size: 58%; }

.mk-skel[data-mk-anim="dots"] { align-items: center; }
.mk-skel[data-mk-anim="dots"] i { inline-size: 9px; block-size: 9px; border-radius: 50%; }

.mk-skel[data-mk-anim="ring"] { align-items: center; justify-content: center; }
.mk-skel[data-mk-anim="ring"] i {
  inline-size: 38px; block-size: 38px; border-radius: 50%;
  background: none; border: 5px solid rgba(var(--mk-ink), .13);
  border-block-start-color: var(--mk-p);
}

.mk-skel[data-mk-anim="wave"] { align-items: flex-end; gap: 5px; }
.mk-skel[data-mk-anim="wave"] i { flex: 1; background: var(--mk-p); opacity: .55; }
.mk-skel[data-mk-anim="wave"] i:nth-child(1) { block-size: 42%; }
.mk-skel[data-mk-anim="wave"] i:nth-child(2) { block-size: 72%; }
.mk-skel[data-mk-anim="wave"] i:nth-child(3) { block-size: 54%; }
.mk-skel[data-mk-anim="wave"] i:nth-child(4) { block-size: 88%; }
.mk-skel[data-mk-anim="wave"] i:nth-child(5) { block-size: 66%; }

.mk-skel[data-mk-anim="pulse"] { flex-direction: column; justify-content: center; }
.mk-skel[data-mk-anim="pulse"] i { block-size: 22px; inline-size: 62%; border-radius: 6px; }

.mk-skel[data-mk-anim="orbit"] { align-items: center; }
.mk-skel[data-mk-anim="orbit"] i { inline-size: 100%; block-size: 9px; }

.mk-skel[data-mk-anim="grid"] {
  display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: 1fr; gap: 5px;
}
.mk-skel[data-mk-anim="grid"] i { inline-size: 100%; block-size: 100%; }

.mk-skel[data-mk-anim="sweep"] { align-items: center; }
.mk-skel[data-mk-anim="sweep"] i { inline-size: 100%; block-size: 11px; }

@keyframes mk-breathe { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }

/* ---------- the examples ----------
 *
 * A horizontal rail of the boards this page can show. Each one names a real
 * design, so each one also opens for real — and tools/check_design_links.py
 * reads the same data-app-design attribute here that it reads on a gallery
 * card, which is what keeps a renamed design from silently becoming a card
 * that opens the wrong thing.
 */
.mk-tools { display: flex; align-items: center; gap: .6rem; margin-block-start: .75rem; }

.mk-play {
  inline-size: 30px; block-size: 30px; flex: none;
  border: 1px solid var(--border); border-radius: 8px;
  display: grid; place-items: center; color: var(--text);
}
.mk-play:hover { border-color: rgba(var(--ink), .28); }
.mk-play svg { inline-size: 11px; block-size: 11px; }
.mk-play .mk-i-play { display: none; }
.mk-paused .mk-play .mk-i-play { display: block; }
.mk-paused .mk-play .mk-i-pause { display: none; }

.mk-rail {
  display: flex; gap: .4rem; overflow-x: auto; scrollbar-width: thin;
  padding-block-end: .25rem;
}
.mk-ex {
  flex: none;
  font-size: .82rem; color: var(--sub);
  border: 1px solid var(--border-soft); border-radius: 99px;
  padding: .28rem .8rem;
  white-space: nowrap;
  transition: border-color .16s, color .16s, background-color .16s;
}
.mk-ex:hover { color: var(--text); border-color: rgba(var(--ink), .24); }
.mk-ex.on {
  color: var(--text); font-weight: 600;
  border-color: transparent;
  background: color-mix(in srgb, var(--accent-warm) 18%, transparent);
}

/* ---------- the prompt ---------- */
.mk-form { display: flex; gap: .5rem; margin-block-end: .9rem; }
.mk-input {
  flex: 1; min-inline-size: 0;
  font: inherit; font-size: .95rem; color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border); border-radius: 10px;
  padding: .62rem .85rem;
}
.mk-input::placeholder { color: var(--muted); }
.mk-input:focus-visible { outline: 2px solid var(--accent-warm); outline-offset: 1px; }
/* A prompt that matched no widget says so on the field rather than in a
   message: there is nothing to explain, and the board that is already on
   screen is a better answer than an empty one. */
.mk-nomatch .mk-input { border-color: var(--bad); }

@media (max-width: 900px) {
  /* Twelve tracks in a phone column is a thumbnail, and a thumbnail proves
     nothing. Six tracks, and every tile takes at least half — so the board
     stays a board instead of becoming a column of slivers. */
  .mk-board { grid-template-columns: repeat(6, minmax(0, 1fr)); --mk-row: 68px; }
  .mk-tile { grid-column: span min(var(--mk-span, 4), 6); }
  .mk-form { flex-direction: column; }
}
