/*
 * MakeHybrid design system.
 *
 * A superset of the original styles.css class vocabulary: every selector that
 * file defined is defined here too, with the same meaning, so a page can move
 * between the two stylesheets by changing one <link> and nothing else.
 * docs.html deliberately still points at styles.css -- the Unlimited API
 * reference belongs to the other track and is not changed from here.
 *
 * Layers, in order: tokens -> base -> layout -> components -> docs -> utility.
 */

/* ---------------------------------------------------------------- tokens -- */

:root {
  color-scheme: light;

  /* Brand. --blue is kept as the historical name of the accent. */
  --blue: #2563eb;
  /* The foreground for anything sitting ON --blue. A hard-coded #fff was
     fine in light mode and 2.43:1 in dark, where --blue is a pale tint. */
  --on-accent: #fff;
  --blue-dark: #1d4ed8;
  --blue-soft: #eff4ff;
  --blue-line: #c7d9fb;

  --bg: #f7f8fa;
  --bg-sunken: #eef1f5;
  --card: #ffffff;
  --border: #e4e7ec;
  --border-strong: #d2d7df;

  --text: #121417;
  --text-soft: #40464f;
  --muted: #666d78;
  --faint: #6b7280;

  --danger: #d6342c;
  --danger-soft: #fdeceb;
  --ok: #17824a;
  --ok-soft: #e7f5ec;
  --warn: #9a6400;
  --warn-soft: #fdf3e0;
  --violet: #6d3fd4;
  --violet-soft: #f1ecfd;

  /*
   * The brand mark, [@], as two mask shapes: the brackets and the @. Not colours: each is painted by
   * .brand-mark with a colour that is decided there, which is the whole
   * reason the mark is two layers instead of one picture.
   *
   * Defined once, in :root, so the dark block below inherits them -- the
   * shape does not change between themes, only what paints it.
   */
  --brand-mark-ink: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M6.6 3.2H2.2v25.6h4.4M25.4 3.2h4.4v25.6h-4.4' fill='none' stroke='%23000' stroke-width='4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  --brand-mark-accent: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cpath d='M18.88 13.12v3.74a1.92 1.92 0 0 0 3.84 0v-.86a6.72 6.72 0 1 0-2.74 5.4M18.88 16a2.88 2.88 0 1 1-5.76 0a2.88 2.88 0 1 1 5.76 0' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");

  /* Code surfaces stay dark in both themes -- a terminal reads as a terminal. */
  --code-bg: #10141b;
  --code-fg: #d6dde8;
  --code-dim: #7d8899;

  --radius-sm: 6px;
  --radius: 10px;
  --radius-lg: 14px;

  --shadow-sm: 0 1px 2px rgba(16, 20, 27, .05);
  --shadow: 0 1px 3px rgba(16, 20, 27, .07), 0 8px 24px -12px rgba(16, 20, 27, .14);
  --shadow-lg: 0 2px 6px rgba(16, 20, 27, .07), 0 24px 56px -24px rgba(16, 20, 27, .26);

  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
  /*
   * Headings read this instead, so a white label can give them a face of
   * their own without touching body copy. Ours are the same font: the
   * token exists for the brands, and costs nothing when nobody sets it.
   */
  --font-heading: var(--font);
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
          "Liberation Mono", monospace;

  --header-h: 58px;
  --page-max: 1120px;
  --sidebar-w: 268px;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;

    --blue: #7aa5ff;
    --on-accent: #0b1220;
    --blue-dark: #9bbcff;
    --blue-soft: #16203a;
    --blue-line: #2b3c63;

    --bg: #0d1016;
    --bg-sunken: #0a0d12;
    --card: #141922;
    --border: #242c38;
    --border-strong: #333d4d;

    --text: #eef1f6;
    --text-soft: #c2c9d4;
    --muted: #949cab;
    --faint: #99a1b0;

    --danger: #ff8b84;
    --danger-soft: #2d1817;
    --ok: #68d79a;
    --ok-soft: #10251a;
    --warn: #e9b65c;
    --warn-soft: #2a2113;
    --violet: #b89bf5;
    --violet-soft: #1e1733;

    --code-bg: #090c11;
    --code-fg: #d6dde8;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
    --shadow: 0 1px 3px rgba(0, 0, 0, .5), 0 8px 24px -12px rgba(0, 0, 0, .6);
    --shadow-lg: 0 2px 6px rgba(0, 0, 0, .5), 0 24px 56px -24px rgba(0, 0, 0, .8);
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --blue: #7aa5ff;
  --on-accent: #0b1220;
  --blue-dark: #9bbcff;
  --blue-soft: #16203a;
  --blue-line: #2b3c63;

  --bg: #0d1016;
  --bg-sunken: #0a0d12;
  --card: #141922;
  --border: #242c38;
  --border-strong: #333d4d;

  --text: #eef1f6;
  --text-soft: #c2c9d4;
  --muted: #949cab;
  --faint: #99a1b0;

  --danger: #ff8b84;
  --danger-soft: #2d1817;
  --ok: #68d79a;
  --ok-soft: #10251a;
  --warn: #e9b65c;
  --warn-soft: #2a2113;
  --violet: #b89bf5;
  --violet-soft: #1e1733;

  --code-bg: #090c11;
  --code-fg: #d6dde8;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .4);
  --shadow: 0 1px 3px rgba(0, 0, 0, .5), 0 8px 24px -12px rgba(0, 0, 0, .6);
  --shadow-lg: 0 2px 6px rgba(0, 0, 0, .5), 0 24px 56px -24px rgba(0, 0, 0, .8);
}

/* ------------------------------------------------------------------ base -- */

* { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  /*
   * A link to a heading on the page it is already on should travel, not
   * teleport. A jump leaves the reader to work out whether the page
   * changed or merely moved; a scroll shows them the distance and they
   * arrive already knowing where they are.
   *
   * Anchors on this site are mostly in a sticky-header layout, so the
   * scroll-margin-top rules further down are the other half of this --
   * without them smooth scrolling just glides the target neatly under the
   * header. Both halves, or neither.
   *
   * Overridden by the prefers-reduced-motion block at the end of this
   * file: its universal selector covers <html> and carries !important, so
   * a reader who has asked their system for less movement still gets the
   * instant jump. Motion is the default here, not the requirement.
   */
  scroll-behavior: smooth;
}

body {
  margin: 0;
  font: 400 15px/1.6 var(--font);
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3, h4 {
  font-family: var(--font-heading);
  color: var(--text);
  letter-spacing: -.015em;
  line-height: 1.25;
  font-weight: 650;
}

h1 { font-size: 30px; margin: 0 0 10px; }
h2 { font-size: 20px; margin: 36px 0 12px; }
h3 { font-size: 15px; margin: 0 0 12px; }

p { margin: 0 0 12px; }
p.lead { color: var(--muted); font-size: 16px; margin-bottom: 26px; }

hr { border: 0; border-top: 1px solid var(--border); margin: 28px 0; }

/*
 * One rule for every image on every page.
 *
 * There was none, which is fine while a stylesheet has no images in it and
 * stops being fine the moment one does: an <img> renders at its intrinsic
 * width, so a 320px portrait inside a card at a 320px viewport overflows
 * its own column and gives the whole page horizontal scroll.
 */
img { max-width: 100%; height: auto; }

::selection { background: var(--blue-soft); color: var(--text); }

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--blue);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 8px;
  z-index: 100;
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 8px 14px;
  font-weight: 600;
}
.skip-link:focus { left: 8px; }

/* ---------------------------------------------------------------- layout -- */

header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  height: var(--header-h);
  padding: 0 22px;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 40;
}

header .brand {
  display: flex;
  align-items: center;
  /*
   * The site's own name reads as a wordmark, so it takes the heading face
   * rather than the body one. A white label whose body copy is a serif was
   * putting its name in that serif while the menu beside it and every
   * heading under it were its sans -- the one word that says whose page
   * this is, in the only face on the page that nothing else used.
   *
   * Ours is unchanged: --font-heading is --font until a brand sets one.
   */
  font-family: var(--font-heading);
  /* Tight: the mark's brackets reach the edges of its box, so the box's
     own edge is the mark's edge and this is the whole distance to the name. */
  gap: 7px;
  font-weight: 700;
  /* The site's own name, a notch up from the menu beside it: it is the
     one word in the header that says whose page this is. */
  font-size: 17px;
  letter-spacing: -.02em;
  color: var(--text);
}
header .brand a { color: inherit; text-decoration: none; }
header .brand:hover { text-decoration: none; }

/*
 * THE PLATE: every line arriving at one point, and one line leaving it.
 *
 * Drawn on the server so it is in the page before any script runs; plate.js
 * only moves the point. Every stroke is currentColor, so it inverts with
 * the theme and takes a brand's accent for the line that leaves.
 */
.plate { margin: 0 0 34px; }
.plate-frame {
  width: 100%;
  overflow: hidden;
  border-top: 1px solid var(--border);
  color: var(--text);
}
.plate-frame svg { display: block; width: 100%; height: auto; }
/* The ones arriving are quiet; the one leaving is the point of the drawing. */
.plate-lines { opacity: .42; }
.plate-out { color: var(--blue); }
.plate-handle {
  cursor: ew-resize;
  outline: none;
  /* It sits in a page somebody scrolls: a graphic that eats a vertical
     swipe because it wanted a horizontal one is a graphic that traps them. */
  touch-action: pan-y;
}
/* The focus ring is drawn, not borrowed: an outline on an SVG group is not
   reliably visible across browsers. */
.plate-ring { opacity: 0; }
.plate-handle:focus-visible .plate-ring { opacity: 1; }
[data-plate][data-dragging] [data-plate-dot] { r: 5; }
.plate-caption { display: block; max-width: 72ch; margin: 10px 0 0; }

/*
 * THE TEAM: four job titles, and the record under them.
 *
 * The cards were in the generic feature grid, which packs to 210px columns
 * -- four job titles of very different lengths, so "PPC Analyst --
 * Conversion Tracking Lead" wrapped to three lines beside one that fit on
 * one, and the row read ragged. Their own grid gives them equal columns
 * and a baseline to sit on.
 */
.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 22px 28px;
  align-items: start;
}
/*
 * Subgrid, so the four titles share a row and the four specialty lines
 * share the next one. Without it "PPC Analyst -- Conversion Tracking
 * Lead" takes three lines, its neighbours take one, and the specialties
 * below them sit at four different heights. A browser without subgrid
 * gets what it got before, which is merely ragged rather than broken.
 */
.team-grid { grid-template-rows: auto auto; }
.team-grid .feature { grid-row: span 2; display: grid; grid-template-rows: subgrid; gap: 6px; }
/*
 * Each card lifts a little as it is reached, one after the next.
 *
 * `--n` is its place in the row, so the four arrive in order rather than
 * together -- which reads as a list being set down rather than a page
 * flashing. `is-live` is added by the script, so without it the cards are
 * simply there: no script, no waiting, nothing hidden.
 */
.team-grid.is-live .feature { opacity: 0; transform: translateY(8px); }
.team-grid.is-live.is-seen .feature {
  opacity: 1;
  transform: none;
  transition: opacity .5s ease, transform .5s cubic-bezier(.22, .61, .36, 1);
  transition-delay: calc(var(--n, 0) * 90ms);
}
@media (prefers-reduced-motion: reduce) {
  .team-grid.is-live .feature,
  .team-grid.is-live.is-seen .feature { opacity: 1; transform: none; transition: none; }
}

.team-record, .team-next { margin-top: 34px; max-width: 74ch; }
.team-proof {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px 20px;
  margin: 16px 0 10px;
}

/*
 * CASE STUDIES: one brand's client results, twenty-two of them on one page.
 *
 * Built to be READ DOWN rather than scanned across. Each case is a column of
 * prose with its figures at the foot, because the figures only mean anything
 * after the list of what was done -- which is the page's whole argument, and
 * the reason they are not a row of big numbers at the top.
 */
.case-jump ul {
  list-style: none;
  margin: 0 0 34px;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 10px;
}
.case-jump li { margin: 0; }
.case-jump a {
  display: inline-block;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--muted);
}
.case-jump a:hover { border-color: var(--border-strong); color: var(--text); text-decoration: none; }

/*
 * The three columns under the hero: the guides, the calls and the
 * templates. Wider cells than the plain feature grid, because each link
 * is a sentence-length label rather than a word, and 210px columns break
 * them into stacks of two words.
 */
.case-after { margin: 0 0 40px; }
.case-after > .muted { max-width: 74ch; margin: 0 0 18px; }
.case-after .feature-grid { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 18px 24px; }
.case-links {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: grid;
  gap: 6px;
}
.case-links li { margin: 0; font-size: 13.5px; }
.case-back { max-width: 68ch; margin-top: 34px; padding-top: 26px; border-top: 1px solid var(--border); }

.cases { display: grid; gap: 34px; }
/*
 * One case: the prose, and its figures beside it.
 *
 * Ported from a text-only site, this was a single 68ch column on a 1184px
 * page -- two thirds of the width unused, and the figures below a list long
 * enough to scroll past. The MEASURE stays 68ch, because that is what prose
 * wants; the LAYOUT widens, so the numbers are read with the work that
 * produced them rather than after it.
 *
 * It collapses below 900px, where there is no second column to have.
 */
.case {
  padding-top: 26px;
  border-top: 1px solid var(--border);
  display: grid;
  gap: 18px 36px;
  align-items: start;
}
.case-body { max-width: 68ch; min-width: 0; }
@media (min-width: 900px) {
  .case { grid-template-columns: minmax(0, 1fr) minmax(230px, 310px); }
  .case-metrics { position: sticky; top: calc(var(--header-h) + 18px); }
}
.case h2 { margin: 0 0 4px; }
.case .case-meta { margin: 0 0 16px; }
.case p { margin: 0 0 10px; }
.case-work-head { margin-top: 18px; }
.case-work { margin: 0 0 18px; padding-left: 1.1em; }
.case-work li { margin: 0 0 4px; }

/* The figures: value, change, and what it is -- in that order, because the
   change is the claim and the label is only there to say what changed. */
.case-metrics {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px 20px;
}
.case-metrics li { margin: 0; position: relative; padding-top: 7px; }
/*
 * THE BAR. Its length is the figure's own percentage; a figure that states
 * none has no bar, because a bar implies a comparison and inventing one
 * would be inventing a claim.
 *
 * It grows from nothing when the case is first scrolled to -- `is-seen` is
 * put on by cases.js. Without the script it is simply already its full
 * length, which is the honest resting state rather than a page of empty
 * rails.
 */
.case-bar {
  display: block;
  position: absolute;
  top: 0;
  left: 0;
  height: 2px;
  width: var(--at, 0%);
  max-width: 100%;
  background: var(--blue);
  opacity: .5;
  border-radius: 2px;
}
.cases.is-live .case-bar { width: 0; }
.cases.is-live .case.is-seen .case-bar {
  width: var(--at, 0%);
  transition: width .9s cubic-bezier(.22, .61, .36, 1);
}
@media (prefers-reduced-motion: reduce) {
  .cases.is-live .case-bar,
  .cases.is-live .case.is-seen .case-bar { width: var(--at, 0%); transition: none; }
}
.case-figure { display: block; font-size: 19px; line-height: 1.2; }
.case-change { display: block; font-size: 13px; color: var(--blue); }
.case-label { display: block; font-size: 13px; color: var(--muted); }

/* The name is a link home like any other now; the home page is one page. */
.brand-home { display: inline-flex; align-items: center; min-width: 0; }
/* The name is the one thing in the header that may give way; the mark is not. */
.brand-home .brand-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/*
 * The brand mark: two brackets holding one opening between them.
 *
 * It was a lone capital M, white, on a violet gradient. Make.com is an
 * automation and integration platform with a strongly violet identity
 * selling into developer and automation tooling, which overlaps this; a
 * violet rounded square with an M in it sat about as close to their mark
 * as a mark can without meaning to. A logo cannot cure a name conflict --
 * that is not what this is for -- but it can stop the identity adding to
 * one. Six directions were drawn side by side and this is the one that was
 * picked; the page they were drawn on is gone, and is in git if it is ever
 * wanted back.
 *
 * Drawn here rather than in markup so the mark lives in exactly one file:
 * changing it is this rule, not fourteen pages.
 *
 * Two mask layers rather than one picture, and that is the load-bearing
 * part. The left bracket is painted in `currentColor`, so it follows the
 * text beside it and needs no dark-mode variant at all; the right is
 * painted in --blue, so a white-labelled brand's own accent reaches the
 * mark exactly the way it already reaches every button on the page. A
 * baked-in two-colour image would lose both.
 *
 * The element itself paints nothing; see the @supports block below for
 * why that matters.
 */
.brand-mark {
  width: 22px;
  height: 22px;
  flex: none;
  position: relative;
}

/*
 * BOTH brackets are pseudo-elements, and that is not a style choice.
 *
 * The first version masked the element itself for one bracket and used
 * ::after for the other. A mask applies to an element's whole subtree,
 * pseudo-elements included -- so the second bracket was being clipped by
 * the first bracket's mask, and since the two shapes do not overlap, it
 * was clipped to nothing. The header rendered a lone `[`, which is a
 * perfectly valid stylesheet and the wrong mark. Nothing but looking at it
 * would have caught that.
 *
 * Behind @supports because the fallback for a mask nobody honours is two
 * solid 22px blocks -- a black square in the header, which is worse than
 * no mark at all. Without mask support there is simply no mark.
 */
@supports ((-webkit-mask-image: linear-gradient(#000, #000)) or (mask-image: linear-gradient(#000, #000))) {
  .brand-mark::before,
  .brand-mark::after {
    content: "";
    position: absolute;
    inset: 0;
    -webkit-mask-size: contain;
            mask-size: contain;
    -webkit-mask-position: center;
            mask-position: center;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
  }
  .brand-mark::before {
    background-color: currentColor;
    -webkit-mask-image: var(--brand-mark-ink);
            mask-image: var(--brand-mark-ink);
  }
  .brand-mark::after {
    background-color: var(--blue);
    -webkit-mask-image: var(--brand-mark-accent);
            mask-image: var(--brand-mark-accent);
  }
}

/*
 * A white-labelled brand with a name but no logo of its own falls back to
 * its own initial, which brand.js writes in. The mask has to come off for
 * that: the brackets are pseudo-elements, and a letter sitting under two
 * opaque layers is a letter nobody sees.
 */
.brand-mark.is-initial {
  background: var(--blue);
  border-radius: 6px;
  display: grid;
  place-items: center;
  color: var(--on-accent);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: -.04em;
}
.brand-mark.is-initial::before,
.brand-mark.is-initial::after { content: none; }

header nav {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}
header nav a {
  margin-left: 0;
  font-size: 13.5px;
  color: var(--text-soft);
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
/*
 * A menu link the page carries but does not show yet -- the Inbox, until
 * nav-session.js finds a session. Written out because the phone layout
 * below gives `header nav a` a display, which beats the browser's own
 * rule for [hidden] and would show it on a phone and nowhere else.
 */
header nav a[hidden] { display: none; }
header nav a:hover { background: var(--bg-sunken); color: var(--text); text-decoration: none; }
header nav a[aria-current="page"] { color: var(--blue); background: var(--blue-soft); }
header nav .muted { padding: 0 6px; }
/*
 * The public site's menu: the products, the guides and the price beside
 * the name, then the sign-in at the right edge, ahead of the language and
 * theme controls. The nav takes the row's spare width and `.nav-end` --
 * the sign-in, or the first of the member's own links that nav-session.js
 * puts in its place -- spends it.
 */
header nav.site-nav { flex: 1 1 auto; }
header nav.site-nav .nav-end { margin-inline-start: auto; }

/*
 * The header is sticky, so anything an in-page link targets has to reserve
 * its height -- otherwise "Plans" scrolls the top of the first card
 * underneath it. The docs page sets this per-element; this covers the
 * marketing pages.
 */
:where(main, .home-stack, .home-simple) > section[id],
:where(main, .home-stack, .home-simple) > [id]:not(:first-child) {
  scroll-margin-top: calc(var(--header-h) + 16px);
}

main {
  max-width: 760px;
  margin: 0 auto;
  padding: 44px 20px 64px;
}
main.wide { max-width: var(--page-max); }
main.narrow { max-width: 440px; }

footer {
  text-align: center;
  color: var(--muted);
  font-size: 12.5px;
  padding: 28px 20px 40px;
  border-top: 1px solid var(--border);
}
footer a { color: var(--muted); }
footer a:hover { color: var(--text); }
/*
 * The mcprush badge, on a line of its own under the footer links.
 *
 * ── WHY A BACKGROUND AND NOT AN <img> ──────────────────────────────────
 *
 * It comes in two themes and the reader can change theme without
 * reloading (the toggle in render.ts), so `prefers-color-scheme` on a
 * <picture> is not enough on its own -- it would ignore the toggle. Two
 * <img> tags switched by CSS would answer that and fetch both.
 *
 * A background image is switched by the same rules as everything else
 * here and only the matching one is ever fetched. The link carries an
 * aria-label, so it still has a name to a screen reader.
 *
 * Its own line rather than inline among the links: the badge is 20px
 * tall against 12.5px text, so inline it drags the line box around and
 * the links stop sitting level with each other.
 *
 * `data-brand-hide` on the link takes it off a white-label host
 * altogether -- a reseller's footer must not advertise our name.
 *
 * LIGHT IS THE DEFAULT HERE, and the dark rules below mirror the ones at
 * the top of this file: dark when the system says so and the reader has
 * not forced light, and dark when they have forced dark.
 */
.foot-badge {
  display: inline-block;
  width: 111px;
  height: 20px;
  /*
   * It sits IN the line of links, so it is centred on them rather than
   * hung from the baseline: an empty inline-block takes its baseline from
   * its own bottom edge, which left the badge riding high against the
   * words. `middle` lines its centre up with the middle of the text.
   *
   * And a badge is not another link in the list, so it keeps its distance
   * from the "llms.txt" it follows -- the separator between the links is
   * a middot with a space either side, which is not enough to read as
   * "and this is something else".
   */
  vertical-align: middle;
  margin-left: 14px;
  opacity: 0.85;
  background: center / 111px 20px no-repeat
    url("https://mcprush.com/badge/makehybrid-mcp/score.svg?theme=light");
}
.foot-badge:hover { opacity: 1; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .foot-badge {
    background-image: url("https://mcprush.com/badge/makehybrid-mcp/score.svg");
  }
}
:root[data-theme="dark"] .foot-badge {
  background-image: url("https://mcprush.com/badge/makehybrid-mcp/score.svg");
}
:root[data-theme="light"] .foot-badge {
  background-image: url("https://mcprush.com/badge/makehybrid-mcp/score.svg?theme=light");
}

/* ------------------------------------------------------------ components -- */

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 18px;
  box-shadow: var(--shadow-sm);
}
.card h3 { margin: 0 0 12px; font-size: 15px; }
/*
 * Trim the trailing gap under a card's last block -- but a <dialog> is not
 * one of its blocks.
 *
 * This rule is more specific than `.modal`, so on a card whose last child
 * is the verification dialog it was setting `margin-bottom: 0` on a box
 * that centres itself with `margin: auto`. Auto margins split the free
 * space between them; with the bottom one pinned to zero, all of it went to
 * the top and the dialog sat flush against the bottom of the screen. It
 * looked like a positioning bug in the modal and was a text rule reaching
 * into the top layer.
 */
.card > :last-child:not(dialog) { margin-bottom: 0; }

.card-tight { padding: 14px 16px; }

form { display: flex; flex-direction: column; gap: 12px; max-width: 380px; }
form.wide-form { max-width: none; }

label {
  font-size: 12.5px;
  color: var(--text-soft);
  font-weight: 600;
  display: block;
  margin-bottom: -4px;
}

input, select, textarea {
  padding: 10px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  font: inherit;
  font-size: 14.5px;
  width: 100%;
  background: var(--card);
  color: var(--text);
  transition: border-color .12s, box-shadow .12s;
}
/*
 * THE CHEVRON IS OURS, SO IT CAN SIT WHERE WE PUT IT.
 *
 * A native <select> draws its arrow hard against the right edge and
 * `padding-inline-end` does not move it -- measured in the browser at 12,
 * 34 and 48px, all three identical. So the control gives up its native
 * appearance and takes a mark of our own, set in from the edge by the same
 * amount as the text is on the other side. It also stops the longest
 * option running under the arrow, which is what clipped "Той, яким
 * користується агент" mid-word.
 *
 * `:not([multiple]):not([size])` because a list box has no arrow to move,
 * and the logical side flips for Arabic.
 */
select:not([multiple]):not([size]) {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%236b7280' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  background-size: 12px 12px;
  padding-inline-end: 34px;
}
[dir="rtl"] select:not([multiple]):not([size]) { background-position: left 13px center; }
:root[data-theme="dark"] select:not([multiple]):not([size]),
:root:not([data-theme="light"]) select:not([multiple]):not([size]) {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%239aa4b2' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E");
}
/* A light page is the default; only an actually dark one takes the pale mark. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) select:not([multiple]):not([size]) {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%236b7280' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E");
  }
}

input::placeholder, textarea::placeholder { color: var(--faint); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--blue) 18%, transparent);
}
input:disabled, select:disabled, textarea:disabled { opacity: .6; cursor: not-allowed; }
textarea { resize: vertical; min-height: 84px; font-family: var(--mono); font-size: 13px; }

button {
  padding: 10px 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--blue);
  color: var(--on-accent);
  font: inherit;
  font-weight: 600;
  font-size: 14px;
  cursor: pointer;
  transition: background .12s, border-color .12s, opacity .12s;
  line-height: 1.3;
}
button:hover { background: var(--blue-dark); }
button:disabled { opacity: .5; cursor: default; }

/*
 * NO LABEL IS EVER WIDER THAN ITS OWN BUTTON. In any language.
 *
 * Every button on this site is sized by a layout -- a share of a row, a
 * grid track, the width of a phone -- and a label is translated long after
 * that layout was settled. "Start free trial" is two words in English and
 * four in Ukrainian; whichever button is narrower than its words draws them
 * straight through its own border, which is what a reader sees as broken.
 *
 * `overflow-wrap: break-word` is the floor: a line breaks between words
 * where it can, and inside one word where it cannot. It costs nothing in
 * any language whose label already fits, and it is the only rule here that
 * holds for a word nobody has written yet.
 */
button, a.btn { overflow-wrap: break-word; }

button.secondary {
  background: var(--card);
  color: var(--text);
  border-color: var(--border-strong);
}
button.secondary:hover { background: var(--bg-sunken); }
button.ghost { background: transparent; color: var(--blue); border-color: transparent; }
button.ghost:hover { background: var(--blue-soft); }
button.danger { background: var(--danger-soft); color: var(--danger); border-color: transparent; }
button.danger:hover { background: var(--danger-soft); filter: brightness(.96); }
button.small { padding: 6px 11px; font-size: 12.5px; width: auto; }
button.block { width: 100%; }

:root[data-theme="dark"] button.secondary,
:root[data-theme="dark"] button.danger { color: var(--text); }

/*
 * `.btn` exists so a navigation control can be an <a> and still look like a
 * button. Wrapping a <button> in an <a> is invalid HTML -- interactive
 * content inside a link -- and gives screen readers two nested controls and
 * the keyboard two tab stops for one action.
 */
a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 16px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--blue);
  color: var(--on-accent);
  font: inherit;
  font-weight: 600;
  font-size: 14px;
  line-height: 1.3;
  cursor: pointer;
  text-decoration: none;
  transition: background .12s, border-color .12s;
}
a.btn:hover { background: var(--blue-dark); text-decoration: none; }
a.btn.secondary {
  background: var(--card);
  color: var(--text);
  border-color: var(--border-strong);
}
a.btn.secondary:hover { background: var(--bg-sunken); }
a.btn.ghost { background: transparent; color: var(--blue); border-color: transparent; }
a.btn.ghost:hover { background: var(--blue-soft); }
a.btn.small { padding: 6px 11px; font-size: 12.5px; }
a.btn.block { width: 100%; }
.pricing a.btn { width: 100%; }

.btn-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

.error { color: var(--danger); font-size: 13px; min-height: 18px; }
/* Out of the flow entirely, not merely zero-height: an empty slot was still
   taking the form's 12px gap and a paragraph margin above every submit
   button. role="alert" still announces, because writing text re-enters it. */
.error:empty { display: none; }
.ok-text { color: var(--ok); font-size: 13px; }
.muted { color: var(--muted); font-size: 13.5px; }
.small-text { font-size: 12.5px; }

.row {
  /* A long account name used to push Remove past the right edge with nothing
     able to wrap. */
  flex-wrap: wrap;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 11px 0;
  border-bottom: 1px solid var(--border);
  gap: 12px;
}
.row:last-child { border-bottom: none; }
.row:first-child { padding-top: 0; }
/* The id a member passes as account_id, under each connected account. */
.account-id { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 4px; }

.dot {
  width: 8px; height: 8px; border-radius: 50%;
  display: inline-block; margin-right: 7px; flex-shrink: 0;
  background: var(--faint);
}
.dot.healthy, .dot.ok { background: var(--ok); }
.dot.needs_reconnect, .dot.checkpoint, .dot.credentials { background: var(--warn); }
.dot.halted, .dot.banned, .dot.error { background: var(--danger); }
.dot.paused, .dot.stopped { background: var(--faint); }

code, pre, .mono { font-family: var(--mono); font-size: 13px; }
/*
 * A paragraph of them is usually one long address, or several. Break it
 * anywhere rather than let it push the page sideways on a phone --
 * `anywhere` and not `break-word` because it is the paragraph's
 * min-content width that a grid column is sized from. `pre` keeps its
 * own horizontal scroll; code is read line by line.
 */
p.mono { overflow-wrap: anywhere; }

pre {
  background: var(--code-bg);
  color: var(--code-fg);
  padding: 15px 16px;
  border-radius: var(--radius-sm);
  overflow-x: auto;
  line-height: 1.6;
  margin: 0 0 14px;
  border: 1px solid rgba(255, 255, 255, .06);
  tab-size: 2;
}
pre:last-child { margin-bottom: 0; }

code.inline {
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 12.5px;
  /*
   * `nowrap` was right for what this class was made for -- `/v1`,
   * `X-API-KEY`, a path -- and wrong for the one place it carries a whole
   * JSON error body, which at 320px hung 67px past the edge of the screen
   * and gave the landing page horizontal scroll.
   *
   * `pre-wrap` keeps the spaces inside a chip and `anywhere` breaks a token
   * only when it would otherwise overflow, so every short chip still sits
   * on one line exactly as before.
   */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--text-soft);
}

/* Code block with a copy affordance. */
.code-wrap { position: relative; margin: 0 0 14px; }
.code-wrap pre { margin: 0; }
.copy-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  padding: 4px 9px;
  font-size: 11.5px;
  font-weight: 600;
  border-radius: 5px;
  background: rgba(255, 255, 255, .09);
  color: var(--code-fg);
  border: 1px solid rgba(255, 255, 255, .14);
  opacity: 0;
  transition: opacity .12s, background .12s;
  cursor: pointer;
}
.code-wrap:hover .copy-btn,
.copy-btn:focus-visible { opacity: 1; }
.copy-btn:hover { background: rgba(255, 255, 255, .17); }
.copy-btn.copied { color: #8fe0ae; }
@media (hover: none) { .copy-btn { opacity: 1; } }

.keyval {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--blue-soft);
  border: 1px solid var(--blue-line);
  border-radius: var(--radius-sm);
  padding: 9px 11px;
  word-break: break-all;
}

.badge {
  display: inline-block;
  /*
   * .option-card and .plan-card are flex columns, which BLOCKIFIES an
   * inline-block child and stretches it across the cross axis. The pill
   * became a full-width bar with its label stranded at the left -- the
   * loudest thing wrong with the connect and pricing cards. align-self is
   * inert outside a flex or grid parent, so every inline use is untouched.
   */
  align-self: start;
  font-size: 11px;
  font-weight: 600;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--blue-soft);
  color: var(--blue);
  border: 1px solid var(--blue-line);
  letter-spacing: .01em;
  vertical-align: middle;
}
.badge.neutral { background: var(--bg-sunken); color: var(--muted); border-color: var(--border); }
.badge.ok { background: var(--ok-soft); color: var(--ok); border-color: transparent; }
.badge.warn { background: var(--warn-soft); color: var(--warn); border-color: transparent; }
.badge.danger { background: var(--danger-soft); color: var(--danger); border-color: transparent; }
.badge.violet { background: var(--violet-soft); color: var(--violet); border-color: transparent; }

.kbd {
  font-family: var(--mono);
  font-size: 11px;
  padding: 2px 6px;
  border-radius: 5px;
  border: 1px solid var(--border-strong);
  border-bottom-width: 2px;
  background: var(--card);
  color: var(--muted);
}

.hidden { display: none !important; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
table th, table td {
  text-align: left;
  padding: 9px 10px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
table th {
  color: var(--muted);
  font-weight: 600;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .05em;
}
table tr:last-child td { border-bottom: none; }
.table-scroll { overflow-x: auto; }

.method {
  font-weight: 700;
  font-size: 10.5px;
  padding: 3px 7px;
  border-radius: 5px;
  margin-right: 8px;
  letter-spacing: .04em;
  font-family: var(--mono);
  display: inline-block;
  vertical-align: middle;
  text-transform: uppercase;
}
.method.get { background: var(--ok-soft); color: var(--ok); }
.method.post { background: var(--blue-soft); color: var(--blue); }
.method.patch, .method.put { background: var(--warn-soft); color: var(--warn); }
.method.delete { background: var(--danger-soft); color: var(--danger); }

.endpoint {
  margin-bottom: 24px;
  scroll-margin-top: calc(var(--header-h) + 16px);
}
.endpoint .path {
  font-size: 14px;
  font-weight: 600;
  font-family: var(--mono);
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: wrap;
  word-break: break-all;
}

/* Callouts. */
.callout {
  border: 1px solid var(--border);
  border-left: 3px solid var(--muted);
  background: var(--bg-sunken);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin: 0 0 14px;
  font-size: 13.5px;
  color: var(--text-soft);
}
.callout > :last-child { margin-bottom: 0; }
.callout b, .callout strong { color: var(--text); }
.callout.info { border-left-color: var(--blue); background: var(--blue-soft); }
.callout.warn { border-left-color: var(--warn); background: var(--warn-soft); }
.callout.danger { border-left-color: var(--danger); background: var(--danger-soft); }

/*
 * The card a finished link ends on (complete/result-card.ts), in the connect
 * link and the hosted link alike. A mark that says how it went before a word
 * is read, the heading, one or two sentences, the facts, and what to do next.
 * Logical properties throughout: the same card is read right to left in Arabic.
 */
.result { text-align: center; }
.result-mark {
  width: 52px;
  height: 52px;
  margin: 2px auto 14px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.result-ok .result-mark { background: var(--ok-soft); color: var(--ok); }
.result-warn .result-mark { background: var(--warn-soft); color: var(--warn); }
.result-danger .result-mark { background: var(--danger-soft); color: var(--danger); }
.result-info .result-mark { background: var(--blue-soft); color: var(--blue); }
.result h1 { margin: 0 0 6px; }
.result-lead { margin: 0 0 18px; color: var(--muted); }
.result-rows { margin: 0 0 18px; text-align: start; border-top: 1px solid var(--border); }
.result-rows > div {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 9px 0;
  border-bottom: 1px solid var(--border);
  font-size: 14px;
}
.result-rows dt { color: var(--muted); }
.result-rows dd { margin: 0; text-align: end; overflow-wrap: anywhere; }
.result .btn-row { justify-content: center; }
.result-note { margin: 14px 0 0; font-size: 13px; color: var(--muted); }

/*
 * The app shell (docs/POST-LOGIN-UI.md): a sidebar and a content area, on the pages a member works in -- the dashboard,
 * the account, the brand, the booking page and the admin. The sidebar holds where a member can go; the page holds what
 * they are doing. On a phone it is the same list laid along the top and scrolled sideways, so nothing is hidden behind a
 * button. Logical properties throughout, so the same shell reads right to left.
 *
 * Rooms and the Inbox are not in it: they are two-pane apps with a list of their own, and a second column beside it
 * would take the room from the thing they are for. They are in the sidebar as places to go.
 */
main.app-shell {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  column-gap: 40px;
  align-items: start;
}
.app-content { min-width: 0; }
.app-side {
  position: sticky;
  top: calc(var(--header-h) + 20px);
  padding-block: 4px;
}
.app-side nav { display: flex; flex-direction: column; gap: 2px; }
.app-side-group {
  margin: 20px 0 6px;
  padding-inline: 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}
.app-side nav > .app-side-group:first-of-type { margin-top: 0; }
.app-side a {
  display: block;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  color: var(--text-soft);
  font-size: 14px;
  font-weight: 500;
  text-decoration: none;
}
.app-side a:hover { background: var(--bg-sunken); color: var(--text); text-decoration: none; }
.app-side a[aria-current="page"] { background: var(--blue-soft); color: var(--blue); font-weight: 600; }
.app-side a[hidden] { display: none; }
@media (max-width: 900px) {
  main.app-shell { grid-template-columns: minmax(0, 1fr); }
  .app-side { position: static; margin-bottom: 20px; border-bottom: 1px solid var(--border); overflow-x: auto; scrollbar-width: none; }
  .app-side::-webkit-scrollbar { display: none; }
  .app-side nav { flex-direction: row; gap: 4px; }
  .app-side-group { display: none; }
  .app-side a { flex: none; white-space: nowrap; padding: 10px 14px; margin-bottom: -1px; border-radius: 0; border-bottom: 2px solid transparent; }
  .app-side a[aria-current="page"] { background: transparent; border-bottom-color: var(--blue); }
}
[data-view]:focus { outline: none; }

.setup-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.setup-list { list-style: none; margin: 0 0 4px; padding: 0; counter-reset: setup; }
.setup-list > li {
  counter-increment: setup;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 0;
  border-top: 1px solid var(--border);
}
.setup-text { flex: 1 1 auto; min-width: 0; }
.setup-text p { margin-top: 2px; }
.setup-mark {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 1px solid var(--border-strong);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}
.setup-mark::before { content: counter(setup); }
.setup-list > li[data-state="done"] .setup-mark { background: var(--ok-soft); border-color: transparent; color: var(--ok); }
.setup-list > li[data-state="done"] .setup-mark::before { content: "\2713"; }
.setup-list > li .setup-done { display: none; }
.setup-list > li[data-state="done"] .setup-done { display: inline-block; }
.setup-list > li[data-state="done"] a.btn { display: none; }
.setup-list > li[data-state="done"] .setup-text b { color: var(--muted); }
@media (max-width: 560px) {
  .setup-list > li { flex-wrap: wrap; }
  .setup-text { flex-basis: calc(100% - 44px); }
  .setup-list > li a.btn, .setup-list > li .setup-done { margin-inline-start: 40px; }
}

.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin: 0 0 16px; }
.tile { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px 16px; }
.tile-label { font-size: 13px; }
.tile-value { font-size: 26px; font-weight: 600; line-height: 1.2; margin-top: 2px; }
.callout.ok { border-left-color: var(--ok); background: var(--ok-soft); }
/*
 * A card that is on screen but not switched on yet -- the dashboard's
 * subscription gate (applyGate in dashboard.js).
 *
 * The prose is NOT dimmed. A member who has not paid is exactly the one
 * reading this card to find out what they would get, so every word of it
 * keeps full contrast; dimming the explanation would take away the only
 * thing the card is still good for. What a dim would have said is said by
 * the note under the heading and by the controls, which the browser already
 * renders inert (input:disabled / button:disabled above).
 */
.card.is-gated > .gate-note { margin-top: 0; }

/* Numbered steps. */
.steps { list-style: none; counter-reset: step; margin: 0; padding: 0; }
.steps > li {
  counter-increment: step;
  position: relative;
  padding: 0 0 20px 38px;
  border-left: 1px solid var(--border);
  margin-left: 13px;
}
.steps > li:last-child { border-left-color: transparent; padding-bottom: 0; }
.steps > li::before {
  content: counter(step);
  position: absolute;
  left: -13px;
  top: -2px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--card);
  border: 1px solid var(--border-strong);
  color: var(--text-soft);
  font-size: 12px;
  font-weight: 700;
  display: grid;
  place-items: center;
}
.steps > li > b:first-child,
.steps > li > strong:first-child { display: block; margin-bottom: 3px; }

/*
 * A short numbered how-to inside a card.
 *
 * Deliberately not .steps, which draws a numbered disc and a rule down the
 * side: right for the three stages of getting an account connected, far too
 * loud for "click this, then that". This is a plain ordered list with its
 * indent pulled in to the card's own rhythm.
 */
.howto {
  margin: 0 0 12px;
  padding-left: 20px;
}
.howto > li { margin-bottom: 5px; }
.howto > li:last-child { margin-bottom: 0; }

/* Segmented control / tabs. */
.seg {
  display: inline-flex;
  padding: 3px;
  gap: 3px;
  background: var(--bg-sunken);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.seg button, .seg .tab {
  background: transparent;
  color: var(--muted);
  border: none;
  padding: 6px 13px;
  font-size: 13px;
  font-weight: 600;
  border-radius: 6px;
  cursor: pointer;
}
.seg button:hover, .seg .tab:hover { color: var(--text); background: transparent; }
.seg button[aria-selected="true"], .seg .tab[aria-selected="true"], .seg button[aria-pressed="true"] {
  background: var(--card);
  color: var(--text);
  box-shadow: var(--shadow-sm);
}

/* Choice cards -- two ways to do the same thing. */
.option-grid {
  display: grid;
  /* min() lets the single-column case collapse to the container instead of
     overflowing it: at 360px the card's content box is ~234px, narrower than
     a bare 260px floor, and the cards broke out through its rounded edge. */
  grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
  gap: 16px;
}
/*
 * A grid's gap sits between its own children and leaves nothing after
 * itself, and `.card` carries only a bottom margin -- so a card following
 * a grid sat flush against it, which is what the account page looked like.
 * Today only that page has the adjacency; this is written for any element
 * so the next one to appear does not reintroduce it.
 */
.option-grid + * { margin-top: 18px; }
/*
 * Three cards whose third sums up the two before it: one column on a
 * phone, three on a wide screen, and in between two -- with the third
 * as wide as both above it rather than alone in a half.
 */
.option-grid.trio { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 560px) {
  .option-grid.trio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .option-grid.trio > :nth-child(3) { grid-column: 1 / -1; }
}
@media (min-width: 900px) {
  .option-grid.trio { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .option-grid.trio > :nth-child(3) { grid-column: auto; }
}
/*
 * Two across, never three.
 *
 * `auto-fit` is right for cards that hold a sentence each and wrong for
 * cards that hold a list of ten article titles: at this container's width
 * it fits three and leaves the fourth alone on a row of its own, with
 * every title in all four wrapping twice to make it fit. The general index
 * is the one grid here whose cards have real content in them.
 */
@media (min-width: 760px) {
  .option-grid.pairs { grid-template-columns: 1fr 1fr; }
}

.option-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 18px;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-sm);
}
.option-card.recommended { border-color: var(--blue); box-shadow: 0 0 0 1px var(--blue), var(--shadow-sm); }
.option-card :is(h3, h4) { margin: 8px 0 6px; font-size: 15.5px; }
/*
 * The body no longer grows. It used to carry `flex: 1`, with the form a
 * sibling AFTER it, so on the shorter of the two cards the form was shoved
 * to the bottom and the middle of the card was a chasm. Content now runs
 * top-down and the leftover height falls at the BOTTOM of the card, where it
 * reads as padding rather than as something missing.
 */
.option-card .option-body { flex: 0 0 auto; }
/* `form` caps at 380px for the 408px auth card. Inside a wider card that
   left every input, the textarea and the full-width submit ending 67px short
   of the copy above them. */
.option-card form,
.modal form:not(.modal-dismiss) { max-width: none; }
.option-card form { margin-top: 14px; }
.option-card .option-cta { margin-top: 14px; }
/* The two routes are a comparison, so their headings and first lines must sit
   on the same baseline regardless of how much copy each one carries. */
.option-grid { align-items: start; }

/*
 * "How do you want to connect?": one button a way, and the panel of the one pressed beneath them.
 * A button here is a choice, not an action, so it is drawn as a card -- the global `button` is a filled blue pill.
 */
.ways-label { margin: 16px 0 10px; font-size: 15px; }
.ways { align-items: stretch; }
.way {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
  width: 100%;
  padding: 14px 16px;
  text-align: start;
  font-weight: 400;
  background: var(--card);
  color: var(--text);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-sm);
}
.way:hover { background: var(--bg-sunken); }
.way[aria-expanded="true"] {
  background: var(--blue-soft);
  border-color: var(--blue);
  box-shadow: 0 0 0 1px var(--blue);
}
.way-name { font-weight: 600; font-size: 14.5px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.way-hint { font-size: 13px; color: var(--muted); }
/* The panel opens under the row of buttons, full width: a form is too wide for a third of a row. */
.way-panels { margin-top: 14px; }
.way-panel[hidden] { display: none; }
.way-panels .option-card { max-width: 720px; }

/* ----------------------------------------------------------------- modal -- */

/*
 * A challenge is a hard stop: nothing else on the connect card can be done
 * until it is answered. It gets a modal rather than a notice further down a
 * long page, which is read as commentary on a page that still looks usable.
 */
.modal {
  /*
   * A modal dialog is centred by the user agent through `margin: auto` on an
   * `inset: 0` box -- but only while nothing else claims those properties.
   * Declared here so the dialog's position does not depend on which other
   * rules happen to match it, which is how it ended up sitting low and left.
   */
  position: fixed;
  inset: 0;
  margin: auto;
  /* Taller than the viewport is a dead end otherwise: no scroll, no buttons. */
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  width: min(460px, calc(100vw - 32px));
  height: fit-content;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--text);
  box-shadow: 0 18px 50px rgba(0, 0, 0, .45);
}
.modal::backdrop { background: rgba(8, 11, 18, .66); }
.modal h3 { font-size: 17px; letter-spacing: -.01em; }
.modal > p.muted { margin-bottom: 16px; }
.modal form:not(.modal-dismiss) { margin-top: 4px; }
.modal-dismiss { position: absolute; top: 10px; right: 10px; margin: 0; }
.modal-dismiss button {
  width: 30px;
  height: 30px;
  padding: 0;
  font-size: 19px;
  line-height: 1;
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;
  border-radius: 8px;
}
.modal-dismiss button:hover { background: var(--bg-sunken); color: var(--text); border-color: var(--border); }

.waiting { display: flex; align-items: center; gap: 10px; color: var(--muted); margin: 2px 0 0; }
.spinner {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  border: 2px solid var(--border);
  border-top-color: var(--blue);
  border-radius: 50%;
  animation: mh-spin .8s linear infinite;
}
@keyframes mh-spin { to { transform: rotate(360deg); } }
/* A spinner that never stops is a worse signal than none for anyone who finds
   motion distracting; the text alone still says what is happening. */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; border-top-color: var(--border); }
}

/* ------------------------------------------------------------------ home -- */

.hero { margin-bottom: 30px; }
.hero h1 {
  font-size: clamp(30px, 5vw, 44px);
  line-height: 1.12;
  /*
   * The measure, in ems, so it is a number of CHARACTERS per line at every
   * size rather than a width. Right for every hero but the home page's,
   * whose title is half as long again -- see --hero-measure below.
   */
  max-width: 17em;
  letter-spacing: -.03em;
  margin-bottom: 14px;
}
.hero .lead { max-width: 56ch; font-size: 17px; }
/*
 * The hero's eyebrow is a row of chips, not one: whose product this is,
 * then the three things it does. The brand badge is first and keeps its
 * own markup -- on a white-label host its name slot is the only place the
 * first line of the page says whose product this is, so the three joined
 * it rather than replacing it.
 *
 * It wraps rather than shrinking. A chip whose words have been squeezed is
 * harder to read than a chip on the next line, and the copy column here is
 * 754px on a desktop and a phone's width below that.
 */
.hero-tags { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.hero-tags .badge { margin: 0; }
/*
 * THE MIDDLE BADGE IS THE ONE TO LOSE ON A PHONE.
 *
 * Three of them wrap to two rows under about 640px, and the second row
 * carries one badge alone, which reads as a stray rather than as a set.
 * Two sit on one row and say what this is.
 *
 * It is the middle one that goes, not the last, because the sentence
 * directly below already says the AI is the one doing the work -- while
 * "Safe LinkedIn API" and the growth line are the two claims nothing else
 * in the hero makes.
 *
 * Hidden, not removed: the badge is still in the markup, still translated
 * and still read by anything that does not apply CSS.
 */
@media (max-width: 639px) {
  .hero-tags .badge.on-wide { display: none; }
}

.hero-cta { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 22px; }
/* Transparent to the flex row until the hero is two columns (see 1000px). */
.hero-pair { display: contents; }
.hero-cta button { width: auto; padding: 11px 20px; font-size: 14.5px; }

/*
 * The four AI clients, under the hero's buttons: marks only, no names.
 *
 * They are drawn in currentColor from one set of definitions at the top of
 * the page (.ai-marks), so the same four work on a white background, a dark
 * one and a brand's own accent. The box around each is the page's own
 * button box at a square size -- these ARE buttons, and a mark floating in
 * the margin does not read as one.
 */
/*
 * THE NAME ITSELF, and the two pages now carry it identically.
 *
 * They did differ, in weight alone: 600 on the home page, inherited 400
 * on the API page's. That looked harmless and was not. --note-ratio is
 * MEASURED, once, on one page, and a weight the measurement did not see
 * is a width the arithmetic does not know about -- Arabic at 400 came
 * out wider than at 600 (the fallback face differs) and took two lines
 * on the API page at sizes where the home page's fitted on one.
 *
 * So there is one weight, and this class is on both pages. One
 * declaration, because everything else that used to live here -- a
 * `--ai-row` cap, `nowrap`, a size that filled the whole row -- belonged
 * to a line that had the row to itself, and `.hero-ais-line` holds all
 * of it for both pages now.
 */
.hero-clients-note { font-weight: 600; }
/*
 * How long this language's line is, at a 100px font, over 100. Measured,
 * not guessed: change the sentence and this number changes with it --
 * these are the second set, taken after "or MakeHybrid" left the line
 * and "and APIs" took its place, and every one of them fell (Turkish,
 * the longest before, from 32.26 to 23.25).
 *
 * English is the fallback for a language that has none of its own.
 */
:root { --note-ratio: 18.48; }
:lang(ar) { --note-ratio: 19.99; }
:lang(de) { --note-ratio: 21.92; }
:lang(en) { --note-ratio: 18.48; }
:lang(es) { --note-ratio: 21.97; }
:lang(fr) { --note-ratio: 19.91; }
:lang(hi) { --note-ratio: 15.52; }
:lang(id) { --note-ratio: 20.43; }
:lang(it) { --note-ratio: 19.70; }
:lang(ja) { --note-ratio: 21.51; }
:lang(ko) { --note-ratio: 18.75; }
:lang(nl) { --note-ratio: 19.62; }
:lang(pl) { --note-ratio: 18.53; }
:lang(pt-BR) { --note-ratio: 23.58; }
:lang(ru) { --note-ratio: 19.83; }
:lang(tr) { --note-ratio: 23.25; }
:lang(uk) { --note-ratio: 20.30; }
:lang(zh-Hans) { --note-ratio: 17.26; }

/*
 * ONE LINE ON A PHONE, AND WHAT IT COSTS.
 *
 * Asked for as one line at the row's full width. Measured in a browser at
 * 390px, as uppercase with its tracking, the line fits in English at
 * 10.66px -- and in Turkish only at 7.71px, which is not a size anybody
 * reads. The letters are what make it long, not the words.
 *
 * Dropping the uppercase and the tracking buys 32%: the same Turkish line
 * fits at 10.18px, and every one of the seventeen fits above 10px. So on
 * a phone this is sentence case, and the small-caps look is a desktop
 * thing. That is the trade, taken deliberately: one line at a readable
 * size beats two lines of handsome capitals.
 *
 * 2.5vw is the tightest language's ceiling (2.56vw at 320px) with a
 * little held back. Below 320px it stops shrinking and is allowed to
 * wrap again rather than disappear.
 */
@media (max-width: 999px) {
  /*
   * BOTH LINES FILL THE ROW, IN WHATEVER LANGUAGE THE PAGE IS IN.
   *
   * One size for all seventeen cannot do this. The size has to be the
   * longest language's, so English -- which is nowhere near the longest --
   * stopped two inches short of the edge. The fix is a size per language,
   * and the arithmetic is exact rather than approximate:
   *
   *   the row is always (100vw - 32px); the gutter does not change
   *   a line's width is (font-size x k), k being its own constant
   *   so font-size = (100vw - 32px) / k fills the row precisely
   *
   * Every k below was measured in a real browser, as the width of that
   * language's line at a 100px font divided by 100. They are divided by
   * 1.015 here, holding 1.5% back: the site ships no webfont, and the
   * system fallbacks it lands on differ by about that much.
   *
   * The caps are what stops a 900px-wide phone from setting a headline in
   * 40px. Re-measure with tools in scratchpad if a string changes; the
   * hero test checks the fit.
   */
  :lang(ar) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 19.48), 28px); }
  :lang(de) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 23.33), 28px); }
  :lang(en) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 22.41), 28px); }
  :lang(es) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 22.46), 28px); }
  :lang(fr) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 26.23), 28px); }
  :lang(hi) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 15.18), 28px); }
  :lang(id) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 24.26), 28px); }
  :lang(it) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 19.37), 28px); }
  :lang(ja) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 18.97), 28px); }
  :lang(ko) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 17.15), 28px); }
  :lang(nl) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 23.77), 28px); }
  :lang(pl) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 25.27), 28px); }
  :lang(pt-BR) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 25.20), 28px); }
  :lang(ru) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 22.83), 28px); }
  :lang(tr) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 20.93), 28px); }
  :lang(uk) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 23.99), 28px); }
  :lang(zh-Hans) .hero-price { font-size: clamp(10px, calc((100vw - 32px) / 15.36), 28px); }

}
.hero-clients { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 0; padding: 0; }
.hero-clients > li { margin: 0; }
.hero-clients a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  background: var(--card);
  color: var(--text-soft);
  box-shadow: var(--shadow-sm);
}
.hero-clients a:hover,
.hero-clients a:focus-visible { color: var(--text); border-color: var(--blue); }
/*
 * In the hero's own row the marks are buttons among buttons, so they take
 * the height of the words beside them and the row's own gap rather than
 * their own. The line the row used to carry above it is now the group's
 * name (aria-label): a heading over one item of a line breaks the line.
 */
/* The marks and "every other client" are one group and wrap as one.
   The short page carries the same group under its lead, with its own
   line above it, so the group and the button are not scoped to the row. */
/*
 * One box size and one gap for the whole group, so the pair of buttons
 * above it can be exactly as wide as the row without anybody keeping two
 * numbers in step by hand. Six boxes and the five gaps between them.
 */
.hero-cta, .hero-ais, .hero-start { --ai-box: 48px; --ai-gap: 10px; --ai-n: 7; }
/* On all three, not only .hero-ais: the grid at 1000px and the pair of
   buttons both size themselves against this row, and a number that only
   one of them can read is a number kept in step by hand. */
.hero-cta, .hero-ais, .hero-start { --ai-row: calc(var(--ai-n) * var(--ai-box) + (var(--ai-n) - 1) * var(--ai-gap)); }
/* The marks and "every other client" on one line, the line's name under it. */
.hero-ais { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
/* Under the two buttons on the API page, and not touching them. */
.hero-cta + .hero-ais { margin-top: 16px; }
/*
 * `center`, not `stretch`: the <ul> of marks is taller than the marks
 * inside it -- a list item carries the line's leading, 68px around a
 * 48px box -- and it centres them in that height. The seventh button is
 * not in the list, so stretch left it at the TOP of the row, 10px above
 * the six it is meant to be one of. Centring the row puts all seven on
 * the same line.
 */
.hero-ais-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ai-gap); }
.hero-ais-marks { display: flex; align-items: center; gap: var(--ai-gap); }
/*
 * Scoped to the group rather than to the hero's row, because the short
 * page carries the same group outside that row and the two were drifting:
 * 44px marks there, 48px here.
 */
.hero-ais .hero-clients { margin: 0; gap: var(--ai-gap); }
.hero-ais .hero-clients a { width: var(--ai-box); height: var(--ai-box); border-radius: var(--radius); }
/*
 * Square, like the five marks beside it. It used to be as wide as its two
 * letters, which made the sixth box in a row of six the odd one out -- and
 * the row reads as a row of equals, not as five marks and a word.
 *
 * `a.btn.hero-ai` rather than `.hero-ai`: `.hero-cta a.btn` sets a width
 * and a padding of its own further down this file, and would otherwise
 * win on specificity and leave this one box 62px wide in a row of 48s.
 */
.hero-ais a.btn.hero-ai {
  width: var(--ai-box);
  min-width: var(--ai-box);
  height: var(--ai-box);
  padding: 0;
  text-align: center;
  /* It holds a mark now, not two letters, so it centres one like the six do. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius);
  /* And the same ink as the six, or the seventh box reads as the loud one. */
  color: var(--text-soft);
}
.hero-ais a.btn.hero-ai:hover,
.hero-ais a.btn.hero-ai:focus-visible { color: var(--text); border-color: var(--blue); }
.hero-ais a.btn.hero-ai svg { width: 22px; height: 22px; fill: currentColor; }
/*
 * The score under the row, flush with the last box's outer edge.
 *
 * `--ai-row` rather than `100%`: the column is as wide as its widest
 * child, and on a phone that is the caption rather than the row, so
 * `100%` would hang the badge 20px past the button it is meant to line
 * up with. `flex-end` rather than `text-align: right` so Arabic puts it
 * on the other side with everything else.
 */
/*
 * THE ROW'S NAME AND THE SCORE: ONE LINE, EXACTLY AS WIDE AS THE ROW.
 *
 * It starts where the first mark starts and ends where the last button
 * ends, with the score at that far edge -- so the marks, the words and
 * the score are one block of one width, at every width, on both pages.
 *
 * `--ai-row` IS THAT WIDTH, and using it rather than `100%` is the whole
 * mechanism. `100%` is the COLUMN, which is 802px on the home page's
 * hero and 580 on the API page's, and a line that wide hangs the score
 * 180px past the button it is meant to line up under. `--ai-row` is the
 * row's own arithmetic -- seven boxes and six gaps -- so it follows the
 * marks when a phone shrinks them (396px on a desktop, 338 at 390,
 * 286 at 320) and the three blocks cannot drift apart.
 *
 * WHAT IS LEFT FOR THE WORDS IS THE ROW LESS THE SCORE, and the words
 * are sized to fill exactly that. --note-ratio is how long this
 * language's sentence is at a 100px font over 100, measured in a
 * browser, so the space divided by it is the size that ends the sentence
 * where the score begins.
 *
 * AND IT IS SIZED WITH SLACK, WHICH IS THE WHOLE OF WHY THIS WORKS.
 *
 * REPORTED, twice, and the second time is the instructive one. The size
 * was computed to fill the space EXACTLY: English came out 216.2px
 * against 217px of room, a fit by eight tenths of a pixel. It held in
 * the browser it was measured in and wrapped on a real phone.
 *
 * It had to. This page asks for `ui-sans-serif, system-ui,
 * -apple-system` -- a stack whose whole point is that it resolves to a
 * DIFFERENT TYPEFACE on every platform. --note-ratio is one number,
 * measured once, in one browser, against whatever that browser picked
 * (Inter, here). iOS picks SF Pro and adds optical tracking at small
 * sizes; Android picks Roboto, which is 7% narrower. No single measured
 * number can be exact for all of them, so a size computed to the pixel
 * is a size that is wrong somewhere.
 *
 * --ais-slack is the answer: take 10% less room than there is. Measured
 * across the families this stack really resolves to, the realistic
 * spread is under 1% (Inter 18.48, Helvetica 18.52, FreeSans 18.57) and
 * tracking adds a few more, so a tenth is several times the worst case
 * and costs only type size -- English goes from 11.7px to 10.6px on a
 * phone, and nothing on a desktop moves, because the 13px cap was
 * already binding there.
 *
 * THE FLOOR IS 8px, and it is low on purpose: a floor that bites is a
 * floor that throws the slack away. At 390px the four longest languages
 * want 8.3 to 8.7px, so 9px would have clamped them back to an exact
 * fit -- the very bug above. Here the floor binds only under about
 * 380px, where no size fits one line anyway.
 *
 * Below it the sentence wraps rather than shrink further: the BLOCK is
 * the row's width either way, which is what holds the three together,
 * and only the words take a second line.
 */
.hero-ais-line {
  /*
   * The score's own width (`.foot-badge`, a 111x20 SVG) and the air
   * before it. Named rather than typed into the clamp below, because the
   * words' size is arithmetic on both and a changed badge has to move
   * the words with it.
   */
  --ais-score: 111px;
  --ais-space: 10px;
  /* How much of the room to actually use. See the paragraph above: the
     stack resolves to a different typeface on every platform, so the
     measured ratio is an estimate and this is its margin. */
  --ais-slack: 0.9;
  display: flex;
  align-items: center;
  gap: var(--ais-space);
  width: var(--ai-row);
  /* A row wider than the screen is not possible today -- 286 against the
     288 a 320px screen leaves -- but a changed box or gap must not be
     able to make it so. */
  max-width: 100%;
  margin: 6px 0 0;
  line-height: 1.35;
  color: var(--muted);
}
/*
 * The words take what the score leaves. `min-width: 0` because a flex
 * item will not go narrower than its longest word without it, and that
 * refusal is what pushes a row past the screen.
 */
.hero-ais-line > :first-child {
  flex: 1 1 auto;
  min-width: 0;
  max-width: none;
  margin: 0;
  white-space: normal;
  font-size: clamp(8px, calc((var(--ai-row) - var(--ais-score) - var(--ais-space)) * var(--ais-slack) / var(--note-ratio)), 13px);
}
/* It never gives its width up and it never grows: the words do both. */
.hero-ais-line .hero-ais-badge { display: flex; flex: 0 0 auto; }

/*
 * ON A DESKTOP THE LINE GOES ABOVE THE MARKS, ON THE API PAGE TOO.
 *
 * It names the row, and a name belongs before the thing it names when
 * there is room for it there. On a phone there is not: the blocks stack,
 * and a heading sitting between the two buttons and the marks reads as
 * separating them rather than introducing them, so below 1000px it stays
 * underneath (the margin on `.hero-ais-line` above is for that case).
 *
 * PLACED, NOT `order`ed. The markup is the row and then the line, which
 * is the order a phone stacks them in, and `order` would leave a screen
 * reader hearing one sequence while the eye reads another at one width
 * and not the other. Two explicit grid rows say the same thing without
 * that: the same technique the home page's hero already uses, and the
 * same 1000px it uses, so the two pages change shape together.
 *
 * Scoped to `.hero-words` -- the API page's own column -- because on the
 * home page this element is `display: contents` and its children are the
 * hero's grid items. Making it a grid there would take them back out of
 * that grid and undo the whole arrangement.
 */
@media (min-width: 1000px) {
  .hero-words .hero-ais { display: grid; }
  .hero-words .hero-ais .hero-ais-line { grid-row: 1; margin: 0 0 8px; }
  .hero-words .hero-ais .hero-ais-row { grid-row: 2; }
}

.hero-ais-badge { display: flex; margin-inline-start: auto; }
.hero-ais-badge .foot-badge { margin-left: 0; }

/* The group's own caption: same small uppercase line, now underneath. */
.hero-ais .hero-clients-note { margin: 0; }
.hero-clients svg { width: 22px; height: 22px; fill: currentColor; }
/* Out of the flow entirely: it draws nothing of its own. */
.ai-marks { position: absolute; width: 0; height: 0; overflow: hidden; }

.home-stack { display: flex; flex-direction: column; gap: 18px; }
.home-stack > .card { margin-bottom: 0; }

.pricing {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  gap: 18px;
  margin: 0;
  align-items: stretch;
}
.pricing .card,
.plan-card {
  margin-bottom: 0;
  display: flex;
  flex-direction: column;
  flex: 1 1 0;
  width: 50%;
  min-width: 0;
  position: relative;
}
.pricing .price,
.price {
  font-size: 34px;
  font-weight: 700;
  margin: 6px 0 8px;
  letter-spacing: -.03em;
}
.price span.muted { font-size: 14px; font-weight: 400; letter-spacing: 0; }
.pricing button { width: 100%; }
.plan-card.featured { border-color: var(--blue); box-shadow: 0 0 0 1px var(--blue), var(--shadow); }
/* Beside the plan card, what the plan buys. Same column rules as the card. */
.plan-includes .plan-list li b { color: var(--text); }
.plan-list { margin: 0 0 18px; padding-left: 0; list-style: none; flex: 1; }
.plan-list li {
  margin: 9px 0;
  padding-left: 24px;
  position: relative;
  color: var(--text-soft);
  font-size: 13.5px;
}
.plan-list li::before {
  content: "";
  position: absolute;
  left: 2px;
  top: 7px;
  width: 11px;
  height: 6px;
  border-left: 2px solid var(--ok);
  border-bottom: 2px solid var(--ok);
  transform: rotate(-45deg);
}
.plan-cta { margin-top: auto; }
.plan-cta p { margin: 10px 0 0; }
/* A link line brand.js emptied still takes a line, so the cards' buttons stay level. */
.plan-cta p:empty::before { content: "\00a0"; }

/* The guides index: one subject gathered from every section, above them. */
.topic { margin-bottom: 18px; }
.topic-list { columns: 2; column-gap: 32px; }
.topic-list li { break-inside: avoid; }

.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 16px;
}
/*
 * The four things said about an account's safety. Wider cells than the
 * feature grid's, because each of these is a sentence or three rather
 * than a label, and four of them in a row of 210px columns is four
 * columns of broken words.
 */
.safe-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px 24px;
  margin-top: 14px;
}
.feature h3, .feature h4 { margin: 0 0 5px; font-size: 14.5px; }
.feature p { margin: 0; color: var(--muted); font-size: 13.5px; }
.feature p + p { margin-top: 8px; }

/* ------------------------------------------------------------------ auth -- */

.auth-wrap {
  min-height: calc(100vh - var(--header-h));
  display: grid;
  grid-template-columns: 1fr;
  place-items: start center;
  padding: 48px 20px 64px;
}
.auth-card {
  width: 100%;
  max-width: 408px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 30px 28px;
  box-shadow: var(--shadow-lg);
}
.auth-card h1 { font-size: 23px; margin-bottom: 6px; }
.auth-card form { max-width: none; gap: 14px; }
.auth-card .lead { font-size: 14px; margin-bottom: 22px; }

/*
 * "Continue with Google" / "Continue with GitHub", above the email form on
 * our own hosts. Drawn in the card's own neutral button, not in each
 * provider's brand colour: two saturated buttons at the top of a sign-in
 * card pull harder than the thing most people actually use, and the marks
 * alone say whose they are. The rule under them is a real separator, so the
 * email form below reads as the other way in rather than as an afterthought.
 */
.providers { display: grid; gap: 10px; margin: 0 0 4px; }
.provider {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: var(--bg-sunken);
  color: var(--text);
  border: 1px solid var(--border);
  font-weight: 600;
}
.provider:hover:not(:disabled) { background: var(--blue-soft); border-color: var(--blue); }
.provider-mark { width: 18px; height: 18px; flex: 0 0 auto; }
.providers-or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 8px 0 0;
  font-size: 12.5px;
  color: var(--muted);
}
.providers-or::before, .providers-or::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
}
.auth-foot {
  margin: 20px 0 0;
  padding-top: 18px;
  border-top: 1px solid var(--border);
  font-size: 13px;
  color: var(--muted);
  text-align: center;
}
.auth-aside {
  width: 100%;
  max-width: 408px;
  margin: 16px auto 0;
  font-size: 12.5px;
  color: var(--faint);
  text-align: center;
}
.label-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  /* The bare `label` rule tightens the gap to its input with a negative
     margin. Inside a flex container that margin applies to the label box,
     not the row, so the row has to carry it or the field below sits 4px
     lower than its neighbours. */
  margin-bottom: -4px;
}
.label-row label { margin-bottom: 0; }
.label-row a { font-size: 12.5px; font-weight: 500; }

/* ------------------------------------------------------------------ docs -- */

.docs-shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  gap: 36px;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 0 20px;
  align-items: start;
}
.docs-shell.with-toc {
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr) 190px;
}

.docs-sidebar {
  position: sticky;
  top: var(--header-h);
  max-height: calc(100vh - var(--header-h));
  overflow-y: auto;
  padding: 24px 6px 48px 0;
  font-size: 13.5px;
  scrollbar-width: thin;
}

.doc-search { position: relative; margin-bottom: 18px; }
.doc-search input { padding: 8px 11px 8px 30px; font-size: 13.5px; }
.doc-search::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 50%;
  width: 11px;
  height: 11px;
  margin-top: -7px;
  border: 1.6px solid var(--faint);
  border-radius: 50%;
  pointer-events: none;
}
.doc-search::after {
  content: "";
  position: absolute;
  left: 20px;
  top: 50%;
  width: 6px;
  height: 1.6px;
  margin-top: 3px;
  background: var(--faint);
  transform: rotate(45deg);
  pointer-events: none;
}

.side-group { margin-bottom: 20px; }
.side-group > h4 {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--faint);
  margin: 0 0 7px 10px;
  font-weight: 700;
}
.side-group ul { list-style: none; margin: 0; padding: 0; }

.side-link {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 5px 10px;
  border-radius: var(--radius-sm);
  color: var(--text-soft);
  line-height: 1.4;
}
.side-link:hover { background: var(--bg-sunken); color: var(--text); text-decoration: none; }
.side-link[aria-current="true"],
.side-link.active {
  background: var(--blue-soft);
  color: var(--blue);
  font-weight: 600;
}
.side-link .method { margin-right: 0; font-size: 9px; padding: 2px 5px; flex: none; }
.side-link .side-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/*
 * margin: 0 overrides `main { margin: 0 auto }`. An auto margin makes a grid
 * item size to its content rather than its column, and the widest code line
 * in the catalog then pushed the column out under "On this page".
 */
.docs-main { padding: 24px 0 72px; min-width: 0; margin: 0; }
.docs-main h2 {
  scroll-margin-top: calc(var(--header-h) + 16px);
  padding-top: 8px;
  margin-top: 44px;
}
.docs-main h2:first-of-type { margin-top: 8px; }
.docs-main h3 { font-size: 16px; margin: 26px 0 10px; scroll-margin-top: calc(var(--header-h) + 16px); }

.onthispage {
  position: sticky;
  top: var(--header-h);
  padding: 28px 0 40px;
  font-size: 12.5px;
  max-height: calc(100vh - var(--header-h));
  overflow-y: auto;
}
.onthispage h4 {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--faint);
  margin: 0 0 8px;
  font-weight: 700;
}
.onthispage ul { list-style: none; margin: 0; padding: 0; }
.onthispage a {
  display: block;
  padding: 4px 0 4px 11px;
  border-left: 2px solid var(--border);
  color: var(--muted);
  line-height: 1.45;
}
.onthispage a:hover { color: var(--text); text-decoration: none; }
.onthispage a.active { border-left-color: var(--blue); color: var(--blue); font-weight: 600; }

/* Anchor affordance on headings. */
.anchor {
  color: var(--faint);
  margin-left: 8px;
  font-weight: 400;
  opacity: 0;
  text-decoration: none;
  font-size: .8em;
}
h2:hover .anchor,
h3:hover .anchor,
.op > summary:hover .anchor,
.anchor:focus-visible { opacity: 1; }

/* Endpoint card used by the generated reference. */
.op {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  padding: 16px 18px;
  margin: 0 0 14px;
  scroll-margin-top: calc(var(--header-h) + 16px);
  box-shadow: var(--shadow-sm);
}
/*
 * A group of keys one AI client registered, on the account page. The rows
 * inside are ordinary .row, so only the disclosure itself needs saying.
 */
.row-group > summary {
  cursor: pointer;
  padding: 11px 0;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.row-group > summary::-webkit-details-marker { display: none; }
/* On the summary line itself, so it cannot drift when the text wraps. */
.row-group > summary .chev {
  flex: none;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: translateY(-2px) rotate(45deg);
}
.row-group[open] > summary .chev { transform: translateY(2px) rotate(-135deg); }
.row-group > .row { padding-left: 14px; }
.row-group + .row-group, .row-group + .row, .row + .row-group { border-top: 1px solid var(--border); }

.op > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: -16px -18px;
  padding: 16px 18px;
  border-radius: var(--radius);
}
.op > summary::-webkit-details-marker { display: none; }
.op > summary:hover { background: var(--bg-sunken); }
.op[open] > summary { margin-bottom: 4px; border-bottom-left-radius: 0; border-bottom-right-radius: 0; }
.op .op-path {
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 600;
  word-break: break-all;
  min-width: 0;
}
.op .op-summary { color: var(--muted); font-size: 13px; width: 100%; margin: 0; }
.op .op-body { padding-top: 14px; border-top: 1px solid var(--border); margin-top: 12px; }

.filter-bar {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
  margin-bottom: 16px;
}
.filter-bar input { max-width: 300px; }
.result-count { color: var(--muted); font-size: 13px; }

.breadcrumb { font-size: 12.5px; color: var(--muted); margin-bottom: 10px; }
.breadcrumb a { color: var(--muted); }

.pager {
  display: flex;
  justify-content: space-between;
  gap: 14px;
  margin-top: 44px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}
.pager a {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 11px 14px;
  flex: 1 1 0;
  background: var(--card);
}
.pager a:hover { border-color: var(--blue); text-decoration: none; }
.pager .pager-dir { display: block; font-size: 11px; color: var(--faint); text-transform: uppercase; letter-spacing: .06em; }
.pager .pager-next { text-align: right; }

/* The sidebar toggle only exists on narrow screens. */
.sidebar-toggle { display: none; }

.theme-toggle {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text-soft);
  padding: 5px 9px;
  border-radius: var(--radius-sm);
  font-size: 12.5px;
  line-height: 1;
  cursor: pointer;
}
.theme-toggle:hover { background: var(--bg-sunken); }

/* --------------------------------------------------------------- utility -- */

.stack-sm > * + * { margin-top: 8px; }
.stack > * + * { margin-top: 16px; }
.mt-0 { margin-top: 0 !important; }
.mb-0 { margin-bottom: 0 !important; }

/*
 * The quiet last line of a card, under whatever it was asking you to do.
 *
 * `p` is `margin: 0 0 12px`, so a paragraph after a button row had no top
 * margin at all and the only gap was its line-height -- the reference
 * link sat against the bottom of "Add a card" as though it were part of
 * the control. Named rather than an `.mt-3` utility, because the existing
 * utilities here are only ever zero and one member of a numeric scale is
 * not a scale.
 */
.card-foot { margin: 18px 0 0; }
.nowrap { white-space: nowrap; }
.flex-between { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }

/* The TOC card from the original stylesheet. */
.toc p { margin: 0; }
.toc a { white-space: nowrap; }

/* ----------------------------------------------------------- responsive -- */

@media (max-width: 1080px) {
  .docs-shell.with-toc { grid-template-columns: var(--sidebar-w) minmax(0, 1fr); }
  .onthispage { display: none; }
}

@media (max-width: 860px) {
  .docs-shell, .docs-shell.with-toc { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .docs-sidebar {
    position: static;
    max-height: none;
    overflow: visible;
    padding: 16px 0 0;
    border-bottom: 1px solid var(--border);
    margin-bottom: 20px;
  }
  .docs-sidebar[hidden] { display: none; }
  .sidebar-toggle {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin: 18px 0 0;
    width: auto;
  }
  .docs-main { padding-top: 16px; }
}

/*
 * THE COMPACT HEADER: the name, the call, and a burger holding the rest.
 *
 * It was the phone's header, drawn at 639px and under. It is now drawn
 * whenever the menu does not fit on one row -- a question about words
 * rather than about widths, so it is asked of the row itself: the script
 * at the end of THEME_SCRIPT lays the row out in full, reads whether the
 * links fell onto a second line, and writes data-nav="compact" or "wide"
 * on <html>. A tablet, a half-width window and a language that writes
 * longer words each get the menu under the burger at their own width,
 * rather than a header two rows deep at a width this file guessed.
 *
 * The links used to scroll sideways here, which meant the fourth one and
 * everything after it -- including, on a signed-in screen, Inbox and the
 * way to the dashboard -- lived past the right edge of a 375px screen
 * with nothing but a fading mask to say so. They are all in the sheet
 * now. The <nav> stays in the page, hidden: it is still where
 * nav-session.js and member-nav.js put a member's own links, and the
 * sheet is built by copying its children each time it opens.
 */
.nav-burger, .nav-sheet { display: none; }
[data-nav="compact"] {
  --header-h: 54px;
}
/* One sticky row, and only three things in it: the name, the call, and
   the menu button that holds everything else. */
[data-nav="compact"] header {
  flex-wrap: nowrap;
  gap: 6px;
  padding: 0 8px 0 14px;
}
/* The name takes the row's spare width, so the menu button is against the
   right edge: the two are the whole of the compact header now. */
[data-nav="compact"] header .brand { flex: none; margin-inline-end: auto; }
[data-nav="compact"] header nav { display: none; }
[data-nav="compact"] header > .header-tools { display: none; }
[data-nav="compact"] .nav-burger {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--card);
  color: var(--text);
  cursor: pointer;
}
[data-nav="compact"] .nav-burger[aria-expanded="true"] { background: var(--bg-sunken); }
[data-nav="compact"] .nav-sheet:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: 10px;
  position: absolute;
  top: calc(100% + 6px);
  inset-inline-end: 8px;
  min-width: 220px;
  max-width: calc(100vw - 16px);
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--card);
  box-shadow: 0 12px 32px rgba(15, 23, 42, .18);
}
/* Inside the sheet, which is only ever open in the compact header: these
   need no attribute of their own. */
.nav-sheet-links { display: flex; flex-direction: column; gap: 2px; }
.nav-sheet-links a {
  padding: 9px 10px;
  border-radius: 8px;
  color: var(--text);
  font-size: 14.5px;
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nav-sheet-links a:hover { background: var(--bg-sunken); }
/* The page the reader is on, marked in the sheet as it is in the row on a
   desktop: the copy keeps the attribute, but not the `header nav` selector. */
.nav-sheet-links a[aria-current="page"] { color: var(--blue); background: var(--blue-soft); }
.nav-sheet-links a.btn { color: var(--on-accent); justify-content: center; }
.nav-sheet > .header-tools { margin: 0; padding-top: 10px; border-top: 1px solid var(--border); justify-content: space-between; }
.nav-sheet .lang-menu-list { position: static; margin-top: 6px; box-shadow: none; }

/*
 * With JavaScript off nothing writes data-nav, so nothing above matches:
 * the menu is still in the row and the burger that would hold it is
 * hidden. Let the header grow to what is in it rather than keep a 58px
 * row the wrapped menu hangs out of -- two lines of menu is a worse
 * header than one, and a menu cut off at the bottom edge is not a menu.
 */
@media (max-width: 999px) {
  html:not([data-nav]) header {
    height: auto;
    min-height: var(--header-h);
    flex-wrap: wrap;
    padding: 8px 14px;
  }
}

@media (max-width: 639px) {
  main { padding: 28px 16px 48px; }
  .pricing { flex-direction: column; flex-wrap: wrap; }
  .pricing .card, .plan-card { width: 100%; max-width: none; }
  .topic-list { columns: 1; }
  .hero h1 { font-size: 28px; }
  .auth-wrap { padding: 24px 16px 48px; }
  .auth-card { padding: 24px 20px; border-radius: var(--radius); }
  .docs-shell { padding: 0 16px; }
  .pager { flex-direction: column; }
  .pager .pager-next { text-align: left; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
}

@media print {
  header, footer, .docs-sidebar, .onthispage, .copy-btn, .pager { display: none !important; }
  .docs-shell { grid-template-columns: 1fr; }
  body { background: #fff; }
}

/* ------------------------------------------------- small screens, touch -- */

/*
 * Anything under 16px makes iOS zoom the page on focus, and the verification
 * modal autofocuses its code box -- so the one moment the member most needs
 * to see the whole dialog is the moment the page jumps. Only below the
 * pointer-fine widths, so the denser desktop form keeps its sizing.
 */
@media (max-width: 860px) {
  input,
  select,
  textarea { font-size: 16px; }
  textarea { font-size: 15px; }
}

/*
 * A coarse pointer needs a target it can actually hit. The visual size of
 * these controls is deliberate, so the padding grows rather than the type.
 */
@media (pointer: coarse) {
  button,
  a.btn {
    min-height: 44px;
  }
  button.small,
  a.btn.small {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding-block: 0;
    padding-inline: 14px;
  }
  .modal-dismiss button { width: 44px; height: 44px; }
  header nav a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }
  .option-card summary { padding-block: 10px; }
}

/*
 * The connect card is reached by #connect from the landing page and from the
 * dashboard's own links. The existing scroll-margin rule only covers direct
 * children of main, and this one is nested inside the step rail, so the
 * sticky header sat on top of the heading it had just jumped to.
 */
.steps > li[id],
main[id] { scroll-margin-top: calc(var(--header-h) + 16px); }

/* The hero's actions are <a class="btn">, which the button-only rule above
   never matched, so the primary CTA rendered at ordinary body-button size. */
.hero-cta a.btn { width: auto; padding: 13px 22px; font-size: 15.5px; }
/*
 * And on anything wider the same, for the hero alone: the row is as wide
 * as the words above it and the buttons share it evenly, so the price and
 * the ask under it read as one block rather than as a sentence with two
 * small links after it. Not `.hero-cta` everywhere -- the short home
 * page's closing pair is centred under centred text, and stretching it
 * would make two buttons the width of the page.
 */
/*
 * On a desktop the hero's buttons are as wide as the words above them: the
 * row fills the copy column and the three share it evenly, so the price and
 * the ask under it read as one block rather than as a sentence with three
 * small links after it.
 *
 * From 1000px up, because that is where the hero becomes two columns and
 * the copy column is a block with a width of its own. Below it the hero is
 * one centred stack -- the title, the picture, the lead and the price each
 * centred on their own width -- and a row stretched to the full page there
 * would be the one thing wider than everything around it.
 *
 * Not `.hero-cta` everywhere either: the short home page's closing pair is
 * centred under centred text, and stretching it would make two buttons the
 * width of the page.
 */
@media (min-width: 1000px) {
  /*
   * The buttons are as wide as their own words, not a share of the row.
   *
   * They used to divide it evenly, which was right when the row was two
   * or three of them; now it also holds four square marks and a button of
   * two letters, and an even share left "Start free trial" a two-line
   * button at 1000px while "AI" had room for a sentence. Every item keeps
   * its own width and the row wraps if a language needs it to.
   */
  .hero .hero-cta { align-items: stretch; }
  /*
   * `white-space: nowrap` used to be here, and it is what put a label
   * outside its own button.
   *
   * The pair below shares its width between two buttons (`flex: 1 1 0`),
   * so each gets half whatever its label is; nowrap meant a label longer
   * than half could not wrap and simply drew past the border -- "Почати
   * безкоштовний пробний період" by 9px, Polish by 14. Letting it wrap
   * costs a two-line button in three languages, which `align-items:
   * stretch` above keeps the same height as its neighbour. A label
   * outside its box is not a trade worth one line.
   */
  .hero .hero-cta a.btn { white-space: normal; }
  /*
   * And the pair sits over the row rather than beside it, exactly as wide:
   * two buttons over six boxes, sharing that width between them. The width
   * is the group's own arithmetic, so adding a seventh client moves both
   * lines together.
   * This is the home page's hero and the two pages that copied it; none
   * of them has anything under the pair any more, so what it buys now is
   * two blocks of equal width -- the pair and, on the home page, the row
   * of marks beside it.
   */
  .hero .hero-cta .hero-pair {
    display: flex;
    gap: var(--ai-gap);
    /*
     * As wide as the row under it, and never narrower than its own words.
     * `min-content` is the floor the labels set, `min-width` the one the
     * row sets: English lands exactly on the row, and a language whose
     * "Start free trial" is longer widens the pair instead of wrapping it.
     * Sharing a fixed row evenly is what used to give German a two-line
     * button.
     */
    width: max-content;
    min-width: calc(var(--ai-n) * var(--ai-box) + (var(--ai-n) - 1) * var(--ai-gap));
    max-width: 100%;
    flex-wrap: wrap;
  }
  /*
   * And a label never wraps here either -- the same rule the phone has.
   *
   * `flex: 1 1 0` with `min-width: 0` was the opposite of what the box
   * above promises: it said the pair widens for a long label, while the
   * buttons inside it divided whatever width the pair had and wrapped
   * their labels to fit. "Kostenlosen Test starten" came out two lines
   * tall beside a one-line "Gespräch buchen", in eleven languages.
   *
   * With `max-content` as the base the pair is as wide as both labels
   * actually need, floored at the row of marks so English still lines up
   * with it. If even the column cannot hold them, `max-width: 100%` and
   * the wrap above drop them onto a line each, both still one line deep.
   */
  .hero .hero-cta .hero-pair > a.btn {
    flex: 1 1 auto;
    min-width: max-content;
    max-width: 100%;
    white-space: nowrap;
  }
}
/*
 * On a phone the hero's buttons are one above the other, each the width of
 * the screen.
 *
 * They used to share a row, and at 375px that gave each of them half of it
 * -- which "Start free trial" does not fit on, so the primary call to
 * action was a two-line button next to another two-line button. A full-width
 * button is the easiest thing on the page to hit and its label fits on one
 * line in every language, which is the whole argument.
 */
@media (max-width: 639px) {
  /* The row itself is the screen: under a centred column it would otherwise
     be as wide as its widest label, and a "full-width" button inside it
     would be that width too. */
  .hero-cta { flex-wrap: wrap; gap: 8px; align-items: stretch; width: 100%; }
  .hero-cta a.btn { flex: 1 1 100%; min-width: 0; padding: 12px 10px; font-size: 15px; line-height: 1.25; text-align: center; }
  /*
   * The hero's pair, as a desktop has it: the trial and the call, side by
   * side on one line, the marks on the line under them.
   *
   * The call used to be taken out here and left to the header, because
   * three buttons on a 375px screen gave each of them two lines. The three
   * were never the problem -- the marks were the third -- and the pair
   * splitting ONE line between them, rather than each taking a line of its
   * own, is what the desktop hero does. The header keeps a button of its
   * own, and it is the trial now, not the call.
   */
  .hero-cta .hero-pair { display: flex; gap: 8px; width: 100%; }
  /*
   * A LABEL NEVER WRAPS; THE ROW DOES.
   *
   * These two used to share the line evenly (`flex: 1 1 0`), each getting
   * half the screen whatever its label was, and the label wrapped where
   * half a phone was not enough for it. Half a phone is 175px at 390, and
   * "Спробувати безкоштовно" is not 175px at any size worth reading, so
   * seven languages drew a two-line button next to a one-line one.
   *
   * So the base is the label's own width (`min-width: max-content`) and
   * the label is forbidden to wrap. Where both fit, they still share the
   * line and grow into it; where they do not, the PAIR wraps and each
   * takes a full-width line of its own -- which every label fits on, in
   * every language, down to 320px. One extra line in two languages beats
   * a two-line button in seven.
   *
   * `max-width: 100%` is the guard nowrap needs: a label wider than the
   * screen shrinks its button to the screen rather than drawing past it.
   */
  .hero-cta .hero-pair { flex-wrap: wrap; }
  .hero-cta .hero-pair > a.btn {
    flex: 1 1 auto;
    min-width: max-content;
    max-width: 100%;
    white-space: nowrap;
    /*
     * A notch smaller than a full-width button, because half a phone is
     * what these labels have to fit in. Fluid rather than flat: at 14px
     * eight languages wanted a second row at 360px, and at the size this
     * gives them -- 12.7px there, 14px from 390 up -- only the two
     * longest do.
     */
    padding: 11px 8px;
    font-size: clamp(12.5px, calc((100vw - 72px) / 22.7), 14px);
  }
  /*
   * The marks and the one button for every other client share the line
   * under the pair.
   */
  /*
   * Seven boxes now, not six, and all seven stay on ONE line.
   *
   * 44px was a flat floor, and seven of those plus six 5px gaps is 338px
   * -- more than a 360px screen has after its gutters, so the row wrapped
   * and drew six marks over one, with the seventh button stranded under
   * them. That is what reads as the MCP button going missing.
   *
   * So the box is the width the screen actually leaves, capped at 44px
   * rather than floored at it: 44 from 375px up, 42 at 360, 37 at 320.
   * Below 44 it is no longer the comfortable tap Apple asks for, and a
   * row of seven that is all there is still better than a row of six with
   * the seventh somewhere else. The 64px is the 32px of gutter plus the
   * six 5px gaps, and 2px of slack so sub-pixel rounding cannot wrap it.
   */
  .hero-cta, .hero-ais { --ai-box: min(44px, calc((100vw - 64px) / 7)); --ai-gap: 5px; }
  .hero-ais .hero-clients { flex: 0 1 auto; }
  .hero-ais a.btn.hero-ai { flex: 0 0 auto; }
}

/* ----------------------------------------------------------- marketplace -- */

/*
 * A logo belonging to somebody else, on a plate.
 *
 * The white is deliberately a literal rather than a token: it is not a
 * surface colour that should follow the theme, it is a sheet of paper to
 * print a logo on. Most logos are dark ink on transparency, and on the dark
 * theme's #141922 card they would simply disappear.
 */
.logo-mark {
  width: 40px;
  height: 40px;
  flex: none;
  border-radius: 8px;
  object-fit: contain;
  background: #fff;
  border: 1px solid var(--border);
  padding: 4px;
}

/* A face, cropped square and round, however it was shot. */
.avatar { width: 72px; height: 72px; flex: none; border-radius: 50%; object-fit: cover; }

/*
 * A figure beside a column of text, which stacks on a phone by itself.
 *
 * None of the three flex rows already here would do: `.row` is
 * space-between with a rule under it, `.flex-between` spreads, and `.check`
 * is a form control. The 220px basis on the text column is what makes it
 * wrap rather than squeeze.
 */
.media { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; }
.media > :last-child { flex: 1 1 220px; min-width: 0; }

/*
 * Cards in a row of equals, rather than a row of comparisons.
 *
 * `.option-grid` sets `align-items: start` so two cards being compared
 * share a baseline. In a row of three products that leaves each card as
 * tall as its own text and the buttons at three different heights.
 */
.option-grid.even { align-items: stretch; }
.option-grid.even > .option-card { height: 100%; }

/* A list that is a list, not a list of things you get. `.plan-list` ticks
   every item, which reads as "included" wherever it is used. */
.plain-list { margin: 0 0 12px; padding-left: 18px; color: var(--text-soft); }
.plain-list li { margin: 6px 0; }

/* A price inside a product card. `.price` is 34px, which is right for the
   two plan cards and shouts over the heading of anything smaller. */
.meta-price { font-size: 15px; font-weight: 650; color: var(--text); letter-spacing: -.01em; margin: 0 0 8px; }

/* The action at the foot of a card, pushed down so a row of cards lines up. */
.card-cta { margin-top: auto; padding-top: 14px; }

/*
 * The messaging channels' gates: the address, and what arrives through
 * it. Two columns where the card is wide enough for them and one where
 * it is not, because the first line of the first item is a list of eight
 * channel names and squeezing it into half a column helps nobody.
 */
.api-gates {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 10px 20px;
}
.api-gates > li { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text-soft); }
.api-gates code.inline { display: block; margin-bottom: 1px; }

/*
 * A textarea for prose rather than for code.
 *
 * The base `textarea` is monospace at 13px, which is right for a request
 * body and wrong for "what are you working on" -- it makes an ordinary
 * sentence look like something that has to be typed exactly.
 */
textarea.prose {
  font-family: inherit;
  font-size: 13.5px;
  min-height: 72px;
  line-height: 1.5;
}

/* The price is the number a visitor came for, so it sits on its own line
   under the headline rather than as a clause inside the lead paragraph.

   One line, at every width and in every language. A price that wraps reads
   as two facts -- a price, and then some other thing -- rather than as the
   one sentence it is, so the line never wraps and the type shrinks instead.
   The three sizes are one size: the number and the tail are `em` of the
   whole, so they shrink together and keep their proportions, and the whole
   follows the viewport until it reaches the size a desktop wants. The
   coefficient is what the longest of the seventeen languages needs at
   320px; see the measurement in the hero tests. */
.hero-price {
  margin: 0 0 10px;
  white-space: nowrap;
  /*
   * As wide as the row allows and still one line in all seventeen.
   *
   * Measured in a browser: on a 320px screen the longest of them -- French
   * -- fits up to 11.15px, which is 3.48vw. 3.35vw keeps 4% back for the
   * system fallbacks, whose widths differ by about that much, and it fills
   * the row where 3.1vw left a tenth of it empty.
   */
  font-size: clamp(10px, 3.35vw, 17px);
  color: var(--text);
}
.hero-price strong { font-size: 1.41em; font-weight: 700; letter-spacing: -.02em; }
.hero-price .muted { font-size: .85em; }

/* ------------------------------------------------------------- webhooks -- */

.events { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin: 0; }
.events legend { font-size: 12.5px; font-weight: 600; color: var(--text-soft); padding: 0 6px; }
.check {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13.5px;
  font-weight: 400;
  color: var(--text);
  margin: 0;
  padding: 5px 0;
}
.check input { width: auto; margin: 0; }
/* A secret shown once has to survive being copied out of a narrow column. */
.break-all { overflow-wrap: anywhere; word-break: break-all; }

/* The dialog's own actions, so dismissing it is not a 30px glyph hunt. */
.modal-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}
.modal-actions button { width: auto; flex: 1 1 auto; }

/*
 * A card's blurb may be mostly one unbreakable token.
 *
 * The blurb of a generated page starts with the route, and a route like
 * /linkedin/v2/{account_id}/posts/{post_id}/comments/{comment_id}/reactions
 * has no space in it to wrap at -- so it ran straight out of the card and
 * was clipped by the grid. It shows on the English index too; it is only
 * obvious in a language whose own words are short enough that the path is
 * the widest thing on the page.
 */
.option-card .option-body p { overflow-wrap: anywhere; }

/* ------------------------------------------------------------ languages -- */

/*
 * The language picker in the footer of a written page.
 *
 * Plain links, wrapping, because there are seventeen of them and they are
 * the one piece of chrome that must work with no JavaScript: a picker a
 * crawler cannot follow is a picker that does half its job. The separator
 * is a border rather than a character so it never ends up read aloud.
 */
/*
 * A row of links under the footer's main line: the language picker on a
 * written page, and the links to the written sections on every other page.
 *
 * Those links are the reason this exists twice over. The glossary,
 * templates, notes and examples are thousands of pages and NOTHING on the
 * site linked to them -- they were reachable only from sitemap.xml, which
 * is the weakest form of discovery there is and leaves them orphaned for
 * ranking purposes. It is also how the language picker became invisible:
 * it lives on those pages, and there was no way to reach one.
 */
.lang-row,
.foot-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 4px 10px;
  margin-top: 14px;
  line-height: 1.7;
}
/* The fallback sits above the legal line, where the columns go. */
.foot-nav { margin-bottom: 14px; }
.lang-row a,
.foot-nav a { white-space: nowrap; }
.lang-row a:hover,
.foot-nav a:hover { color: var(--text); }

/*
 * The footer's columns: one per written section, its recent pages in it.
 *
 * The row above was the first attempt and it was not enough. Four words on
 * one line, in the footer's 12.5px muted grey, under a sentence about
 * LinkedIn affiliation -- the links were there and nobody saw them, which
 * the brief put plainly: "hardly even noticable".
 *
 * A column each, with real titles under it, is what a footer is for. It
 * also does the job the one-line row could not: a crawler arriving on any
 * page now has a dozen article URLs in front of it instead of four index
 * pages it has to decide to open.
 *
 * `text-align: start` rather than `left` -- `footer` centres everything,
 * and these have to un-centre in a way that still flips for Arabic.
 */
.foot-guides { margin-bottom: 26px; padding-bottom: 26px; border-bottom: 1px solid var(--border); }
.foot-cols {
  display: grid;
  /* Five sections, and five columns fit in the row: 170px each leaves
     the widest of them room for a title cut to one line. */
  grid-template-columns: repeat(auto-fit, minmax(min(170px, 100%), 1fr));
  gap: 22px 28px;
  max-width: 1040px;
  margin: 0 auto;
  padding-bottom: 22px;
  text-align: start;
}
.foot-col h2 {
  margin: 0 0 10px;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text);
}
.foot-col h2 a { color: inherit; text-decoration: none; }
.foot-col h2 a:hover { text-decoration: underline; }
.foot-col ul { list-style: none; margin: 0; padding: 0; }
.foot-col li { margin: 0 0 6px; line-height: 1.45; }
/*
 * One line per link, cut with an ellipsis.
 *
 * These are generated titles and some of them are long, and a column of
 * two- and three-line entries reads as a paragraph rather than a list.
 * The cost is that two titles sharing a first few words can be cut to the
 * same text, which is why render.ts puts the whole title in `title=` --
 * hovering, or a screen reader, still gives the reader the difference.
 */
.foot-col li a {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.foot-col a { text-decoration: none; }
.foot-col a:hover { text-decoration: underline; color: var(--text); }
.foot-all { display: inline-block; margin-top: 8px; font-weight: 500; }

/*
 * Right-to-left, for the languages that are written that way.
 *
 * Only the rules the translated pages actually use -- the written pages,
 * the funnel, and the account and dashboard screens. A blanket flip of the
 * whole stylesheet would reach the reference pages, which are not
 * translated and would be flipped for nothing -- and the rules below are
 * the ones whose left/right is decoration rather than meaning. Code is
 * NOT one of them: a curl command reads left-to-right in every language,
 * so `pre` and inline code are pinned back.
 *
 * A field that holds an identifier -- an email, a URL, a key, a host --
 * carries `dir="auto"` in the markup instead of a rule here: empty or Latin,
 * it lays out left-to-right, so `price_…` does not render as `…price_`.
 *
 * Inline code needs `unicode-bidi: isolate` as well. On an inline element
 * `direction` does nothing by itself, so `direction: ltr` alone left a
 * header like `t=<seconds>,v1=<hex>` to the surrounding Arabic, which
 * reorders its punctuation.
 */
[dir="rtl"] .callout {
  border-left: 1px solid var(--border);
  border-right: 3px solid var(--muted);
}
[dir="rtl"] .callout.info { border-right-color: var(--blue); border-left-color: var(--border); }
[dir="rtl"] .callout.warn { border-right-color: var(--warn); border-left-color: var(--border); }
[dir="rtl"] .callout.danger { border-right-color: var(--danger); border-left-color: var(--border); }
[dir="rtl"] .callout.ok { border-right-color: var(--ok); border-left-color: var(--border); }
[dir="rtl"] .plain-list { padding-left: 0; padding-right: 18px; }
[dir="rtl"] .skip-link { left: auto; right: -9999px; }
[dir="rtl"] .skip-link:focus { left: auto; right: 8px; }
/* The numbered steps -- the dashboard's four, the landing page's "how it
 * starts" -- draw their disc and rule on the side a reader starts from. */
[dir="rtl"] .steps > li {
  padding: 0 38px 20px 0;
  border-left: 0;
  border-right: 1px solid var(--border);
  margin-left: 0;
  margin-right: 13px;
}
[dir="rtl"] .steps > li:last-child { border-right-color: transparent; }
[dir="rtl"] .steps > li::before { left: auto; right: -13px; }
[dir="rtl"] .howto { padding-left: 0; padding-right: 20px; }
/* The dialog's × sits in the corner the title does not start from. */
[dir="rtl"] .modal-dismiss { right: auto; left: 10px; }
[dir="rtl"] pre { direction: ltr; text-align: left; }
[dir="rtl"] code.inline { direction: ltr; unicode-bidi: isolate; }

/* ------------------------------------------------- header tools (right) -- */

/*
 * Language and theme, in the corner of the header.
 *
 * The footer keeps its language row. These are not two copies of one
 * control: this one is for the reader who has just arrived and is reading
 * the wrong language, and that one is for the reader who got to the bottom
 * -- and on a written page that does not exist in every language, the
 * footer row is the only one of the two that can say so.
 *
 * They carry no `data-brand-hide`: brand.js strips MakeHybrid's own
 * promotional furniture from a white-labelled page, and these are not
 * that. A customer's own customers still need to read in their language.
 */
.header-tools {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-inline-start: auto;
}

/*
 * A <details> rather than a <select>: the list is real links, so a crawler
 * follows them and a reader can open one in a new tab. The cost is having
 * to position the panel, which is all the rest of this block is.
 */
.lang-menu { position: relative; }

.lang-menu > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 9px;
  border: 1px solid var(--border);
  border-radius: 7px;
  font-size: 12.5px;
  color: var(--text-soft);
  background: var(--card);
  white-space: nowrap;
}
/* Safari still paints its own triangle without this. */
.lang-menu > summary::-webkit-details-marker { display: none; }

.lang-menu > summary::after {
  content: "";
  width: 0;
  height: 0;
  border-inline: 3.5px solid transparent;
  border-top: 4px solid currentColor;
  opacity: .65;
}
.lang-menu > summary:hover { color: var(--text); border-color: var(--border-strong); }
.lang-menu[open] > summary::after { transform: rotate(180deg); }

.lang-menu-list {
  position: absolute;
  /* `inset-inline-end` rather than `right` so it flips for Arabic. */
  inset-inline-end: 0;
  top: calc(100% + 6px);
  z-index: 50;
  display: grid;
  /*
   * Seventeen languages is too many for one column on a laptop and far
   * too many for a phone, so it wraps into however many fit and scrolls
   * only if it still cannot.
   */
  grid-template-columns: repeat(2, minmax(104px, 1fr));
  gap: 1px 10px;
  max-height: min(64vh, 420px);
  overflow-y: auto;
  padding: 8px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 9px;
  box-shadow: var(--shadow-lg);
}

.lang-menu-list a {
  padding: 5px 7px;
  border-radius: 5px;
  font-size: 12.5px;
  color: var(--text-soft);
  white-space: nowrap;
}
.lang-menu-list a:hover { background: var(--bg-sunken); color: var(--text); }

/*
 * One icon button, at the right end of the header. It shows the mode in
 * force and a press moves to the next one; its label says the mode in
 * words. The picture is chosen here, from `data-theme` on <html> --
 * which THEME_BOOT sets before the first paint -- rather than by a
 * script, so the icon is never the wrong one for a moment.
 *
 * Quiet, not accented. --blue belongs to the one thing in the header a
 * reader is being asked to press; a theme control wearing it competes
 * with the trial button beside it, and the loudest thing on the page
 * ends up being the one that changes the least.
 */
.theme-toggle {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  margin: 0;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: var(--card);
  color: var(--text-soft);
  cursor: pointer;
  flex: none;
}
.theme-toggle:hover { color: var(--text); border-color: var(--border-strong); background: var(--bg-sunken); }
.theme-toggle .theme-icon { display: none; }
:root:not([data-theme="light"]):not([data-theme="dark"]) .theme-toggle .theme-icon[data-mode="system"],
:root[data-theme="light"] .theme-toggle .theme-icon[data-mode="light"],
:root[data-theme="dark"] .theme-toggle .theme-icon[data-mode="dark"] { display: block; }
@media (pointer: coarse) {
  .theme-toggle { width: 44px; height: 44px; }
}

/*
 * The header is the one row on the page with no slack in it. On a phone
 * the nav already wraps; the language menu tightens, and the theme
 * button, one icon wide, stays.
 */
@media (max-width: 720px) {
  .lang-menu > summary { padding: 4px 7px; font-size: 12px; }
  .lang-menu-list { grid-template-columns: repeat(2, minmax(92px, 1fr)); }
}

/*
 * Project rooms: a chat app.
 *
 * Two columns under the header: the organization's rooms, the open room's
 * menu and what every room shares on the left, the conversation on the
 * right. The conversation scrolls inside its column and the composer stays
 * put under it, the way every chat app a member already knows behaves. The
 * left column, hidden, gives its width back to the conversation.
 *
 * Under 960px the conversation has the screen, and the left column is a
 * drawer that opens over it from a button in the room's header.
 */
main.rooms-app {
  max-width: none;
  padding: 0;
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  /* --rooms-top is the header's real height, measured by rooms.js: on a
     phone the header wraps onto two lines and --header-h is `auto`. */
  height: calc(100dvh - var(--rooms-top, 58px));
  min-height: 420px;
}
main.rooms-app:has(> .rooms-left.hidden) { grid-template-columns: minmax(0, 1fr); }

/*
 * The side panel, open: a third column. The conversation gives the width,
 * not the room list -- somebody who opened the panel is reading it, and the
 * list of rooms is how they got here.
 */
main.rooms-app:has(> .rooms-right:not(.hidden)) { grid-template-columns: 280px minmax(0, 1fr) 340px; }
main.rooms-app:has(> .rooms-left.hidden):has(> .rooms-right:not(.hidden)) { grid-template-columns: minmax(0, 1fr) 340px; }
.rooms-right { border-right: 0; border-left: 1px solid var(--border); }
.rooms-right .seg { margin-bottom: 8px; }
.rooms-right textarea { width: 100%; }
.rooms-right details > summary { cursor: pointer; margin: 12px 0 8px; font-weight: 600; font-size: 13.5px; }

/*
 * HOW THE ROOM LOOKS, AS PICTURES RATHER THAN DROPDOWNS.
 *
 * Reading "Tasks shown as: A board" is slower than seeing a board. Every
 * tile is the same markup -- a 46x32 frame holding six <i> -- and the
 * rules below arrange those six into a picture of the thing. The frame is
 * deliberately crude: it is a reminder of a shape, not a screenshot, and
 * the word under it is what actually names the choice and what a screen
 * reader reads.
 */
.look-set + .look-set { margin-top: 14px; }
.look-set-name { margin: 0 0 6px; font-size: 11.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.look-tiles { display: flex; flex-wrap: wrap; gap: 8px; }
.look-tile, .look-preset {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 6px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--card);
  color: var(--text-soft);
  font: inherit;
  font-size: 11.5px;
  font-weight: 500;
  line-height: 1.25;
  text-align: center;
  cursor: pointer;
}
.look-tile:hover, .look-preset:hover { border-color: var(--border-strong); background: var(--bg-sunken); }
.look-tile[aria-pressed="true"] { border-color: var(--blue); background: var(--blue-soft); color: var(--text); }
.look-tile:focus-visible, .look-preset:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }
.look-word { max-width: 76px; }

.look-pic {
  display: grid;
  width: 46px;
  height: 32px;
  padding: 3px;
  gap: 2px;
  align-content: start;
  border: 1px solid var(--border-strong);
  border-radius: 3px;
  background: var(--bg);
  overflow: hidden;
}
.look-pic i { display: block; min-height: 3px; border-radius: 1px; background: var(--border-strong); }
/* Three parts unless a picture asks for more. */
.look-pic i:nth-child(n + 4) { display: none; }
.look-tile[aria-pressed="true"] .look-pic { border-color: var(--blue); }
.look-tile[aria-pressed="true"] .look-pic i { background: var(--blue); }

/* Which tab a room opens on: the strip on top, with one of three lit. */
.look-pic[data-pic^="defaultTab-"] { grid-template-columns: repeat(3, 1fr); grid-template-rows: 5px 1fr; }
.look-pic[data-pic^="defaultTab-"] i:nth-child(4) { display: block; grid-column: 1 / -1; background: var(--border); }
.look-pic[data-pic^="defaultTab-"] i:nth-child(-n + 3) { opacity: .35; }
.look-pic[data-pic="defaultTab-tasks"] i:nth-child(1),
.look-pic[data-pic="defaultTab-chat"] i:nth-child(2),
.look-pic[data-pic="defaultTab-files"] i:nth-child(3) { opacity: 1; }

/* A board is columns of cards; a list is rows; a table is rows of cells. */
.look-pic[data-pic="taskView-board"] { grid-template-columns: repeat(3, 1fr); grid-auto-rows: 7px; }
.look-pic[data-pic="taskView-board"] i { display: block; }
.look-pic[data-pic="taskView-board"] i:nth-child(n + 5) { opacity: .4; }
.look-pic[data-pic="taskView-list"] { grid-auto-rows: 5px; }
.look-pic[data-pic="taskView-table"] { grid-template-columns: 2fr 1fr 1fr; grid-auto-rows: 5px; }
.look-pic[data-pic="taskView-table"] i { display: block; }
.look-pic[data-pic="taskView-table"] i:nth-child(n + 4) { opacity: .45; }

/* What the board is gathered into: that many columns. */
.look-pic[data-pic^="taskGroup-"] { grid-auto-rows: 1fr; }
.look-pic[data-pic="taskGroup-status"] { grid-template-columns: repeat(3, 1fr); }
.look-pic[data-pic="taskGroup-priority"] { grid-template-columns: repeat(4, 1fr); }
.look-pic[data-pic="taskGroup-priority"] i:nth-child(4) { display: block; }
.look-pic[data-pic="taskGroup-assignee"] { grid-template-columns: repeat(3, 1fr); }
.look-pic[data-pic="taskGroup-assignee"] i { border-radius: 50%; align-self: center; height: 9px; width: 9px; justify-self: center; }
.look-pic[data-pic="taskGroup-none"] { grid-template-columns: 1fr; }
.look-pic[data-pic="taskGroup-none"] i:nth-child(n + 2) { display: none; }

/* The rooms column: wide, narrow, or not there. */
.look-pic[data-pic^="nav-"] { grid-template-rows: 1fr; }
.look-pic[data-pic="nav-list"] { grid-template-columns: 13px 1fr; }
.look-pic[data-pic="nav-rail"] { grid-template-columns: 6px 1fr; }
.look-pic[data-pic="nav-hidden"] { grid-template-columns: 1fr; }
.look-pic[data-pic^="nav-"] i:nth-child(n + 3) { display: none; }
.look-pic[data-pic="nav-hidden"] i:nth-child(2) { display: none; }
.look-pic[data-pic^="nav-"] i:last-of-type { opacity: .4; }

/* And the conversation: a tab, a column beside, or a strip under. */
.look-pic[data-pic="chatPlace-tab"] { grid-template-columns: repeat(3, 1fr); grid-template-rows: 5px 1fr; }
.look-pic[data-pic="chatPlace-tab"] i:nth-child(1) { opacity: 1; }
.look-pic[data-pic="chatPlace-tab"] i:nth-child(2), .look-pic[data-pic="chatPlace-tab"] i:nth-child(3) { opacity: .3; }
.look-pic[data-pic="chatPlace-tab"] i:nth-child(4) { display: block; grid-column: 1 / -1; opacity: .4; }
.look-pic[data-pic="chatPlace-side"] { grid-template-columns: 1fr 13px; grid-template-rows: 1fr; }
.look-pic[data-pic="chatPlace-side"] i:nth-child(1) { opacity: .4; }
.look-pic[data-pic="chatPlace-side"] i:nth-child(n + 3) { display: none; }
.look-pic[data-pic="chatPlace-below"] { grid-template-rows: 1fr 9px; }
.look-pic[data-pic="chatPlace-below"] i:nth-child(1) { opacity: .4; }
.look-pic[data-pic="chatPlace-below"] i:nth-child(n + 3) { display: none; }

/* The controls: a bar across the top, or a short one down in the work. */
.look-pic[data-pic="toolbar-top"] { grid-template-rows: 5px 1fr; }
.look-pic[data-pic="toolbar-top"] i:nth-child(2) { opacity: .4; }
.look-pic[data-pic="toolbar-top"] i:nth-child(3) { display: none; }
.look-pic[data-pic="toolbar-inline"] { grid-template-rows: 6px 4px 1fr; }
.look-pic[data-pic="toolbar-inline"] i:nth-child(1) { opacity: .4; }
.look-pic[data-pic="toolbar-inline"] i:nth-child(2) { justify-self: end; width: 55%; }
.look-pic[data-pic="toolbar-inline"] i:nth-child(3) { opacity: .4; }

/* Space, type and corners: the same bars, differently. */
.look-pic[data-pic^="rhythm-"] { grid-auto-rows: 4px; }
.look-pic[data-pic="rhythm-tight"] { gap: 1px; }
.look-pic[data-pic="rhythm-normal"] { gap: 4px; }
.look-pic[data-pic="rhythm-airy"] { gap: 8px; }
.look-pic[data-pic="rhythm-airy"] i:nth-child(3) { display: none; }
.look-pic[data-pic^="typeScale-"] { gap: 3px; }
.look-pic[data-pic="typeScale-small"] { grid-auto-rows: 3px; }
.look-pic[data-pic="typeScale-normal"] { grid-auto-rows: 5px; }
.look-pic[data-pic="typeScale-large"] { grid-auto-rows: 8px; }
.look-pic[data-pic="typeScale-large"] i:nth-child(3) { display: none; }
.look-pic[data-pic^="shape-"] { padding: 4px; align-content: stretch; }
.look-pic[data-pic^="shape-"] i:nth-child(n + 2) { display: none; }
.look-pic[data-pic="shape-square"] i { border-radius: 0; }
.look-pic[data-pic="shape-soft"] i { border-radius: 5px; }
.look-pic[data-pic="shape-round"] i { border-radius: 11px; }

/* A card: its title, or its title and what is on it. */
.look-pic[data-pic^="card-"] { grid-auto-rows: 4px; gap: 3px; padding: 5px; }
.look-pic[data-pic="card-plain"] i:nth-child(n + 2) { display: none; }
.look-pic[data-pic="card-rich"] i:nth-child(2), .look-pic[data-pic="card-rich"] i:nth-child(3) { width: 60%; opacity: .45; }

/* The agents along the top, and how wide the conversation runs. */
.look-pic[data-pic^="agentStrip-"] { grid-template-rows: 6px 1fr; }
.look-pic[data-pic^="agentStrip-"] i:nth-child(1) { border-radius: 50%; width: 6px; justify-self: start; }
.look-pic[data-pic="agentStrip-hidden"] i:nth-child(1) { visibility: hidden; }
.look-pic[data-pic^="agentStrip-"] i:nth-child(2) { opacity: .4; }
.look-pic[data-pic^="agentStrip-"] i:nth-child(3) { display: none; }
.look-pic[data-pic^="chatWidth-"] { grid-auto-rows: 4px; }
.look-pic[data-pic="chatWidth-narrow"] i { width: 60%; justify-self: center; }

/* The accent is the one tile that is simply the colour it names. */
.look-pic[data-pic^="accent-"] { padding: 4px; align-content: stretch; }
.look-pic[data-pic^="accent-"] i:nth-child(n + 2) { display: none; }
.look-pic[data-pic="accent-default"] i { background: #2563eb; }
.look-pic[data-pic="accent-violet"] i { background: #6d4aff; }
.look-pic[data-pic="accent-blue"] i { background: #1f6feb; }
.look-pic[data-pic="accent-green"] i { background: #17824a; }
.look-pic[data-pic="accent-amber"] i { background: #a35c00; }
.look-pic[data-pic="accent-rose"] i { background: #b23052; }
.look-tile[aria-pressed="true"] .look-pic[data-pic^="accent-"] i { background: inherit; }

/*
 * A preset, as a picture of the room it makes: the navigation down one
 * side, the work in the middle, the conversation where that preset puts
 * it. Drawn from the preset's own values, so these cannot drift from
 * ui.ts -- there is no second table here saying what Jira looks like.
 */
#sidekickPresets { display: flex; flex-wrap: wrap; gap: 8px; }
.look-preset { width: 76px; }
.look-mini {
  display: grid;
  width: 60px;
  height: 40px;
  padding: 2px;
  gap: 2px;
  border: 1px solid var(--border-strong);
  border-radius: 3px;
  background: var(--bg);
  grid-template-columns: 13px minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
}
.look-mini i { border-radius: 1px; background: var(--border-strong); }
.look-mini .look-mini-nav { grid-row: 1 / -1; }
.look-mini .look-mini-chat { display: none; }
/*
 * The work area carries the shape of the view, drawn as a gradient rather
 * than as more elements: columns for a board, rows for a list, a grid for
 * a table. It is the difference a person is actually choosing between, and
 * without it every preset was a coloured bar beside a grey rectangle.
 */
.look-mini .look-mini-work { background: var(--border); }
.look-mini[data-view="board"] .look-mini-work {
  background-image: repeating-linear-gradient(90deg, var(--border-strong) 0 7px, transparent 7px 10px);
}
.look-mini[data-view="list"] .look-mini-work {
  background-image: repeating-linear-gradient(180deg, var(--border-strong) 0 3px, transparent 3px 7px);
}
.look-mini[data-view="table"] .look-mini-work {
  background-image:
    repeating-linear-gradient(180deg, var(--border-strong) 0 3px, transparent 3px 6px),
    repeating-linear-gradient(90deg, var(--bg) 0 1px, transparent 1px 9px);
}
.look-mini .look-mini-chat { background: var(--border); }
.look-mini .look-mini-chat::after { content: ""; }
.look-mini[data-nav="rail"] { grid-template-columns: 5px minmax(0, 1fr); }
.look-mini[data-nav="hidden"] { grid-template-columns: minmax(0, 1fr); }
.look-mini[data-nav="hidden"] .look-mini-nav { display: none; }
.look-mini[data-chat="side"] { grid-template-columns: 13px minmax(0, 1fr) 13px; }
.look-mini[data-chat="side"][data-nav="rail"] { grid-template-columns: 5px minmax(0, 1fr) 13px; }
.look-mini[data-chat="side"][data-nav="hidden"] { grid-template-columns: minmax(0, 1fr) 13px; }
.look-mini[data-chat="side"] .look-mini-chat {
  display: block;
  background-image: repeating-linear-gradient(180deg, var(--border-strong) 0 2px, transparent 2px 6px);
}
.look-mini[data-chat="below"] { grid-template-rows: minmax(0, 1fr) 11px; }
.look-mini[data-chat="below"] .look-mini-nav { grid-row: 1 / -1; }
.look-mini[data-chat="below"] .look-mini-chat {
  display: block;
  /* A line above it, because a strip of rows under a list of rows is not a
     strip at this size -- it is more list. */
  border-top: 1px solid var(--text-soft);
  background-image: repeating-linear-gradient(180deg, var(--border-strong) 0 2px, transparent 2px 5px);
}
.look-mini[data-shape="square"] i { border-radius: 0; }
.look-mini[data-shape="round"] i { border-radius: 4px; }
.look-mini[data-accent="violet"] .look-mini-nav { background: #6d4aff; }
.look-mini[data-accent="blue"] .look-mini-nav { background: #1f6feb; }
.look-mini[data-accent="green"] .look-mini-nav { background: #17824a; }
.look-mini[data-accent="amber"] .look-mini-nav { background: #a35c00; }
.look-mini[data-accent="rose"] .look-mini-nav { background: #b23052; }
.look-mini[data-accent="default"] .look-mini-nav { background: #2563eb; }

/*
 * How a room looks, set by the people in it (rooms/ui.ts). Every one of
 * these is a value from that table: the page renders what it is told and
 * nothing it was not.
 */
/*
 * THE THREE SCALES, AS TOKENS.
 *
 * Space, type and corner. Everything below reads these rather than a
 * number of its own, which is the whole reason a preset can make the room
 * feel like somewhere else: one write on `main.rooms-app` moves every
 * surface at once, and no rule has a private opinion to keep in step.
 *
 * The values on `main.rooms-app` ARE the defaults -- 12px, 10px/12px,
 * 10px, 14.5px -- and they are exactly what this room measured before the
 * scales existed, so the default room did not move a pixel when they
 * arrived.
 */
main.rooms-app {
  --room-gap: 12px;
  --room-pad: 10px 12px;
  --room-colpad: 10px;
  --room-row: 9px;
  --room-radius: 10px;
  --room-radius-lg: 12px;
  --room-font: 14.5px;
}
main.rooms-app[data-rhythm="tight"] { --room-gap: 6px; --room-pad: 5px 9px; --room-colpad: 7px; --room-row: 4px; }
main.rooms-app[data-rhythm="airy"] { --room-gap: 20px; --room-pad: 15px 18px; --room-colpad: 16px; --room-row: 15px; }
main.rooms-app[data-type-scale="small"] { --room-font: 13px; }
main.rooms-app[data-type-scale="large"] { --room-font: 16px; }
main.rooms-app[data-shape="square"] { --room-radius: 2px; --room-radius-lg: 3px; }
main.rooms-app[data-shape="round"] { --room-radius: 16px; --room-radius-lg: 20px; }

main.rooms-app .rooms-tab { font-size: var(--room-font); }
main.rooms-app .room-msg { padding-block: var(--room-row); }
main.rooms-app .tasks-list { gap: var(--room-gap); }
main.rooms-app .tasks-item { padding: var(--room-pad); border-radius: var(--room-radius); }
main.rooms-app .tasks-board { gap: var(--room-gap); }
main.rooms-app .tasks-col { padding: var(--room-colpad); border-radius: var(--room-radius-lg); }
main.rooms-app .tasks-col ul { gap: var(--room-gap); }
main.rooms-app[data-chat-width="wide"] .room-messages { max-width: none; }

/*
 * WHERE THE CONVERSATION IS.
 *
 * A tab is the default and needs nothing: the panes take turns in one
 * column. The other two make the room a grid -- the head and the tabs
 * across the top, the work and the conversation sharing what is left --
 * and the chat pane is simply never hidden (rooms.js), so "which tab" and
 * "is the chat up" stop being the same question.
 */
main.rooms-app[data-chat-place="side"] .rooms-chat,
main.rooms-app[data-chat-place="below"] .rooms-chat { display: grid; min-height: 0; }
main.rooms-app[data-chat-place="side"] .rooms-chat {
  grid-template-columns: minmax(0, 1fr) minmax(240px, 24vw);
  grid-template-rows: auto auto minmax(0, 1fr);
}
main.rooms-app[data-chat-place="below"] .rooms-chat {
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1.4fr) minmax(0, 1fr);
}
main.rooms-app[data-chat-place="side"] .rooms-chat-head,
main.rooms-app[data-chat-place="side"] .rooms-tabs { grid-column: 1 / -1; }
main.rooms-app[data-chat-place="side"] #tabTasks,
main.rooms-app[data-chat-place="side"] #tabFiles { grid-area: 3 / 1; }
main.rooms-app[data-chat-place="side"] #tabChat {
  grid-area: 3 / 2;
  border-inline-start: 1px solid var(--border);
}
main.rooms-app[data-chat-place="below"] #tabTasks,
main.rooms-app[data-chat-place="below"] #tabFiles { grid-row: 3; }
main.rooms-app[data-chat-place="below"] #tabChat { grid-row: 4; border-top: 1px solid var(--border); }
/* Its tab button has nothing left to select. */
main.rooms-app[data-chat-place="side"] #tabChatBtn,
main.rooms-app[data-chat-place="below"] #tabChatBtn { display: none; }
/* A column that narrow reads better without the full-width hint under it. */
main.rooms-app[data-chat-place="side"] .rooms-hint { display: none; }

/*
 * THE NAVIGATION, AS A RAIL.
 *
 * The same destinations in 64px, and the two halves of the rail are drawn
 * differently on purpose.
 *
 * THE ROOMS keep their own first letter, on a tile. A letter is the room's
 * own in every language and no icon could stand for "Eden"; a tile is what
 * stops a column of letters reading as loose characters floating in a
 * margin, which is what it was.
 *
 * THE FIXED DESTINATIONS get icons. They are the same seven things in
 * every language, and their initials are not distinguishing: "AI agents"
 * and "Your AI keys" are both A in English and both Ш in Ukrainian, so a
 * rail of initials there said nothing at all. The icons are line marks in
 * currentColor at one weight, defined once at the top of rooms.html.
 *
 * The name stays for a pointer and a screen reader -- the text is hidden
 * from the eye, not from the accessibility tree, and `title` is set by
 * rooms.js from the label the page was translated with.
 */
main.rooms-app[data-nav="rail"] { grid-template-columns: 64px minmax(0, 1fr); }
main.rooms-app[data-nav="rail"]:has(> .rooms-right:not(.hidden)) { grid-template-columns: 64px minmax(0, 1fr) 340px; }
main.rooms-app[data-nav="hidden"] { grid-template-columns: minmax(0, 1fr); }
main.rooms-app[data-nav="hidden"]:has(> .rooms-right:not(.hidden)) { grid-template-columns: minmax(0, 1fr) 340px; }
main.rooms-app[data-nav="hidden"] > .rooms-left { display: none; }
main.rooms-app[data-nav="rail"] > .rooms-left {
  align-items: center;
  gap: 0;
  padding: 10px 0;
  overflow-x: hidden;
}
main.rooms-app[data-nav="rail"] .rooms-side-head,
main.rooms-app[data-nav="rail"] .rooms-nav-title,
main.rooms-app[data-nav="rail"] .rooms-left form,
main.rooms-app[data-nav="rail"] .rooms-left .tree-more,
main.rooms-app[data-nav="rail"] .rooms-left .tree-chats,
main.rooms-app[data-nav="rail"] .rooms-left .tree-add { display: none; }
/* The two screens of the app, as two marks rather than a clipped segmented control. */
main.rooms-app[data-nav="rail"] .rooms-switch {
  display: flex;
  flex-direction: column;
  gap: 4px;
  width: auto;
  margin: 0 0 10px;
  padding: 0;
  border: 0;
  background: none;
}
main.rooms-app[data-nav="rail"] .rooms-left .rooms-list { width: 100%; }
main.rooms-app[data-nav="rail"] .rooms-left .tree-row { justify-content: center; }
main.rooms-app[data-nav="rail"] .rooms-nav {
  width: 100%;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
/* One tile, whatever is in it: a letter, or a mark. */
main.rooms-app[data-nav="rail"] .rooms-left .tree-name,
main.rooms-app[data-nav="rail"] .rooms-nav-item,
main.rooms-app[data-nav="rail"] .rooms-switch .tab {
  position: relative;
  display: grid;
  place-items: center;
  /*
   * `flex: none` before the size, and it is doing the work.
   *
   * Both of these sit in a flex container that already had an opinion: a
   * room's link is `flex: 1` in its row and a switch tab is `flex: 1 1 0`
   * in the switch. A `flex-basis` of 0 BEATS `width`/`height` on the main
   * axis, so the tiles came out 63px wide in the one and 22px tall in the
   * other -- the sizes below were being set and then ignored.
   */
  flex: none;
  width: 42px;
  height: 42px;
  margin: 0 auto 5px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--room-radius);
  background: var(--bg-sunken);
  color: var(--text-soft);
  font-size: 0;
  overflow: hidden;
}
main.rooms-app[data-nav="rail"] .rooms-switch .tab { margin-bottom: 0; }
main.rooms-app[data-nav="rail"] .rooms-left .tree-name:hover,
main.rooms-app[data-nav="rail"] .rooms-nav-item:hover,
main.rooms-app[data-nav="rail"] .rooms-switch .tab:hover { border-color: var(--border-strong); color: var(--text); }
main.rooms-app[data-nav="rail"] .rooms-left .tree-name:focus-visible,
main.rooms-app[data-nav="rail"] .rooms-nav-item:focus-visible,
main.rooms-app[data-nav="rail"] .rooms-switch .tab:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
/* A room is its letter; a destination is its mark, and never its letter. */
main.rooms-app[data-nav="rail"] .rooms-left .tree-name::before {
  content: attr(data-initial);
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
}
main.rooms-app[data-nav="rail"] .rail-icon { display: block; width: 20px; height: 20px; }
main.rooms-app[data-nav="rail"] .rooms-left .tree-name[aria-current="true"],
main.rooms-app[data-nav="rail"] .rooms-switch .tab[aria-selected="true"] {
  background: var(--blue);
  border-color: transparent;
  color: var(--on-accent);
}
/*
 * The count beside a destination is a number in a tile 42px wide: it does
 * not fit beside the mark and it floated above it. A dot in the corner
 * says the same thing -- there is something in there -- in the room it has.
 */
main.rooms-app[data-nav="rail"] .rooms-nav-item > b { font-size: 0; }
main.rooms-app[data-nav="rail"] .rooms-nav-item > b:not(:empty) {
  position: absolute;
  top: 5px;
  inset-inline-end: 5px;
  /* The pill it is when the rail is a list has a 20px floor and side
     padding; a dot has neither, so both are cleared here. */
  min-width: 0;
  padding: 0;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--blue);
}

/*
 * THE CONTROLS: a bar of their own, or folded into the work.
 *
 * `inline` puts the one thing a person came to do -- add a task -- first,
 * and lets the sort and filter sit quietly under it. It is the shape of a
 * screen that is a document rather than a console.
 */
main.rooms-app[data-toolbar="inline"] .tasks-quick { order: -1; }
main.rooms-app[data-toolbar="inline"] .tasks-bar {
  justify-content: flex-end;
  margin: 0;
  font-size: 12.5px;
  color: var(--muted);
}
main.rooms-app[data-toolbar="inline"] .tasks-bar .seg { order: 2; }

/* A plain card is its title. The facts are still on the task itself, and
   in the list and the table, where reading them is the point. */
main.rooms-app[data-card="plain"] .tasks-col .tasks-meta { display: none; }

main.rooms-app[data-accent="violet"] { --blue: #6d4aff; --blue-soft: #efeaff; }
main.rooms-app[data-accent="blue"] { --blue: #1f6feb; --blue-soft: #e6f0ff; }
main.rooms-app[data-accent="green"] { --blue: #17824a; --blue-soft: #e7f5ec; }
main.rooms-app[data-accent="amber"] { --blue: #a35c00; --blue-soft: #fdf0dd; }
main.rooms-app[data-accent="rose"] { --blue: #b23052; --blue-soft: #fde9ee; }

/*
 * WORK AS RECORDS: the table.
 *
 * The same rows as the list, in columns. `grid-template-columns` on each
 * row rather than a real <table>, because the rows are the list's own <li>
 * elements with the list's own behaviour on them, and a table element
 * would mean a second renderer for the same task.
 */
.tasks-list.is-table { gap: 0; }
.tasks-list.is-table .tasks-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 7em) minmax(0, 6em) minmax(0, 7em) minmax(0, 7em);
  align-items: center;
  gap: 10px;
  border-radius: 0;
  border-inline-width: 0;
  border-block-start-width: 0;
  border-left: 3px solid transparent;
}
.tasks-list.is-table .tasks-item.is-high { border-left-color: var(--warn); }
.tasks-list.is-table .tasks-item.is-urgent { border-left-color: var(--danger); }
.tasks-list.is-table .tasks-cell { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tasks-list.is-table .tasks-cell.is-tick { overflow: visible; }
.tasks-list.is-table .tasks-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.tasks-list.is-table .tasks-head {
  background: var(--bg-sunken);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--muted);
  border-left-color: transparent;
}
.tasks-list.is-table > li input[type="checkbox"] { margin: 0; }
@media (max-width: 760px) {
  /* Six columns do not fit a phone. Three do: the tick, the title and
     where the task has got to -- which is what a phone can read anyway.
     The count here has to match the cells left showing, or the row wraps
     and the heading reads "TASK FOR" over "STATE". */
  .tasks-list.is-table .tasks-item { grid-template-columns: auto minmax(0, 1fr) minmax(0, 6em); }
  .tasks-list.is-table .tasks-cell.is-who,
  .tasks-list.is-table .tasks-cell.is-rank,
  .tasks-list.is-table .tasks-cell.is-due { display: none; }
}
/* The group a task list is gathered into, when it is gathered at all. */
.tasks-group { margin: 14px 0 4px; font-size: 12.5px; font-weight: 600; letter-spacing: .03em; text-transform: uppercase; color: var(--muted); }
.tasks-group:first-child { margin-top: 0; }

/*
 * The room picture on the landing page: the tasks a room opens on, and the
 * panel's private question. Plain elements -- nothing here can be pressed.
 */
.rooms-preview-tasks { padding: 8px 12px; border-bottom: 1px solid var(--border); }
.rooms-preview-task { display: flex; align-items: center; gap: 8px; margin: 4px 0; font-size: 13px; }
.rooms-preview-task .tasks-chip { flex: none; }
.rooms-preview-side-ask {
  margin: 4px 0 2px;
  padding: 6px 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  font-size: 12.5px;
  color: var(--text-soft);
}

.rooms-side {
  min-height: 0;
  overflow-y: auto;
  padding: 12px 14px 16px;
  background: var(--card);
  border-right: 1px solid var(--border);
}
.rooms-side-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
.rooms-title { font-size: 18px; margin: 0; }
.rooms-side-head h2 { font-size: 15px; margin: 0; }
/*
 * The way between the two screens of the installed app. Full width of the
 * left column, so it reads as where you are rather than a button, and the
 * links carry `.tab` so they share the segmented control's own look.
 */
.rooms-switch { display: flex; width: 100%; margin-bottom: 10px; }
.rooms-switch .tab { flex: 1; text-align: center; text-decoration: none; }
.rooms-list { list-style: none; margin: 0 0 10px; padding: 0; }
.rooms-list a {
  display: block;
  padding: 7px 10px;
  border-radius: 8px;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rooms-list a:hover { background: var(--bg-sunken); }
.rooms-list a[aria-current] { background: var(--blue-soft); color: var(--blue); font-weight: 600; }

/*
 * The list as a tree: rooms, and under the open one its chats. A row is a
 * name and its ⋮; a chat's settings drop down under its name, indented.
 */
.rooms-tree .tree-row { position: relative; display: flex; align-items: center; gap: 2px; border-radius: 8px; }
.rooms-tree .tree-row:hover, .rooms-tree .tree-row:focus-within { background: var(--bg-sunken); }
.rooms-side .rooms-tree .tree-name {
  flex: 1;
  min-width: 0;
  display: block;
  padding: 6px 10px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  text-align: start;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.rooms-tree .tree-room.is-open > .tree-row .tree-name { font-weight: 600; }
.rooms-tree .tree-name[aria-current] { background: var(--blue-soft); color: var(--blue); font-weight: 600; }
.rooms-side .rooms-tree .tree-more {
  flex: none;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--muted);
  font-size: 16px;
  line-height: 1;
  opacity: 0;
  cursor: pointer;
}
.rooms-tree .tree-row:hover .tree-more, .rooms-tree .tree-more:focus-visible, .rooms-tree .tree-more[aria-expanded="true"],
.rooms-tree .tree-name[aria-current] + .tree-more { opacity: 1; }
.rooms-tree .tree-more:hover { background: var(--border); color: var(--text); }
@media (hover: none) { .rooms-tree .tree-more { opacity: 1; } }
.rooms-tree .tree-chats { list-style: none; margin: 2px 0 6px; padding: 0 0 0 12px; border-inline-start: 1px solid var(--border); margin-inline-start: 12px; }
.rooms-side .rooms-tree .tree-chats .tree-name { padding: 5px 10px; font-size: 13px; }
.rooms-side .rooms-tree .tree-add { border: 0; background: none; padding: 5px 10px; color: var(--blue); font-size: 12.5px; font-weight: 600; cursor: pointer; }
.rooms-tree .tree-add:hover { text-decoration: underline; }
.rooms-tree .tree-rename { flex: 1; min-width: 0; margin: 2px 0; }
.rooms-tree .tree-menu {
  position: absolute;
  z-index: 20;
  top: calc(100% + 2px);
  inset-inline-end: 0;
  display: flex;
  flex-direction: column;
  min-width: 150px;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card);
  box-shadow: 0 8px 24px rgba(15, 23, 42, .14);
}
.rooms-side .rooms-tree .tree-menu button { padding: 6px 10px; border: 0; border-radius: 6px; background: none; color: var(--text); font-size: 13px; text-align: start; cursor: pointer; }
.rooms-tree .tree-menu button:hover, .rooms-tree .tree-menu button:focus-visible { background: var(--bg-sunken); }
.rooms-tree .tree-menu button.is-danger { color: var(--danger); }
.rooms-tree .tree-settings { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 8px; padding: 8px 10px; border-radius: 8px; background: var(--bg-sunken); }
.rooms-tree .tree-set-head { margin: 4px 0 0; font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.rooms-tree .tree-set-head:first-child { margin-top: 0; }
.rooms-side .rooms-tree label.tree-set { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 6px; font-size: 12px; }
.rooms-tree .tree-set span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rooms-side .rooms-tree .tree-set select { width: auto; max-width: 140px; padding: 2px 4px; font-size: 12px; }
/* A setting whose choices are long sentences gets the row to itself. */
.rooms-side .rooms-tree label.tree-set.is-wide { grid-template-columns: minmax(0, 1fr); gap: 2px; }
.rooms-side .rooms-tree .tree-set.is-wide select { max-width: none; width: 100%; }
.rooms-tree .tree-set-note { margin: 0; font-size: 11.5px; color: var(--muted); }
/* The rail's marks are in the markup at every width; only a rail shows them. */
.rail-icon { display: none; flex: none; fill: none; stroke: currentColor; }

.rooms-chat-of:not(:empty)::before { content: " / "; color: var(--muted); font-weight: 400; }
.rooms-chat-of { color: var(--text-soft); font-weight: 500; }
.rooms-inline { display: flex; gap: 8px; max-width: none; margin: 0 0 14px; }
.rooms-inline input { flex: 1; min-width: 0; }
.rooms-panel { border-top: 1px solid var(--border); padding: 6px 0 5px; }
.rooms-panel.rooms-first { border-top: 0; padding-top: 0; }
.rooms-panel > summary, .rooms-panel-title { cursor: pointer; font-weight: 600; font-size: 13.5px; color: var(--text); margin: 0; }
.rooms-panel-title { cursor: default; }
.rooms-panel[open] > summary { margin-bottom: 4px; }
.rooms-panel form { max-width: none; }

/*
 * The side columns are dense on purpose: a member should see every panel
 * of a room, open, without scrolling a 900-pixel window. So the controls
 * here are a size smaller than a form page's, related fields share a row,
 * and each panel says what it is for in one short line, not a paragraph.
 */
.rooms-side form, .rooms-form { display: flex; flex-direction: column; gap: 5px; margin: 0; }
.rooms-side form.rooms-inline { flex-direction: row; }
.rooms-side label:not(.check), .rooms-side legend { font-size: 11.5px; margin: 0; padding: 0; }
.rooms-side input, .rooms-side select, .rooms-side textarea { padding: 4px 7px; font-size: 12.5px; line-height: 1.35; }
.rooms-side textarea.prose { min-height: 0; font-size: 12.5px; line-height: 1.35; }
.rooms-side button { padding: 4px 10px; font-size: 12.5px; line-height: 1.35; }
.rooms-side .rooms-list a { padding: 5px 10px; }
.rooms-side .btn-row { gap: 6px; }
.rooms-side .check { padding: 0; gap: 7px; font-size: 12.5px; line-height: 1.35; align-items: flex-start; }
.rooms-side .check input { margin-top: 2px; }
.rooms-side fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.rooms-field { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rooms-pair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 6px; }
/* A select whose choices are words, not values, keeps its own width rather than cutting them off. */
.rooms-pair.select-first { grid-template-columns: minmax(0, max-content) minmax(6em, 1fr); }
.rooms-pair.select-last { grid-template-columns: minmax(7em, 1fr) minmax(0, max-content); }
/* Which key answers, for the room and for each of its chats: one row each,
   the key on the left and the model beside it, in the one place. */
.brain-row { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); align-items: end; }
.brain-row + .brain-row { margin-top: 6px; }
.brain-row input:disabled { opacity: .5; }
.rooms-checks { display: flex; flex-wrap: wrap; gap: 0 12px; }
.rooms-note { margin: 0 0 4px; font-size: 12px; line-height: 1.35; color: var(--muted); }
.rooms-rows { list-style: none; margin: 0 0 6px; padding: 0; font-size: 12.5px; color: var(--text-soft); }
.rooms-rows li { display: flex; align-items: center; justify-content: space-between; gap: 6px; padding: 2px 0; min-width: 0; }
.rooms-rows li > span { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.rooms-rows button { padding: 1px 6px; font-size: 12px; }
/* A row's button keeps its width: squeezed, a CJK label wraps one character per line. */
.rooms-rows button, .rooms-connect button { flex: none; white-space: nowrap; }
.rooms-connect { border: 1px solid var(--border); border-radius: 8px; padding: 6px 8px; margin: 0 0 8px; font-size: 12.5px; }
.rooms-connect-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.rooms-connect .rooms-note { margin: 3px 0 0; }

/*
 * Project rooms have two columns: the rooms, with the open room's menu under
 * them, and the conversation. What used to be a third column -- the room's
 * agents, channels, task tools and settings -- opens as a dialog from that
 * menu, so the conversation keeps its width and each part gets room to
 * breathe.
 */
.rooms-nav { display: flex; flex-direction: column; gap: 2px; margin: 0 0 12px; padding-top: 12px; border-top: 1px solid var(--border); }
/* The new-room form above it has no margin of its own in the column: the space is the menu's. */
.rooms-side form#newRoom { margin-bottom: 14px; }
.rooms-nav-title { margin: 0 0 4px; padding: 0 10px; font-size: 11px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.rooms-side button.rooms-nav-item,
.rooms-nav-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 7px 10px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--text);
  font-size: 13.5px;
  font-weight: 500;
  text-align: start;
  cursor: pointer;
}
.rooms-nav-item:hover, .rooms-nav-item:focus-visible { background: var(--bg-sunken); }
.rooms-nav-item b:empty { display: none; }
.rooms-nav-item b { min-width: 20px; padding: 0 6px; border-radius: 999px; background: var(--bg-sunken); color: var(--text-soft); font-size: 11.5px; font-weight: 600; line-height: 20px; text-align: center; }
.rooms-nav-item.is-wanted b { background: var(--warn-soft); color: var(--warn); }

/* A room's part, in a dialog: a title with Close beside it, and the room's usual spacing, a size up. */
dialog.rooms-panel-dialog { width: min(560px, calc(100vw - 24px)); padding: 18px 20px 20px; }
.rooms-dialog-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 10px; }
.rooms-dialog-head h2 { margin: 0; font-size: 18px; }
.rooms-dialog-head button { flex: none; }
.rooms-panel-dialog .rooms-form, .rooms-panel-dialog form { display: flex; flex-direction: column; gap: 10px; max-width: none; margin: 0; }
.rooms-panel-dialog form.rooms-inline { flex-direction: row; }
.rooms-panel-dialog .rooms-note { margin: 0 0 10px; font-size: 13px; line-height: 1.45; }
/*
 * WHERE EVERYTHING LANDS: one row per link, for the whole workspace.
 *
 * A table rather than the list beside it, because this one answers four
 * things about each row -- what, where, into which room and chat, and
 * when it last carried anything -- and four facts in a run-on line is the
 * shape nobody reads. On a phone the head goes and each row becomes its
 * own block with its label in front, which is the only way four columns
 * fit in 358px.
 */
.rooms-subhead { margin: 18px 0 8px; font-size: 13.5px; color: var(--text); }
.rooms-subhead:first-of-type { margin-top: 0; }
.rooms-table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 0 0 10px; }
.rooms-table th {
  text-align: start;
  font-weight: 600;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
  padding: 0 10px 6px 0;
  border-bottom: 1px solid var(--border);
}
.rooms-table td { padding: 7px 10px 7px 0; border-bottom: 1px solid var(--border); color: var(--text-soft); vertical-align: top; }
.rooms-table tr:last-child td { border-bottom: 0; }
.rooms-table td:last-child, .rooms-table th:last-child { padding-right: 0; text-align: end; }
.rooms-table button { padding: 1px 6px; font-size: 12px; white-space: nowrap; }
.rooms-table .rooms-table-empty { color: var(--muted); }
/* A link that has carried nothing says so rather than showing an empty cell. */
.rooms-table .rooms-never { color: var(--muted); }
@media (max-width: 560px) {
  .rooms-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .rooms-table tr { display: block; padding: 8px 0; border-bottom: 1px solid var(--border); }
  .rooms-table tr:last-child { border-bottom: 0; }
  .rooms-table td { display: flex; gap: 8px; border: 0; padding: 1px 0; }
  .rooms-table td::before { content: attr(data-label); color: var(--muted); flex: none; min-width: 7.5em; }
  .rooms-table td:last-child { text-align: start; margin-top: 4px; }
  .rooms-table td:last-child::before { content: none; }
}

.rooms-panel-dialog .rooms-rows { margin: 0 0 14px; font-size: 13.5px; }
.rooms-panel-dialog .rooms-rows li { padding: 6px 0; border-bottom: 1px solid var(--border); }
.rooms-panel-dialog .rooms-rows li:last-child { border-bottom: 0; }
/* A dialog has the width to say a row whole: it wraps rather than trailing off. */
.rooms-panel-dialog .rooms-rows li > span { white-space: normal; overflow: visible; }
@media (max-width: 480px) {
  .rooms-panel-dialog .rooms-pair, .rooms-panel-dialog .rooms-pair.select-first, .rooms-panel-dialog .rooms-pair.select-last { grid-template-columns: minmax(0, 1fr); }
}
.rooms-panel-dialog .rooms-rows button { padding: 3px 9px; font-size: 12.5px; }
.rooms-panel-dialog .rooms-panel { padding: 12px 0; }
.rooms-panel-dialog .rooms-panel.rooms-first { padding-top: 0; }
.rooms-panel-dialog .rooms-panel[open] > summary { margin-bottom: 10px; }
.rooms-panel-dialog .rooms-panel > summary { font-size: 14px; }
.rooms-panel-dialog .btn-row { margin-top: 2px; }
.rooms-panel-dialog fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.rooms-panel-dialog label:not(.check), .rooms-panel-dialog legend { font-size: 12.5px; margin: 0 0 2px; padding: 0; }
.rooms-panel-dialog .rooms-connect { padding: 10px 12px; margin: 0 0 12px; }
.rooms-panel-dialog .room-chip { font-size: 12px; padding: 2px 8px; }
.rooms-panel-dialog .rooms-chips { gap: 5px; }
.rooms-danger { margin-top: 8px; padding-top: 14px; border-top: 1px solid var(--border); }

/*
 * A room's tasks: a bar (view, sort, show), a line to add one, then a list
 * or a board of three columns. A task is a row or a card: its title opens
 * it, and its chips say how urgent, for whom, by when, how many links and
 * where it came from.
 */
.tasks-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; margin: 0 0 12px; }
.tasks-pick { display: inline-flex; align-items: center; gap: 6px; margin: 0; font-size: 13px; color: var(--muted); font-weight: 500; }
.tasks-pick select { width: auto; padding: 4px 8px; font-size: 13px; }
form.tasks-quick { display: flex; flex-direction: row; gap: 8px; margin: 0 0 12px; max-width: none; }
form.tasks-quick input { flex: 1; min-width: 0; }
form.tasks-quick button { flex: none; width: auto; }
.tasks-list, .tasks-board ul { list-style: none; margin: 0; padding: 0; }
.tasks-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.tasks-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: 10px;
  background: var(--card);
}
.tasks-item.is-high { border-left-color: var(--warn); }
.tasks-item.is-urgent { border-left-color: var(--danger); }
.tasks-item > input[type="checkbox"] { width: auto; margin: 3px 0 0; flex: none; }
.tasks-body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 5px; }
button.tasks-title {
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.35;
  text-align: start;
  cursor: pointer;
  overflow-wrap: anywhere;
}
button.tasks-title:hover { color: var(--blue); }
.tasks-item.is-done button.tasks-title { color: var(--muted); text-decoration: line-through; font-weight: 500; }
.tasks-meta { display: flex; flex-wrap: wrap; gap: 4px 6px; }
.tasks-chip { padding: 1px 7px; border-radius: 999px; background: var(--bg-sunken); color: var(--text-soft); font-size: 11.5px; line-height: 1.6; }
.tasks-chip.is-high { background: var(--warn-soft); color: var(--warn); font-weight: 600; }
.tasks-chip.is-urgent { background: var(--danger-soft); color: var(--danger); font-weight: 600; }
.tasks-chip.is-low { color: var(--muted); }
.tasks-chip.is-doing { background: var(--blue-soft); color: var(--blue); }
.tasks-chip.is-late { background: var(--danger-soft); color: var(--danger); }
/* Ready to be taken by an assistant: the one chip that is about who can do the task, not when. */
.tasks-chip.is-ready { background: var(--ok-soft); color: var(--ok); font-weight: 600; }
.tasks-chip.is-source { background: none; border: 1px dashed var(--border-strong); }
/*
 * The board is as many columns as the room is gathered into -- three
 * states, four priorities, or one per person -- so the count is not a
 * number in this file. `grid-auto-flow: column` lays them all in one row
 * and `grid-auto-columns` gives each a floor; past that the board scrolls
 * sideways, the way a board does, rather than wrapping a fourth column
 * onto a second row under the first three.
 */
.tasks-board {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(190px, 1fr);
  gap: 12px;
  align-items: start;
  margin-bottom: 14px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: 4px;
}
.tasks-col { min-width: 0; padding: 10px; border-radius: 12px; background: var(--bg-sunken); }
.tasks-col h3 { margin: 0 0 8px; font-size: 12.5px; font-weight: 600; letter-spacing: .03em; text-transform: uppercase; color: var(--muted); }
.tasks-col ul { display: flex; flex-direction: column; gap: 8px; min-height: 40px; }
.tasks-col.is-over { outline: 2px dashed var(--blue); outline-offset: -2px; }
.tasks-col .tasks-item { cursor: grab; }
.tasks-item.is-dragged { opacity: .5; }
.tasks-none { color: var(--faint); font-size: 13px; text-align: center; padding: 6px 0; }
.tasks-auto { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; padding-top: 12px; border-top: 1px solid var(--border); font-size: 13px; }
.tasks-auto label { margin: 0; font-weight: 600; font-size: 13px; }
.tasks-auto select { width: auto; max-width: 100%; padding: 4px 8px; font-size: 13px; }
@media (max-width: 720px) {
  .tasks-board { grid-template-columns: minmax(0, 1fr); }
}
#taskLinks a { min-width: 0; overflow-wrap: anywhere; }

/*
 * Files and docs: a room's documents by link, each with what it is (a Google
 * Doc, a sheet...), who added it, and Preview / PDF / Remove; then files,
 * then notes. A preview is a large dialog with Google's own page in it.
 */
form.docs-add { display: flex; flex-direction: row; flex-wrap: wrap; gap: 8px; margin: 0 0 8px; max-width: none; }
form.docs-add input[type="url"] { flex: 2 1 260px; min-width: 0; }
form.docs-add input[maxlength] { flex: 1 1 160px; min-width: 0; }
form.docs-add input[type="file"] { flex: 1 1 220px; min-width: 0; }
form.docs-add button { flex: none; width: auto; }
.docs-heading { margin: 18px 0 8px; font-size: 12.5px; font-weight: 600; letter-spacing: .03em; text-transform: uppercase; color: var(--muted); }
.docs-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.docs-item { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--card); }
.docs-kind { flex: none; min-width: 92px; padding: 2px 8px; border-radius: 6px; background: var(--bg-sunken); color: var(--text-soft); font-size: 11.5px; font-weight: 600; text-align: center; }
.docs-item.is-doc .docs-kind { background: var(--blue-soft); color: var(--blue); }
.docs-item.is-sheet .docs-kind { background: var(--ok-soft); color: var(--ok); }
.docs-item.is-slides .docs-kind { background: var(--warn-soft); color: var(--warn); }
.docs-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.docs-body a { font-weight: 600; overflow-wrap: anywhere; }
.docs-actions { flex: none; display: flex; gap: 6px; align-items: center; }
.docs-actions button, .docs-actions .btn { padding: 4px 10px; font-size: 12.5px; }
.docs-none { color: var(--muted); font-size: 13.5px; padding: 6px 0; }
dialog.rooms-dialog.docs-preview-dialog { width: min(1100px, calc(100vw - 24px)); height: min(88dvh, 900px); padding: 14px 16px; display: none; flex-direction: column; gap: 8px; overflow: hidden; }
dialog.rooms-dialog.docs-preview-dialog[open] { display: flex; }
dialog.docs-preview-dialog .rooms-dialog-head { margin: 0; }
dialog.docs-preview-dialog .rooms-dialog-head h2 { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 16px; }
dialog.docs-preview-dialog .rooms-dialog-head .btn-row { flex: none; gap: 6px; }
#previewFrame { flex: 1; width: 100%; min-height: 0; border: 1px solid var(--border); border-radius: 8px; background: #fff; }
@media (max-width: 640px) {
  .docs-item { flex-wrap: wrap; }
  .docs-kind { min-width: 0; }
  .docs-actions { width: 100%; justify-content: flex-end; }
}

/* The built-in agents: one chip per @handle, so fourteen fit in five short rows. */
.rooms-chips { display: flex; flex-wrap: wrap; gap: 3px; margin: 0 0 5px; }
.room-chip {
  display: inline-flex;
  align-items: center;
  padding: 1px 6px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--card);
  color: var(--text);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.45;
}
.rooms-side button.room-chip { padding: 1px 6px; font-size: 11px; font-weight: 500; line-height: 1.45; }
button.room-chip:hover { background: var(--bg-sunken); }
.room-chip[aria-pressed="true"] { border-color: color-mix(in srgb, var(--blue) 45%, var(--border-strong)); background: var(--blue-soft); color: var(--blue); }
.room-chip[aria-pressed="false"] { color: var(--faint); text-decoration: line-through; }
.room-chip.needs-connect { border-style: dashed; }
.room-chip:disabled, .room-chip[aria-disabled="true"] { opacity: .55; text-decoration: line-through; cursor: default; }

.rooms-center { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.rooms-center > .callout { margin: 12px 18px 0; }
.rooms-empty { margin: auto; max-width: 560px; padding: 40px 24px; text-align: center; }
/* Taller than a phone's screen, it scrolls inside the column rather than
   spilling over the footer under the fixed-height app. */
.rooms-center:has(> .rooms-empty:not(.hidden)) { overflow-y: auto; }
/* Its buttons sit under the centred words, not at the left edge of the column. */
.rooms-empty .btn-row { justify-content: center; }
.rooms-join { margin: 40px auto; width: calc(100% - 32px); max-width: 420px; }
.rooms-chat { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.rooms-chat-head { display: flex; align-items: center; gap: 10px; padding: 12px 18px; border-bottom: 1px solid var(--border); }
.rooms-chat-title { flex: 1; min-width: 0; }
.rooms-chat-title h2 { font-size: 17px; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The installed app's two buttons: beside the title on a wide screen, on a line of their own on a phone. */
.rooms-chat-head #installApp, .rooms-chat-head #pushToggle { width: auto; flex: none; }
@media (max-width: 600px) {
  .rooms-chat-head { flex-wrap: wrap; row-gap: 8px; }
  .rooms-chat-head .rooms-chat-title { min-width: 45%; }
  .rooms-chat-head #installApp, .rooms-chat-head #pushToggle { order: 10; flex: 1 1 40%; padding: 6px 11px; font-size: 13px; }
}

/* A conversation, so it reads like one: each message a block with who said
   it above what they said, the room's own notices quieter than people.
   `pre-wrap` because a message's line breaks are the writer's, and an
   agent's numbered list is lines. */
.room-messages { flex: 1; min-height: 0; overflow-y: auto; padding: 14px 18px; }
.room-msg { max-width: 820px; margin: 0 auto; padding: 9px 0; border-bottom: 1px solid var(--border); }
.room-msg:last-child { border-bottom: 0; }
.room-msg-who { font-size: 12.5px; color: var(--text-soft); margin-bottom: 2px; }
.room-msg-who b { color: var(--text); }
.room-msg-text { white-space: pre-wrap; overflow-wrap: anywhere; line-height: 1.55; }
/* A room or chat shared to the web: the conversation as a page, its chats one after another. */
.shared-room { max-width: 860px; padding-block: 28px 40px; }
.shared-room h1 { font-size: 26px; margin-bottom: 18px; }
.shared-chat + .shared-chat { margin-top: 28px; }
.shared-chat h2 { font-size: 18px; margin: 0 0 6px; padding-bottom: 6px; border-bottom: 1px solid var(--border); }
.shared-cta { margin-top: 32px; }
.room-msg.is-system .room-msg-text { color: var(--text-soft); font-size: 13px; font-style: italic; }
.room-msg.is-ai .room-msg-who b { color: var(--blue); }
.rooms-typing { margin: 0; padding: 0 18px; min-height: 18px; }
form.room-composer {
  display: flex;
  flex-direction: row;
  gap: 10px;
  align-items: flex-end;
  max-width: none;
  margin: 0;
  padding: 10px 18px 4px;
  border-top: 1px solid var(--border);
}
.room-composer textarea { flex: 1; min-height: 46px; max-height: 220px; margin: 0; }

/* @ in the composer: the room's agents in a list just above it. */
form.room-composer { position: relative; }
.room-mentions {
  position: absolute;
  inset-inline-start: 18px;
  bottom: calc(100% + 4px);
  width: min(380px, calc(100% - 36px));
  max-height: 268px;
  overflow-y: auto;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
  z-index: 20;
}
.room-mentions li { padding: 5px 8px; border-radius: 6px; cursor: pointer; min-width: 0; }
.room-mentions li[aria-selected="true"] { background: var(--blue-soft); }
.room-mention-who { font-size: 13px; color: var(--text); }
.room-mention-who b { color: var(--blue); font-weight: 600; }
.room-mention-about { font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* An @agent's card: what it does, whether it answers here, and where to read more. */
.room-card {
  position: fixed;
  z-index: 70;
  width: 280px;
  max-width: calc(100vw - 16px);
  padding: 10px 12px;
  background: var(--card);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: var(--shadow-lg);
  font-size: 12.5px;
  line-height: 1.45;
}
.room-card-head { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; margin-bottom: 2px; }
.room-card-head b { font-size: 13.5px; color: var(--text); }
.room-card-head span { color: var(--blue); }
.room-card-meta { color: var(--muted); font-size: 11.5px; margin: 0 0 6px; }
.room-card p { margin: 0 0 6px; color: var(--text-soft); }
.room-card .room-card-quote { white-space: pre-wrap; font-style: italic; }
.room-card .room-card-warn { color: var(--warn, var(--text-soft)); }
.room-card a { font-size: 12px; overflow-wrap: anywhere; }
.room-card > :last-child { margin-bottom: 0; }
.rooms-hint { margin: 0; padding: 0 18px 12px; }

/* The buttons that open and close a drawer exist only where there are drawers. */
.rooms-close, .rooms-open-left, .rooms-chat-head [data-open="right"] { display: none; }
.rooms-drawer-head { display: none; }

@media (max-width: 960px) {
  main.rooms-app,
  main.rooms-app:has(> .rooms-left.hidden) { grid-template-columns: minmax(0, 1fr); }
  /*
   * And the room's own navigation setting gives way here.
   *
   * `[data-nav="rail"]` is a more specific selector than `main.rooms-app`,
   * so it kept winning inside this query -- the column stayed 62px wide
   * while `.rooms-side` was taken out of flow into a drawer, leaving 62px
   * of nothing beside a squeezed conversation. Below this width the left
   * column is a drawer whatever the room is set to, so the grid is one
   * column whatever it is set to.
   */
  main.rooms-app[data-nav="rail"],
  main.rooms-app[data-nav="hidden"],
  main.rooms-app[data-nav="rail"]:has(> .rooms-right:not(.hidden)),
  main.rooms-app[data-nav="hidden"]:has(> .rooms-right:not(.hidden)) { grid-template-columns: minmax(0, 1fr); }
  /* A drawer is a drawer: it shows the rooms by name, never as a rail. */
  main.rooms-app[data-nav="hidden"] > .rooms-left { display: flex; }
  main.rooms-app[data-nav="rail"] > .rooms-left { align-items: stretch; gap: revert; padding: revert; }
  main.rooms-app[data-nav="rail"] > .rooms-left .rooms-side-head,
  main.rooms-app[data-nav="rail"] > .rooms-left .rooms-nav-title,
  main.rooms-app[data-nav="rail"] > .rooms-left form { display: revert; }
  main.rooms-app[data-nav="rail"] > .rooms-left .rooms-switch {
    display: flex;
    flex-direction: revert;
    gap: revert;
    margin: revert;
    padding: revert;
    border: revert;
    background: revert;
  }
  main.rooms-app[data-nav="rail"] > .rooms-left .tree-name,
  main.rooms-app[data-nav="rail"] > .rooms-left .rooms-nav-item,
  main.rooms-app[data-nav="rail"] > .rooms-left .rooms-switch .tab {
    display: revert;
    width: auto;
    height: auto;
    margin: revert;
    padding: revert;
    border: revert;
    border-radius: revert;
    background: revert;
    color: revert;
    font-size: revert;
  }
  main.rooms-app[data-nav="rail"] > .rooms-left .tree-name::before { content: none; }
  main.rooms-app[data-nav="rail"] > .rooms-left .rail-icon { display: none; }
  /* No room for a third column: the panel lies over the conversation. */
  main.rooms-app:has(> .rooms-right:not(.hidden)),
  main.rooms-app:has(> .rooms-left.hidden):has(> .rooms-right:not(.hidden)) { grid-template-columns: minmax(0, 1fr); }
  .rooms-right { position: fixed; inset: var(--rooms-top, 0) 0 0 auto; width: min(340px, 90vw); z-index: 40; box-shadow: var(--shadow-lg, 0 10px 40px rgba(0,0,0,.2)); }
  /* Over the header too, so the drawer's own Close is always on screen. */
  .rooms-side {
    position: fixed;
    top: 0;
    bottom: 0;
    width: min(340px, 88vw);
    z-index: 60;
    box-shadow: var(--shadow-lg);
    transition: transform .18s ease;
  }
  .rooms-left { left: 0; transform: translateX(-105%); }
  main.show-left .rooms-left { transform: none; }
  .rooms-close, .rooms-open-left { display: inline-flex; }
  .rooms-drawer-head { display: flex; justify-content: flex-end; }
  /* With the side columns in drawers, nothing open grows the page like any
     other, instead of scrolling inside a box the height of the screen. */
  main.rooms-app:has(> .rooms-center > .rooms-empty:not(.hidden)) { height: auto; min-height: calc(100dvh - var(--rooms-top, 58px)); }
}

/* A token or an invite link is one long word: wrap it rather than cut it off. */
.room-token { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-all; }
/*
 * The invite link, as a field.
 *
 * `code.room-token` is the link; the `<pre>` ones beside it are secrets a
 * reader has to see whole, and they keep wrapping. A link is for taking,
 * so this one is one line, clipped at the end, in a box that looks like
 * the input it behaves as -- pressing it copies. Three wrapped lines of
 * token made the dialog about the token.
 */
code.room-token {
  flex: 1;
  min-width: 0;
  display: block;
  padding: 9px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--card);
  color: var(--text-soft);
  font-size: 13px;
  line-height: 1.4;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
code.room-token:hover { border-color: var(--blue); color: var(--text); }
code.room-token:focus-visible { outline: 2px solid var(--blue); outline-offset: 1px; }

/*
 * A button that is its mark. The word stays for a screen reader and for
 * the pointer (`title`), so nothing is lost by not drawing it.
 */
.icon-btn {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 38px;
  height: 38px;
  padding: 0;
}
.btn-icon { width: 18px; height: 18px; fill: none; stroke: currentColor; }
/*
 * A callout straight after a list of steps. The last step has no bottom
 * padding of its own, so without this the callout sat against the step's
 * last line and read as part of it.
 */
.steps + .callout { margin-top: 28px; }

/* White-label settings: the done-for-you prices, a step apart from the choice above them. */
#readyPrices { margin-top: 12px; }

/* Project room settings: a dialog over the rooms, presets first, then every number they set. */
.rooms-side-head .rooms-settings-open { padding: 3px 9px; font-size: 12px; margin-inline-start: auto; }
dialog.rooms-dialog {
  width: min(640px, calc(100vw - 24px));
  max-height: calc(100dvh - 32px);
  padding: 20px 22px;
  border: 1px solid var(--border-strong);
  border-radius: 14px;
  background: var(--card);
  color: var(--text);
  box-shadow: var(--shadow-lg);
  /* Long settings scroll down, never sideways. */
  overflow-x: hidden;
}
dialog.rooms-dialog::backdrop { background: rgba(16, 20, 27, .45); }
form.rooms-settings { max-width: none; gap: 12px; }
.rooms-settings h2 { font-size: 18px; margin: 0; }
.rooms-presets { display: grid; grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); gap: 8px; }
.rooms-preset {
  display: flex;
  flex-direction: column;
  gap: 3px;
  margin: 0;
  padding: 9px 10px;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  font-weight: 400;
  font-size: 12px;
  color: var(--text-soft);
  cursor: pointer;
  /* The radio inside is out of sight and out of the layout: placed against its own card, one pixel wide. */
  position: relative;
}
.rooms-preset b { font-size: 13.5px; color: var(--text); }
.rooms-preset input { position: absolute; top: 0; left: 0; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.rooms-preset:has(input:checked) { border-color: var(--blue); background: var(--blue-soft); }
.rooms-preset:has(input:focus-visible) { outline: 2px solid var(--blue); outline-offset: 2px; }
fieldset.rooms-set { border: 1px solid var(--border); border-radius: 10px; margin: 0; padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 8px; }
fieldset.rooms-set legend { font-weight: 600; font-size: 13.5px; color: var(--text); padding: 0 4px; }
/* The white-label page's ready-made products: each one's mark beside its name. */
fieldset.rooms-set legend .muted { font-weight: 400; }
.ready-logo { width: 20px; height: 20px; border-radius: 5px; vertical-align: -5px; margin-right: 4px; }

/* The white-label page's assistant: a short conversation above the full form. */
.wl-assistant { margin-bottom: 18px; }
.wl-chat { list-style: none; margin: 0 0 10px; padding: 0; display: flex; flex-direction: column; gap: 6px; max-height: 360px; overflow-y: auto; }
.wl-msg { max-width: 88%; padding: 8px 12px; border-radius: 12px; line-height: 1.45; overflow-wrap: anywhere; }
.wl-bot { align-self: flex-start; background: var(--bg-sunken); color: var(--text); border-bottom-left-radius: 4px; }
.wl-me { align-self: flex-end; background: var(--blue-soft); color: var(--text); border: 1px solid var(--blue-line); border-bottom-right-radius: 4px; }
.wl-swatch { display: inline-block; width: 14px; height: 14px; border-radius: 4px; border: 1px solid var(--border-strong); vertical-align: -2px; margin-right: 6px; }
.wl-logo-preview { width: 32px; height: 32px; border-radius: 6px; object-fit: contain; vertical-align: middle; margin-right: 8px; background: var(--card); border: 1px solid var(--border); }
.wl-chips { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 10px; }
.wl-chips:empty { display: none; }
/* The white-label page's share card editor: the card at half size, and its upload buttons. */
.wl-card-preview { display: block; width: 100%; max-width: 600px; height: auto; aspect-ratio: 1200 / 630; margin: 10px 0 6px; border-radius: 10px; border: 1px solid var(--border); background: var(--bg-sunken); }
.wl-upload { margin-top: 8px; }
.wl-card-bg { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.wl-card-bg input[type="color"] { width: 56px; height: 36px; padding: 2px; flex: none; }
.wl-card-logo { border: 0; padding: 0; margin: 14px 0 6px; display: flex; flex-wrap: wrap; gap: 6px 16px; }
.wl-card-logo legend { padding: 0; margin-bottom: 6px; font-weight: 600; }
.wl-assistant form.wl-ask { display: flex; flex-direction: row; align-items: stretch; gap: 8px; max-width: none; margin: 0; }
.wl-ask input { flex: 1; min-width: 0; margin: 0; }
/* The white-label page's own sending address: the part before the @ and the domain, on one line. */
.wl-address { display: flex; align-items: center; gap: 6px; }
.wl-address input { min-width: 0; }
.wl-address input:first-child { flex: 1 1 0; }
.wl-address input:last-child { flex: 2 1 0; }
.wl-records td { overflow-wrap: anywhere; word-break: break-all; }
.wl-assistant .wl-ask button { width: auto; flex: none; }
.wl-all > summary { cursor: pointer; font-weight: 600; margin: 0 0 12px; }
.rooms-set-row { display: grid; grid-template-columns: minmax(0, 1fr) 220px; gap: 10px; align-items: center; }
.rooms-set-row label { margin: 0; font-size: 13px; font-weight: 500; color: var(--text); }
.rooms-set-row input, .rooms-set-row select { padding: 5px 8px; font-size: 13px; }
.rooms-unit { display: block; font-size: 11.5px; font-weight: 400; color: var(--muted); }
.rooms-usage { list-style: none; margin: 0; padding: 0; font-size: 12px; color: var(--muted); }
/* Save and Cancel stay in view at the foot of the dialog while the settings above them scroll. */
.rooms-set-actions {
  justify-content: flex-end;
  position: sticky;
  bottom: -20px;
  margin: 0 -22px -20px;
  padding: 12px 22px 16px;
  background: var(--card);
  border-top: 1px solid var(--border);
}
@media (max-width: 560px) {
  .rooms-set-row { grid-template-columns: minmax(0, 1fr); gap: 3px; }
}

/* A mention held for a member, above the composer. */
.room-handoffs { display: flex; flex-direction: column; gap: 6px; padding: 8px 18px 0; }
.room-handoff {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 7px 10px;
  border: 1px solid color-mix(in srgb, var(--blue) 35%, var(--border));
  border-radius: 10px;
  background: var(--blue-soft);
  font-size: 13px;
}
.room-handoff span { flex: 1; min-width: 12em; }
.room-handoff button { padding: 4px 10px; font-size: 12.5px; }

/* White-label settings: a Google Ads ID beside its button, a customer picker beside the ID. */
.btn-row .gads-cid { width: 15ch; flex: none; font-family: var(--mono); }
.btn-row .gads-org { width: auto; flex: 1 1 14em; min-width: 0; }
#gadsStep .row .badge { margin-inline-start: 6px; }
#gadsStep h3 { margin-top: 20px; }
/* Admin: a partner's Google Ads link request, with the note staff leave for it. */
[data-gads] > div:first-child { flex: 1 1 18em; min-width: 0; }
[data-gads] input { margin-top: 6px; padding: 6px 9px; }
/* A Google Ads change an agent proposed: amber, because applying it changes a live account. */
.room-change { border-color: color-mix(in srgb, var(--warn) 40%, var(--border)); background: var(--warn-soft); }
.room-change details { flex-basis: 100%; font-size: 12.5px; }
.room-change summary { cursor: pointer; color: var(--muted); }
.room-change pre { max-height: 180px; overflow: auto; margin: 6px 0 0; padding: 8px; background: var(--card); border-radius: 8px; font-size: 12px; white-space: pre-wrap; word-break: break-word; }
/* From the account to the customer, in the direction the line reads. */
.gads-arrow { display: inline-block; }
[dir="rtl"] .gads-arrow { transform: scaleX(-1); }

/* Guides: a page's questions, each a heading a reader can scan for, its answer under it. */
.faq h3 { margin: 18px 0 4px; font-size: 16px; }
.faq h3:first-child { margin-top: 6px; }
.faq p { margin-top: 0; }

/*
 * The project rooms landing page's example room: the room's own classes
 * (.rooms-chat-head, .room-msg, .room-handoff), in a frame of its own,
 * at a reading width rather than the app's full-height columns. Nothing
 * in it can be pressed, so its "buttons" are spans dressed as buttons.
 */
.rooms-preview { margin: 0; }
.rooms-preview-frame {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
/*
 * Three columns, because a room is not only its messages: there are other
 * rooms on the left and the room's own settings on the right, and a
 * picture of the middle alone made the product look like one chat.
 *
 * The rails are decoration -- `aria-hidden`, because a screen reader
 * reading out a list of invented room names and a column of dots learns
 * nothing the caption does not say better.
 *
 * They go at 860px, not at the phone breakpoint. The middle column is
 * what the picture is OF, and at 700px the three of them leave the
 * messages a column too narrow to read a sentence in -- which is the one
 * thing this drawing has to do.
 */
.rooms-preview-frame { display: grid; grid-template-columns: 1fr; }
.rooms-preview-list, .rooms-preview-side { display: none; }
.rooms-preview-main { min-width: 0; display: flex; flex-direction: column; }
@media (min-width: 860px) {
  .rooms-preview-frame { grid-template-columns: 170px minmax(0, 1fr) 180px; }
  .rooms-preview-list, .rooms-preview-side { display: block; padding: 14px 14px 16px; }
  .rooms-preview-list { border-inline-end: 1px solid var(--border); background: var(--bg); }
  .rooms-preview-side { border-inline-start: 1px solid var(--border); background: var(--bg); }
}
.rooms-preview-rail-title {
  margin: 0 0 10px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.rooms-preview-room {
  margin: 0 0 4px;
  padding: 6px 9px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
  color: var(--text-soft);
  overflow-wrap: anywhere;
}
/* The one being read, so the list reads as a place you are rather than a menu. */
.rooms-preview-room.is-current { background: var(--card); color: var(--text); font-weight: 600; box-shadow: var(--shadow-sm); }
.rooms-preview-new { margin: 10px 0 0; padding: 0 9px; font-size: 13px; color: var(--blue); font-weight: 600; }
.rooms-preview-side-group {
  margin: 14px 0 6px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-soft);
}
.rooms-preview-side-group:first-of-type { margin-top: 0; }
.rooms-preview-side-row {
  display: flex;
  align-items: center;
  gap: 7px;
  margin: 0 0 5px;
  font-size: 13.5px;
  color: var(--text);
  overflow-wrap: anywhere;
}
/*
 * A dot, not a switch. A switch is a control and invites a press, and
 * nothing in this picture can be pressed; a dot says on or off without
 * promising either.
 */
.rooms-preview-side-row.is-on::before,
.rooms-preview-side-row.is-off::before {
  content: "";
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--blue);
}
.rooms-preview-side-row.is-off::before { background: var(--border-strong); }
.rooms-preview-side-row.is-off { color: var(--text-soft); }
.rooms-preview-side-note { margin: 14px 0 0; font-size: 12.5px; line-height: 1.45; color: var(--text-soft); }

.rooms-preview .rooms-chat-title h3 { font-size: 16px; margin: 0; }
.rooms-preview .room-messages { overflow: visible; }
.rooms-preview .room-handoff { max-width: 820px; margin: 10px auto 0; }
.rooms-preview .room-handoff .rooms-preview-btn { flex: none; min-width: 0; }
.rooms-preview-btn {
  display: inline-flex;
  align-items: center;
  padding: 4px 10px;
  border: 1px solid var(--blue);
  border-radius: var(--radius-sm);
  background: var(--blue);
  color: #fff;
  font-size: 12.5px;
  font-weight: 600;
}
.rooms-preview-btn.secondary { background: var(--card); color: var(--text); border-color: var(--border-strong); }
.rooms-preview-composer {
  margin: 0 18px 14px;
  padding: 12px 14px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: 13.5px;
}
.rooms-preview figcaption { margin-top: 8px; }

/* A room's tabs: its conversation, and the work beside it. */
.rooms-tabs { padding: 8px 18px 0; }
.rooms-tab { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.rooms-work { overflow-y: auto; padding: 12px 18px; gap: 10px; }
.rooms-work .rooms-rows { font-size: 13.5px; }
.rooms-rows li:has(.rooms-error) { flex-wrap: wrap; }
.rooms-error { flex-basis: 100%; color: var(--danger); }
.rooms-qr { display: block; width: 220px; height: 220px; margin: 8px auto 0; background: #fff; padding: 6px; border-radius: 8px; }

/* ------------------------------------------------------ builders' group -- */

/*
 * The builders' group on the landing page: what it is on the left, the
 * join form on the right.
 *
 * `form` caps at 380px for the auth card, so in this card's right column
 * every field stopped ~130px short of the card's edge while the copy on
 * the left ran the full width of its own. And `.option-grid` starts both
 * columns at the top and leaves each as tall as its content, so the copy
 * ended half way down a card the form filled. The columns now stretch,
 * the form fills its column, and the founder's line is pushed to the foot
 * of the copy, level with the submit button beside it. Where the copy is
 * the taller column, the textarea takes up the difference, so the two
 * buttons still end on one line.
 *
 * Two columns from a fixed width rather than `auto-fit`, so the stacked
 * layout -- where the founder's line needs a rule under it as well, or the
 * form's first label reads as its caption -- has a breakpoint to hang on.
 */
.community-grid { grid-template-columns: 1fr; align-items: stretch; }
@media (min-width: 721px) {
  .community-grid { grid-template-columns: 1fr 1fr; }
}
.community-grid form { max-width: none; }
.community-grid textarea { flex: 1 0 auto; }
.community-copy { display: flex; flex-direction: column; }
/* Beside a taller form, the words sit in the middle of the room above the founder's row. */
.community-copy > :first-child { margin-block: auto; }
.join-top, .join-bottom { display: flex; flex-direction: column; gap: 12px; }

/*
 * THE DIVIDER AND THE TEXTAREA ARE ONE LINE.
 *
 * The two columns already ended level -- both bottoms are the card's --
 * but their tops were not: the rule above the founder sat 16 to 114px
 * below the top edge of "What are you working on?", the distance changing
 * with the language and the width, because the textarea absorbs whatever
 * slack the taller column leaves.
 *
 * No amount of padding fixes that, because the number is different every
 * time. A shared ROW does: the card's grid gets two of them, each column
 * borrows them with `subgrid`, and the second row starts at one y for
 * both. The founder's border-top and the textarea's top edge are then the
 * same line by construction.
 *
 * Only where there are two columns, and only where subgrid exists; an
 * engine without it keeps what it had, which is a cosmetic difference and
 * not a broken page.
 */
@supports (grid-template-rows: subgrid) {
  @media (min-width: 721px) {
    .community-grid { grid-template-rows: 1fr auto; row-gap: 12px; }
    .community-copy,
    .community-grid .join-form {
      display: grid;
      grid-row: 1 / -1;
      grid-template-rows: subgrid;
      gap: 12px;
    }
    /* In a grid row, auto margins centre it exactly as they did in flex. */
    .community-copy > :first-child { margin-block: auto; }
  }
}
/* An empty status line kept the form's 12px gap and its own 12px margin,
   so "Ask to join" sat 36px under the textarea where every other gap is
   12. Out of the flow rather than `display: none` like `.error:empty`: a
   polite live region that is not in the tree when its text arrives is not
   reliably announced, where role="alert" is. */
.community-grid .ok-text:empty { position: absolute; margin: 0; }

.founder {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 12px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.founder-photo { width: 52px; height: 52px; border-radius: 12px; flex: none; object-fit: cover; }
.founder-who { flex: 1 1 96px; min-width: 0; margin: 0; font-size: 13.5px; line-height: 1.35; }
.founder-who b { display: block; color: var(--text); }
.founder-who span { color: var(--muted); }
 /*
  * Level with "Ask to join" beside them. The row centres the photo and the
  * name, which are taller than a button, so centring these too left them
  * sitting eight pixels above the form's submit at the foot of the other
  * column; they end where the column ends instead.
  */
.founder-actions { display: flex; flex-wrap: wrap; gap: 8px; flex: none; align-self: flex-end; }
.founder-cta, .founder-book { flex: none; gap: 8px; }
.founder-cta svg { flex: none; }
@media (max-width: 720px) {
  .founder { padding-bottom: 16px; border-bottom: 1px solid var(--border); }
  /* Under the name, the two buttons side by side; a long label wraps inside its button. */
  .founder-actions { flex: 1 1 100%; flex-wrap: nowrap; }
  .founder-actions .founder-book { flex: none; }
  .founder-actions .founder-cta { flex: 1 1 auto; min-width: 0; padding-inline: 12px; text-align: center; }
}

/*
 * The installed app's Inbox (inbox.html): the rooms' layout with two
 * columns, the conversations and the open one. Under 960px the list is a
 * drawer, as the rooms' are.
 */
main.rooms-app.inbox-app { grid-template-columns: 300px minmax(0, 1fr); }
main.rooms-app.inbox-app:has(> .rooms-left.hidden) { grid-template-columns: minmax(0, 1fr); }
.inbox-status { margin-bottom: 8px; width: 100%; }
.inbox-status button { flex: 1; }
.inbox-filters { margin-bottom: 8px; }
.inbox-list a.inbox-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 1px 8px;
  align-items: baseline;
  position: relative;
}
.inbox-list .inbox-who { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.inbox-list .inbox-when { font-size: 11px; color: var(--muted); }
.inbox-list .inbox-last, .inbox-list .inbox-chan {
  grid-column: 1 / -1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 400;
}
.inbox-list .inbox-last { font-size: 12px; color: var(--text-soft); }
.inbox-list .inbox-chan { font-size: 11px; color: var(--muted); }
.inbox-list a.is-unread .inbox-who, .inbox-list a.is-unread .inbox-last { font-weight: 600; color: var(--text); }
.inbox-list .inbox-unread { position: absolute; right: 8px; bottom: 6px; font-size: 10.5px; padding: 0 6px; }
.inbox-tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  padding: 8px 18px;
  border-bottom: 1px solid var(--border);
  font-size: 12.5px;
  color: var(--text-soft);
}
.inbox-tools label { margin: 0; font-size: 12.5px; }
.inbox-tools select, .inbox-tools input { width: auto; padding: 3px 7px; font-size: 12.5px; }
.inbox-tools-gap { margin-inline-start: 8px; }
/* The label tools sit in the bar as if unwrapped (.hidden takes them out for a website chat's conversation). */
.inbox-label-tools { display: contents; }
/* A colour, in a form: the swatch at a field's height, not a hairline. */
.rooms-form input[type="color"] { height: 38px; padding: 3px; cursor: pointer; }
.room-msg .inbox-ai { margin-inline-start: 4px; font-size: 11px; font-weight: 600; color: var(--text-soft); }
.inbox-chips { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.inbox-label button { padding: 0 0 0 4px; font-size: 12px; line-height: 1; background: none; border: 0; color: inherit; }
.inbox-label-form { display: inline-flex; flex-direction: row; align-items: center; gap: 4px; margin: 0; max-width: none; }
.inbox-label-form input { width: 150px; }
.inbox-label-form button { width: auto; padding: 3px 9px; font-size: 12px; }
.room-msg.is-outgoing .room-msg-who b { color: var(--blue); }
.room-msg.is-note { background: var(--warn-soft); border-radius: 8px; padding: 8px 10px; border-bottom: 0; margin-bottom: 4px; }
.room-msg.is-activity .room-msg-text { color: var(--text-soft); font-size: 13px; font-style: italic; }
.inbox-file { display: inline-block; margin-top: 4px; font-size: 13px; }
.inbox-file img { display: block; max-width: min(320px, 100%); max-height: 260px; border-radius: 8px; border: 1px solid var(--border); }
.inbox-send { display: flex; flex-direction: column; gap: 6px; align-items: stretch; }
.inbox-send .check { font-size: 12px; white-space: nowrap; margin: 0; }
.room-composer.is-note textarea { background: var(--warn-soft); }
@media (max-width: 960px) {
  main.rooms-app.inbox-app, main.rooms-app.inbox-app:has(> .rooms-left.hidden) { grid-template-columns: minmax(0, 1fr); }
}

/*
 * Illustrations: what the product does, shown moving rather than described.
 *
 * Plain elements and CSS, no pictures: the words in them are the page's own
 * (translated like every other word, renamed by brand.js on a brand's host),
 * and a reader who asks for less motion gets every frame at once, still.
 * illus.js plays the sequences ([data-illus-seq]: one child at a time, an AI
 * agent's after a moment of typing) and the cycles ([data-illus-cycle]),
 * only while they are on screen.
 */
.illus {
  position: relative;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background:
    radial-gradient(120% 90% at 0% 0%, var(--blue-soft), transparent 60%),
    var(--card);
  padding: 16px;
  overflow: hidden;
}

/* The hero's exchange: one ask, one answer, then what was done, one line each. */
.illus-ask { gap: 9px; padding: 18px; }
.illus-ask .ill-msg { font-size: 14px; }
.ill-done {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  padding: 8px 11px;
  border-radius: 10px;
  background: var(--card);
  border: 1px solid var(--border);
  font-size: 13.5px;
  color: var(--text);
}
.ill-done::after { content: "✓"; margin-inline-start: auto; color: var(--ok); font-weight: 700; }
.ill-agent { font-style: normal; font-size: 13px; line-height: 1; }

/*
 * The hub: who asks and what is the member's own on the left, their
 * Service and Product hybrid in the middle, everything it reaches on the
 * right.
 *
 * One rhythm runs the whole picture. Every row on both sides is
 * `--hub-pitch` tall, whatever its chip holds, so a side's rows sit at
 * known heights rather than at whatever its text happened to measure; and
 * both sides are centred on the processor. That is what lets the three
 * cords a side plug in at the same three heights, mirrored -- centre, and
 * one pitch either way -- and lets the processor's side pins be drawn
 * exactly there instead of as a repeating band a cord lands on by luck.
 */
.illus-hub {
  --hub-pitch: 44px;
  /*
   * The gap between a side and the processor: the stub from a chip to the
   * collector, then the cord from the collector to a pin. Both are as
   * short as they can be and still read as cables, because at the page's
   * full width the two columns in and the four out leave the picture
   * almost nothing to spare -- and a language whose words are longer than
   * English's has even less. Turkish is the longest of the seventeen and
   * is what these numbers were measured against.
   */
  --hub-stub: 10px;
  --hub-cord: 24px;
  /* Between two chips in the same row. Not a cable, so not a cable's length. */
  --hub-gap: 8px;
  display: grid;
  grid-template-columns: max-content max-content max-content max-content max-content;
  justify-content: center;
  align-items: center;
  gap: 0;
  margin-top: 14px;
  /* Narrow sides: at the page's full width the two columns in and the four out leave little to spare. */
  padding: 26px 8px;
}
.hub-side { position: relative; display: grid; grid-auto-rows: var(--hub-pitch); align-items: center; column-gap: var(--hub-gap); }
/* In: three rows of two -- the assistant, then the thing of the member's own it reaches. */
.hub-in {
  grid-template-columns: max-content max-content;
  justify-items: end;
  /*
   * Wider than the other side's, because the rail runs down the middle of
   * THIS gap: it is how much of the cable shows either side of that line.
   * At 8px there were four pixels a side and the pulse crossed them too
   * fast to see. The stub grows with it so it still reaches the rail.
   *
   * Only here. The out side has four columns to this one's two, so the
   * same 14px there costs three gaps instead of one, and that clipped
   * Turkish and Russian at 1280px -- the widths this picture was measured
   * against in the first place.
   */
  --hub-gap: 22px;
  --hub-stub: 13px;
}
/*
 * The inner chip fills its column, the outer one does not.
 *
 * Both columns are `max-content`, so a chip narrower than the widest in
 * its column has room to spare -- and with the side right-aligned that
 * room falls on its LEFT. On the outer column that is the ragged edge the
 * picture wants; on the inner one it was a gap between the cord and the
 * chip it plugs into, as wide as the difference between "Your members"
 * and "Your app/API". Filling the track closes it, and keeps every cord
 * to the processor the same length.
 */
.hub-in .hub-chip { justify-self: stretch; justify-content: center; }
/* Out, four across: the channels, the task tools, other agents, any API. */
.hub-out { grid-template-columns: repeat(4, max-content); }
.hub-chip {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 5px 10px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--card);
  font-size: 12.5px;
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
}
.hub-chip.is-lead { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); font-weight: 600; }
/*
 * The AI clients as their own marks, two to a chip -- the same six the
 * hero offers, drawn from the same definitions. Words would be six names
 * in three chips either way; the marks say "and others like them" in a
 * way a list of six cannot, which is the point of the line underneath.
 */
/*
 * The six AI clients as one badge, each mark in its own circle.
 *
 * Three rows of names said "these three, and these three" about things
 * that are one thing: every client that speaks MCP. One frame says it
 * once, and the line under it says the rest. Its width is a token so the
 * line can be centred on it without measuring anything at runtime.
 */
/*
 * The badge's own size, as tokens: the line under it is placed from these
 * rather than from the column's bottom edge, which is half a row lower and
 * left the line adrift of the thing it names.
 */
.hub-in { --hub-badge-w: 146px; --hub-badge-h: 101px; }
.hub-badge {
  /* The line under it is placed from this box, so the box has to be the frame itself. */
  position: relative;
  grid-column: 1;
  grid-row: 1 / 4;
  align-self: center;
  justify-self: end;
  width: var(--hub-badge-w);
  /* Stated, not left to the six circles, so the line below can be placed from it. */
  height: var(--hub-badge-h);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 9px;
  padding: 13px 14px;
  border: 1px solid var(--border-strong);
  border-radius: 20px;
  background: var(--card);
  box-shadow: var(--shadow-sm);
  color: var(--text);
}
.hub-badge > span {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
}
.hub-badge svg { display: block; width: 17px; height: 17px; fill: currentColor; }
/*
 * And the line itself: the badge's own child, taken out of flow so it is
 * not a seventh circle, and hung off the badge's bottom edge. It was
 * placed against the side's grid before, which is right only while the
 * badge happens to sit at that grid's start -- stacked, the badge is at
 * the end of its column and the line drifted 35px to the left of it.
 * Anchored here it is centred under the badge in every layout, because it
 * is measured from the badge and from nothing else.
 */
.hub-any {
  position: absolute;
  top: 100%;
  inset-inline: 0;
  text-align: center;
  margin-top: 8px;
  font-size: 11px;
  color: var(--muted);
}

/*
 * "Your Brand" as a processor: a die inside a package, pins on every side
 * (the ::before rows top and bottom, the ::after columns left and right),
 * and a slow pulse.
 */
.hub-core {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  /*
   * Narrow enough that the four columns out and the two in still fit beside
   * it at the page's full width, in every language. The name wraps onto two
   * even lines rather than widening the die, which is the one thing here
   * that nothing else can give room back for.
   */
  width: max-content;
  min-width: 194px;
  max-width: 214px;
  min-height: 150px;
  padding: 18px 22px;
  border-radius: 14px;
  background:
    linear-gradient(var(--card), var(--card)) padding-box,
    linear-gradient(135deg, var(--blue), var(--blue-line) 60%, var(--blue)) border-box;
  border: 1.5px solid transparent;
  box-shadow: inset 0 0 0 7px var(--card), inset 0 0 0 8px var(--blue-line), var(--shadow);
  text-align: center;
  animation: hub-breathe 3.2s ease-in-out infinite;
}
/* Each paints only its outer bands, so it can sit above the pulse without covering the die. */
.hub-core::before,
.hub-core::after {
  content: "";
  position: absolute;
  pointer-events: none;
  /* All four sides, one movement: see @keyframes hub-port. */
  animation: hub-port 2.4s ease-in-out infinite;
}
.hub-core::before {
  inset: -9px 20px;
  background:
    repeating-linear-gradient(90deg, var(--blue-line) 0 4px, transparent 4px 13px) top / 100% 9px no-repeat,
    repeating-linear-gradient(90deg, var(--blue-line) 0 4px, transparent 4px 13px) bottom / 100% 9px no-repeat;
}
/*
 * The side pins: three a side, where the three cords actually land --
 * centre, and one pitch either way. They used to be a repeating band, so
 * whether a cord met a pin or the gap between two was decided by how tall
 * the chips above it happened to be.
 */
.hub-core::after {
  /*
   * The accent, where the pins top and bottom are the line colour: these
   * are the ten the cords actually land on, and the processor's own pulse
   * washes a band of --blue-soft over exactly this strip, which a pin in
   * the line colour disappears into. They reach a little further out than
   * that band, so the cord is seen to end ON something.
   */
  --pin: linear-gradient(var(--blue), var(--blue));
  inset: 0 -12px;
  /*
   * 0 and 100% rather than `left` and `right`: a keyword followed by a
   * length is the three-value position syntax, and the whole declaration
   * is dropped when a `/` size follows it -- which is how these pins came
   * to be absent rather than merely misplaced.
   */
  background:
    var(--pin) 0 calc(50% - var(--hub-pitch)) / 12px 5px no-repeat,
    var(--pin) 0 calc(50% - var(--hub-pitch) / 2) / 12px 5px no-repeat,
    var(--pin) 0 50% / 12px 5px no-repeat,
    var(--pin) 0 calc(50% + var(--hub-pitch) / 2) / 12px 5px no-repeat,
    var(--pin) 0 calc(50% + var(--hub-pitch)) / 12px 5px no-repeat,
    var(--pin) 100% calc(50% - var(--hub-pitch)) / 12px 5px no-repeat,
    var(--pin) 100% calc(50% - var(--hub-pitch) / 2) / 12px 5px no-repeat,
    var(--pin) 100% 50% / 12px 5px no-repeat,
    var(--pin) 100% calc(50% + var(--hub-pitch) / 2) / 12px 5px no-repeat,
    var(--pin) 100% calc(50% + var(--hub-pitch)) / 12px 5px no-repeat;
}
.hub-core .brand-mark { width: 30px; height: 30px; }
/* "Your Brand"'s logo: a place for one, until a brand's host puts its own there (brand.js). */
.hub-core .brand-mark.is-placeholder {
  border: 2px dashed var(--blue-line);
  border-radius: 9px;
  background: var(--blue-soft);
}
.hub-core .brand-mark.is-placeholder::before,
.hub-core .brand-mark.is-placeholder::after { content: none; }
/* Balanced, or a name this long leaves one word alone on its last line. */
/*
 * 15px, not 16: the name became possessive ("Your Brand's"), which at 16px
 * took a third line. The comment on .hub-core says which way this trade
 * goes -- the name wraps onto two even lines rather than widening the die,
 * because the die is the one thing here nothing else can give room back for.
 */
.hub-core b { font-size: 15px; text-wrap: balance; }
.hub-core small { font-size: 11.5px; color: var(--muted); line-height: 1.35; text-wrap: balance; }

/*
 * The cables. Traffic runs both ways: a blue pulse going out, a green one
 * coming back.
 *
 * Within a row, each chip links to the one before it; the first chip of a
 * row links to its side's collector. The collector is what the three cords
 * leave from, which is how four rows out and three rows in can still plug
 * into the processor at the same three heights.
 */
.hub-in .hub-chip::after,
.hub-in .hub-chip:nth-child(2)::before,
.hub-in .hub-chip:nth-child(4)::before,
.hub-out .hub-chip::before,
.hub-bus > span {
  content: "";
  position: absolute;
  height: 3px;
  margin-top: -1.5px;
  border-radius: 2px;
  background:
    radial-gradient(circle, var(--blue) 0 2.5px, transparent 3px) 0 50% / 9px 9px no-repeat,
    radial-gradient(circle, var(--ok) 0 2.5px, transparent 3px) 100% 50% / 9px 9px no-repeat,
    linear-gradient(var(--blue-line), var(--blue-line));
  animation: hub-cable 2.4s linear infinite;
  animation-delay: calc(var(--n, 0) * -.37s);
}
/* A link within a row is short, so its pulse runs quicker over it. */
.hub-in .hub-chip::after,
.hub-out .hub-chip::before { top: 50%; width: var(--hub-stub); animation-duration: 1.2s; }
/* In, the row runs right: every chip links rightwards, the inner one to the collector. */
.hub-in .hub-chip::after { left: 100%; }
/* Out, the row runs left, back towards the processor. */
.hub-out .hub-chip::before { right: 100%; }

/*
 * A side's collector, and the three cords from it into the processor.
 *
 * The cords are children rather than one element's background layers
 * because each carries its own pulse at its own offset; the collector is
 * the bar they all leave from, spanning that side's rows.
 */
.hub-bus {
  position: relative;
  /*
   * Above the processor's own pins (.hub-core::after), on BOTH sides. The
   * out bus is already above them because it comes after the processor in
   * the markup; the in bus comes before it, so without this the left pins
   * were painted over the ends of the cords that land on them -- the cable
   * disappeared into the port on one side and ran through it on the other.
   */
  z-index: 1;
  align-self: center;
  width: calc(var(--hub-cord) + var(--hub-stub));
  /* As tall as the three cords reach, which is one pitch either side of centre. */
  height: calc(var(--hub-pitch) * 2);
}
.hub-bus::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: 3px;
  border-radius: 2px;
  background: var(--blue-line);
}
/*
 * The cords are always the part nearest the processor and the stubs the
 * part nearest the chips, so on the way in they are the far side of the
 * bus and on the way out the near one. The collector stands between them.
 */
/*
 * Both sides have a collector now.
 *
 * In used to have none: three rows into three pins is one cord each, and a
 * bar across them would have been a junction with nothing to junction. With
 * five cords and three rows it is the opposite -- two of them would start
 * in mid-air -- so the bar is what they leave from, exactly as on the way
 * out. It stands between the chips' stubs and the cords on both sides.
 */
.hub-bus-in::before { inset-inline-start: var(--hub-stub); }
.hub-bus-out::before { inset-inline-start: var(--hub-cord); }
/*
 * Out has four rows, so its collector reaches past the cords to meet the
 * first and last of them. In has three, which the cords already span.
 */
.hub-bus-out::before { top: calc(var(--hub-pitch) * -.5); bottom: calc(var(--hub-pitch) * -.5); }
.hub-bus > span { width: var(--hub-cord); }
/* From the collector to the processor, the way out's run from it the other way. */
.hub-bus-in > span { width: var(--hub-cord); inset-inline-end: 0; }
.hub-bus-out > span { inset-inline-start: 0; }
/*
 * The two cables the extra cords on the left come FROM.
 *
 * Those cords leave the collector at a quarter and three quarters of the
 * bus -- between the rows, not on one -- so on their far side they would
 * have ended at the bar and nowhere else. These carry them on through the
 * gap between two chips to the vertical rail, which is where that column's
 * traffic comes from. The gap is real: a row is a fixed pitch and a chip
 * is shorter than it, so a line at the midpoint crosses nothing.
 *
 * Hung off the chip above and the chip below rather than off the side,
 * because a chip is the only box here whose edges are both the rail's gap
 * and the collector's stub, whatever a language does to the column's width.
 */
.hub-in .hub-chip:nth-child(2)::before,
.hub-in .hub-chip:nth-child(4)::before {
  /* From the badge's own edge, a whole column gap away. */
  inset-inline-start: calc(var(--hub-gap) * -1);
  inset-inline-end: calc(var(--hub-stub) * -1);
}
/* Below the first row, and above the last: a quarter and three quarters. */
.hub-in .hub-chip:nth-child(2)::before { top: calc(50% + var(--hub-pitch) / 2); --n: 1; }
.hub-in .hub-chip:nth-child(4)::before { top: calc(50% - var(--hub-pitch) / 2); --n: 3; }
.hub-bus > span:nth-child(1) { top: 0; --n: 0; }
.hub-bus > span:nth-child(2) { top: 25%; --n: 1; }
.hub-bus > span:nth-child(3) { top: 50%; --n: 2; }
.hub-bus > span:nth-child(4) { top: 75%; --n: 3; }
.hub-bus > span:nth-child(5) { top: 100%; --n: 4; }
.hub-chip:nth-child(1) { --n: 0; } .hub-chip:nth-child(2) { --n: 1; } .hub-chip:nth-child(3) { --n: 2; } .hub-chip:nth-child(4) { --n: 3; }
.hub-chip:nth-child(5) { --n: 4; } .hub-chip:nth-child(6) { --n: 5; } .hub-chip:nth-child(7) { --n: 6; } .hub-chip:nth-child(8) { --n: 7; }
.hub-chip:nth-child(9) { --n: 8; } .hub-chip:nth-child(10) { --n: 9; } .hub-chip:nth-child(11) { --n: 10; } .hub-chip:nth-child(12) { --n: 11; }
.hub-chip:nth-child(13) { --n: 12; } .hub-chip:nth-child(14) { --n: 13; } .hub-chip:nth-child(15) { --n: 14; } .hub-chip:nth-child(16) { --n: 15; }
@keyframes hub-cable {
  from { background-position: -9px 50%, calc(100% + 9px) 50%, 0 0; }
  to { background-position: calc(100% + 9px) 50%, -9px 50%, 0 0; }
}
@keyframes hub-cable-v {
  from { background-position: 50% -9px, 50% calc(100% + 9px), 0 0; }
  to { background-position: 50% calc(100% + 9px), 50% -9px, 0 0; }
}
/*
 * The ports, lit.
 *
 * Every pin on every side of the package brightens and dims together, on
 * the cables' own 2.4s and with no delay of its own, so the four sides
 * are one movement rather than four. It peaks at the turn of the cycle,
 * which is where the travelling dots are when they reach the package:
 * the ports are brightest exactly when something arrives at them.
 *
 * Opacity rather than a colour: a colour inside a gradient cannot be
 * interpolated without registering a custom property, and this is paint
 * the compositor can do on its own.
 */
@keyframes hub-port {
  0%, 100% { opacity: 1; }
  50% { opacity: .45; }
}
@keyframes hub-breathe {
  0%, 100% { box-shadow: inset 0 0 0 7px var(--card), inset 0 0 0 8px var(--blue-line), var(--shadow), 0 0 0 0 var(--blue-soft); }
  50% { box-shadow: inset 0 0 0 7px var(--card), inset 0 0 0 8px var(--blue-line), var(--shadow), 0 0 0 10px var(--blue-soft); }
}
@media (prefers-reduced-motion: reduce) {
  .hub-in .hub-chip::after, .hub-in .hub-chip::before, .hub-out .hub-chip::before, .hub-side::before, .hub-side::after, .hub-core, .hub-core::before, .hub-core::after { animation: none; }
}

/*
 * Narrower than the three columns need: one under the other, in, the
 * processor, out, each a grid of equal columns. Every chip of the row
 * nearest the processor drops a cable to a bus, the bus one to the
 * processor; a chip further away links to the one beside it, as on a desktop.
 */
/*
 * The home page's title used to need a measure of its own: one
 * 72-character run that took three lines in nine languages at the shared
 * 17em, so it got 18.8em here and the phone block divided the screen by
 * that same 18.8. Both numbers are gone with the sentence they were cut
 * for -- the title is two sentences with a <br> between them, the break is
 * in the markup, and an em measure would only be the trap it already was
 * on .simple-h1: it shrinks exactly as fast as the font, so it is never
 * the slack it looks like.
 */

@container hub (max-width: 1060px) {
  .illus-hub {
    grid-template-columns: minmax(0, 1fr);
    gap: 22px;
    justify-items: center;
    /*
     * The processor is given an exact width here, rather than the wide
     * layout's max-content, because the cords below depend on knowing
     * where its pins are -- and a pin band that is a few pixels out is a
     * row of cords that each miss by the same few pixels.
     */
    --hub-core-w: 214px;
    /* Its pin band: the package, less the 20px its pins stop short of each end. */
    --hub-pins: calc(var(--hub-core-w) - 40px);
  }
  .hub-core { width: var(--hub-core-w); max-width: var(--hub-core-w); }
  /*
   * `--hub-out-gap` rather than a bare 8: the cords leave at the columns'
   * centres and the bus has to END on the outer two of them, so the gap
   * is arithmetic both of them do and not a number typed in two places.
   */
  .hub-side { position: relative; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); grid-auto-rows: auto; --hub-out-gap: 8px; gap: var(--hub-out-gap); width: 100%; max-width: 560px; }
  /*
   * In keeps its pairs: three across and filled DOWN each column, so a
   * column is still the assistant over the thing of the member's own it
   * reaches, as it is side by side on a wide screen.
   */
  /* And it loses the wide layout's right-alignment, or its cords miss the bus. */
  /*
   * `minmax(0, 1fr)`, not `1fr`: a track's default floor is its own
   * min-content, and a chip is one unbreakable line of words, so three
   * equal columns would rather overrun the picture than get narrower --
   * which is what they did below about 414px, by 86px at 320. The floor
   * is removed here and the words are made to give way below.
   */
  .hub-in { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: repeat(2, auto); grid-auto-flow: column; justify-items: stretch; }
  /*
   * And the row's height is a token rather than a measurement, because
   * the badge's cords have to cross it: see `--hub-chip-h` below.
   */
  .hub-in .hub-chip { height: var(--hub-chip-h); }
  /*
   * The die's own top and bottom pin rows go: in this layout the two
   * sides draw a row each in the same place, over the same width, and
   * phased on the cords that land on them. Two rows of pins at two
   * rhythms in one place is a row whose pins are of uneven width.
   */
  .hub-core::before { content: none; }
  /*
   * Nothing plugs into the processor's sides here, so these are not
   * landings -- they are the package's own pins, and there is no cord
   * whose height they have to agree with. A full band down each side, at
   * the rhythm of the bands top and bottom, reads as a chip; the wide
   * layout's five, spread down a die this tall, read as five pins that
   * forgot the rest.
   */
  .hub-core::after {
    /* 20px short of each end, as the bands top and bottom stop 20px short of each side. */
    inset: 20px -12px;
    background:
      repeating-linear-gradient(180deg, var(--blue-line) 0 4px, transparent 4px 13px) 0 0 / 12px 100% no-repeat,
      repeating-linear-gradient(180deg, var(--blue-line) 0 4px, transparent 4px 13px) 100% 0 / 12px 100% no-repeat;
  }
  .hub-chip { justify-content: center; }
  /*
   * AND A CHIP TAKES A SECOND LINE RATHER THAN RUNNING OVER ITS
   * NEIGHBOUR.
   *
   * Four columns of channels fit down to 440px in twelve of the
   * seventeen languages and no further, and what stops the other five is
   * one chip each: "Yapay zekâ ajanları", "API personalizzata",
   * "وكلاء الذكاء الاصطناعي", "واجهة API مخصصة", "AI エージェント". A chip
   * whose words are wider than it is does not clip -- it runs over the
   * one beside it -- and the cord above each of these is drawn from the
   * chip's own top edge, so a taller chip keeps its cord. The row grows,
   * which is the honest answer, and the picture is readable in a column
   * that one language would otherwise have decided the width of.
   */
  .hub-out .hub-chip {
    white-space: normal;
    text-align: center;
    line-height: 1.2;
    /*
     * And a word with nowhere to break breaks anyway, rather than
     * spilling: Italian's "API personalizzata" is one 14-letter word in a
     * cell that holds twelve at the narrowest the picture gets. A broken
     * word is read with a frown; a word lying across the chip beside it
     * is not read at all.
     *
     * `anywhere` rather than `break-word`, and `min-width: 0` on what is
     * inside: a chip is a flex row, and a flex item will not go below its
     * longest word unless both are said. `break-word` alone wraps the
     * text and leaves the box exactly as wide as the word was.
     */
    overflow-wrap: anywhere;
  }
  .hub-out .hub-chip > * { min-width: 0; }
  /* The cords and their collectors are the wide layout's; here a side has its own bus. */
  .hub-bus { display: none; }
  /* The chips reach the bus below them and nothing above: the badge's own
   * two cords run past them to it. */
  .hub-in .hub-chip::before { content: none; }
  .hub-in .hub-chip::after,
  .hub-badge::before,
  .hub-badge::after,
  .hub-out .hub-chip::before,
  .hub-side::before,
  .hub-side::after {
    content: "";
    position: absolute;
    left: 50%;
    width: 3px;
    height: 11px;
    margin: 0 0 0 -1.5px;
    border-radius: 2px;
    background:
      radial-gradient(circle, var(--blue) 0 2.5px, transparent 3px) 50% 0 / 9px 9px no-repeat,
      radial-gradient(circle, var(--ok) 0 2.5px, transparent 3px) 50% 100% / 9px 9px no-repeat,
      linear-gradient(var(--blue-line), var(--blue-line));
    animation: hub-cable-v 1.6s linear infinite;
    animation-delay: calc(var(--n, 0) * -.37s);
  }
  /*
   * Same specificity as the wide layout's pair connector, or it keeps the
   * sideways cord it draws there, which here would run out of a chip's
   * edge into the chip beside it.
   */
  .hub-in .hub-chip:nth-child(2)::before,
  .hub-in .hub-chip:nth-child(4)::before { content: none; }
  /*
   * One row of chips now, so every one of them reaches the bus itself --
   * and reaches INTO it, as the cords under the processor do: a cord that
   * stops a pixel short of the line it plugs into is a cord that is not
   * plugged in. `top: 100%` is the chip's PADDING box, so the 1px of its
   * own bottom border is part of the distance, and the last 1.5px puts
   * the end on the bus's middle rather than on its edge.
   */
  .hub-in .hub-chip::after {
    top: 100%;
    left: 50%;
    height: calc(var(--hub-in-pad) + var(--hub-bus-drop) + 2.5px);
  }
  .hub-out .hub-chip::before { top: auto; right: auto; bottom: 100%; }
  /*
   * From the bus to the processor, above it and below it -- one cord per
   * pin rather than one into the middle.
   *
   * It is drawn as the processor's own pin pattern (4px every 13px, from
   * 20px in) over the width of that band, so each cord lands ON a pin
   * instead of on the package between two. The pulse stays on the bus
   * above it and on the stubs from the chips; these are the last few
   * pixels, where a travelling dot would read as noise rather than as
   * traffic.
   */
  .hub-side::before {
    /*
     * Laid out across the whole side and CLIPPED to the package's width,
     * rather than being that width: a percentage in `background-size`
     * resolves against the element's own box, so a band that is 174px
     * wide can only step in fractions of 174px -- and the cords are
     * placed in fractions of the SIDE. Stretched and clipped, the step
     * below is in the same units the cords are.
     */
    left: 0;
    right: 0;
    width: auto;
    margin: 0;
    clip-path: inset(0 calc((100% - var(--hub-pins)) / 2));
    /*
     * One pin centred on the band's middle, tiled both ways from there --
     * `repeat-x` from a positioned tile rather than a repeating gradient,
     * which can only start at an edge. The middle is where the middle
     * cord comes down, so that cord lands ON a pin; and each side's tile
     * is a whole fraction of the distance to its next cord, so the others
     * land on pins too instead of on the package between two.
     */
    background-image: linear-gradient(90deg, var(--blue-line) 0 4px, transparent 4px 100%);
    background-repeat: repeat-x;
    background-position: calc(50% - 2px) 0;
    background-size: var(--hub-pin-step, 13px) 100%;
    /*
     * Not the cords' travelling dot -- these are ports, not cable -- but
     * the same brightening the other two sides do, in the same phase.
     */
    animation: hub-port 2.4s ease-in-out infinite;
  }
  /*
   * And room under the badge for the line that hangs off it: the bus runs
   * 9.5px below this side, which is where that line is. The padding moves
   * the bus and the pin band with it, and the bottom chip's stub is as
   * much longer, so the cords still reach.
   */
  /*
   * Stacked, the badge is the ROW ABOVE the three chips, not a fourth
   * thing beside them: one frame the width of the side, its six marks
   * spread across it, the line centred under it. Beside them it took the
   * first column and left the third empty, and the whole top half of the
   * picture sat left of the middle the processor and the four columns
   * under it are centred on.
   */
  .hub-badge {
    grid-column: 1 / -1;
    grid-row: 1;
    justify-self: stretch;
    width: auto;
    height: auto;
    grid-template-columns: repeat(6, 32px);
    /* Spread, not huddled in the middle of a frame three times their width. */
    justify-content: space-evenly;
    margin-bottom: var(--hub-badge-drop);
  }
  /*
   * The line goes ABOVE the badge here. Under it is where the two cords
   * leave from, and a caption across their path is a caption with two
   * cables through it. Side by side the cords leave the badge's right
   * edge, so there it stays underneath.
   */
  .hub-any { top: auto; bottom: 100%; margin-top: 0; margin-bottom: 8px; }
  .hub-in {
    /* Wide enough between the three chips for a cord to pass down it. */
    --hub-in-gap: 22px;
    /* Badge to chips, which is as much of each cord as is seen on its own. */
    --hub-badge-drop: 20px;
    /* One line of 12.5px text in 5px of padding, bordered. The chips are
       held to it, so the badge's cords can cross the row they are in. */
    --hub-chip-h: 32px;
    /* The room under the chips, and where in it the bus sits. */
    --hub-in-pad: 26px;
    --hub-bus-drop: 9.5px;
    column-gap: var(--hub-in-gap);
    /* Room above for the line, and below the chips for the bus. */
    padding-top: var(--hub-in-pad);
    padding-bottom: var(--hub-in-pad);
  }
  /*
   * The badge's own two cords: down from its bottom edge, through the two
   * gaps between the three chips, to the bus -- so they reach the
   * processor rather than stopping at the row in front of it. Each gap's
   * centre is a column and half a gap from the side it starts at, which
   * is the same arithmetic the grid lays the columns out with.
   */
  .hub-badge::before,
  .hub-badge::after {
    top: 100%;
    /*
     * The drop to the chips' row, the grid's own 8px before it, the row
     * itself, the padding under it and the bus below that -- then onto
     * the bus's middle, as the chips' cords do.
     *
     * The row's height used to be a 32 typed in here, with a note saying a
     * chip that wrapped would move the bus as well as these cords. On a
     * narrow phone a chip has to wrap, so it is `--hub-chip-h` now and the
     * chips are held to it: whatever the row is, these end on the bus.
     */
    height: calc(var(--hub-badge-drop) + 8px + var(--hub-chip-h) + var(--hub-in-pad) + var(--hub-bus-drop) + 2.5px);
  }
  .hub-badge::before { left: calc((100% - 2 * var(--hub-in-gap)) / 3 + var(--hub-in-gap) / 2); }
  .hub-badge::after { left: calc((100% - 2 * var(--hub-in-gap)) * 2 / 3 + var(--hub-in-gap) * 1.5); }
  /*
   * In: its cords are half a column apart -- a chip, then the badge's
   * cord in the gap beside it -- and seven pins to that distance keeps
   * the 13-14px rhythm this package has always had.
   */
  .hub-in::before { top: calc(100% + 11px); --hub-pin-step: calc((100% + var(--hub-in-gap)) / 42); }
  /* Out: a whole column apart, and five pins to half of that. */
  .hub-out::before { top: -22px; --hub-pin-step: calc((100% + var(--hub-out-gap)) / 40); }
  /* The buses, from the first column's centre to the last's. */
  .hub-in::after,
  .hub-out::after {
    left: calc(12.5% - 3px);
    right: calc(12.5% - 3px);
    width: auto;
    height: 3px;
    margin: 0;
    background:
      radial-gradient(circle, var(--blue) 0 2.5px, transparent 3px) 0 50% / 9px 9px no-repeat,
      radial-gradient(circle, var(--ok) 0 2.5px, transparent 3px) 100% 50% / 9px 9px no-repeat,
      linear-gradient(var(--blue-line), var(--blue-line));
    animation-name: hub-cable;
    animation-duration: 2.4s;
  }
  .hub-in::after { top: calc(100% + var(--hub-bus-drop)); }
  /*
   * In is three columns wide, not four, so its bus runs between other
   * centres: a column and half a gap in, which is where the outer chips'
   * cords come down. Ending 1.5px short of that -- the cord's own half
   * width -- puts the bus's end under the cord rather than beside it, so
   * the two meet as one solid corner instead of a line that overshoots
   * and a cord that hangs off it.
   */
  .hub-in::after {
    left: calc((100% - 2 * var(--hub-in-gap)) / 6 - 1.5px);
    right: calc((100% - 2 * var(--hub-in-gap)) / 6 - 1.5px);
  }
  .hub-out::after {
    top: -12.5px;
    /* Four columns here, so its outer centres are an eighth of what the gaps leave. */
    left: calc((100% - 3 * var(--hub-out-gap)) / 8 - 1.5px);
    right: calc((100% - 3 * var(--hub-out-gap)) / 8 - 1.5px);
  }
  /* A row further out reaches the processor through the chip in front of it. */
  .hub-out .hub-chip:nth-child(n + 5)::before { height: 8px; }
}
@container hub (max-width: 528px) {
  /*
   * Two across, and the pairs land as rows on their own: the source order
   * is assistant then what it reaches, so a row IS a pair again and the
   * chain runs down the columns, as it does on the other side.
   */
  /*
   * In stays three across, where out goes to two: the badge above it
   * spans those three and its two cords come up from the outer two, so
   * two across would leave one chip alone on a row with a cord running
   * down through the chip above it.
   */
  .hub-in {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: auto;
    grid-auto-flow: row;
    /* Three chips across a phone: the gap gives way before the words do. */
    --hub-in-gap: 10px;
  }
  /*
   * Then the words. The gap had already given way and the three chips
   * still wanted 356px, so on a phone they are set tighter and smaller.
   *
   * And they may take a second line, because in English they fit on one
   * at this size and in seven other languages they do not: "Ваше
   * приложение или API" wants 127px of the 105 a third of a 390px phone
   * leaves it, and a chip whose words are wider than it is does not clip
   * -- it runs over the chip beside it. Wrapping was gated at 327px here,
   * which is the width English needs it at, and every language whose
   * words are longer spent the 60px in between spilling.
   *
   * The row's height is a token (`--hub-chip-h`) and not a measurement
   * precisely so a second line is possible: the badge's two cords cross
   * this row to reach the bus, and they are drawn from that token.
   */
  .hub-in { --hub-chip-h: 42px; }
  .hub-in .hub-chip {
    padding-inline: 7px;
    gap: 5px;
    font-size: 11.5px;
    white-space: normal;
    text-align: center;
    line-height: 1.15;
  }
}
/*
 * TWO COLUMNS OF CHANNELS, AND WHY THE TWO SIDES PART COMPANY HERE.
 *
 * The side facing in has three chips and the side facing out has
 * sixteen, so they stop fitting at different widths -- and 528 above is
 * where the THREE stop, not the sixteen. The sixteen hold four across
 * down to 464px: below that "Messenger" and "Instagram" are one word
 * wider than a cell and there is nowhere inside them to break. Measured
 * in all seventeen languages, and four across is what keeps the picture
 * roughly as tall as the words it stands beside rather than half as tall
 * again.
 */
@container hub (max-width: 464px) {
  .hub-out { grid-template-columns: repeat(2, 1fr); }
  /* Two columns here, so a quarter of what the one gap leaves. */
  .hub-out::after {
    left: calc((100% - var(--hub-out-gap)) / 4 - 1.5px);
    right: calc((100% - var(--hub-out-gap)) / 4 - 1.5px);
  }
  .hub-out .hub-chip:nth-child(n + 3)::before { height: 8px; }
}
/*
 * And narrower than a 360px phone, the padding gives way as well: two
 * lines are already allowed above, and down here the words need the last
 * few pixels either side of them as well.
 */
@container hub (max-width: 327px) {
  .hub-in .hub-chip { padding-inline: 5px; }
}

/*
 * The three widths below are the window widths these blocks used to ask
 * about, each less the 32px of page padding between the window and this
 * frame: 1100 -> 1060, 560 -> 528, 359 -> 327 (the page's gutter is
 * 20px a side on a wide screen and 16px on a phone). They were always really
 * about how much room the picture had, and measuring the window for it
 * was the approximation. A phone behaves exactly as it did.
 */
/*
 * THE PICTURE'S OWN BOX IS WHAT IT LAYS ITSELF OUT AGAINST.
 *
 * It has two shapes -- three columns side by side, or one thing under
 * another -- and which one fits depends on how much room the picture has,
 * not on how wide the window is. Beside the hero's words it has about
 * 500px at any window size, so the question `@media` answers is the wrong
 * question: it would give a 500px column the three-column picture on a
 * desktop and the stacked one on a laptop, for no reason a reader could
 * see.
 *
 * A container asks the right one. Every `@container hub` block above is
 * the stacked shape, and it applies wherever this frame is narrow --
 * in the hero at every width, and anywhere else the picture is put in a
 * narrow place later.
 */
.hub-frame { container-type: inline-size; container-name: hub; }

/*
 * THE HERO WITH THE PICTURE BESIDE IT, INSIDE THE PAGE.
 *
 * Two columns, the words in one and the picture in the other, and both
 * ending on the same line. `--hero-copy` is the words' column; the
 * picture takes what is left of the page's 1080, and 20px separate them.
 *
 * THE COLUMNS ARE THE SAME HEIGHT because the picture's card is stretched
 * to the row and the drawing inside it is centred, rather than the card
 * being as tall as the drawing happens to be. A drawing of fixed parts
 * cannot be asked to grow -- its rows are a pitch the cords are measured
 * from -- so the card grows and the drawing sits in the middle of it.
 *
 * And the words' column is set at the width where the words are the
 * TALLER of the two in every language, so the card always grows to them
 * and never the other way round. That is the whole trick: one side can
 * be stretched and the other cannot, so the one that cannot is the one
 * that sets the height.
 */
.hero-illus { min-width: 0; }
.hero-illus .illus-hub { margin-top: 0; }
@media (min-width: 1120px) {
  .hero-split {
    /*
     * 580 + 20 + 480 = 1080, the page's content box. 580 is as wide as
     * the words can be while the picture still has the 480 its four
     * columns of channels need, and at it the two sides come out level:
     * 567 and 567 in English, and the words taller in all sixteen
     * others, which is the way round the stretch can fix.
     */
    --hero-copy: 580px;
    display: grid;
    /* The words get their width; the picture takes the rest of the page. */
    grid-template-columns: var(--hero-copy) minmax(0, 1fr);
    column-gap: 20px;
    /*
     * Centred against each other: the taller side sets the row and the
     * shorter one sits in the middle of it, rather than hanging from the
     * top with all the slack underneath.
     */
    align-items: center;
  }
  .hero-split .hero-words { min-width: 0; }
  .hero-split .hero-illus { min-height: 0; }
  /*
   * THE PICTURE WITHOUT ITS CARD, and with the air above and below it
   * taken in. Beside the words it is one of two things on a row rather
   * than a panel on a page, and a border around it drew a box around
   * half the hero.
   */
  .hero-split .hero-illus .illus-hub {
    border: 0;
    background: none;
    padding-block: 8px;
  }

  /*
   * THE TITLE'S SIZE, PER LANGUAGE, EACH THE LARGEST THAT TAKES TWO
   * LINES IN THIS COLUMN.
   *
   * The measure cannot do it: 17em shrinks exactly as fast as the font,
   * so a smaller size is the same number of lines -- the trap this
   * stylesheet already names on the phone title. The column is the
   * measure here, 580px, and the size is what varies.
   *
   * Seven languages reach two lines at a size worth having. The other
   * ten would need 27 to 34px, which is a title that has stopped being
   * one, so they keep 44 and take three lines: Arabic's does not fit in
   * two at any size this page would set it in. Measured, one language at
   * a time, at 1512px.
   */
  .hero-split h1 { font-size: var(--hero-title, 44px); }
  :root { --hero-title: 44px; }
  :lang(en) { --hero-title: 41px; }
  :lang(nl) { --hero-title: 39px; }
  :lang(de) { --hero-title: 36px; }
  :lang(tr) { --hero-title: 36px; }
  /*
   * Three more, and one correction. Arabic reaches two lines at 42px --
   * an earlier measurement here said six lines at 44 and four at 40, and
   * it was wrong: `getClientRects()` returns one rect per BIDI RUN, so
   * an Arabic line with "LinkedIn" and "API" in it counts as three.
   * Counting distinct line positions instead says three at 44 and two at
   * 42. Russian takes four at 44 and three at 38; Ukrainian four and
   * three at 42.
   */
  :lang(ar) { --hero-title: 42px; }
  :lang(uk) { --hero-title: 42px; }
  :lang(ru) { --hero-title: 38px; }
}
/* Under that, one column, and the picture after the words it illustrates. */
@media (max-width: 1119px) {
  .hero-illus { margin-top: 26px; max-width: 600px; margin-inline: auto; }
}

/* The hub and what it does, one card with a line between them. */
.hub-card { padding: 0; overflow: hidden; margin-top: 14px; }
.hub-card > .illus-hub { margin-top: 0; border: 0; border-bottom: 1px solid var(--border); border-radius: 0; }
.hub-card > .feature-grid { padding: 20px; }

/* A channel's colour, as a dot beside its name. */
.ch { display: inline-block; width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }
.ch-linkedin { background: #0a66c2; }
.ch-google-ads { background: #4285f4; }
.ch-whatsapp { background: #25d366; }
.ch-instagram { background: linear-gradient(135deg, #f58529, #dd2a7b 55%, #8134af); }
.ch-messenger { background: #0084ff; }
.ch-telegram { background: #229ed9; }
.ch-x { background: var(--text); }
.ch-discord { background: #5865f2; }
.ch-slack { background: conic-gradient(#e01e5a 0 25%, #36c5f0 0 50%, #2eb67d 0 75%, #ecb22e 0); }
.ch-email { background: #8a94a6; }
.ch-web { background: var(--blue); }
.ch-tiktok { background: linear-gradient(135deg, #25f4ee, #fe2c55); }
.ch-chatwoot { background: #1f93ff; }
.ch-hubspot { background: #ff7a59; }
.ch-asana { background: #f06a6a; }
.ch-clickup { background: linear-gradient(135deg, #7b68ee, #fd71af); }
.ch-agents { background: conic-gradient(var(--blue), #8b5cf6, #22c55e, var(--blue)); }
.ch-api { background: var(--text-soft); border-radius: 2px; }

/* "See it work": four short scenes, the LinkedIn API's first and widest. */
.illus-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.illus-card { margin: 0; display: flex; flex-direction: column; gap: 10px; }
.illus-card figcaption { display: flex; flex-direction: column; gap: 2px; }
.illus-card figcaption b { font-size: 16px; color: var(--text); }
.illus-card figcaption span { font-size: 13.5px; color: var(--text-soft); }
/*
 * On the use-case index the name is a link to the page the card plays.
 * It is still a heading, so it keeps the heading's colour and says it is
 * a link by behaving like one rather than by being blue.
 */
.illus-card figcaption b a { color: inherit; text-decoration: none; }
.illus-card figcaption b a:hover,
.illus-card figcaption b a:focus-visible { text-decoration: underline; }
.illus-card .illus { flex: 1; min-height: 250px; }
@media (max-width: 760px) { .illus-grid { grid-template-columns: minmax(0, 1fr); } }

/* Chat scenes: bubbles that arrive one after another. */
.illus-chat { display: flex; flex-direction: column; gap: 8px; }
/* The room scene on the Rooms page is drawn with the room's own classes; its typing dots sit in each message. */
.rooms-preview .room-msg { position: relative; }
.rooms-preview .room-msg.is-typing::after { left: 0; }
.ill-msg {
  position: relative;
  max-width: 86%;
  margin: 0;
  padding: 7px 11px;
  border-radius: 14px;
  font-size: 13px;
  line-height: 1.45;
  background: var(--bg-sunken);
  color: var(--text);
}
.ill-msg.is-me { align-self: flex-end; background: var(--blue); color: var(--on-accent); border-bottom-right-radius: 4px; }
.ill-msg.is-ai { align-self: flex-start; background: var(--card); border: 1px solid var(--border-strong); border-bottom-left-radius: 4px; }
.ill-msg.is-person { align-self: flex-start; border-bottom-left-radius: 4px; }
.ill-who { display: block; font-size: 11px; font-weight: 600; color: var(--muted); margin-bottom: 1px; }
.ill-msg.is-ai .ill-who { color: var(--violet); }
.ill-card {
  align-self: stretch;
  margin: 0;
  padding: 8px 11px;
  border-radius: 10px;
  font-size: 12.5px;
  font-weight: 600;
  border: 1px solid transparent;
}
.ill-ok { background: var(--ok-soft); color: var(--ok); }
.ill-note { background: var(--blue-soft); color: var(--blue-dark); border-color: var(--blue-line); }

/* The inbox scene: every channel's message in one list. */
.illus-inbox { display: flex; flex-direction: column; gap: 6px; }
.ill-row {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 10px;
  background: var(--card);
  border: 1px solid var(--border);
  font-size: 13px;
}
.ill-row b { font-weight: 600; }
.ill-row span { color: var(--text-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* Played by illus.js: what has not arrived yet holds its place, unseen, so nothing jumps. */
[data-illus-seq].is-playing > * { transition: opacity .35s ease, transform .35s ease; }
[data-illus-seq].is-playing > :not(.is-on):not(.is-typing) { opacity: 0; transform: translateY(6px); }
[data-illus-seq] > .is-typing > :not(.ill-who):not(.room-msg-who) { visibility: hidden; }
[data-illus-seq] > .is-typing::after {
  content: "";
  position: absolute;
  left: 12px;
  bottom: 10px;
  width: 30px;
  height: 8px;
  background: radial-gradient(circle, var(--muted) 0 3px, transparent 3.5px) 0 50% / 10px 8px repeat-x;
  animation: ill-typing 1s steps(4) infinite;
  clip-path: inset(0 100% 0 0);
}
@keyframes ill-typing { to { clip-path: inset(0 -10% 0 0); } }

/* The white-label scene: one window, three brands, the same product. */
.illus-brand { display: grid; place-items: center; }
.ill-window {
  position: relative;
  width: min(100%, 360px);
  height: 196px;
  border-radius: 12px;
  border: 1px solid var(--border-strong);
  background: var(--card);
  box-shadow: var(--shadow);
  overflow: hidden;
}
.ill-skin { position: absolute; inset: 0; display: flex; flex-direction: column; opacity: 0; transition: opacity .6s ease; }
.ill-skin.is-on { opacity: 1; }
.ill-addr { margin: 8px 10px; padding: 4px 10px; border-radius: 999px; background: var(--bg-sunken); font: 11.5px var(--mono); color: var(--text-soft); }
.ill-head { display: flex; align-items: center; gap: 8px; padding: 6px 14px 10px; border-bottom: 1px solid var(--border); font-weight: 700; font-size: 14px; }
.ill-logo { width: 20px; height: 20px; border-radius: 6px; background: var(--skin); }
.ill-body { display: flex; flex-direction: column; gap: 7px; padding: 12px 14px; }
.ill-bar { height: 8px; border-radius: 4px; background: var(--bg-sunken); }
.ill-bar.w60 { width: 60%; } .ill-bar.w80 { width: 80%; } .ill-bar.w40 { width: 40%; }
.ill-btn { margin-top: 4px; width: 92px; height: 22px; border-radius: 6px; background: var(--skin); }
.ill-skin.s1 { --skin: var(--blue); }
.ill-skin.s2 { --skin: #e8590c; }
.ill-skin.s3 { --skin: #0f9d77; }

/* The dashboard's three steps to connect an AI, the current one lit. */
.illus-steps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; padding: 12px; margin: 12px 0; }
.ill-step {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--card);
  font-size: 13px;
  color: var(--text-soft);
  transition: border-color .3s ease, box-shadow .3s ease;
}
.ill-step b { color: var(--text); font-size: 13.5px; }
.ill-step .ill-num {
  display: inline-grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--bg-sunken);
  font-size: 12px;
  font-weight: 700;
  color: var(--text-soft);
}
.ill-step.is-on { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
.ill-step.is-on .ill-num { background: var(--blue); color: var(--on-accent); }
.ill-chip { align-self: flex-start; padding: 3px 9px; border-radius: 999px; background: var(--bg-sunken); font: 11.5px var(--mono); color: var(--text-soft); }
/* A picture of the button, not the button: pale, so nobody tries to press it. */
.ill-pill { align-self: flex-start; padding: 3px 9px; border-radius: 7px; border: 1px solid var(--blue); background: var(--blue-soft); color: var(--blue); font-size: 12px; font-weight: 600; }
@media (max-width: 640px) { .illus-steps { grid-template-columns: minmax(0, 1fr); } }
.ill-step .ill-msg { align-self: flex-end; font-size: 12px; padding: 5px 9px; }
/* The Inbox screen before there is a conversation: the scene, then the three steps. */
.inbox-empty-title { margin: 0 0 8px; font-size: 24px; line-height: 1.25; }
.inbox-illus { text-align: left; margin: 14px auto 6px; max-width: 420px; }
.inbox-how { text-align: left; margin: 12px auto; max-width: 440px; padding-left: 20px; font-size: 14px; color: var(--text-soft); display: flex; flex-direction: column; gap: 4px; }
.inbox-how b { color: var(--text); }
/* What a new channel needs pasted elsewhere: a snippet, an address. Selectable, and never wider than the dialog. */
.inbox-add-code { white-space: pre-wrap; overflow-wrap: anywhere; max-height: 180px; overflow-y: auto; font-size: 12px; user-select: all; }
/* The dashboard's longer explanation, folded under the steps. */
.dash-ai-more { margin: 4px 0 12px; }
.dash-ai-more > summary { cursor: pointer; font-weight: 600; font-size: 14px; color: var(--blue); margin-bottom: 8px; }

/* Still, for whoever asked for less motion: every frame shown, nothing running. */
@media (prefers-reduced-motion: reduce) {
  .hub-core, .hub-core::before, .hub-core::after, .hub-in .hub-chip::after, .hub-in .hub-chip::before, .hub-out .hub-chip::before, .hub-side::before, .hub-side::after, [data-illus-seq] > .is-typing::after { animation: none; }
  [data-illus-seq].is-playing > * { transition: none; }
  .ill-skin { transition: none; }
}

/* ----------------------------------------------------- book a call -- */

/*
 * The popup book.js opens: who the call is with, then the days as a row of
 * chips (scrolling sideways on a phone), the day's times as a grid, and the
 * email form. One column, never wider than a phone needs.
 */
.book-dialog {
  width: min(520px, calc(100vw - 24px));
  max-height: calc(100dvh - 32px);
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 16px;
  background: var(--card);
  color: var(--text);
  box-shadow: var(--shadow-lg);
}
.book-dialog::backdrop { background: rgba(15, 23, 42, .45); }
.book-head { display: flex; align-items: flex-start; gap: 12px; padding: 18px 18px 12px; border-bottom: 1px solid var(--border); }
.book-photo { width: 44px; height: 44px; flex: none; border-radius: 10px; }
.book-head h2 { margin: 0 0 2px; font-size: 18px; }
.book-head p { margin: 0; font-size: 13.5px; }
.book-head > div { flex: 1; min-width: 0; }
.book-close { flex: none; width: 34px; height: 34px; padding: 0; font-size: 22px; line-height: 1; }
.book-body { padding: 16px 18px 20px; overflow-y: auto; }
.book-body > p { margin-top: 0; }
.book-days { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px; margin-bottom: 12px; scrollbar-width: thin; }
.book-day {
  flex: none;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  min-width: 64px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card);
  color: var(--text);
  font-size: 12.5px;
  cursor: pointer;
}
.book-day b { font-size: 14px; }
.book-day[aria-pressed="true"] { border-color: var(--blue); background: var(--blue-soft); color: var(--blue); }
.book-times { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px; margin-bottom: 12px; }
.book-time {
  padding: 9px 6px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--card);
  color: var(--text);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}
.book-time:hover, .book-time:focus-visible, .book-day:hover { border-color: var(--blue); }
.book-picked { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; }
.link-button { padding: 0; border: 0; background: none; color: var(--blue); font: inherit; font-size: 13.5px; cursor: pointer; text-decoration: underline; }
/*
 * A link-button under the pointer was turning into a filled blue button:
 * `button:hover` sets a background and outranks `.link-button`, which had
 * no hover rule of its own to say otherwise. It looks like a link, so it
 * behaves like one -- the underline is the hover state.
 */
.link-button:hover { background: none; color: var(--blue-dark); }
/*
 * "Use WhatsApp instead", between the two contact fields. A form is a
 * column of stretched children, so without this the link spans the form
 * and its underline runs the whole width.
 */
.book-swap { align-self: start; margin-top: -4px; }
.book-note { padding: 8px 10px; border-radius: 8px; background: var(--warn-soft); color: var(--warn); font-size: 13.5px; }
.book-done { font-size: 16px; font-weight: 600; }

/* ---------------------------------------------------- booking pages -- */
/*
 * A member's booking page (book-page.html at /book/<slug>): the same steps
 * as the popup, inline in one card. And its settings (booking.html): the
 * fields in a grid that folds to one column on a phone.
 */
.book-page { max-width: 640px; }
.book-card h1 { margin: 2px 0 4px; }
.book-card .book-body { padding: 14px 0 0; }
.booking-settings form { max-width: none; }
.booking-settings .option-card form { gap: 8px; }
.booking-settings .option-card form p { margin: 0; }
.booking-fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px 18px; }
.booking-fields label { margin-bottom: 6px; }
.booking-slug { display: flex; align-items: center; gap: 4px; }
.booking-slug input { flex: 1; min-width: 0; }
.booking-days { border: 0; padding: 0; margin: 0; min-width: 0; }
.booking-days legend { font-size: 12.5px; color: var(--text-soft); font-weight: 600; padding: 0; margin-bottom: 6px; }
.booking-days > div { display: flex; flex-wrap: wrap; gap: 2px 14px; }
.booking-hours { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.booking-call { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 6px 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
.booking-call:last-child { border-bottom: none; }
.booking-call b { font-weight: 600; }

/* ----------------------------------------------------- the home page -- */
@media (max-width: 639px) {
  /*
   * No forced break in the title on a phone.
   *
   * The break exists so a desktop reads two lines instead of one long run.
   * A phone is already wrapping the first sentence, so forcing the second
   * onto its own line only adds a short ragged line in the middle -- "Your
   * LinkedIn does it." alone, with half the width empty beside it. Hidden,
   * the wrap goes back to using every line it is given.
   *
   * The space before the <br> in the markup is what makes this safe: with
   * the break gone the two sentences are separated exactly as they were
   * before it existed. Japanese and Chinese have none, because they had
   * none.
   */
  .simple-h1 br { display: none; }

}

/*
 * The landing page: the story, then the price, then the written half.
 *
 * Narrower than the page it sits in. `main.wide` is 1120px because the
 * reference and the docs have tables in them that need it; this is prose,
 * and prose read across 1120px is prose nobody finishes.
 */
.home-simple { max-width: 920px; margin: 0 auto; }
.home-simple .hero { margin-bottom: 34px; }

/*
 * THE PRICE AND THE APPS, ON ONE LINE.
 *
 * Two blocks of the same width: the pair of buttons on the left, the row
 * of client marks on the right. Both are --ai-n boxes wide (the arithmetic
 * is in .hero-cta's own rules further up), so neither has a number of its
 * own to keep in step, and `space-between` puts the gap in the middle
 * rather than at an edge.
 *
 * `flex-end` on the cross axis: the buttons and the marks sit on one
 * baseline, with the price above the one and the line naming the other
 * above the other. The two captions are therefore level too, which is the
 * whole reason the line moved from under the marks to over them.
 *
 * It wraps below about 1000px, where the pair gives up its minimum width,
 * and each block then takes a line of its own, still in document order.
 */
.hero-start { margin-top: 26px; }
.hero-start .hero-price { margin: 0; }
/* Beside the price rather than 26px under it: they are one block. */
.hero-start .hero-cta { margin-top: 10px; }
/* Under the pair while the two are a stack. */
.hero-start > .hero-ais { margin-top: 20px; }
/* The white label's line belongs to the price, under the whole of it. */
/*
 * EVEN LINES, NOT AN ORPHAN.
 *
 * One line is what this was asked to be, and one line is not something it
 * can have: measured at 390px, the sentence fits the 358px it has at
 * 6.5px in Japanese and 7.4px in English, against the 12.5px it is set
 * at. Nothing readable gets there, and the shortest wording that would is
 * no longer the sentence.
 *
 * What it CAN stop is the way it broke -- "...і ваша / власна вебадреса.",
 * a full line and then two words. `balance` divides the words evenly
 * between the lines it needs, so the second line is half the sentence
 * rather than its tail. A browser that does not know the property keeps
 * the ragged break, which is what it has today.
 */
.hero-start-note { margin-top: 14px; text-wrap: balance; }

/*
 * 1000px, and not a pixel of it is arbitrary: it is where .hero-cta's own
 * rules give the pair its minimum width of --ai-n boxes, which is what
 * makes the two blocks equal. Below it the pair is as wide as its words,
 * the two would be a row of unequal halves, and the line over the marks
 * would be right-aligned over nothing. So below it they are a stack --
 * a plain block, not a column, because .hero-cta takes the width of the
 * screen on a phone and a flex item's `width: 100%` is 100% of whatever
 * the item was handed.
 */
@media (min-width: 1000px) {
  /*
   * One grid of two columns and two rows, not two blocks side by side.
   *
   * Side by side, each block set its own two heights and the two captions
   * landed two pixels apart with their text on different baselines -- the
   * price sits in a 38px box and the line over the marks in an 18px one.
   * Sharing a grid, the captions ARE one row and the controls are the
   * other, so "level" is the layout rather than two numbers kept equal by
   * hand. `baseline` on the two captions, because what a reader lines up
   * is the type, not the boxes it sits in.
   *
   * The wrappers dissolve (`display: contents`) so their children are the
   * grid's items; the four placements below are explicit, because
   * auto-placement would read them in document order and fill the first
   * row with the price and the buttons.
   *
   * `justify-content: start`, not `space-between`: the marks belong beside
   * the buttons, as the next thing to reach for, rather than pushed to the
   * far edge of a column with 128px of nothing in the middle. Both columns
   * are --ai-n boxes wide -- the pair takes that as a minimum here -- so
   * they are two equal blocks, and the spare width falls at the end.
   */
  .hero-start {
    display: grid;
    grid-template-columns: max-content max-content;
    justify-content: start;
    /*
     * The same gap the marks have between them, and the pair of buttons
     * between its two. One row, one rhythm: 28px here made the step from
     * "Book a call" to the first mark read as a bigger join than any
     * other in the line, which it is not.
     */
    column-gap: var(--ai-gap);
    row-gap: 10px;
  }
  /* brand.js REMOVES an unoffered .hero-ais rather than hiding it, so
     dissolving it here cannot leave a hidden box taking a column. */
  .hero-start > .hero-start-buy,
  .hero-start > .hero-ais { display: contents; }
  .hero-start .hero-price { grid-area: 1 / 1; align-self: baseline; margin: 0; }
  .hero-start .hero-cta { grid-area: 2 / 1; margin: 0; }
  .hero-start .hero-ais-row { grid-area: 2 / 2; }
  /*
   * THE ROW'S NAME AND THE SCORE, ABOVE THE MARKS, IN THE MARKS' OWN
   * COLUMN -- SO ROW 1 IS THE PRICE AND THIS, AND ROW 2 IS THE BUTTONS
   * AND THE MARKS.
   *
   * Above, because on a desktop this line is a HEADING over the row: it
   * names what the marks are for, and a name belongs before the thing
   * it names when there is room to put it there. On a phone there is
   * not -- the blocks stack and a heading between the buttons and the
   * marks separates them -- so down there it stays underneath, which is
   * what the rule outside this block already does.
   *
   * Column 2, not both columns: both put its left edge under the price,
   * 406px from the first mark it is naming. Sharing column 2 with the
   * row gives the two one left edge and one width.
   *
   * The column is `max-content` and this line is a fixed `--ai-row`
   * wide, the row's own width, so it cannot drag the track out behind
   * it -- the fear that once put it across both columns, back when the
   * name filled the whole row with nothing left for the score.
   *
   * `baseline`, like the price beside it: what a reader lines up across
   * a row is the type, not the boxes it sits in, and the score inside
   * this line is centred on that same type.
   */
  .hero-start .hero-ais-line { grid-area: 1 / 2; margin: 0; align-self: baseline; }
  /*
   * AND THE WHITE LABEL GETS A LINE TO ITSELF, now row 3.
   *
   * It used to share one with the score, written around it with
   * `padding-inline-end` so the two did not collide. The score sits with
   * the marks it belongs to now, so the sentence has the whole width --
   * which is what it wanted, being a sentence.
   */
  .hero-start .hero-start-note {
    grid-area: 3 / 1 / 4 / 3;
    margin: 0;
    align-self: center;
  }
}

/*
 * The page's first heading, and two lines rather than one run of three
 * sentences.
 *
 * THE BREAK IS IN THE MARKUP, not in this measure. A `max-width` that
 * happens to break after "does it." on one machine breaks mid-clause on
 * another: the site ships no webfont, and the system fallbacks it lands on
 * differ by about 15% in width. So `simTitle` carries a `<br>`, in all
 * seventeen languages -- the localiser requires a translation to have the
 * same tags in the same order as the English, which is what keeps them
 * from drifting apart.
 *
 * 42px is the ceiling: English is two lines there and three at 44px.
 * The ceiling is set by the longer of the two lines -- "You say what to
 * do. Your AI and LinkedIn do it." is 47 characters, and 47 characters at
 * 44px want about 1030px against the 920px this column is. That is the
 * exchange rate on this title: five more characters in that sentence cost
 * six pixels of type everywhere.
 *
 * 23px is the floor, and it is low because the whole title is 82
 * characters and three lines of a 343px column is what they have to fit
 * in. At 24px English is four lines on a 360px phone and at 23px it is
 * three; 414, 375 and 360 all hold three at 23. A 320px screen is four
 * whatever is done short of 20px type, which is not a title any more.
 *
 * NO MEASURE, and that is deliberate. A `max-width` in `em` shrinks at
 * exactly the rate the font does, so it is never the slack it looks like:
 * `20em` read as 960px at 48px and as 840px at 42px, and at 42px it --
 * not the column -- was what put the title on three lines. The same
 * mistake cost a day on the long page's title. The break is the <br>, the
 * width is .home-simple's own 920px, and there is no third thing with an
 * opinion.
 */
.simple-h1 {
  line-height: 1.12;
  letter-spacing: -.03em;
  margin: 0 0 14px;
}
/*
 * `.hero h1`, NOT `.simple-h1`, and that is the whole reason this rule
 * exists.
 *
 * This title was an <h2> while the page was two pages -- the document's one
 * h1 belonged to the long version. Making it the h1 put it inside
 * `.hero h1` further up this file, which is a class AND an element and so
 * beats a bare class: it took that rule's 44px and, worse, its `max-width:
 * 17em`. 17em at 44px is 748px against a 920px column, so the longest of
 * the two sentences -- 922px at 44px -- broke across two lines and the
 * title rendered on three. The <br> was still there; it had simply stopped
 * being the thing that decided the break.
 *
 * So: no measure at all (the column is the measure, as the note above
 * says), and 42px, where the longer sentence is 880px and fits with 40 to
 * spare. 43px is 901 and also fits; 44px is 922 and does not. Measured in
 * the browser at 920px, not estimated.
 */
.hero h1.simple-h1 {
  max-width: none;
  font-size: clamp(23px, 4.4vw, 42px);
  /*
   * No line of this title is one word.
   *
   * `balance`, not `pretty`, and the difference is the whole of this
   * paragraph.
   *
   * `pretty` protects the LAST line of a run from holding one word. That
   * is not this title's failure. Its failure is the FIRST line taking all
   * it can and leaving the rest of the sentence stranded: Ukrainian read
   * [713 409 894] against a 920px column -- a 409px scrap of "це роблять."
   * on a line of its own -- and Polish, Italian and Hindi were worse, at
   * 157, 168 and 183. `pretty` cannot reach any of them, because the <br>
   * ends each run one sentence early and a run's last line is all it
   * protects.
   *
   * `balance` evens the lines of each run instead, so the same Ukrainian
   * sentence reads [545 577] and no line is a scrap. Measured in the
   * browser across all seventeen languages: four titles with a stranded
   * line before, none after. Where it is not supported the title wraps as
   * it always did and the non-breaking space before the first sentence's
   * last word still catches the one-word case, so nothing here is
   * load-bearing on its own.
   *
   * `keep-all` is KOREAN'S ALONE, and the `:lang()` on it is the whole
   * point of this paragraph.
   *
   * Korean otherwise breaks between syllable blocks in the middle of a
   * word, and `keep-all` stops that. But the property does not only bind
   * syllables: it suppresses the break opportunity BETWEEN any two
   * CJK characters. Korean has spaces to break at instead, so it loses
   * nothing. Japanese and Chinese have none -- a sentence is one run of
   * characters and every break it gets is one of the ones this property
   * takes away. Unscoped, it made the Japanese title a single unbreakable
   * 546px word and dragged the whole page 172px past a 390px screen.
   *
   * So: Korean asks for it, nobody else is given it.
   */
  text-wrap: balance;
}
.hero h1.simple-h1:lang(ko) { word-break: keep-all; }
/*
 * A BIGGER TITLE ON A PHONE, WITHOUT A LINE MORE THAN IT HAS NOW.
 *
 * 23px is the floor of the clamp above, and on a phone the clamp never
 * leaves it: 4.4vw is 17px at 390. A flat bump is not available -- at
 * 24px Arabic, Portuguese and Turkish each take another line, while
 * English has room to nearly 26 and Hindi to 30.
 *
 * So the size is per language, and a fraction of the width rather than a
 * number of pixels. That is what keeps the breaks the same at every phone
 * width: the line and the box it has to fit grow together, so a language
 * calibrated at 390px is still right at 360 and at 430.
 *
 * Each ratio is the largest font -- the smallest divisor -- at which that
 * language's title still takes no more lines than it does today, measured
 * on a real page load at 320, 360, 390 and 430, with the override served
 * in the stylesheet rather than set afterwards: `text-wrap: balance` does
 * not always give the same answer to a restyle that it gives to a first
 * layout, and an earlier round of this was calibrated against the wrong
 * one. Seven languages have room and ten do not; the ones that do not sit
 * at the floor and are unchanged -- 17.3 for the six that cannot grow
 * even at 430px, which keeps the floor in force across the whole range.
 *
 * The 23px floor is why nothing can get SMALLER than it is today: at
 * 320px every ratio lands under it. The 34px ceiling is reached by no
 * language inside this range -- it is there so a 430px window on a
 * desktop cannot run the title away.
 *
 * Scoped to 430px, not the 639px the rest of the phone block uses: above
 * that the title already scales with 4.4vw and takes fewer lines, and
 * there are no measurements up there.
 */
@media (max-width: 430px) {
  /* On :root, not on the title: a custom property set on the element
     itself beats the one a :lang() rule puts on <html>, so a fallback
     there would hand every language the same ratio. */
  :root { --title-ratio: 15.57; }
  :lang(en) { --title-ratio: 14; }
  :lang(ar) { --title-ratio: 15.57; }
  :lang(de) { --title-ratio: 17.3; }
  :lang(es) { --title-ratio: 13.2; }
  :lang(fr) { --title-ratio: 17.3; }
  :lang(hi) { --title-ratio: 12; }
  :lang(id) { --title-ratio: 17.3; }
  :lang(it) { --title-ratio: 12.4; }
  :lang(ja) { --title-ratio: 13.6; }
  :lang(ko) { --title-ratio: 13.6; }
  :lang(nl) { --title-ratio: 17.3; }
  :lang(pl) { --title-ratio: 14; }
  :lang(pt-BR) { --title-ratio: 15.57; }
  :lang(ru) { --title-ratio: 17.3; }
  :lang(tr) { --title-ratio: 15.57; }
  :lang(uk) { --title-ratio: 14; }
  :lang(zh-Hans) { --title-ratio: 17.3; }
  .hero h1.simple-h1 { font-size: clamp(23px, calc((100vw - 32px) / var(--title-ratio)), 34px); }
}
.simple-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 34px; }
.simple-steps > li { margin: 0; }
.simple-steps h2 { font-size: 21px; margin: 0 0 6px; }
.simple-steps > li > p { max-width: 62ch; color: var(--text-soft); margin: 0 0 14px; }
.simple-steps .illus { max-width: 620px; }
.simple-keeps { margin-top: 34px; }

/*
 * The short page's one picture: what you say on the left, the member's own
 * brand in the middle, what happens on the right -- three pairs, lit one
 * at a time.
 *
 * One at a time is the whole design OF THE BOXES. Four boxes lighting
 * together say "things happen"; four lighting in turn say which sentence
 * caused which result, which is the only thing this drawing is for. The
 * die is placed across all four rows rather than drawn four times,
 * because there is one brand and everything runs through it.
 *
 * The CORDS are the other half and do not take turns: traffic through
 * the brand never stops, and a cord that is dark until its row's turn
 * reads as a cord that is not connected. So every one of them carries
 * the same two-way cable the hub illustration uses, pointing inwards
 * from both sides.
 */
.sim-flow { padding: 26px 24px; margin-top: 30px; }
.sim-ends {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--muted);
}
.sim-ends-out { color: var(--blue); text-align: end; }

.sim-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 36px max-content 36px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
}
.sim-said {
  margin: 0;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg);
  padding: 11px 13px;
  font-size: 14px;
  line-height: 1.45;
}
.sim-shown {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  padding: 11px 13px;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.sim-shown strong { display: flex; align-items: center; gap: 8px; font-size: 14px; }
.sim-shown .muted { font-size: 12.5px; line-height: 1.45; }

/*
 * The brand. A dashed square rather than our mark: on a white-label host
 * brand.js replaces it with that customer's own logo (applyLook), so the
 * middle of the picture becomes theirs without this page knowing.
 */
.sim-die {
  grid-column: 3;
  grid-row: 1 / 5;
  align-self: center;
  justify-self: center;
  /*
   * As wide as its own line, not a width chosen once in English.
   *
   * The die line is one short sentence and is meant to read as one. At a
   * fixed 196px the English brand line ("Your LinkedIn account, your AI.")
   * wanted 170px and had 166, so it broke in two four pixels short of
   * fitting -- and every other language missed by its own margin.
   *
   * max-content lets the card take exactly the line it is given: 198px in
   * English, 271px in Japanese, no emptier than the sentence needs. The cap
   * is the widest brand line of the seventeen plus a little, measured in a
   * browser rather than guessed. Our OWN host says more (", your name on
   * all of it") and no honest width fits that on one line, so there it
   * wraps -- balanced, and into a card no wider than the cap.
   */
  width: max-content;
  min-width: 196px;
  max-width: 276px;
  border: 1px solid var(--blue-line);
  border-radius: var(--radius-lg);
  background: var(--card);
  padding: 20px 14px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.sim-die b { font-size: 17px; line-height: 1.2; }
.sim-die small { font-size: 11.5px; line-height: 1.45; color: var(--muted); text-wrap: balance; }
.sim-die .brand-mark.is-placeholder {
  width: 32px;
  height: 32px;
  border: 2px dashed var(--blue-line);
  border-radius: 9px;
  background: none;
  -webkit-mask: none;
  mask: none;
}
.sim-die .brand-mark.is-placeholder::before,
.sim-die .brand-mark.is-placeholder::after { content: none; }

/*
 * A cord. The same cable the hub draws (hub-cable, above): a blue pulse
 * one way and a green one coming back, so the drawing says the traffic
 * is two-way without anybody having to read a label.
 *
 * Both sides point IN. On the left the blue pulse runs from the sentence
 * into the brand, which is the direction it already ran; on the right the
 * cord carries the same animation reversed, so its blue pulse runs from
 * the result back into the brand and the green one out to the result.
 * The brand in the middle is what everything plugs into, from both ends.
 */
.sim-cord {
  position: relative;
  height: 3px;
  border-radius: 2px;
  background:
    radial-gradient(circle, var(--blue) 0 2.5px, transparent 3px) 0 50% / 9px 9px no-repeat,
    radial-gradient(circle, var(--ok) 0 2.5px, transparent 3px) 100% 50% / 9px 9px no-repeat,
    linear-gradient(var(--blue-line), var(--blue-line));
  animation: hub-cable 2.2s linear infinite;
  animation-delay: calc(var(--n, 0) * -.28s);
}
/* Out of the brand: the same cable, running the other way. */
.sim-cord-out { animation-direction: reverse; }
/* Row by row, so four cables are not in lockstep. */
.sim-run-1 { --n: 0; }
.sim-run-2 { --n: 1; }
.sim-run-3 { --n: 2; }
.sim-run-4 { --n: 3; }

.sim-said-1 { animation: sim-said 18s ease-out infinite; animation-delay: .4s; }
.sim-said-2 { animation: sim-said 18s ease-out infinite; animation-delay: 4.9s; }
.sim-said-3 { animation: sim-said 18s ease-out infinite; animation-delay: 9.4s; }
.sim-said-4 { animation: sim-said 18s ease-out infinite; animation-delay: 13.9s; }
@keyframes sim-said {
  0%        { border-color: var(--border-strong); background: var(--bg); }
  4%, 16%   { border-color: var(--blue); background: var(--card); }
  22%, 100% { border-color: var(--border-strong); background: var(--bg); }
}

.sim-shown-1 { animation: sim-shown 18s ease-out infinite; animation-delay: 1.6s; }
.sim-shown-2 { animation: sim-shown 18s ease-out infinite; animation-delay: 6.1s; }
.sim-shown-3 { animation: sim-shown 18s ease-out infinite; animation-delay: 10.6s; }
.sim-shown-4 { animation: sim-shown 18s ease-out infinite; animation-delay: 15.1s; }
@keyframes sim-shown {
  0%        { border-color: var(--border); background: var(--card); transform: translateX(0); }
  5%        { border-color: var(--blue); background: var(--blue-soft); transform: translateX(4px); }
  14%, 100% { border-color: var(--blue-line); background: var(--card); transform: translateX(0); }
}

/* The three notes under the picture, side by side where there is room. */
.sim-three { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; margin-top: 34px; }
.sim-three h2 { font-size: 19px; }
.sim-three > li > p { margin-bottom: 0; }

/*
 * Narrow: the pairs stack, and the cords stand up rather than going away.
 *
 * They used to be hidden here, on the argument that a cord drawn between
 * two boxes side by side says "this reaches that" and the same cord
 * between two boxes stacked says nothing. True of a horizontal one -- and
 * hiding them took the only thing in the picture that moves ALL the time
 * out of it. What was left is four boxes lighting one at a time over
 * eighteen seconds, which on a phone, where the whole eighteen seconds is
 * rarely on screen at once, reads as a still picture.
 *
 * So each pair keeps one cord, turned on its end between the sentence and
 * the result, carrying the hub's own cable downwards: the blue dot in,
 * the green one back. The second cord of the pair goes, because two of
 * them stacked read as one cable with a gap in the middle.
 */
@media (max-width: 860px) {
  .sim-grid { grid-template-columns: minmax(0, 1fr); }
  .sim-cord {
    width: 3px;
    height: 30px;
    margin: 2px auto;
    background:
      radial-gradient(circle, var(--blue) 0 2.5px, transparent 3px) 50% 0 / 9px 9px no-repeat,
      radial-gradient(circle, var(--ok) 0 2.5px, transparent 3px) 50% 100% / 9px 9px no-repeat,
      linear-gradient(var(--blue-line), var(--blue-line));
    animation-name: hub-cable-v;
  }
  .sim-cord-out { display: none; }
  /*
   * And the pairs light twice as often.
   *
   * Eighteen seconds over four pairs is one every four and a half, which
   * is fine on a desktop where all four are on screen and the eye catches
   * whichever moves. Stacked, one pair fills the screen, so a reader sees
   * THAT pair light once every eighteen seconds and reasonably concludes
   * the picture is a still. Twelve seconds is three per pair; the
   * keyframes are in percentages, so only the clock changes.
   */
  .sim-said-1, .sim-said-2, .sim-said-3, .sim-said-4,
  .sim-shown-1, .sim-shown-2, .sim-shown-3, .sim-shown-4 { animation-duration: 12s; }
  .sim-said-1 { animation-delay: .3s; }
  .sim-said-2 { animation-delay: 3.3s; }
  .sim-said-3 { animation-delay: 6.3s; }
  .sim-said-4 { animation-delay: 9.3s; }
  .sim-shown-1 { animation-delay: 1.1s; }
  .sim-shown-2 { animation-delay: 4.1s; }
  .sim-shown-3 { animation-delay: 7.1s; }
  .sim-shown-4 { animation-delay: 10.1s; }
  .sim-die { grid-column: auto; grid-row: auto; order: -1; width: 100%; min-width: 0; max-width: 320px; }
  .sim-shown { margin-bottom: 8px; }
  .sim-three { grid-template-columns: minmax(0, 1fr); gap: 26px; }
  .sim-ends-out { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .sim-cord { animation: none; }
  .sim-said-1, .sim-said-2, .sim-said-3, .sim-said-4 { animation: none; }
  .sim-shown-1, .sim-shown-2, .sim-shown-3, .sim-shown-4 { animation: none; border-color: var(--blue-line); }
}

/*
 * What the page closes on: the group, the white label, the safety, the
 * ready-made work, the price and the written half. None of them is part of
 * the story above them, so they are spaced like chapters rather than like
 * paragraphs.
 *
 * `> section` rather than the `sim-` id prefix they used to carry: that
 * prefix only ever existed to keep them apart from a second home page in
 * the same document, and there is one home page now.
 */
.home-simple > section:not(.hero),
.home-simple > h2 { margin-top: 44px; }
/* The price's heading is outside the section it names, because .pricing is
   a flex row and a heading inside it would be one of the cards. The two
   are one block, so only the first of them takes the chapter's gap. */
.home-simple > h2 + section { margin-top: 10px; }
/*
 * The cards without their paragraph and their three points: a mark, a
 * name, a price and the way in. Less padding, because there is less in
 * them and a card that is mostly air reads as a card that is waiting to
 * load.
 */
.ship-brief .option-card { padding: 16px; }
.ship-brief .card-cta { padding-top: 12px; }

/*
 * The six client pages (/claude, /chatgpt and the rest).
 *
 * The mark sits above the heading at the size of a product logo rather
 * than of a button: on these pages it is what the page is about, not one
 * choice among six. It takes the text colour, as it does in the hero, so
 * it is one mark in both themes rather than two assets.
 */
.conn-mark {
  width: 48px;
  height: 48px;
  display: block;
  margin: 0 0 14px;
  fill: currentColor;
  color: var(--text);
}
/*
 * The address, which is the one thing on the page somebody has to copy.
 * Selectable and wrapping: it is read on a phone as often as on a desk,
 * and an address that overflows its box is an address nobody can copy.
 */
.conn-address {
  margin: 0 0 12px;
}
.conn-address code {
  display: inline-block;
  max-width: 100%;
  overflow-wrap: anywhere;
  padding: 10px 14px;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  background: var(--card);
  font-size: 1.05em;
  user-select: all;
}

/*
 * The address, once copy-address.js has wrapped it.
 *
 * Styled here rather than built here: without the script the `<code>`
 * above is the whole of it and still reads as a field, so a reader with
 * no JavaScript loses the press and keeps the address.
 */
.conn-address-row {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 100%;
  flex-wrap: wrap;
}
/* It behaves as a button, so it says so on the way past. */
.conn-address-row code { cursor: pointer; }
.conn-address-row code:hover { border-color: var(--blue); }
.conn-address-row code:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }
.conn-copy { width: 40px; height: 40px; }
/*
 * Reserved, not inserted: the word appears where it will appear, so the
 * row does not jump sideways at the moment somebody presses it.
 */
.conn-copied {
  font-size: 13px;
  color: var(--green, var(--text-soft));
  opacity: 0;
  transition: opacity .15s ease;
}
.conn-copied.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .conn-copied { transition: none; }
}
/* The other five marks, for somebody who pressed the wrong one. */
.conn-others {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin: 16px 0 0;
  padding: 0;
}
.conn-others > li { margin: 0; }
.conn-others a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card);
  color: var(--text-soft);
}
.conn-others a:hover,
.conn-others a:focus-visible { color: var(--text); border-color: var(--blue); }
.conn-others svg { width: 20px; height: 20px; fill: currentColor; }

/* The prompts an expert runs the Google Ads agent with (rooms.html). */
.rooms-prompts section + section { margin-top: 1.5rem; }
.rooms-prompts h3 { margin: 0 0 .5rem; text-transform: uppercase; letter-spacing: .04em; }
.rooms-prompt { padding: .75rem 0; border-top: 1px solid var(--border); }
.rooms-prompts section > .rooms-prompt:first-of-type { border-top: 0; }
.rooms-prompt-body {
  margin: .35rem 0 .6rem;
  white-space: pre-wrap;
  max-height: 7.5em;
  overflow: hidden;
  /* Long prompts are read in the box once they are picked, not here. */
  mask-image: linear-gradient(to bottom, #000 5.5em, transparent);
}

/* The Google Ads accounts screen (rooms.html). */
.rooms-check { display: flex; align-items: center; gap: .5rem; margin: .4rem 0; }
.rooms-check input { width: auto; margin: 0; }
/* Its second half is a section of its own, not a continuation of the form above. */
#gadsDialog h3 { margin: 1.5rem 0 .25rem; }
#gadsDialog h3:first-of-type { margin-top: .75rem; }
