/* ------------------------------------------------------------------
   --ink IS TWO DIFFERENT THINGS AND THIS FILE IS LOADED BY BOTH.

   tl-system.css defines it as a bare RGB triplet, `--ink: 246,243,236`, for
   use inside rgb() and rgba(). crm.css and shell.css define it as a colour,
   `--ink: #f3f2f0`. This stylesheet is loaded by every site, so a rule here
   that asks for --ink first and --text only as a fallback resolved to a
   colour on Shell and to the string "246,243,236" everywhere else, which
   is invalid.

   It was invisible for as long as it only touched `color`, because an
   invalid colour makes the property inherit and the inherited value is the
   page's text colour, which is what was wanted anyway. It became visible
   the moment one of them set a `background`: background does not inherit,
   so the currency toggle's chosen option lost its fill and both sides of a
   two-way control looked the same.

   The order is therefore always --text first with --ink as the fallback:
   --text where it exists, and --ink only on Shell and CRM, where it is a
   colour. Twenty three rules were the wrong way round on 29 September, and
   verify/checks/components.js now holds the order.
   ------------------------------------------------------------------ */
/* ==========================================================================
   Turtle Labs components.

   One definition per thing, used by all seven sites. A surface supplies
   colour and nothing else.

   This exists because the same handful of components had been written eight
   and nine times over. A text input was styled independently as `form input`,
   `.referform input`, `.sheet-form input`, `.audit-form input`, `.reaudit
   input`, `.field input`, `.signin-form input` and `.inline-form input`; the
   selectable choice card as `.pf-opt`, `.q-scale`, `.q-opts` and `.g-opt`.
   Every correction then had to be made eight times, and the ones that were
   missed are what a design review keeps finding.

   The rule for adding to this file: a component goes here the second time it
   is needed. The rule for using it: a site may set tokens, and may not
   redefine padding, radius, border or type. checks/components.js enforces
   the second half.

   Tokens a surface is expected to provide, all with fallbacks here so a
   component still renders if one is missing:

     --bg --bg2 --surface --text --muted --faint --line --line2
     --cta --cta-ink --cta-text --accent-soft
     --r-sm --r-md --r-lg --r-pill

   The accent is the one that bites. Three surfaces name it --cta, three name
   it --accent, and tl-system named it --green and nothing else, so every
   filled component on the group, Useful Store and BrandOS surfaces resolved
   its ink to the literal #fff and rendered white text on a white card in the
   light theme. Each surface now declares --cta and --cta-ink, and the
   fallbacks below chain through all three names so a new surface that forgets
   is merely wrong rather than invisible. checks/tokens.js asserts the
   declaration.

   ========================================================================== */

/* ---- prose keeps a measure, on every site --------------------------------

   The rule lived in tl-system.css, and Shell and WebOS do not load that file,
   so their paragraphs had no measure at all: 120 and 143 characters a line on
   pages that are otherwise careful. This file is the one thing all eight
   sites load, which makes it the only place a rule like this can be true
   everywhere.

   :where() so it carries no specificity. Anything that has deliberately set
   its own width still wins, and this only reaches the paragraphs nobody had
   thought about, which are the ones that were wrong.
   ------------------------------------------------------------------------ */
:where(p, li, dd, blockquote) { max-width: var(--measure, 68ch); }

/* ---- rhythm --------------------------------------------------------------
   Two primitives instead of a margin on every element. Stacks own the space
   between their children, so a component dropped into one is spaced without
   knowing where it is.
   ------------------------------------------------------------------------ */
.tl-stack        { display: flex; flex-direction: column; gap: var(--tl-gap, 18px); }
.tl-stack-sm     { --tl-gap: 10px; }
.tl-stack-lg     { --tl-gap: 28px; }
.tl-row          { display: flex; flex-wrap: wrap; gap: var(--tl-gap, 14px); align-items: center; }
.tl-row-tight    { --tl-gap: 8px; }
/* A heading in a row is one of the row's items, not a paragraph: its bottom
   margin pushed the text up off the icon's centre line. Only direct children
   of a card had the margin cleared, and these sit inside .tl-row. */
.tl-row > :is(h2, h3, h4) { margin: 0; }

/* ---- section heading -----------------------------------------------------
   Eyebrow, heading, lede. Written out per section across four sites, with the
   gaps between the three parts different every time.
   ------------------------------------------------------------------------ */
.tl-head { display: flex; flex-direction: column; gap: 12px; margin-bottom: 32px; }
.tl-head .tl-eyebrow { margin: 0; }
.tl-head h2, .tl-head h3 { margin: 0; }
.tl-head p { margin: 0; color: var(--muted); font-size: clamp(15.5px, 1.6vw, 18px);
             line-height: 1.6; max-width: 62ch; }
.tl-head.is-centred { align-items: center; text-align: center; }
.tl-head.is-centred p { margin-inline: auto; }

.tl-eyebrow { font-size: 11.5px; letter-spacing: .16em; text-transform: uppercase;
              font-weight: 800; color: var(--cta-text, var(--cta, var(--accent, var(--muted))));
              display: inline-flex; align-items: center; gap: 10px; }
.tl-eyebrow::before { content: ''; width: 22px; height: 2px; border-radius: 2px;
                      background: currentColor; flex: none; }

/* ---- surfaces ------------------------------------------------------------
   One card. Variants change colour and weight, never the geometry, which is
   why a corner or a padding can be corrected in one place.
   ------------------------------------------------------------------------ */
.tl-card {
  background: var(--surface, var(--bg2));
  border: 1px solid var(--line2, var(--line));
  border-radius: var(--r-lg, 18px);
  padding: clamp(20px, 2.2vw, 30px);
  display: flex; flex-direction: column; gap: 14px;
}
.tl-card.is-quiet   { background: transparent; }
.tl-card.is-raised  { background: var(--bg2, var(--surface)); }
.tl-card.is-accent  { border-color: var(--cta, var(--accent, var(--green))); }
/* The one a reader is meant to take: filled, not outlined. */
.tl-card.is-chosen  { background: var(--cta, var(--accent, var(--green))); border-color: var(--cta, var(--accent, var(--green)));
                      color: var(--cta-ink, var(--accent-ink, var(--green-ink, #fff))); position: relative; }
/* Opacity, on text, on a filled card, is how a deliberate ink becomes an
   accidental one: .82 of white on the WebOS blue is 4.27 and fails. A
   secondary voice on a filled card is a different colour, not the same
   colour worn thin, and here that colour is the card's own ink. */
.tl-card.is-chosen :is(p, li, span):not(.tl-badge) { color: inherit; opacity: 1; }
.tl-card.is-chosen :is(h2, h3, h4, strong) { color: inherit; opacity: 1; }
.tl-card > :is(h2, h3, h4) { margin: 0; }
.tl-card > p:not([class]) { margin: 0; color: var(--muted); line-height: 1.6; }
.tl-card > p { margin: 0; line-height: 1.6; }

/* Equal-width card grids, one definition. */
.tl-cards { display: grid; gap: clamp(12px, 1.4vw, 20px);
            grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--tl-col, 260px)), 1fr)); }
.tl-cards.is-wide   { --tl-col: 320px; }
.tl-cards.is-narrow { --tl-col: 200px; }

/* ---- badge ---------------------------------------------------------------
   The pill. Takes its colour from --tone, so one rule covers a status, a
   category and a price label.
   ------------------------------------------------------------------------ */
.tl-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: var(--r-pill, 999px);
  font-size: 11.5px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  background: color-mix(in srgb, var(--tone, var(--cta, #888)) 14%, transparent);
  /* --cta-text before --cta. A badge is eleven-point uppercase text on a
     14% wash of its own hue, so it needs the ink value of that hue and
     not the fill: on the light theme the BrandOS #00884B is the button
     green and reads 3.39 against the card it sits on. Standing rule 4,
     reaching a label through a fallback chain that named the fill. */
  color: var(--tone-ink, var(--tone, var(--cta-text, var(--cta, var(--accent, var(--green))))));
  border: 1px solid color-mix(in srgb, var(--tone, var(--cta, #888)) 34%, transparent);
  white-space: nowrap;
}
.tl-badge.is-solid { background: var(--tone, var(--cta, var(--accent, var(--green)))); color: var(--cta-ink, var(--accent-ink, var(--green-ink, #fff)));
                     border-color: transparent; }
/* Sitting on the lip of a card, which is where "most chosen" goes. */
.tl-badge.is-pinned { position: absolute; top: -11px; left: 22px; }
.tl-card:has(> .tl-badge.is-pinned) { padding-top: clamp(26px, 2.6vw, 34px); }
@supports not selector(:has(*)) {
  .tl-card > .tl-badge.is-pinned + * { margin-top: 8px; }
}
/* A badge pinned to a filled card takes the card's ink, not the spectrum hue
   its position happens to give it: a crimson tag on a green card reads as a
   warning rather than as a recommendation. */
.tl-card.is-chosen > .tl-badge.is-solid {
  background: var(--cta-ink, var(--accent-ink, #fff));
  color: var(--cta, var(--accent, var(--green)));
}
/* A pinned pill hangs half off the card, so half of it is over the PAGE and
   half over the card, and it needs an edge on the half that has left.
   Measured 26 September: on WebOS dark the pill was 1.05:1 against the page,
   and once that was fixed the light theme showed the same fault the other way
   round, a white pill at 1.04:1 on a near-white page. Reported as "the labels
   read as not placed right", which is what an invisible outline looks like.
   The ring is the card's own fill, so it disappears against the card and
   draws the silhouette exactly where the pill leaves it. */
.tl-card.is-chosen > .tl-badge.is-pinned {
  box-shadow: 0 0 0 2px var(--cta, var(--accent, var(--green)));
}

/* And a button on one. The secondary weight is accent-tinted, which is
   accent-on-accent once the card is filled with the accent: measured at 1:1,
   which is not low contrast, it is no contrast. On a filled card the button
   inverts, the way the plan cards already do it by hand. */
.tl-card.is-chosen .btn,
.tl-card.is-chosen .btn.primary {
  background: var(--cta-ink, var(--accent-ink, #fff));
  border-color: var(--cta-ink, var(--accent-ink, #fff));
  color: var(--cta, var(--accent, var(--green)));
}
@media (hover: hover) {
  .tl-card.is-chosen .btn:hover,
  .tl-card.is-chosen .btn.primary:hover {
    filter: brightness(1.08);
    background: var(--cta-ink, var(--accent-ink, #fff));
    color: var(--cta, var(--accent, var(--green)));
  }
}

/* ---- form ----------------------------------------------------------------
   A field is a label and a control. The label is always present and always
   above: a placeholder is not a label, it disappears the moment somebody
   starts typing, and that is when they most need to know what the box is.
   ------------------------------------------------------------------------ */
.tl-form { display: flex; flex-direction: column; gap: 16px; }

/* ---- ONE FIELD AND ITS BUTTON --------------------------------------------
   Reported 28 September: "button and email input field is too wide,
   unnecessary, keep them in one line make it half each."

   He is right and the reason is worth writing down. A form inherits the
   width of the section it sits in, and a section here is the full measure of
   the page. That is correct for prose and wrong for a control: an input
   thirteen hundred pixels wide is asking for an email address in a box that
   would hold a paragraph, so it reads as a much bigger commitment than
   typing one word. The field is sized to what goes in it.

   The consent line and the note below it keep the full width of the form,
   because those are prose and prose wants the measure. */
.tl-form.is-inline { display: grid; max-width: 620px;
                     grid-template-columns: minmax(0, 1fr) auto;
                     gap: 14px 12px; align-items: end; }
.tl-form.is-inline > .field,
.tl-form.is-inline > .tl-field { grid-column: 1; margin: 0; }
.tl-form.is-inline > button { grid-column: 2; grid-row: 1; white-space: nowrap; }
.tl-form.is-inline > .agree,
.tl-form.is-inline > p { grid-column: 1 / -1; }
@media (max-width: 620px) {
  /* One below the other, and still not the width of the page. */
  .tl-form.is-inline { grid-template-columns: minmax(0, 1fr); }
  .tl-form.is-inline > button { grid-column: 1; grid-row: auto; }
}
/* Two fields to a row on anything wider than a phone: a six-field form in one
   column reads as a much longer form than it is. */
.tl-form-grid { display: grid; gap: 14px 16px; grid-template-columns: 1fr; }
@media (min-width: 620px) { .tl-form-grid { grid-template-columns: 1fr 1fr; } }
.tl-form-grid .tl-field.is-full { grid-column: 1 / -1; }

.tl-field { display: flex; flex-direction: column; gap: 6px; min-width: 0; }
.tl-field > label, .tl-label {
  font-size: 12.5px; font-weight: 700; color: var(--text);
  display: flex; align-items: baseline; gap: 7px;
}
.tl-label .tl-opt { font-weight: 400; color: var(--faint); font-size: 11.5px; }

.tl-field :is(input[type=text], input[type=email], input[type=tel], input[type=url],
              input[type=number], input[type=date], input:not([type]), textarea, select),
.tl-input {
  width: 100%; min-width: 0; box-sizing: border-box;
  padding: 11px 14px; font: inherit; font-size: 15px; line-height: 1.4;
  color: var(--text); background: var(--bg, transparent);
  border: 1px solid var(--line, rgba(128,128,128,.3));
  /* --r-md, not --r-sm. Standing rule 7 says a control's corner is --r-md and
     nothing else, and this said --r-sm, so the same control rendered two
     different corners depending on whether it happened to sit inside a form:
     10px on its own, 16px when the generic control rule also matched. The
     help panel's search box is the one outside a form, so it was the one that
     looked wrong. Found 21 September. */
  border-radius: var(--r-md, 14px);
  transition: border-color .18s ease, box-shadow .18s ease;
}
.tl-field textarea { resize: vertical; min-height: 92px; }
/* The arrow was here and was five pixels of filled triangle, which at a
   normal viewing distance is not an arrow, it is a speck. Reported 24
   September as the control having no arrow at all, which is what it amounts
   to. Drawn below as a 13px stroked chevron instead. */
.tl-field select { appearance: none; -webkit-appearance: none; padding-right: 40px;
  cursor: pointer; }
.tl-field :is(input, textarea, select):focus-visible {
  outline: none; border-color: var(--cta, var(--accent, var(--green)));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cta, var(--accent, var(--green))) 22%, transparent);
}
.tl-field :is(input, textarea, select)[aria-invalid="true"] { border-color: var(--invalid, #E4553F); }
.tl-field .tl-note { font-size: 14.5px; color: var(--faint); }
.tl-field .tl-note.is-error { color: var(--invalid, #E4553F); }

/* A checkbox with its sentence, which is always a consent line here. */
.tl-check { display: flex; gap: 10px; align-items: flex-start;
            font-size: 14.5px; line-height: 1.55; color: var(--muted); }
.tl-check input { margin: 2px 0 0; flex: none; accent-color: var(--cta, var(--accent, var(--green))); }

/* ---- button --------------------------------------------------------------
   The second full copy of a component, found by the gallery rather than by
   reading: tl-system.css and webos.css each defined the whole .btn family,
   with different padding (28px against 20px) and opposite secondary styling,
   and the tracker loaded neither, so its buttons were the browser's.

   The secondary weight is accent-tinted rather than a hairline outline. Most
   buttons on these sites are the secondary one, and as a transparent pill it
   read as a disabled control next to body text.
   ------------------------------------------------------------------------ */
/* Every chain here ends in a literal, added 27 September.
   .btn reached for --cta, then --accent, then --green, and defined none of
   them: this file is the component layer and the tokens live in the site
   stylesheets. A page that loads tl-components.css and nothing else therefore
   got `background: <invalid>` (transparent) and `color: <#fff>`, because the
   ink chain alone had a literal at the end. A white label on a transparent
   ground, which on the panel's white card was an invisible Sign in button:
   the control was there, 48px tall and clickable, and could not be seen.
   Reported 27 September as "/studio is completely white".
   A literal at the end of every chain cannot be overridden by accident, since
   any site that defines --cta still wins the var() lookup first. */
.btn, .tl-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 14px clamp(20px, 2vw, 28px);
  font-family: inherit; font-weight: 700; font-size: 15px; line-height: 1.2;
  text-decoration: none; cursor: pointer; border-radius: var(--r-pill, 999px);
  border: 1px solid var(--cta, var(--accent, var(--green, #34C759)));
  color: var(--cta-text, var(--cta, var(--accent, var(--green, #34C759))));
  background: var(--accent-soft, color-mix(in srgb, var(--cta, var(--accent, var(--green, #34C759))) 10%, transparent));
  transition: transform .3s cubic-bezier(.22,.61,.36,1), background-color .25s ease,
              border-color .25s ease, color .25s ease, box-shadow .3s ease;
}
@media (hover: hover) {
  .btn:hover, .tl-btn:hover {
    background: var(--cta, var(--accent, var(--green, #34C759)));
    color: var(--cta-ink, var(--accent-ink, var(--green-ink, #fff)));
    border-color: var(--cta, var(--accent, var(--green, #34C759)));
    transform: translateY(-2px);
  }
}
.btn.primary, .tl-btn.primary {
  background: var(--cta, var(--accent, var(--green, #34C759)));
  border-color: var(--cta, var(--accent, var(--green, #34C759)));
  color: var(--cta-ink, var(--accent-ink, var(--green-ink, #fff)));
}
@media (hover: hover) {
  .btn.primary:hover, .tl-btn.primary:hover {
    filter: brightness(1.08); transform: translateY(-2px);
    color: var(--cta-ink, var(--accent-ink, var(--green-ink, #fff)));
    box-shadow: 0 10px 24px rgba(0,0,0,.18);
  }
}
/* A third weight, for the times a button genuinely should recede: a back
   link, a cancel. Opt in by name rather than by being the default.
   `.ghost` is the name the CRM and Find Your Passion had already given it. */
.btn.quiet, .btn.ghost, .tl-btn.quiet {
  background: transparent; border-color: var(--line); color: var(--text);
}
@media (hover: hover) {
  .btn.quiet:hover, .btn.ghost:hover, .tl-btn.quiet:hover {
    background: var(--bg2, var(--surface)); border-color: var(--text); color: var(--text);
  }
}
.btn:disabled, .tl-btn:disabled { opacity: .45; cursor: default; transform: none; }
.btn.wide, .tl-btn.wide { width: 100%; }
.btn:focus-visible, .tl-btn:focus-visible {
  outline: 2px solid var(--cta, var(--accent, var(--green, #34C759))); outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  .btn, .btn:hover, .btn.primary:hover, .tl-btn, .tl-btn:hover { transform: none; }
}

/* ---- choice --------------------------------------------------------------
   The selectable card: a radio drawn as a target big enough to hit. Written
   four times over as .pf-opt, .q-scale, .q-opts and .g-opt, each with its own
   hit area and its own idea of what "selected" looks like.
   ------------------------------------------------------------------------ */
.tl-choices { display: grid; gap: 10px;
              grid-template-columns: repeat(auto-fit, minmax(min(100%, 230px), 1fr)); }
.tl-choices.is-stacked { grid-template-columns: 1fr; }

.tl-choice {
  position: relative; display: flex; flex-direction: column; gap: 3px;
  padding: 15px 44px 15px 17px; cursor: pointer;
  background: var(--surface, var(--bg2)); color: var(--text);
  border: 1px solid var(--line, rgba(128,128,128,.3));
  border-radius: var(--r-md, 14px);
  transition: border-color .18s ease, background-color .18s ease, transform .18s ease;
}
.tl-choice input { position: absolute; opacity: 0; width: 0; height: 0; }
.tl-choice .tl-choice-t { font-weight: 700; font-size: 15px; }
.tl-choice .tl-choice-d { font-size: 14.5px; color: var(--muted); line-height: 1.5; }
/* The ring, always drawn, so the control reads as choosable before it is hot. */
.tl-choice::after {
  content: ''; position: absolute; top: 50%; right: 16px; transform: translateY(-50%);
  width: 18px; height: 18px; border-radius: 50%;
  border: 2px solid var(--line, rgba(128,128,128,.4));
  transition: border-color .18s ease, box-shadow .18s ease;
}
@media (hover: hover) {
  .tl-choice:hover { border-color: var(--cta, var(--accent, var(--green))); transform: translateY(-1px); }
  .tl-choice:hover::after { border-color: var(--cta, var(--accent, var(--green))); }
}
.tl-choice:has(input:checked) {
  border-color: var(--cta, var(--accent, var(--green)));
  background: var(--accent-soft, color-mix(in srgb, var(--cta, #888) 10%, transparent));
}
.tl-choice:has(input:checked)::after {
  border-color: var(--cta, var(--accent, var(--green)));
  box-shadow: inset 0 0 0 4px var(--cta, var(--accent, var(--green)));
}
.tl-choice:has(input:focus-visible) { outline: 2px solid var(--cta, var(--accent, var(--green))); outline-offset: 2px; }
/* Without :has, the selected state still has to be visible. */
@supports not selector(:has(*)) {
  .tl-choice input:checked ~ .tl-choice-t { text-decoration: underline; }
}
@media (prefers-reduced-motion: reduce) { .tl-choice, .tl-choice:hover { transform: none; } }

/* ---- progress ------------------------------------------------------------
   A stepped form that does not say how long it is asks somebody to commit to
   an unknown number of questions.
   ------------------------------------------------------------------------ */
.tl-progress { display: flex; flex-direction: column; gap: 8px; }
.tl-progress-bar { height: 5px; border-radius: var(--r-pill, 999px);
                   background: var(--line2, rgba(128,128,128,.18)); overflow: hidden; }
.tl-progress-bar > span { display: block; height: 100%; border-radius: inherit;
                          background: var(--cta, var(--accent, var(--green)));
                          transition: width .35s cubic-bezier(.22,.61,.36,1); }
.tl-progress-text { font-size: 12px; font-weight: 700; color: var(--faint);
                    letter-spacing: .04em; }

/* ---- figures -------------------------------------------------------------
   Numbers presented as numbers. A result read as a sentence is a result
   nobody remembers.
   ------------------------------------------------------------------------ */
.tl-figures { display: grid; gap: clamp(12px, 1.4vw, 18px);
              grid-template-columns: repeat(auto-fit, minmax(min(100%, 180px), 1fr)); }
.tl-figure { display: flex; flex-direction: column; gap: 4px;
             padding: clamp(16px, 1.8vw, 22px);
             background: var(--surface, var(--bg2));
             border: 1px solid var(--line2, var(--line));
             border-radius: var(--r-md, 14px);
             border-top: 3px solid var(--tone, var(--cta, var(--accent, var(--green)))); }
.tl-figure-n { font-family: 'Outfit', system-ui, sans-serif; font-weight: 900;
               font-size: clamp(26px, 3vw, 36px); line-height: 1;
               color: var(--tone-ink, var(--tone, var(--cta, var(--accent, var(--green))))); letter-spacing: -.02em; }
.tl-figure-l { font-size: 14.5px; font-weight: 700; color: var(--text); }
.tl-figure-s { font-size: 14.5px; color: var(--faint); line-height: 1.5; }

/* ---- disclosure ----------------------------------------------------------
   Used by the Commons and wanted in several other places, so it lives here.
   ------------------------------------------------------------------------ */
.tl-fold { border-top: 1px solid var(--line2, var(--line)); }
.tl-fold:last-of-type { border-bottom: 1px solid var(--line2, var(--line)); }
.tl-fold > summary { cursor: pointer; padding: 15px 2px; font-weight: 700; font-size: 15.5px;
                     list-style: none; display: flex; align-items: center; gap: 10px; }
.tl-fold > summary::-webkit-details-marker { display: none; }
.tl-fold > summary::before { content: '+'; width: 18px; flex: none; font-weight: 400;
                             font-size: 18px; color: var(--muted); }
.tl-fold[open] > summary::before { content: '\2212'; }
.tl-fold > summary:hover { color: var(--cta-text, var(--cta)); }
.tl-fold-body { padding: 0 2px 20px 28px; }
.tl-fold-body > * { margin-top: 0; }
@media (max-width: 620px) { .tl-fold-body { padding-left: 4px; } }

/* ---- side note -----------------------------------------------------------
   Guidance that belongs beside the thing it is about rather than under it.
   ------------------------------------------------------------------------ */
.tl-aside { display: grid; gap: clamp(18px, 2.4vw, 34px); align-items: start;
            grid-template-columns: 1fr; }
@media (min-width: 900px) { .tl-aside { grid-template-columns: 1.35fr .85fr; } }
.tl-aside-note { display: flex; flex-direction: column; gap: 12px;
                 padding: clamp(18px, 2vw, 24px);
                 background: var(--bg2, var(--surface));
                 border: 1px solid var(--line2, var(--line));
                 border-radius: var(--r-md, 14px); }
.tl-aside-note h3 { margin: 0; font-size: 15px; }
.tl-aside-note ul { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 9px; }
.tl-aside-note li { font-size: 14.5px; line-height: 1.55; color: var(--muted); }
.tl-aside-note li::marker { color: var(--cta, var(--accent, var(--green))); }

/* ---- names the components answer to --------------------------------------
   Four surfaces had already invented a field: `.field` on BrandOS and Shell,
   `.referform` and `.sheet-form` on Find Your Passion. Rather than rewrite
   every form's markup in one go, those names point at the component here and
   their local rules are deleted, so there is one definition either way.

   This list is expected to shrink. A form moves to .tl-field markup when it
   is next touched, and its name comes out of here.
   ------------------------------------------------------------------------ */
.field, .referform label, .sheet-form label, .day-form label {
  display: flex; flex-direction: column; gap: 6px; min-width: 0; margin: 0;
}
.field > span, .referform label > span, .sheet-form label > span {
  font-size: 12.5px; font-weight: 700; color: var(--text);
}
.field :is(input, textarea, select),
.referform :is(input, textarea, select),
.sheet-form :is(input, textarea, select),
.day-form :is(input, textarea) {
  width: 100%; min-width: 0; box-sizing: border-box;
  padding: 11px 14px; font: inherit; font-size: 15px; line-height: 1.4;
  color: var(--text); background: var(--bg, transparent);
  border: 1px solid var(--line, rgba(128,128,128,.3));
  border-radius: var(--r-sm, 10px);
  transition: border-color .18s ease, box-shadow .18s ease;
}
.field textarea, .referform textarea, .sheet-form textarea, .day-form textarea {
  resize: vertical; min-height: 92px;
}

/* ---- a select has to look like a select ----------------------------------
   Reported 24 September against the Studio contact form: "Choose one should
   have a dropdown arrow to show that it is a dropdown."

   The native arrow is drawn by the platform and is the first thing
   appearance:none removes, which is why the control read as an empty text
   box with a word in it. tl-system.css draws one for its own forms; the
   .field component, which is what every server-rendered form on the estate
   actually uses, drew none. So it is here, once, for all of them.

   A stroked chevron rather than a filled triangle, because the triangle is
   what a 2006 form control looks like, and drawn with a mask so it takes
   currentColor and is therefore right in both themes by construction rather
   than by a second declaration somebody has to remember. */
.field:has(> select), .tl-field:has(> select),
.referform :is(label):has(> select), .sheet-form :is(label):has(> select) {
  position: relative;
}
.field:has(> select) select { appearance: none; -webkit-appearance: none; padding-right: 40px; cursor: pointer; }
.field:has(> select)::after, .tl-field:has(> select)::after {
  content: ''; position: absolute; right: 15px; bottom: 17px;
  width: 13px; height: 13px; pointer-events: none;
  background-color: var(--muted, currentColor);
  -webkit-mask: var(--chev) center / 13px 13px no-repeat;
          mask: var(--chev) center / 13px 13px no-repeat;
}
:root { --chev: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 9 7 7 7-7'/%3E%3C/svg%3E"); }

/* Without :has, the chevron goes on the control itself. Two gradients rather
   than a mask, because the fallback should not need a second modern feature. */
@supports not selector(:has(*)) {
  .field select, .tl-field select {
    appearance: none; -webkit-appearance: none; padding-right: 40px; cursor: pointer;
    background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                      linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: calc(100% - 21px) 54%, calc(100% - 15px) 54%;
    background-size: 6px 6px, 6px 6px; background-repeat: no-repeat;
  }
}
.field :is(input, textarea, select):focus-visible,
.referform :is(input, textarea, select):focus-visible,
.sheet-form :is(input, textarea, select):focus-visible,
.day-form :is(input, textarea):focus-visible {
  outline: none; border-color: var(--cta, var(--accent, var(--green)));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cta, var(--accent, var(--green))) 22%, transparent);
}
.referform, .sheet-form { display: flex; flex-direction: column; gap: 16px; }
.referform .row { display: grid; gap: 14px 16px; grid-template-columns: 1fr; }
@media (min-width: 620px) { .referform .row { grid-template-columns: 1fr 1fr; } }

/* ==========================================================================
   Infographic components.

   Every design review so far has produced the same sentence about a different
   page: text-heavy, plain, dull, needs icons, needs colour, make it an
   infographic. That kept recurring because each page was composed by hand, so
   richness depended on whoever wrote it remembering to add it.

   These are the pieces that sentence is asking for. lib/page-kit.js renders
   them from data, so a section built through the kit cannot come out plain:
   it has a slot for the number, the icon and the hue whether or not anyone
   thought about them.
   ========================================================================== */

/* ---- the spectrum ---------------------------------------------------------
   Hand a list of anything this class and its children take the brand spectrum
   in order, looping after nine. Every component below reads --tone, so one
   class colours a whole section without a per-item decision.

   The ink tones, not the display tones: these land on text and on small
   marks, which WCAG asks 4.5:1 of. No literal fallbacks, deliberately: the
   first version of this carried the dark-theme values as fallbacks, and on a
   surface that had not declared the palette every one of them was used on a
   light page at 3.4:1. A missing token should be visibly missing, not quietly
   wrong. tl-system.css and webos.css both declare the set, per theme, which
   between them covers every surface.
   ------------------------------------------------------------------------ */
.tl-spectrum > *:nth-child(9n+1) { --tone: var(--tl-crimson-ink); }
.tl-spectrum > *:nth-child(9n+2) { --tone: var(--tl-red-ink); }
.tl-spectrum > *:nth-child(9n+3) { --tone: var(--tl-amber-ink); }
.tl-spectrum > *:nth-child(9n+4) { --tone: var(--tl-green-ink); }
.tl-spectrum > *:nth-child(9n+5) { --tone: var(--tl-teal-ink); }
.tl-spectrum > *:nth-child(9n+6) { --tone: var(--tl-cyan-ink); }
.tl-spectrum > *:nth-child(9n+7) { --tone: var(--tl-blue-ink); }
.tl-spectrum > *:nth-child(9n+8) { --tone: var(--tl-indigo-ink); }
.tl-spectrum > *:nth-child(9n+9) { --tone: var(--tl-violet-ink); }

/* ---- edged card -----------------------------------------------------------
   A card with its hue down the leading edge. Asked for by name: "gradient
   borders on left of the container". The edge is a gradient from the hue to
   transparent rather than a flat rule, so a column of them reads as a set
   rather than as a row of tabs.
   ------------------------------------------------------------------------ */
.tl-card.is-edged { position: relative; overflow: hidden; padding-left: clamp(20px, 2.2vw, 30px); }
.tl-card.is-edged::before {
  content: ''; position: absolute; inset: 0 auto 0 0; width: 4px;
  background: linear-gradient(180deg,
    var(--tone, var(--cta, var(--accent))) 0%,
    color-mix(in srgb, var(--tone, var(--cta, var(--accent))) 30%, transparent) 100%);
}
/* Topped instead, for a grid where a left edge would read as a margin. */
.tl-card.is-topped { position: relative; overflow: hidden; }
.tl-card.is-topped::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg,
    var(--tone, var(--cta, var(--accent))) 0%,
    color-mix(in srgb, var(--tone, var(--cta, var(--accent))) 25%, transparent) 100%);
}

/* ---- icon chip ------------------------------------------------------------
   A square plate carrying an emoji, an SVG or two letters, tinted with the
   item's hue. Text-based on purpose: an icon font is another network request
   and another thing to go wrong, and two letters read at any size.
   ------------------------------------------------------------------------ */
.tl-icon {
  display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: 38px; height: 38px; border-radius: var(--r-sm, 10px);
  font-size: 17px; line-height: 1; font-weight: 800; letter-spacing: .01em;
  background: color-mix(in srgb, var(--tone, var(--cta, var(--accent))) 15%, transparent);
  color: var(--tone, var(--cta, var(--accent)));
}
.tl-icon.is-sm { width: 30px; height: 30px; font-size: 13.5px; border-radius: var(--r-xs, 8px); }
.tl-icon.is-lg { width: 48px; height: 48px; font-size: 22px; border-radius: var(--r-md, 14px); }
.tl-icon.is-solid { background: var(--tone, var(--cta, var(--accent)));
                    color: var(--cta-ink, var(--accent-ink, var(--green-ink, #fff))); }
.tl-icon.is-round { border-radius: var(--r-pill, 999px); }
/* An emoji must not be recoloured, and must not inherit the plate's weight. */
.tl-icon .emo { font-weight: 400; font-style: normal;
                font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif; }

/* ---- steps ----------------------------------------------------------------
   A numbered sequence: the six fortnights, the five days, the path. The
   number is the ornament, in the item's hue, and the rule between items is
   what makes it read as an order rather than as a list of cards.
   ------------------------------------------------------------------------ */
.tl-steps { display: grid; gap: clamp(12px, 1.4vw, 18px);
            grid-template-columns: repeat(auto-fit, minmax(var(--tl-col, 250px), 1fr)); }
.tl-steps.is-stacked { grid-template-columns: 1fr; gap: 0; }
.tl-step { position: relative; display: flex; flex-direction: column; gap: 8px;
           padding: clamp(18px, 2vw, 24px);
           background: var(--surface, var(--bg2)); border: 1px solid var(--line);
           border-radius: var(--r-md, 14px); }
.tl-steps.is-stacked .tl-step { background: transparent; border: 0;
  border-top: 1px solid var(--line); border-radius: 0;
  display: grid; grid-template-columns: 56px minmax(0, 1fr); gap: 0 16px; align-items: start; }
.tl-steps.is-stacked .tl-step:last-child { border-bottom: 1px solid var(--line); }
.tl-step-n { font-family: 'Outfit', system-ui, sans-serif; font-weight: 900;
             font-size: clamp(22px, 2.4vw, 30px); line-height: 1;
             color: var(--tone, var(--cta, var(--accent))); }
/* Two children and two columns: the number, and everything else in one body.
   The number column is a fixed width, so every row's text starts at the same
   place. It used to be auto, sized to each row's own number, so headings
   started at 59, 63 and 64 pixels down the same list; and before that the
   text was loose children of the grid, so a fourth child or a spanning rule
   put a heading underneath the number instead of beside it. */
.tl-steps.is-stacked .tl-step-n { font-size: clamp(24px, 3vw, 36px); }
.tl-step-body { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.tl-steps.is-stacked .tl-step-body { gap: 4px; }

/* A price list (the products page). One row per item: the name and what it is
   on the left, the price on the right, and a retainer note across the bottom.
   Stacked one under another, as plain stacked steps are, each app took four
   lines and left the right half of the row empty. */
/* The number sits with the name it belongs to, not in the middle of the row:
   centred, it drifted below the name on any item with a note. A narrower
   column and a matched line box put the two on one line. */
.tl-steps.is-priced .tl-step { padding-block: 18px; align-items: start;
  grid-template-columns: 40px minmax(0, 1fr); gap: 0 14px; }
.tl-steps.is-priced .tl-step-n { font-size: 26px; line-height: 1.32; }
.tl-steps.is-priced .tl-step-body { display: grid; grid-template-columns: minmax(0, 1fr) auto;
  column-gap: 24px; row-gap: 2px; align-items: baseline; }
.tl-steps.is-priced .tl-step-body > h3 { grid-column: 1; grid-row: 1; font-size: 17px; }
.tl-steps.is-priced .tl-step-body > p { grid-column: 1; }
.tl-steps.is-priced .tl-step-when { grid-column: 2; grid-row: 1 / span 2; align-self: center;
  justify-self: end; white-space: nowrap; font-size: 15px; letter-spacing: 0; text-transform: none;
  font-weight: 700; color: var(--text, var(--ink)); }
/* The retainer note reads as another line of the same item, so it starts where
   the name and the description start. The shared style indents it behind a
   grey rule, which at this size looked like a layout fault rather than an
   aside, and cost it a line of width. */
.tl-steps.is-priced .tl-step-body > .tl-step-note { grid-column: 1 / -1; margin-top: 6px;
  border-left: 0; padding-left: 0; color: var(--muted); }
@media (max-width: 560px) {
  .tl-steps.is-priced .tl-step-when { grid-row: 1; align-self: baseline; font-size: 14px; }
}
.tl-step-when { font-size: 11.5px; font-weight: 800; letter-spacing: .1em;
                text-transform: uppercase; color: var(--faint); }
.tl-step h3, .tl-step h4 { margin: 0; font-size: 16px; }
.tl-step p { margin: 0; font-size: 15.5px; line-height: 1.6; color: var(--muted); }
.tl-step .tl-step-out { font-size: 14.5px; line-height: 1.55; color: var(--muted);
                        padding-top: 8px; border-top: 1px dashed var(--line2, var(--line)); }
.tl-step .tl-step-out strong { color: var(--tone, var(--text)); }

/* A standing note on a step: why a per-month price repeats, on the four
   BrandOS apps that are retainers. Left rule rather than the dashed top rule
   of tl-step-out, because it is an aside about the step rather than its
   conclusion, and the two must not read as the same kind of thing. */
.tl-step .tl-step-note { font-size: 14.5px; line-height: 1.55; color: var(--muted);
                         border-left: 2px solid var(--line2, var(--line));
                         padding-left: 10px; }

/* A consent row: a checkbox, and the sentence it is consenting to.
   This lived in crm.css alone, so the same markup on BrandOS had no spacing
   and the checkbox sat against its own label. Shared markup, shared rule. */
.agree { display: flex; gap: 9px; align-items: flex-start; margin: 4px 0 18px;
  font-size: 14.5px; line-height: 1.5; color: var(--muted); }
.agree input { margin-top: 3px; flex: none; }
.agree a { color: var(--cta-text, var(--cta)); }

/* Room between the fields of a form, and before the button under them. */
.tl-form .field + .field { margin-top: 14px; }
.tl-form .field + .agree { margin-top: 16px; }
.tl-form .btn { margin-top: 4px; }

/* ---- who / what / why -----------------------------------------------------
   Three short answers instead of three paragraphs. The heading is the
   question, so the copy underneath does not have to restate it.
   ------------------------------------------------------------------------ */
.tl-qa { display: grid; gap: clamp(12px, 1.6vw, 22px);
         grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); }
.tl-qa-item { display: flex; flex-direction: column; gap: 9px; }
.tl-qa-item > h3 { margin: 0; font-size: 14.5px; letter-spacing: .1em;
                   text-transform: uppercase; color: var(--tone, var(--cta-text, var(--cta))); }
.tl-qa-item > p { margin: 0; font-size: 15.5px; line-height: 1.6; color: var(--text); }

/* ---- highlights -----------------------------------------------------------
   The band directly under a hero: what this is, in four or five marks, before
   anyone has to read a paragraph.
   ------------------------------------------------------------------------ */
.tl-highlights { display: grid; gap: clamp(10px, 1.2vw, 16px);
                 grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); }
.tl-highlight { display: flex; gap: 13px; align-items: flex-start;
                padding: clamp(14px, 1.5vw, 18px);
                border: 1px solid var(--line); border-radius: var(--r-md, 14px);
                background: color-mix(in srgb, var(--tone, var(--cta)) 6%, transparent); }
.tl-highlight-b { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.tl-highlight-t { font-size: 14.5px; font-weight: 700; line-height: 1.35; }
.tl-highlight-d { font-size: 14.5px; line-height: 1.5; color: var(--muted); }

/* ---- inclusions -----------------------------------------------------------
   What a package contains, as rows rather than prose. Three columns: the
   mark, what it is, and the one word for what it does.
   ------------------------------------------------------------------------ */
/* Pills: a centred, wrapping row of short names. Matches the sectors on the
   BrandOS home page, which were a vertical list on the website audit page. */
.tl-pills { list-style: none; margin: 0 auto; padding: 0; max-width: 900px;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }
.tl-pill { padding: 11px 22px; border-radius: 999px; background: var(--bg2, var(--surface));
  border: 1px solid var(--line); color: var(--text, var(--ink)); font-size: 15px; line-height: 1.3; }

.tl-includes { list-style: none; margin: 0; padding: 0;
               display: flex; flex-direction: column; }
.tl-include { display: grid; grid-template-columns: auto 1fr auto;
              align-items: center; gap: 12px; padding: 10px 0;
              border-bottom: 1px solid var(--line2, var(--line)); }
.tl-include:last-child { border-bottom: 0; }
.tl-include-n { font-size: 14.5px; font-weight: 700; min-width: 0; }
.tl-include-d { font-size: 14.5px; color: var(--muted); display: block; font-weight: 400; }
.tl-include-v { font-size: 11px; font-weight: 800; letter-spacing: .09em;
                text-transform: uppercase; color: var(--tone, var(--faint)); white-space: nowrap; }
.tl-includes { container-type: inline-size; }
@container (max-width: 330px) {
  .tl-include { grid-template-columns: auto 1fr; }
  .tl-include-v { grid-column: 2; }
}
@supports not (container-type: inline-size) {
  @media (max-width: 560px) {
    .tl-include { grid-template-columns: auto 1fr; }
    .tl-include-v { grid-column: 2; }
  }
}

/* A choice used as a scale point: five across a row, so the label is the
   whole target and the row reads as a scale rather than as five cards. */
.tl-choice.is-compact { padding: 13px 14px; text-align: center; align-items: center; }
.tl-choice.is-compact::after { display: none; }
.tl-choice.is-compact .tl-choice-t { font-size: 13.5px; font-weight: 600; }
.tl-choice.is-compact:has(input:checked) .tl-choice-t { font-weight: 800; }
.tl-choices.is-scale { grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); }

/* ---- phone ----------------------------------------------------------------
   A country code and a number, drawn as one control.

   The two sit in a single bordered box with the rule between them, because
   they are one answer: a select beside an input, each with its own border,
   reads as two unrelated questions.

   The code is a real <select> rather than a scripted combo box. It works with
   scripting off, it is type-ahead searchable on every platform, and it is the
   control a phone already knows how to present full screen.

   Every rule below carries the .tl-field prefix as well. The field reset
   further up is `.tl-field :is(input[type=text], ..., select)`, which is
   (0,2,1): a plain `.tl-phone > select` is (0,1,1) and loses to it whatever
   the source order, which is how the first version of this lost its divider
   and its chevron and rendered as one flat box.
   ------------------------------------------------------------------------ */
.tl-phone {
  display: grid; grid-template-columns: 7.4rem minmax(0, 1fr);
  align-items: stretch; min-width: 0;
  border: 1px solid var(--line, rgba(128,128,128,.3));
  border-radius: var(--r-sm, 10px);
  background: var(--bg, transparent);
  transition: border-color .18s ease, box-shadow .18s ease;
}
.tl-phone:focus-within {
  border-color: var(--cta, var(--accent, var(--green)));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--cta, var(--accent, var(--green))) 22%, transparent);
}

.tl-field .tl-phone > select, .tl-phone > select {
  width: 100%; min-width: 0; text-overflow: ellipsis;
  margin: 0; padding: 11px 26px 11px 13px;
  font: inherit; font-size: 14px; line-height: 1.4;
  color: var(--text); cursor: pointer;
  background-color: transparent;
  border: 0; border-right: 1px solid var(--line, rgba(128,128,128,.3));
  border-radius: 0; box-shadow: none; outline: none;
  appearance: none; -webkit-appearance: none;
  /* A drawn chevron rather than gradient triangles: at 5px those were
     invisible on both themes. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23888' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-position: calc(100% - 9px) 50%;
  background-size: 10px 6px;
  background-repeat: no-repeat;
}
.tl-field .tl-phone > input, .tl-phone > input {
  width: 100%; min-width: 0; margin: 0; padding: 11px 14px;
  font: inherit; font-size: 15px; line-height: 1.4; color: var(--text);
  background: transparent; border: 0; border-radius: 0;
  box-shadow: none; outline: none;
}
.tl-field .tl-phone > select:focus-visible, .tl-phone > select:focus-visible,
.tl-field .tl-phone > input:focus-visible, .tl-phone > input:focus-visible {
  outline: 2px solid var(--cta, var(--accent)); outline-offset: -2px;
}
/* An option list is drawn by the platform and inherits nothing, so it needs
   its own colours or it is white on white on a dark theme in some browsers. */
.tl-phone > select option, .tl-phone > select optgroup {
  background: var(--bg2, var(--surface, #fff)); color: var(--text, #000);
}
@media (max-width: 380px) {
  .tl-phone { grid-template-columns: 6.2rem minmax(0, 1fr); }
  .tl-field .tl-phone > select, .tl-phone > select { padding-left: 10px; font-size: 13px; }
}

/* ---- chip ink ------------------------------------------------------------
   A tag chip is a hue at 15% over the page, with the same hue as its text.
   Both halves have to be chosen together, and until now only one of them was:
   the chip's background came from the data as a fixed rgba, which composites
   correctly over either theme, while its text colour came from the data too,
   as either a fill token (--amber, --cyan) or a literal dark-theme hex. A
   fill is not an ink. Sat on a 15% tint of itself, the group's amber measured
   2.56:1 and the works page's cyan 1.24:1.

   These are the same eight hues moved until each clears 4.5:1 on the worst
   tint it is actually used on, which means the 8% avatar grounds and the 15%
   chip grounds over all five card and page surfaces, not over white. Solved
   rather than picked; the arithmetic is in the light-theme notes.

   Nothing reads these directly. chipInk() in the bundle maps a chip's own
   background to the matching token, so a chip cannot be given ink that does
   not belong to its fill, and a row added in the CMS gets the right one
   without anybody choosing a colour.
   ------------------------------------------------------------------------ */
:root {
  --chip-crimson-ink: #DD606F;
  --chip-red-ink:     #FF5111;
  --chip-amber-ink:   #FF8C00;
  --chip-green-ink:   #00A86B;
  --chip-cyan-ink:    #00A3E0;
  --chip-blue-ink:    #4D94CA;
  --chip-shell-ink:   #F05E48;
  --chip-mint-ink:    #34C759;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --chip-crimson-ink: #BA0016;
    --chip-red-ink:     #B73100;
    --chip-amber-ink:   #995400;
    --chip-green-ink:   #007249;
    --chip-cyan-ink:    #006C93;
    --chip-blue-ink:    #0063AD;
    --chip-shell-ink:   #C02710;
    --chip-mint-ink:    #207936;
  }
}
:root[data-theme="light"] {
  --chip-crimson-ink: #BA0016;
  --chip-red-ink:     #B73100;
  --chip-amber-ink:   #995400;
  --chip-green-ink:   #007249;
  --chip-cyan-ink:    #006C93;
  --chip-blue-ink:    #0063AD;
  --chip-shell-ink:   #C02710;
  --chip-mint-ink:    #207936;
}

/* A card that is a link.
   It inherits the page's text colour rather than the link colour, because a
   card whose every word turns accent-coloured stops reading as a card. The
   affordance is the lift and the cursor, which is what the rest of the kit
   uses. */
a.tl-card.is-door { text-decoration: none; color: inherit; display: block; }
a.tl-card.is-door:hover { transform: translateY(-2px); }
a.tl-card.is-door:focus-visible { outline: 2px solid var(--cta); outline-offset: 3px; }

/* ---- services rows (K.serviceRows) ------------------------------------
   The BrandOS home page's services list, for server-rendered pages. Two
   different designs of the same list on one website read as two companies. */
.tl-svc { border-bottom: 1px solid var(--line); }
.tl-svc-row { border-top: 1px solid var(--line); }
.tl-svc-row > summary { list-style: none; cursor: pointer; display: flex; align-items: center;
  flex-wrap: wrap; gap: 24px; padding: 32px 8px; }
.tl-svc-row > summary::-webkit-details-marker { display: none; }
.tl-svc-n { flex: none; width: 24px; font-size: 14px; font-weight: 700; color: var(--faint, var(--muted)); }
.tl-svc-t { flex: 1; min-width: min(100%, 200px); font-weight: 800; letter-spacing: -.02em;
  font-size: clamp(22px, 3vw, 30px); color: var(--text, var(--ink)); }
.tl-svc-chips { display: flex; flex-wrap: wrap; gap: 12px; }
.tl-svc-chip { border-radius: 999px; padding: 7px 16px; font-size: 13px; font-weight: 600;
  background: var(--bg2, var(--surface)); color: var(--text, var(--ink)); }
.tl-svc-arrow { flex: none; width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--line);
  display: inline-flex; align-items: center; justify-content: center; color: var(--text, var(--ink)); }
.tl-svc-arrow::before { content: "\2192"; }
.tl-svc-row[open] .tl-svc-arrow::before { content: "\2193"; }
.tl-svc-row[open] .tl-svc-t, .tl-svc-row[open] .tl-svc-n { color: var(--greent, var(--green, var(--ink))); }
.tl-svc-body { padding: 0 8px 32px 56px; max-width: 720px; }
.tl-svc-body p { margin: 0 0 14px; color: var(--muted); font-size: 15.5px; }
.tl-svc-body a { font-weight: 700; font-size: 15.5px; }
@media (max-width: 640px) {
  .tl-svc-row > summary { gap: 10px; padding: 22px 4px; }
  .tl-svc-t { flex: 1 1 calc(100% - 40px); min-width: 0; }
  .tl-svc-arrow { margin-left: auto; }
  .tl-svc-body { padding-left: 4px; }
}

/* A line icon takes the card's tone as ink on a tinted plate, where an
   initials plate would repeat the heading and an emoji would be a different
   drawing on every platform. */
.tl-icon.is-line { color: var(--tone, var(--cta, var(--accent))); }
.tl-icon.is-line .tl-line { display: block; }
.tl-icon.is-line.is-solid { color: var(--cta-ink, var(--accent-ink, #fff)); }

/* An icon on its own, outside the plate. `.tl-line` names two things: this
   drawn icon and the store row added 28 September, which is a grid with a
   border and 16px of padding. The row rule was winning on a bare icon, so an
   svg came out 22x32 and read as a dash rather than a picture. Only the plate
   protected it, which is why no page showed it and the design system did.
   Qualified by element because a row is always a div or an a, never an svg. */
svg.tl-line { display: block; width: 22px; height: 22px;
              padding: 0; border-top: 0; }

/* The audit chooser: a sentence, set apart from the hero's own copy without
   becoming a card. */
.tl-choose { margin: 22px 0 0; padding-left: 14px; border-left: 2px solid var(--line);
  font-size: 15.5px; line-height: 1.6; color: var(--muted); max-width: 62ch; }

/* A phrase that must not be split across two lines, such as "free 90-day
   tracker", where the break made it read as two separate offers. */
.nowrap { white-space: nowrap; }

/* ---- a caveat under a row of buttons ---------------------------------------
   Written on 21 September after the Shell homepage shipped with the sentence
   "Shell is being built and is not open yet" sitting flush against the bottom
   of the Join the waitlist button. Measured: zero pixels. The row set no
   bottom margin and the note set no top margin, so the two touched.

   It reads as a fourth line of the button rather than as a caveat about it,
   and the caveat is the more important of the two: it is the sentence that
   stops somebody expecting a product they cannot have yet.

   Defined here rather than per site because .cta-row is declared in three
   stylesheets with three different margins and the note is declared in a
   fourth, which is exactly how nobody noticed. checks/ui-standards.js fails
   the build on a row whose next element is closer than this.
   ------------------------------------------------------------------------ */
.cta-row + p,
.cta-row + .fine,
.cta-row + .note,
.cta-row + small { margin-top: var(--gap-note, 20px); }

/* ---- the same rule, for everything else a note can follow ----------------
   Reported 24 September: "everywhere the supporting text is not having any
   padding and it looks too cramped into the above containers. multiple
   places. please take a note and make a decision to ensure it doesn't happen
   again."

   The decision. A note that explains the thing above it is a separate voice
   from that thing, and a separate voice needs a gap or it is read as the last
   line of what it follows. That was standing rule 11, and rule 11 was written
   about one pairing, .cta-row + p, because that is where it was found. It is
   not a fact about button rows. It is a fact about notes.

   So the pairing is declared once, here, for every block a note can sit
   under: a grid of cards, a list, a form, a table, a figure, a row of pills.
   Adding a new grid gets the clearance without anybody remembering, which is
   the only version of this that survives contact with a deadline.

   Held by verify/checks/cta-clearance.js, which now measures notes under any
   of these rather than only under .cta-row. */
:is(.fst-grid, .pkggrid, .svcgrid, .ecogrid, .idxgrid, .jrgrid, .workgrid,
    .facts, .bullets, .principles, .process, .outcomes, .marks, .tl-cards,
    .tl-steps, .tl-figures, .tl-pills, .wb-lines, .fst-rows, .fst-find,
    .bk-cal, .bk-grid, .btn, button, form, table, figure, ul, ol, dl,
    .review-grid, .sample-find, .find-list, .herobtns, .cta-row, .g-actions,
    .famrows, .fam-index)
  + :is(p, .fine, .note, small) { margin-top: var(--gap-note, 20px); }

/* ---- controls: one standard ------------------------------------------------
   Written on 18 September after a review found, on one page, a 42px select
   beside a 45px text field and a 48px button, and browser-default radio
   buttons at 13px. Adjusting these one page at a time is what had been
   happening; this is the rule, and verify/checks/ui-standards.js fails the
   build when a control on any page falls outside it.

   The rule:
     every control a finger or a pointer has to hit is at least 48px tall
     a radio or a checkbox is 22px, in the page's own accent colour
     a control's corner is --r-md, a button's is --r-pill, and nothing else
     the label of a choice is part of its hit area
   ------------------------------------------------------------------------ */
:where(.tl-form, .field, .tl-field, .referform, .sheet-form, form)
  :is(input:not([type=radio]):not([type=checkbox]):not([type=hidden]), select, textarea) {
  min-height: 48px; border-radius: var(--r-md, 14px); padding: 12px 16px; font-size: 15.5px;
}
:where(.tl-form, .field, .tl-field, form) textarea { min-height: 120px; }

/* A field the browser filled in is still our field.

   Safari and Chrome paint their own pale yellow over anything they autofill
   and ignore `background-color`, so a signed-in operator saw two lemon
   coloured boxes on a black page. Reported on 29 September as "where is this
   form colour coming from", and the honest answer was that it is not coming
   from us at all.

   An inset shadow is what repaints it: the UA background stays where it is
   and this covers it. The colour has to be a real one rather than
   `transparent`, and it is repeated for hover, focus and active because each
   of those repaints the autofill background from scratch.

   -webkit-text-fill-color as well as color, because the same UA style fixes
   the text to near black, which is the rule 4 fault in the other direction:
   a fill chosen without its ink. */
:where(.tl-form, .field, .tl-field, .referform, .sheet-form, form)
  :is(input, textarea, select):is(:-webkit-autofill, :-webkit-autofill:hover,
                                  :-webkit-autofill:focus, :-webkit-autofill:active) {
  -webkit-text-fill-color: var(--text);
  caret-color: var(--text);
  box-shadow: 0 0 0 100px var(--surface, var(--bg2, var(--bg, #0B0B0C))) inset;
}

/* A select must not look like a text field that does nothing: it keeps the
   platform arrow, and the room to show it. */
:where(.tl-form, .field, .tl-field, form) select {
  appearance: auto; cursor: pointer; padding-right: 40px;
}

/* Radios and checkboxes: big enough to read as a choice, and coloured by the
   page rather than by the browser. */
:where(form, .tl-form, .agree, .field, .g-opt, .tl-choice) :is(input[type=radio], input[type=checkbox]) {
  width: 22px; height: 22px; flex: none; accent-color: var(--cta, var(--accent, var(--green)));
  cursor: pointer;
}
.agree { align-items: center; gap: 12px; }
.agree input { margin-top: 0; }

/* A choice row is a target, not a line of text: the whole row is clickable,
   and it carries the same corner as every other card. */
.tl-choice, .g-opt {
  border-radius: var(--r-md, 14px); min-height: 56px; align-items: center; cursor: pointer;
}

/* A band that follows a hero sits under a heading and a line of copy, not
   under a section of its own. 72px of padding below 84px of hero read as a
   page that had lost its form: 171px of nothing between the subheading and
   the first field. */
.hero:has(+ .band) { padding-bottom: clamp(16px, 2vw, 28px); }
.hero + .band { padding-top: clamp(18px, 2.4vw, 32px); }

/* Skip to content: out of the way until it is focused, then the first thing on
   the page. Added 18 September; before it, reaching the content by keyboard
   meant tabbing through the whole header on every page. */
.tl-skip {
  position: absolute; left: 12px; top: -60px; z-index: 300;
  padding: 12px 18px; border-radius: var(--r-md, 14px);
  background: #34C759; color: #04170B;
  font-weight: 700; text-decoration: none; transition: top .15s ease;
}
.tl-skip:focus-visible, .tl-skip:focus { top: 12px; }
#main:focus { outline: none; }

/* ---- accounts and sign-in ----------------------------------------------
   One column, hairlines rather than cards, and nothing per page.

   Lives here rather than in ni.css because the account screens are not
   Natural Intelligence's: they are the estate's, mounted on the host whose
   cookie they use. When a second host takes an account, it loads this file
   already and the screens arrive dressed.

   The controls themselves are not styled here. They are inside .tl-form, so
   the controls block above owns their height, corner and accent, which is the
   whole point of there being one.
   ------------------------------------------------------------------------ */
.acct-col { max-width: 560px; }
/* A sign-in screen is not a hero. The surrounding sites set h1 at their
   landing scale, which pushed "Sign in, or start" onto two lines inside this
   column and made a utility page shout. */
.acct-col h1 { font-size: clamp(28px, 3.4vw, 38px); line-height: 1.28; }
.acct-col .lede { font-size: clamp(15px, 1.5vw, 17px); }
.acct-form { max-width: 460px; }

/* The inline refusal. Danger ink on a danger tint, never a fill under text. */
.acct-alert {
  margin: 0 0 18px; padding: 13px 16px; border-radius: var(--r-md, 14px);
  background: color-mix(in srgb, var(--red, #C80018) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--red, #C80018) 34%, transparent);
  color: var(--text, var(--ink)); font-size: 15.5px; line-height: 1.6;
}

/* Fine print. --faint is solved against its ground in every theme, so this
   never becomes the 3.5:1 grey that caption text drifts into. */
.acct-fine { margin: 14px 0 0; font-size: 14.5px; line-height: 1.6; color: var(--faint); }

.acct-rows { margin: 0 0 28px; padding: 0; border-top: 1px solid var(--line); }
.acct-row { display: flex; flex-wrap: wrap; gap: 8px 20px; align-items: baseline;
            padding: 15px 0; border-bottom: 1px solid var(--line); }
.acct-row dt { flex: none; width: 108px; margin: 0; font-size: 11px; font-weight: 700;
               letter-spacing: .1em; text-transform: uppercase; color: var(--faint); }
.acct-row dd { flex: 1 1 200px; margin: 0; font-size: 15.5px; color: var(--text, var(--ink)); }

.acct-chip { display: inline-block; margin: 0 6px 6px 0; padding: 5px 12px;
             border-radius: var(--r-pill, 999px); border: 1px solid var(--line);
             background: var(--bg2, var(--surface)); font-size: 12.5px; color: var(--muted); }

/* Set apart by a rule and a heading, not by a red box: the box would shout
   every time somebody opened the page to do something ordinary. */
.acct-danger { margin-top: 40px; padding-top: 24px; border-top: 1px solid var(--line); }
.acct-danger h2 { margin: 0 0 8px; font-size: 15.5px; line-height: 1.28; }
.acct-danger p { margin: 0 0 18px; font-size: 15.5px; line-height: 1.6; color: var(--muted); max-width: 60ch; }

/* The one destructive control. Outlined rather than filled, so the primary
   action on the page is still the one that is not irreversible. */
.btn.danger {
  background: transparent; color: var(--red, #C80018);
  border: 1px solid color-mix(in srgb, var(--red, #C80018) 46%, transparent);
}
@media (hover: hover) {
  .btn.danger:hover { background: color-mix(in srgb, var(--red, #C80018) 8%, transparent); }
}

/* A utility button: the one case the control standard allows under 48px.
   40px, never smaller, and only for a control beside a row of content rather
   than a control somebody came to the page to press. Used by the deliverable
   row, where a 48px pair would be taller than the row it sits in. */
.btn.small, .tl-btn.small {
  min-height: 40px; padding: 8px 16px; font-size: 13.5px;
}

/* ---- a label only a screen reader reads ----------------------------------
   .sr-only was written in markup on four pages and defined in exactly one
   stylesheet, webos.css. Everywhere else the class did nothing, so a label
   meant to be heard and not seen was simply seen: "Your website address"
   sitting above the field it names on the BrandOS ecosystem block, and the
   Studio's chat button reading "Questions people ask before enquiring"
   across a 52 pixel circle.

   Defined here because every server-rendered site loads this sheet. A
   utility class that exists in three stylesheets and not the fourth is the
   same class of fault as .cta-row was on 21 September. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---- the guided chooser, the result and the brief ------------------------
   Moved here from crm.css on 24 September, because it was only ever in
   crm.css and four sites render it.

   studio.turtlelabs.co.in/brief was reported that day as "completely off the
   design": no measure, no margins, no colour treatment, a Back control drawn
   as a full-width blue bar and inputs running edge to edge. None of that was
   a design decision. The Studio serves the shared guide component and loads
   tl-system, tl-components and studio.css, and every rule that gives the
   component its shape was in a stylesheet only Shell loads. The page was
   unstyled, and an unstyled page looks exactly like a badly designed one,
   which is why it took a screenshot to find.

   The block was written against CRM's token names, so each one gains the
   shared fallback: --ink to --text, --accent to --cta, --surface to --bg2.
   A site that defines the CRM name still resolves it first and is unchanged.

   This is the same class of fault as .sr-only earlier today and .cta-row on
   21 September: a thing used by several sites, declared in one of them. */
/* ---- the guided chooser ---- */
.guide { max-width: 720px; margin: 0 auto; padding: 0 20px; }
.g-top { padding-top: 28px; }
.g-bar { height: 3px; background: var(--line-soft, var(--line2, var(--line))); }

/* ---- a bar made of sections ---------------------------------------------
   The brand audit asks thirty-nine questions in eleven named sections, and a
   single bar can only say how much is left. These say what is being measured,
   how much of this section is left, and how many sections there are, which is
   the order a reader actually asks them in. Each segment grows by its own
   question count, so the widths ARE the distribution.
   .g-bar is untouched: the Studio brief has six questions and no sections,
   and a segmented bar there would be eleven-elevenths of nothing.
   ------------------------------------------------------------------------ */
.g-secs { display: flex; gap: 3px; list-style: none; margin: 0; padding: 0; }
.g-sec {
  flex: 1 1 0; height: 4px; border-radius: 999px; overflow: hidden;
  background: var(--line-soft, var(--line2, var(--line)));
}
.g-sec > span {
  display: block; height: 100%; width: 0;
  background: var(--accent, var(--cta, var(--green))); transition: width .3s;
}
/* The one being answered. A ring rather than a brighter fill, because the
   fill already means "answered" and two meanings on one property is how a
   progress bar stops being readable. */
.g-sec.is-now { box-shadow: 0 0 0 1.5px var(--accent, var(--cta, var(--green))); }
.g-count .g-now { color: var(--text, var(--ink)); font-weight: 800; }
.g-count .g-of { color: inherit; }
@media (min-width: 620px) {
  .g-count { display: flex; justify-content: space-between; gap: 16px; align-items: baseline; }
}
.g-bar span { display: block; height: 100%; background: var(--accent, var(--cta, var(--green))); transition: width .3s; }
.g-count { font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
  font-weight: 800; color: var(--faint); margin: 10px 0 0; }
.g-count strong { color: var(--text, var(--ink)); }
.g-body { padding: 26px 0 60px; }
.g-kicker { font-size: 11px; letter-spacing: .2em; text-transform: uppercase;
  font-weight: 800; color: var(--accent-text, var(--cta-text, var(--cta, var(--accent)))); margin: 0 0 12px; }
.g-ask { font-size: clamp(26px, 4.5vw, 38px); margin: 0 0 12px; }
.g-why { font-size: 15.5px; color: var(--muted); margin: 0 0 26px; max-width: 58ch; }

.g-opts { display: grid; gap: 8px; margin: 0 0 24px; }
.g-opt { display: flex; align-items: center; gap: 12px; padding: 14px 16px;
  border: 1px solid var(--line); background: var(--surface, var(--bg2, var(--card))); cursor: pointer;
  /* Corner and height come from the control standard in tl-components.css. */ }
.g-opt:hover { border-color: var(--text, var(--ink)); }
.g-opt.on, .g-opt:has(input:checked) { border-color: var(--accent-text, var(--cta-text, var(--cta, var(--accent)))); background: var(--accent-soft, color-mix(in srgb, var(--cta, var(--accent)) 14%, transparent)); }
.g-opt input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.g-opt:focus-within { outline: 2px solid var(--accent, var(--cta, var(--green))); outline-offset: 2px; }
/* A token corner, like every other control. This was the one square thing on
   a page of rounded ones, which is what "sharp edges" was pointing at.
   Standing rule 7 sets the corner once; this had simply never taken it. */
.g-key { flex: none; width: 22px; height: 22px; display: grid; place-items: center;
  border-radius: var(--r-sm, 7px);
  border: 1px solid var(--line); font-size: 11px; font-weight: 800; color: var(--faint); }
.g-opt.on .g-key, .g-opt:has(input:checked) .g-key {
  background: var(--cta); border-color: var(--cta); color: var(--cta-ink); }
.g-label { font-size: 15.5px; font-weight: 500; }
.g-actions { display: flex; gap: 10px; flex-wrap: wrap; }
/* .btn.ghost is the quiet button, defined in tl-components.css. */
.g-note { font-size: 14.5px; color: var(--faint); margin: 12px 0 0; }
/* Back belongs with the buttons it undoes, not on a line of its own below
   them: as a separate form it was centred under the row and read as unrelated
   to it. Pushed to the end of the same row, quiet. */
/* The back control is a form of its own, because it is a GET to an earlier
   step and cannot live inside the form that posts the answer. So it sits
   under the action row rather than in it, quiet and sized to its own label. */
.g-back { display: block; margin: 16px 0 0; }
.g-back button { display: inline-flex; width: auto; }
.g-actions .g-back { margin-left: auto; align-self: center; }
@media (max-width: 560px) { .g-actions .g-back { margin-left: 0; } }
.g-fine { font-size: 14.5px; color: var(--faint); margin: 18px 0 0; }

/* ---- the result ---- */
.recs { display: grid; gap: 10px; margin: 0 0 26px; }
.rec { display: flex; gap: 16px; align-items: flex-start; padding: 18px 20px;
  border: 1px solid var(--line); background: var(--surface, var(--bg2, var(--card))); }
.rec.strong { border-left: 3px solid var(--accent, var(--cta, var(--green))); }
.rec-num { font-size: 11px; font-weight: 800; letter-spacing: .16em; color: var(--faint);
  margin: 3px 0 0; flex: none; }
.rec-main { flex: 1 1 auto; min-width: 0; }
.rec-main h3 { margin: 0 0 4px; font-size: 18px; }
.rec-verb { font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--accent-text, var(--cta-text, var(--cta, var(--accent)))); font-weight: 800; }
.rec-desc { font-size: 15.5px; color: var(--muted); margin: 0 0 6px; }
.rec-because { font-size: 12.5px; color: var(--faint); margin: 0; }
.rec-because em { font-style: normal; color: var(--muted); }
.rec-price { font-size: 17px; font-weight: 800; margin: 0; flex: none; white-space: nowrap; }
.rec-price span { font-size: 11px; font-weight: 400; color: var(--faint); }

.bundle { border: 2px solid var(--accent, var(--cta, var(--green))); padding: 22px; margin: 0 0 18px;
  background: var(--accent-soft, color-mix(in srgb, var(--cta, var(--accent)) 14%, transparent)); }
.bundle-h { font-size: 20px; font-weight: 800; margin: 0 0 6px; }
.bundle-line { font-size: 15.5px; color: var(--muted); margin: 0 0 10px; }
.bundle-maths { font-size: 15.5px; margin: 0 0 16px; }
.bundle form, .pick-form { margin: 0; }
.pick-form { margin-bottom: 26px; }

.also { border-top: 1px solid var(--line-soft, var(--line2, var(--line))); padding-top: 20px; margin-top: 6px; }
.also-h { font-size: 10px; letter-spacing: .2em; text-transform: uppercase;
  font-weight: 800; color: var(--faint); margin: 0 0 12px; }
.also-item { font-size: 15.5px; color: var(--muted); margin: 0 0 8px; }
.also-item strong { color: var(--text, var(--ink)); }
.notes { border-left: 3px solid var(--accent, var(--cta, var(--green))); padding: 4px 0 4px 16px; margin: 22px 0; }
.notes p { font-size: 15.5px; color: var(--muted); margin: 0 0 8px; }
.notes p:last-child { margin: 0; }

/* Every anchor in the estate clears the header.
   ------------------------------------------------------------------------
   A link to #something scrolls that element to the very top of the viewport,
   and every site here has a sticky header 65 to 73 pixels tall, so the thing
   you asked for goes behind it. It does not look like a scroll offset: it
   looks like the link went to the wrong place, because what you land on is
   the middle of one section with the next section's heading in view. That is
   how it was reported on 27 September, about the privacy contents list, and
   it was equally true of /#workbook and of every other in-page link.

   :where() so it carries no specificity at all: any page that wants a
   different offset, as webos.css does for its sections, still wins.
   ------------------------------------------------------------------------ */
:where([id]) { scroll-margin-top: 96px; }

/* ---- "Prices in ___" -----------------------------------------------------
   Every page that prints a price carries this, because detection is a guess.
   It was styled in crm.css alone until 27 September, which was fine while
   Shell's pricing page was the only page that had one.

   --surface against --bg gives the control an edge without a border colour
   that has to be re-solved per theme, and --ink is the ink on both. The
   corner is a token; the 10px literal it carried before predates them.
   ------------------------------------------------------------------------ */
/* 18px above and nothing below put the control flush against whatever it
   priced: on /web/watch the dropdown sat on top of the price card's rule.
   It is a control about the thing below it, so it needs the clearance a
   note gets, which standing rule 13 puts at sixteen pixels minimum. */
.market-switch { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
                 margin: 18px 0 26px; }
.market-switch label { font-size: 13.5px; font-weight: 700;
                       color: var(--muted); letter-spacing: .02em; }
.market-switch select {
  font: inherit; font-size: 14.5px; font-weight: 600;
  color: var(--text, var(--ink)); background: var(--surface, var(--bg2));
  border: 1px solid var(--line); border-radius: var(--r-sm, 10px);
  padding: 8px 12px; min-height: 40px; max-width: 100%; cursor: pointer;
}
.market-switch select:focus-visible {
  outline: 2px solid var(--cta-text, var(--cta)); outline-offset: 2px;
}

/* ---- test another address, from the result -------------------------------
   A field and a quiet button on one line, so a result is not a dead end: the
   address that was tested is in the field, and correcting a typo is an edit
   rather than finding the form again on another page.

   LAYOUT ONLY. The first version of this set the input's height, corner,
   padding, colours and focus ring in studio.css, and verify/checks/
   components.js failed the build for it, correctly: standing rule 7 says a
   control is styled once, here, and never in a page. It is a <form>, so the
   controls block above has already given the input its 48px, its corner and
   its focus ring. All that was ever missing was the flex row.
   ------------------------------------------------------------------------ */
.fst-again { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center;
             margin: 26px auto 0; max-width: 520px; }
.fst-again input { flex: 1 1 240px; min-width: 0; }
.fst-again .btn { flex: none; }

/* The password block on an account page. Set off from the sign-out row above
   it, because it is a setting rather than an action, and the two were reading
   as one list of things to press. */
.acct-pw { margin: 30px 0; padding: 24px 0 4px;
           border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }
.acct-pw h2 { font-size: 17px; letter-spacing: -.01em; margin: 0 0 8px; }
.acct-pw > p { color: var(--muted); font-size: 15.5px; line-height: 1.6;
               max-width: 56ch; margin: 0 0 16px; }
.acct-pw .acct-form { max-width: 420px; }
.acct-pw form + form { margin-top: 12px; }

/* The second way in.
   ------------------------------------------------------------------------
   A <details> rather than a tab or a toggle, so the page still reads as "put
   your address in" and the password is there for somebody who went looking.
   It is markup, so it works with scripting off, which every other control on
   these doors already does.

   It opens itself when there is an error in it, because a wrong password
   inside a closed drawer is a page that appears to have done nothing.
   ------------------------------------------------------------------------ */
.acct-alt { margin: 26px 0 0; padding-top: 22px;
            border-top: 1px solid var(--line-soft, var(--line2, var(--line)));
            max-width: 460px; }
.acct-alt > summary {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 8px;
  font-size: 14.5px; font-weight: 700; color: var(--muted);
  padding: 8px 0; min-height: 32px;
}
.acct-alt > summary::-webkit-details-marker { display: none; }
.acct-alt > summary::after {
  content: ''; width: 7px; height: 7px; border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor; transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .2s ease;
}
.acct-alt[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
.acct-alt > summary:hover { color: var(--text, var(--ink)); }
.acct-alt > summary:focus-visible { outline: 2px solid var(--cta-text, var(--cta)); outline-offset: 3px; }
.acct-alt .acct-form { margin-top: 14px; }

/* ---- the five the store needs --------------------------------------------
   TLUI 1.0.0's README lists nine components the BrandOS store needs that it
   does not have. Four of them already existed here: the currency switch, the
   sticky buy bar, the modal and the cover card. These are the other five,
   built on 27 September on the instruction to complete the system here
   rather than in the package, since the package is generated from this file.

   Each one is a thing the estate has needed and faked before, which is why
   they are components rather than five more page-level rules. The save
   indicator in particular exists three times already, in the sprint tracker,
   in Watch and in the brief flow, and all three say something different.
   ------------------------------------------------------------------------ */

/* 1. A PRICE, and what it used to be.
   The old price is struck through and quiet; the new one is the thing you
   read. The saving is stated as a number rather than left to be worked out,
   because "was 175, now 148.75" is arithmetic and "save 15%" is a reason.
   `del` and `ins` rather than spans: a screen reader then says "deleted" and
   a price that has changed is exactly what those elements are for. */
.tl-price { display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px; }
.tl-price ins { text-decoration: none; font-family: 'Outfit', system-ui, sans-serif;
                font-weight: 800; font-size: clamp(26px, 3vw, 36px); line-height: 1;
                letter-spacing: -.03em; color: var(--text, var(--ink)); }
.tl-price del { font-size: 17px; color: var(--faint); text-decoration-thickness: 1.5px; }
.tl-price-save { font-size: 13px; font-weight: 800; letter-spacing: .08em;
                 text-transform: uppercase; padding: 4px 10px;
                 border-radius: var(--r-pill, 999px);
                 color: var(--cta-ink, #04170B);
                 background: var(--cta, var(--accent, var(--green, #34C759))); }
.tl-price-unit { font-size: 14.5px; color: var(--muted); }

/* 2. A CART LINE.
   Thumbnail, name, price, and the way out. A grid rather than a flex row so
   the remove control sits on the same right edge down the whole cart however
   long a title wraps, which is the thing that makes a list of these read as
   a list rather than as four unrelated rows. */
.tl-line { display: grid; grid-template-columns: 56px minmax(0, 1fr) auto;
           gap: 14px 16px; align-items: center; padding: 16px 0;
           border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }
.tl-line:first-child { border-top: 0; }
.tl-line-art { width: 56px; aspect-ratio: 3 / 4; border-radius: var(--r-sm, 10px);
               background: var(--bg2, var(--surface)); overflow: hidden; }
.tl-line-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tl-line-body { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.tl-line-t { font-weight: 700; font-size: 15.5px; color: var(--text, var(--ink)); }
.tl-line-sub { font-size: 14.5px; color: var(--muted); }
.tl-line-end { display: flex; align-items: center; gap: 14px; }
.tl-line-price { font-family: 'Outfit', system-ui, sans-serif; font-weight: 800;
                 font-size: 16px; color: var(--text, var(--ink)); white-space: nowrap; }
/* A line that is a link, which is what a store list is as against a cart.
   Added 28 September: the Brand Systems store reused .tl-line for its five
   rows and every title, subtitle and price came out underlined, because a
   component built to sit inside a cart had never been an <a>. The underline
   moves to the title on hover, so the row still reads as a link without
   three underlines competing down the page. */
/* The wrapper. A cart gives its lines a container of its own; a store list
   needs the same one, and .tl-rows is a different component (a ruled list of
   <li>) that was reached for by name and silently restyled all five rows. */
.tl-lines { margin: 26px 0 0; }

/* Reported 28 September: "pricing is sitting at right edge, there should be
   some padding to the right." It was these negative margins. They were here
   so the hover background would bleed to the section's edge, and the cost
   was that the price, which is the last thing in the row, sat hard against
   it with nothing after it. The hover tint reaching exactly as far as the
   words is worth more than it reaching the edge. */
a.tl-line { text-decoration: none; color: inherit;
            padding-left: 14px; padding-right: 18px;
            border-radius: var(--r-sm, 10px);
            /* Declared rather than inherited. Reported 29 September: the
               hand appears in Brave and not in Safari. A link whose children
               are all block-level does not get the pointer from Safari on
               its own, and every row here is a grid of spans, so there is no
               inline text for it to infer from. */
            cursor: pointer;
            transition: background .16s ease; }
/* The basket's rows are not links and need the same right-hand room, since
   they carry a price in the same place. */
.tl-line:not(a) { padding-right: 4px; }
a.tl-line:hover { background: var(--bg2, var(--surface)); }
a.tl-line:hover .tl-line-t { text-decoration: underline; text-underline-offset: 3px; }
a.tl-line:focus-visible { outline: 2px solid var(--cta, var(--accent));
                          outline-offset: 2px; }

/* The art slot with no art in it. An empty tile reads as a broken image, so
   where there is no thumbnail it carries the thing's number instead. */
.tl-line-art.is-numbered { display: grid; place-items: center; aspect-ratio: 1;
                           border: 1px solid var(--line-soft, var(--line2, var(--line))); }
.tl-line-n { font-family: 'Outfit', system-ui, sans-serif; font-weight: 800;
             font-size: 17px; letter-spacing: -.02em; color: var(--muted); }
a.tl-line:hover .tl-line-n { color: var(--text, var(--ink)); }

/* A landscape thumbnail, for a thing whose cover is a slide rather than a
   book jacket. The default slot is 3:4 portrait, and a 16:9 page cropped to
   portrait loses the half of it that carries the headline. */
/* Big enough to read the headline on. At 104px these were a colour swatch:
   you could tell there was a cover there and not which one. 168px is where
   the book's own title becomes legible, which is the whole point of putting
   it in the row. */
.tl-line-art.is-wide { width: 168px; aspect-ratio: 16 / 9;
                       border: 1px solid var(--line-soft, var(--line2, var(--line)));
                       box-shadow: 0 6px 16px rgba(0, 0, 0, .26); }
/* Not a.tl-line.is-wide-art, which is what this said first: the store's rows
   are anchors and the basket's are divs, so half of them kept the 56px
   portrait column and the picture sat on top of the words. */
/* padding-block, not the `padding: 22px 0` shorthand this said first. Two
   classes outrank the `a.tl-line` rule above whatever the order, so the
   shorthand silently reset the side padding to zero and the price went back
   to sitting on the right edge, which is what was reported. A shorthand is
   a statement about all four sides, and this rule has an opinion about two
   of them. */
.tl-line.is-wide-art { grid-template-columns: 168px minmax(0, 1fr) auto;
                       padding-block: 22px; }
@media (max-width: 860px) {
  .tl-line.is-wide-art { grid-template-columns: 124px minmax(0, 1fr) auto; }
  .tl-line-art.is-wide { width: 124px; }
}
@media (max-width: 560px) {
  .tl-line.is-wide-art { grid-template-columns: minmax(0, 1fr); }
  /* Full width on a phone rather than a smaller thumbnail. A cover shrunk to
     fit beside the words is neither a picture nor a margin. */
  .tl-line-art.is-wide { width: 100%; }
  .tl-line.is-wide-art .tl-line-end { grid-column: 1; justify-content: space-between; }
}

/* A title that is a link inside a row that is not. The row-as-a-link case is
   handled above by taking the underline off the whole thing; this is the
   other one, where only the name is clickable. */
a.tl-line-t { color: inherit; text-decoration: none; }
a.tl-line-t:hover { text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 560px) { .tl-line-art.is-wide { width: 76px; } }

/* ---- A TOTAL -------------------------------------------------------------
   Three lines that have to be read as arithmetic: what it came to, what came
   off, what is owed. Tabular figures so the amounts line up on their own
   decimal point rather than on wherever the glyphs happen to end, which is
   the difference between a total and a list of numbers. */
.tl-total { margin: 28px 0 0; border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }
.tl-total p { display: flex; justify-content: space-between; gap: 20px;
              margin: 0; padding: 11px 0; font-size: 15.5px; color: var(--muted); }
.tl-total p span:last-child { font-variant-numeric: tabular-nums; }
.tl-total .is-off { color: var(--cta-text, var(--cta, var(--accent))); }
.tl-total .is-total { border-top: 1px solid var(--line-soft, var(--line2, var(--line)));
                      color: var(--text, var(--ink)); font-weight: 700; }
.tl-total .is-total span:last-child { font-family: 'Outfit', system-ui, sans-serif;
                                      font-weight: 800; font-size: 22px; letter-spacing: -.02em; }
/* What comes after a total is a new argument, not a continuation of it.
   Standing rule 13 the other way up: the gap belongs above the heading as
   much as below the note. */
.cart-h2 { margin-top: clamp(30px, 4vw, 46px); }

/* ---- THE HEADER'S SHOP GROUP ---------------------------------------------
   Currency, the door and the basket, from the store design. On the one
   header rather than a second one, and only drawn where there is something
   to buy. */
.bar-shop { display: flex; align-items: center; gap: 10px; }
.cur { display: inline-flex; padding: 3px; border-radius: var(--r-pill, 999px);
       background: var(--bg2, var(--surface));
       border: 1px solid var(--line-soft, var(--line2, var(--line))); }
/* Symbols rather than words, asked for on 28 September: the group was taking
   most of the bar and pushing the menu into the burger earlier than it had
   to. 34px square, so an icon-only control still clears standing rule 7's
   32px, and every one carries a real name in a .sr-only span. */
.cur-opt { appearance: none; border: 0; background: transparent; cursor: pointer;
           min-width: 34px; min-height: 34px; padding: 0 10px;
           display: grid; place-items: center;
           border-radius: var(--r-pill, 999px);
           font-family: 'Outfit', system-ui, sans-serif;
           font-size: 15px; font-weight: 800; line-height: 1; color: var(--muted); }
.cur-opt.on { background: var(--text, var(--ink)); color: var(--bg, var(--page)); }
.cur-opt:focus-visible { outline: 2px solid var(--cta, var(--accent)); outline-offset: 2px; }

.icon-btn { position: relative; display: grid; place-items: center;
            width: 38px; height: 38px; border-radius: var(--r-pill, 999px);
            border: 1px solid var(--line-soft, var(--line2, var(--line)));
            color: var(--text, var(--ink)); text-decoration: none;
            background: transparent; transition: background .16s ease; }
.icon-btn svg { width: 20px; height: 20px; display: block; }
.icon-btn:hover { background: var(--bg2, var(--surface)); }
.icon-btn:focus-visible { outline: 2px solid var(--cta, var(--accent)); outline-offset: 2px; }
.icon-btn.has-n { border-color: var(--cta, var(--accent)); }
/* The count rides the basket rather than sitting beside it, and is only
   drawn when it is not zero: "Cart 0" is a number whose only job is to say
   there is nothing to tell you. */
.cart-n { position: absolute; top: -5px; right: -5px; min-width: 18px; height: 18px;
          padding: 0 5px; border-radius: 999px;
          display: grid; place-items: center;
          background: var(--cta, var(--accent)); color: var(--cta-ink, #04170B);
          font-family: 'Outfit', system-ui, sans-serif;
          font-size: 11px; font-weight: 800; line-height: 1;
          font-variant-numeric: tabular-nums; }
/* The nav becomes a column at 720px, which is where a full-width shop group
   belongs. Above that the nav is still a row, so a full-width child pushes
   the row past the viewport: this said 900px and the header overflowed by
   13px at 768. */
@media (max-width: 720px) {
  .bar-shop { width: 100%; justify-content: space-between; padding-top: 12px;
              border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }
}

/* Between the burger and a comfortable desktop, five nav items plus a theme
   button plus three controls do not fit, and the thing to drop is the
   currency toggle: every page that carries this group also carries a
   "Prices in" switcher in its own content, so nothing becomes unreachable.
   The basket and the door stay, because nothing else offers those. */
@media (min-width: 721px) and (max-width: 1000px) {
  .cur { display: none; }
}

/* ---- THE READER ----------------------------------------------------------
   One page of a book, with somebody's answers in it. Built out of the same
   controls as every other form here rather than a second set: a book is a
   form somebody spends four evenings on, and it should feel like the rest of
   the estate rather than like a document that learned to accept typing. */
/* The measure. `wrap` is the page's full width and a book is prose with
   boxes in it: 1280px of line is unreadable and 1280px of textarea invites
   an essay nobody wants to write. !important because `wrap` sets its own
   max-width and this is the one place that has to win. */
.rd-page, .rd-nav { max-width: 760px !important; margin-inline: auto; }
.rd-run { display: flex; justify-content: space-between; gap: 16px;
          font-size: 11.5px; font-weight: 800; letter-spacing: .14em;
          text-transform: uppercase; color: var(--faint, var(--muted));
          margin: 0 0 18px; }
.rd-n { font-variant-numeric: tabular-nums; }

.rd-fields { display: grid; gap: clamp(18px, 2.4vw, 28px); margin-top: 30px; }
.rd-field { display: flex; flex-direction: column; gap: 8px; }
.rd-label { font-size: 12px; font-weight: 800; letter-spacing: .14em;
            text-transform: uppercase; color: var(--sec-ink, var(--cta-text, var(--cta))); }
/* Taller than an ordinary form field, and in the page's own face. Somebody
   is thinking here, not filling in their address: a three line box invites
   three lines, and the browser's monospace default makes a sentence about
   your brand look like a commit message. */
.rd-field textarea { min-height: 104px; resize: vertical; line-height: 1.6;
                     font-family: inherit; font-size: 16px; }
.rd-field input[type="text"] { font-family: inherit; font-size: 16px; }
/* .tl-form sets 30px between fields, which is right for six fields and too
   tight for six paragraphs somebody is composing. */
.rd-fields .tl-field + .tl-field { margin-top: clamp(22px, 2.6vw, 32px); }

/* The checklist at the foot of a chapter. One row, because six separate
   ticks read as six questions rather than as one checklist. */
.rd-ticks { margin-top: clamp(26px, 3vw, 38px); padding-top: 22px;
            border-top: 1px solid var(--line-soft, var(--line2, var(--line)));
            display: grid; gap: 12px; }
.rd-ticks-h { margin: 0 0 4px; font-size: 12px; font-weight: 800;
              letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.rd-tick { display: grid; grid-template-columns: 22px minmax(0, 1fr);
           gap: 12px; align-items: start; cursor: pointer;
           font-size: 15.5px; line-height: 1.5; }

/* "Saved", and the one case that matters more: not saved. Reserved height,
   so the page does not jump every time it appears and disappears. */
.rd-saved { min-height: 22px; margin: 22px 0 0; font-size: 13.5px;
            color: var(--muted); }
.rd-saved.is-bad { color: var(--tone-red-ink, var(--red, #D71811)); font-weight: 700; }

.rd-nav { display: flex; gap: 12px; align-items: center;
          justify-content: space-between; padding-bottom: clamp(40px, 6vw, 80px); }
.rd-nav .btn.is-off { opacity: .4; pointer-events: none; }

/* How far through. A real figure, counted from what the book asks, so it
   cannot say 103% because a field was retired. */
.rd-bar { height: 6px; border-radius: 999px; margin: 22px 0 30px;
          background: var(--bg2, var(--surface)); overflow: hidden; }
.rd-bar span { display: block; height: 100%; border-radius: 999px;
               background: var(--cta, var(--accent)); transition: width .3s ease; }

.rd-toc { display: grid; gap: clamp(26px, 3vw, 40px); margin-top: 8px; }
.rd-ch h2 { font-size: clamp(17px, 1.6vw, 20px); margin: 0 0 12px; }
.rd-ch-list { list-style: none; margin: 0; padding: 0; }
.rd-ch-list li { border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }
.rd-ch-list a { display: grid; grid-template-columns: 34px minmax(0, 1fr) auto;
                gap: 14px; align-items: baseline; padding: 13px 0;
                text-decoration: none; color: inherit; }
.rd-ch-list a:hover .rd-ch-t { text-decoration: underline; text-underline-offset: 3px; }
.rd-ch-n { font-variant-numeric: tabular-nums; font-size: 13px; color: var(--faint, var(--muted)); }
.rd-ch-t { font-size: 15.5px; }
.rd-ch-s { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums;
           white-space: nowrap; }
.rd-ch-s.is-done { color: var(--tone-green-ink, var(--green)); font-weight: 700; }
.rd-ch-s.is-read { color: var(--faint, var(--muted)); }

/* ---- PAGE SHOTS ----------------------------------------------------------
   Pictures of the product, which for a book is pages out of it. Added 28
   September: the store shipped with none and was five rows of type, which
   tells nobody what they are buying. The handoff had them all along.

   A border rather than a shadow, because these are dark pages on a dark
   ground and a shadow under one is invisible; the edge is the only thing
   that says where the page stops and the site starts. On the light theme it
   is the same rule doing the same job. */
.tl-shot { display: block; width: 100%; height: auto; aspect-ratio: 16 / 9;
           object-fit: cover; border-radius: var(--r-md, 14px);
           border: 1px solid var(--line-soft, var(--line2, var(--line)));
           background: var(--bg2, var(--surface)); }
.tl-shot-hero { width: 100%; max-width: 560px; margin-inline: auto; }
@media (min-width: 980px) { .tl-shot-hero { max-width: none; } }

/* A stack of them, for a hero about a set rather than one thing.
   These are the prototype's own numbers, read out of BrandOS Store.dc.html
   rather than guessed at: 74% wide, 6.5% right and 11% down per card, and a
   rotation sweeping -6deg to +6deg across the five. The first version fanned
   up and to the LEFT off a single offset, which read as a neat stack rather
   than as a spread, and Aditya sent the prototype back with "look at the
   spread".

   The last card in the markup is the one on top, so the books stay in
   catalogue order and 05 leads, exactly as the prototype has it. Later
   siblings paint over earlier ones, which is the one thing about stacking
   order that never needs a z-index. */
.tl-stack { position: relative; display: none; width: 100%; max-width: 620px;
            aspect-ratio: 4 / 3; margin-inline: auto; }
@media (min-width: 980px) { .tl-stack { display: block; } }
.tl-stack-i { position: absolute; width: 74%; height: auto;
              aspect-ratio: 16 / 9; object-fit: cover;
              border-radius: 12px;
              border: 1px solid rgba(246, 243, 236, .1);
              background: var(--bg2, var(--surface));
              box-shadow: 0 18px 44px rgba(11, 11, 12, .28);
              left: calc(var(--i) * 6.5%);
              top: calc(var(--i) * 11%);
              transform: rotate(calc(-6deg + var(--i) * 3deg));
              transition: transform .3s ease; }
/* On a light ground the 10% white edge disappears, so it takes the theme's
   own line instead. Standing rule 4: the fill and its edge together. */
:root[data-theme="light"] .tl-stack-i {
  border-color: var(--line-soft, var(--line2, var(--line)));
  box-shadow: 0 18px 44px rgba(11, 11, 12, .14);
}
@media (hover: hover) {
  .tl-stack:hover .tl-stack-i {
    transform: rotate(calc(-7.5deg + var(--i) * 3.75deg));
  }
}
/* Somebody who has asked for less movement gets none, rather than less. */
@media (prefers-reduced-motion: reduce) {
  .tl-stack-i, .tl-stack:hover .tl-stack-i { transition: none; }
}

/* One shot, big, inside a section that already has something to say. Added
   28 September on "the big bold display makes you feel the product better":
   it goes with the existing content rather than in place of it, so a section
   keeps its argument and gains the evidence. */
.tl-shot-wide { margin: 0 0 26px; display: flex; flex-direction: column; gap: 12px; }
.tl-shot-wide .tl-shot { border-radius: var(--r-lg, 18px);
                         box-shadow: 0 16px 40px rgba(0, 0, 0, .32); }
.tl-shot-wide figcaption { font-size: 14.5px; color: var(--muted); max-width: 62ch; }
/* Below a figure, not above the next thing: a section that ends on a picture
   needs the same clearance a note does. Standing rule 13. */
.tl-shot-wide:last-child { margin-bottom: 0; margin-top: 30px; }

.tl-shots { display: grid; gap: clamp(14px, 1.8vw, 22px);
            grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 26px; }
@media (max-width: 720px) { .tl-shots { grid-template-columns: minmax(0, 1fr); } }
.tl-shot-fig { margin: 0; display: flex; flex-direction: column; gap: 10px; }
/* The caption is a label for the picture, so it sits under it with the gap a
   note gets under anything else. Standing rule 13. */
.tl-shot-fig figcaption { font-size: 13px; font-weight: 700; letter-spacing: .1em;
                          text-transform: uppercase; color: var(--muted); }

/* Owned, and therefore not for sale again. Struck through rather than hidden,
   because a line that vanishes from a cart reads as a bug. */
.tl-line.is-owned .tl-line-price { text-decoration: line-through; color: var(--faint); }
.tl-line.is-owned .tl-line-t { color: var(--muted); }
@media (max-width: 560px) {
  .tl-line { grid-template-columns: 44px minmax(0, 1fr); }
  .tl-line-art { width: 44px; }
  .tl-line-end { grid-column: 2; justify-content: space-between; }
}

/* 3. A QUANTITY STEPPER.
   Named .tl-qty and not .tl-step-n, which is already the ordinal inside
   .tl-steps. Taking it renamed every numbered step on /web and /audit into
   a 40px control and the layout audit reported eight clipped numbers on
   three widths within the minute. A component that takes a name already in
   use does not conflict, it silently becomes the other component.
   Two buttons and a number, and the number is an input so it can be typed
   into: a stepper that only steps takes fifty presses to reach fifty seats,
   which is exactly what the team screen asks for. 40px rather than 48,
   because a stepper is a pair of small controls rather than a field, and
   that is the one size the control standard allows below 48. */
.tl-qty { display: inline-flex; align-items: stretch;
             border: 1px solid var(--line); border-radius: var(--r-md, 14px);
             overflow: hidden; background: var(--surface, var(--bg2)); }
.tl-qty button { width: 40px; min-height: 40px; border: 0; cursor: pointer;
                    background: none; color: var(--text, var(--ink));
                    font-size: 18px; line-height: 1; font-family: inherit; }
.tl-qty button:hover { background: var(--bg2, var(--surface)); }
.tl-qty button:disabled { opacity: .4; cursor: default; }
.tl-qty input { width: 56px; min-height: 40px; border: 0; text-align: center;
                   background: none; color: var(--text, var(--ink));
                   font: inherit; font-weight: 700; font-size: 15.5px;
                   -moz-appearance: textfield; }
.tl-qty input::-webkit-outer-spin-button,
.tl-qty input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.tl-qty :is(button, input):focus-visible {
  outline: 2px solid var(--cta-text, var(--cta)); outline-offset: -2px; }

/* 4. A TOAST.
   Bottom centre, above everything, and it announces itself: role="status"
   with aria-live="polite" in the markup, so "Added to your cart" reaches
   somebody who cannot see the corner of the screen. It does not trap focus
   and it is never the only place an outcome is stated, because a message
   that disappears after four seconds is not where you put the only copy of
   something somebody needs. */
.tl-toast { position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
            z-index: 300; max-width: min(92vw, 460px);
            display: flex; align-items: center; gap: 12px;
            padding: 14px 18px; border-radius: var(--r-md, 14px);
            background: var(--surface, var(--bg2));
            border: 1px solid var(--line);
            box-shadow: 0 18px 44px rgba(0, 0, 0, .28);
            font-size: 15.5px; color: var(--text, var(--ink)); }
.tl-toast[hidden] { display: none; }
.tl-toast-dot { width: 9px; height: 9px; border-radius: 50%; flex: none;
                background: var(--cta, var(--accent, var(--green, #34C759))); }
.tl-toast.is-warn .tl-toast-dot { background: var(--tone-amber, #FFB35C); }
.tl-toast.is-bad  .tl-toast-dot { background: var(--tone-red, #FF6B6B); }
.tl-toast a { color: var(--cta-text, var(--cta)); font-weight: 700; }
@media (prefers-reduced-motion: no-preference) {
  .tl-toast { animation: tl-toast-in .28s cubic-bezier(.22,.61,.36,1); }
  @keyframes tl-toast-in {
    from { opacity: 0; transform: translate(-50%, 12px); }
    to   { opacity: 1; transform: translate(-50%, 0); }
  }
}

/* 5. A SAVE INDICATOR.
   The estate has three of these already, in the sprint tracker, in Watch and
   in the brief, and all three say something different. This is the one, and
   the four states are the four the reader spec names: saving, saved, on this
   device, and not saved.

   THE FAILED STATE IS NOT QUIET. The other three are a dot and a word,
   because they are reassurance and reassurance should not take up room. A
   failure is the one case where somebody has to do something, so it takes a
   colour, a border and a button. Standing rule 4: each dot's colour has its
   ink chosen with it. */
.tl-save { display: inline-flex; align-items: center; gap: 8px;
           font-size: 14.5px; color: var(--muted); }
.tl-save-dot { width: 8px; height: 8px; border-radius: 50%; flex: none;
               background: var(--faint); }
.tl-save.is-saving .tl-save-dot { background: var(--tone-blue, #60A5FA); }
.tl-save.is-saved  .tl-save-dot { background: var(--tone-green, #4ADE80); }
.tl-save.is-local  .tl-save-dot { background: var(--tone-amber, #FFB35C); }
.tl-save.is-failed { color: var(--tone-red-ink, #FF6B6B); font-weight: 700;
                     padding: 8px 12px; border-radius: var(--r-sm, 10px);
                     border: 1px solid var(--tone-red-ink, #FF6B6B); }
.tl-save.is-failed .tl-save-dot { background: var(--tone-red-ink, #FF6B6B); }
.tl-save-retry { font: inherit; font-weight: 700; cursor: pointer;
                 background: none; border: 0; padding: 0 0 0 4px;
                 color: inherit; text-decoration: underline; }
@media (prefers-reduced-motion: no-preference) {
  .tl-save.is-saving .tl-save-dot { animation: tl-save-pulse 1.1s ease-in-out infinite; }
  @keyframes tl-save-pulse { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
}

/* ---- the distance between one section and the next -----------------------
   Declared here because this is the only stylesheet all eight sites load,
   and the whole point of the design system is that a basic like this cannot
   be two different numbers on two sites. It was: BrandOS sat at 144px and
   the Studio at 136px, both capped so they stopped growing while the page
   kept widening, which is what made a page generous at 1280 and cramped at
   2000. Reported 27 September as "before it was spacious, looked elegant,
   now just because the sections are placed too close it looks cheap".

   86 above and 86 below is 172px between sections, which was the 15 to 20%
   asked for, and both sites read it from here rather than declaring it.

   Symmetric on purpose. tl-system.css used to be bottom-led, 40 above and
   128 below, which is the same total and puts the whole gap after the
   content rather than around it: a heading then starts nearer the section
   above than the one it belongs to. --gap-section still exists and still
   means "the bottom of a block", which is what the case shots, the care
   panels and the commons rows use it for.
   ------------------------------------------------------------------------ */
:root { --band-pad: clamp(44px, 7vw, 86px); }

/* ---- the venture tones ---------------------------------------------------
   Moved here from tl-system.css on 27 September, because WebOS does not load
   tl-system.css and the components do. Every `--tone-*-ink` on /web was
   therefore falling back to the raw display hue: a label measured 1.89:1
   against its ground, and the contrast sweep caught eight of them the moment
   the new shapes started asking for the ink value.

   That is the fifth thing found living in one site's stylesheet after .btn,
   .cta-row, .legal-table and .market-switch, and the first that was a token
   rather than a rule. A component cannot ask for a token the page has never
   been given.

   Content names a tone and the theme decides the value, because a colour
   that reads on near-black is usually unreadable on white. The -ink set is
   the readable variant, so a component can ask for it without knowing which
   theme it is in; on near-black the display values already clear 4.5:1, so
   there the two sets are the same.
   ------------------------------------------------------------------------ */
:root {
  /* Venture colours, as tokens rather than fixed hex, because a value that
     reads well on near-black is often unreadable on white. Content names a
     tone and the theme decides the value. */
  --tone-red:#FF6B6B; --tone-orange:#FF8C00; --tone-amber:#FFB35C;
  --tone-yellow:#FFD666; --tone-green:#4ADE80; --tone-blue:#60A5FA;
  --tone-cyan:#67E8F9; --tone-purple:#C084FC;
  /* On near-black these already clear 4.5:1, so the ink set is the same set.
     It exists as separate tokens so a component can ask for the readable
     variant without knowing which theme it is in. */
  --tone-red-ink:#FF6B6B; --tone-orange-ink:#FF8C00; --tone-amber-ink:#FFB35C;
  --tone-yellow-ink:#FFD666; --tone-green-ink:#4ADE80; --tone-blue-ink:#60A5FA;
  --tone-cyan-ink:#67E8F9; --tone-purple-ink:#C084FC;
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --tone-red:#F0504B; --tone-orange:#D86806; --tone-amber:#BD7905;
    --tone-yellow:#AC8204; --tone-green:#089C57; --tone-blue:#2A88F4;
    --tone-cyan:#0995B1; --tone-purple:#B265E9;
    /* Ink tones: small text, labels and anything on a tinted panel. 4.5:1. */
    --tone-red-ink:#D71811; --tone-orange-ink:#AD5304; --tone-amber-ink:#966104;
    --tone-yellow-ink:#896704; --tone-green-ink:#077D46; --tone-blue-ink:#0B69D5;
    --tone-cyan-ink:#07778E; --tone-purple-ink:#9B37E2;
  }
}
:root[data-theme="light"] {
  --tone-red:#F0504B; --tone-orange:#D86806; --tone-amber:#BD7905;
  --tone-yellow:#AC8204; --tone-green:#089C57; --tone-blue:#2A88F4;
  --tone-cyan:#0995B1; --tone-purple:#B265E9;
  /* Ink tones: small text, labels and anything on a tinted panel. 4.5:1. */
  --tone-red-ink:#D71811; --tone-orange-ink:#AD5304; --tone-amber-ink:#966104;
  --tone-yellow-ink:#896704; --tone-green-ink:#077D46; --tone-blue-ink:#0B69D5;
  --tone-cyan-ink:#07778E; --tone-purple-ink:#9B37E2;
}

/* A card with a plate.
   ------------------------------------------------------------------------
   The article's own gradient, carried as data rather than as a file. WebOS's
   journal strip was the shared card with no image, which is three grey boxes
   of text under a heading that says Journal, and it was reported as dull
   twice. There is no photograph to have here, and inventing one is worse
   than having none: the estate's journal has always had a colour per article
   and the Studio's cards draw it. This is the same plate on the same
   component, so the two sites' journals now look like one journal.

   The card hides its own padding at the top so the plate can bleed, which is
   why this is a modifier rather than a nested element.
   ------------------------------------------------------------------------ */
/* A plated card drops its edge rule. The rule is the card's one bit of
   colour, and the plate is a whole band of it: keeping both left a coloured
   sliver running down the side of the image, which reads as a rendering
   fault rather than as a second accent. */
.tl-card.has-plate { padding-top: 0; overflow: hidden; }
.tl-card.has-plate.is-edged { padding-left: clamp(20px, 2.2vw, 30px); }
.tl-card.has-plate.is-edged::before,
.tl-card.has-plate.is-topped::before { content: none; }
.tl-card-plate { display: flex; align-items: flex-end; padding: 14px 16px;
                 min-height: 120px; margin: 0 calc(clamp(20px, 2.2vw, 30px) * -1) 18px;
                 background: var(--bg2, var(--surface)); }
.tl-card-plate-t { font-family: 'Outfit', system-ui, sans-serif; font-weight: 800;
                   font-size: 11.5px; letter-spacing: .13em; text-transform: uppercase;
                   color: #FFFFFF; background: rgba(12, 12, 14, .74);
                   padding: 5px 11px; border-radius: 999px; }
@media (hover: hover) { .tl-card.has-plate:hover .tl-card-plate { filter: brightness(1.08); } }

/* ---- two shapes that are not a card grid ---------------------------------
   Reported twice, first on the Studio and then on WebOS: "a lot of boxes in
   every section". The Studio had seven consecutive card grids and WebOS has
   six, one of them eight boxes wide. A grid is right for a portfolio and for
   a price comparison, and wrong for almost everything else it was being used
   for, because a row of identical boxes says "here are n things" and most
   sections are saying something with more shape than that.

   So the kit has two more shapes. Both were written for one section of the
   Studio first and generalised here rather than copied, which is the whole
   lesson of .btn, .cta-row, .legal-table and .market-switch.

   ROWS: a list where each item takes a rule in its own hue. For a set of
   alternatives the reader scans to find themselves in, or a long set of
   things a card grid would turn into a wall. Eight of anything is a list.

   BAND: columns divided by hairlines and nothing else. For a set of parts
   that are one thing: four jobs in one chain, three stages of one process.
   Separate boxes argue the opposite of that.
   ------------------------------------------------------------------------ */

.tl-rows { list-style: none; margin: 26px 0 0; padding: 0; }
.tl-rows > li { position: relative; padding: 18px 0 18px 22px;
                border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }
.tl-rows > li:first-child { border-top: 0; padding-top: 4px; }
/* The hue is a fill here, on a 3px rule, and the label beside it takes the
   ink value of the same hue. Standing rule 4: they are chosen together. */
.tl-rows > li::before { content: ''; position: absolute; left: 0; top: 20px; bottom: 20px;
                        width: 3px; border-radius: 2px;
                        background: var(--pc, var(--cta)); }
.tl-rows > li:first-child::before { top: 6px; }
.tl-row-kind { display: block; font-size: 11px; font-weight: 800; letter-spacing: .14em;
               text-transform: uppercase; color: var(--pc-ink, var(--cta-text, var(--cta)));
               margin-bottom: 5px; }
.tl-rows strong { display: block; font-size: 17px; letter-spacing: -.01em; margin-bottom: 4px;
                  color: var(--text, var(--ink)); }
.tl-row-lead { display: block; color: var(--muted); font-size: 15.5px; line-height: 1.6;
               max-width: 62ch; }
/* Two columns once there are enough of them that one column runs long. Opt
   in, because a list of four in two columns is a grid again. */
@media (min-width: 900px) {
  .tl-rows.is-split { display: grid; grid-template-columns: 1fr 1fr; column-gap: 40px; }

  /* The TOP ROW is two items, not one.
     Reported 28 September on WebOS: "Messaging Clarity is not aligned
     properly, the bar seems to have more height than others and the text is
     not aligned." Both were the same cause. In one column the first item is
     the top of the list, so it drops its rule and its padding; in two
     columns the top of the list is items one AND two, and only the first was
     getting it. The right column therefore began with a rule above it and
     fourteen more pixels of padding, so its label sat lower than the left
     one's and the left one's hue bar, which is positioned off the padding,
     ran taller.
     There was already a rule here meaning to fix this and it never matched
     anything: li:nth-child(2):first-of-type is false by definition, because
     the first of the type is the first child. A selector that cannot match
     is worse than no selector, because the fault looks addressed. */
  .tl-rows.is-split > li:nth-child(-n+2) { border-top: 0; padding-top: 4px; }
  .tl-rows.is-split > li:nth-child(-n+2)::before { top: 6px; }
}

/* A question on the left that stays put, and the rows beside it. For a
   section whose heading is a question the list answers. */
.tl-split { display: grid; gap: clamp(22px, 3vw, 46px); align-items: start; }
@media (min-width: 900px) {
  .tl-split { grid-template-columns: minmax(270px, .85fr) 1.15fr; }
  .tl-split-side { position: sticky; top: 96px; }
}
.tl-split-side .lede { max-width: 40ch; }
.tl-split-side .fine { margin-top: 18px; }

.tl-band { display: grid; gap: 0; grid-template-columns: 1fr; margin-top: 26px;
           border-top: 1px solid var(--line-soft, var(--line2, var(--line)));
           border-bottom: 1px solid var(--line-soft, var(--line2, var(--line))); }
@media (min-width: 700px)  { .tl-band { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .tl-band.is-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .tl-band.is-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.tl-band > * { display: flex; flex-direction: column; gap: 10px; padding: 26px 24px 24px;
               background: none; border: 0; text-decoration: none;
               border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }
.tl-band > :first-child { border-top: 0; }
@media (min-width: 700px) {
  .tl-band > * { border-top: 0; border-left: 1px solid var(--line-soft, var(--line2, var(--line))); }
  .tl-band > :nth-child(2n + 1) { border-left: 0; }
  .tl-band > :nth-child(n + 3) { border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }
}
@media (min-width: 1100px) {
  .tl-band.is-4 > * { border-top: 0; border-left: 1px solid var(--line-soft, var(--line2, var(--line))); }
  .tl-band.is-4 > :first-child { border-left: 0; }
  .tl-band.is-4 > :nth-child(2n + 1) { border-left: 1px solid var(--line-soft, var(--line2, var(--line))); }
  .tl-band.is-4 > :nth-child(n + 3) { border-top: 0; }
}
@media (min-width: 1000px) {
  .tl-band.is-3 > * { border-top: 0; border-left: 1px solid var(--line-soft, var(--line2, var(--line))); }
  .tl-band.is-3 > :first-child { border-left: 0; }
  .tl-band.is-3 > :nth-child(n + 3) { border-top: 0; }
}
.tl-band h3, .tl-band strong { font-family: 'Outfit', system-ui, sans-serif;
  font-size: 18px; letter-spacing: -.02em; margin: 0; color: var(--text, var(--ink)); }
.tl-band p, .tl-band span { color: var(--muted); font-size: 15.5px; line-height: 1.55; margin: 0; }
/* The ordinal leads in the item's hue. One coloured thing per column: the
   note underneath was in the same hue and at the same weight, so it read as
   a second heading at the bottom of the column and competed with the real
   one. It is a footnote, so it looks like one. */
.tl-band .tl-band-n { font-family: 'Outfit', system-ui, sans-serif; font-weight: 800;
  font-size: 13px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--pc-ink, var(--cta-text, var(--cta))); }
.tl-band .tl-band-note { margin-top: auto; padding-top: 12px; font-size: 14.5px;
  line-height: 1.5; color: var(--faint); }
@media (hover: hover) { a.tl-band-item:hover { background: var(--bg2, var(--surface)); } }

/* ---- hero artwork --------------------------------------------------------
   Every hero in the estate except the group's was an empty right half.
   Reported 27 September: "all the hero sections with left aligned text can
   have a visual element on the right. Try options."

   FIVE MOTIFS, AND WHAT DECIDES BETWEEN THEM

   The group site already had one: growth rings, because its headline is
   about growing. That is the standard these are held to. A hero image here
   is not decoration to fill a gap, it is the page's own idea drawn once, so
   each motif belongs to a kind of page and says what that page says.

   All five are CSS. No file is fetched, nothing blocks paint and nothing
   goes stale when the artwork changes, which is the fault standing rules 15
   and 16 are both about. Only transform and opacity animate, so the whole
   thing stays on the compositor: a blur, a width or a box-shadow animating
   at this size is what halved the frame rate on the ambient layer.

   Every one degrades under prefers-reduced-motion to a still composition
   rather than to nothing, because the still frame is the drawing.

   They are drawn at the hero's ink and the brand spectrum, so they read on
   both themes without a second set of colours.
   ------------------------------------------------------------------------ */

.hero-grid { display: grid; grid-template-columns: 1fr; gap: 40px; align-items: center; }
@media (min-width: 980px) {
  .hero-grid { grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); gap: 56px; }
}
.hero-copy { min-width: 0; }

/* Hidden below the breakpoint. On a phone the artwork would push the
   headline and the buttons below the fold, and the headline is the page. */
.hero-art {
  position: relative; display: none; place-items: center;
  aspect-ratio: 1; width: 100%; max-width: 360px; margin-inline: auto;
  color: var(--text, var(--ink));
}
@media (min-width: 980px) { .hero-art { display: grid; } }

/* The spectrum, once, so a motif can take it as a conic or a linear sweep. */
.hero-art { --ha-spectrum: #C80018, #FF8C00, #FFA500, #00A86B, #0066B2, #00A3E0, #C80018; }

/* ---- 1. rings ------------------------------------------------------------
   Growth. Rings leaving a centre, thinning as they widen, so the outer ones
   read as the faint edge of something rather than six equal circles.
   A border cannot take a gradient, so each ring is a conic gradient masked
   to an annulus; the mask is static and only the scale animates. */
/* THE LEVERS A VARIANT TURNS, and why they are custom properties.
   A family is one construction configured differently per page, not nine
   drawings. So the things that differ between placements are declared here
   with today's values as defaults, and lib/hero-art.js sets them per variant
   in the markup. Nothing renders differently until something sets one.
     --ha-spin  where the spectrum starts, so a placement can lead on a
                different hue without a second gradient
     --ha-dur   the pace, so nine of these on one site are not in lockstep */
/* A DEFAULT BELONGS IN THE FALLBACK, NOT ON THE ELEMENT.
   These were declared here as `--ha-spin: 210deg`, which looked equivalent to
   a fallback and is the opposite of it: a declaration on this element beats
   the value inherited from .hero-art, so every configuration set by
   lib/hero-art.js was silently ignored and every placement drew the default.
   The style attribute was right, the computed value never changed, and a
   check comparing markup passed throughout. Found on 30 September by
   measuring a rotation rather than reading an attribute. */
.ha-rings { position: absolute; inset: 0; display: grid; place-items: center; }
.ha-rings i {
  position: absolute; display: block; width: 100%; height: 100%;
  border-radius: 50%;
  background: conic-gradient(from var(--ha-spin, 210deg), var(--ha-spectrum));
  /* The annulus was 1.6px and read as a hairline at 360px, "almost
     missable". 4px, and because each ring is drawn at scale(.1) and grown to
     scale(1), the stroke is scaled with it: it starts thick and thins as it
     widens, which is the shape of the thing anyway. Reported 27 September. */
  -webkit-mask: radial-gradient(closest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
          mask: radial-gradient(closest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
  opacity: 0; transform: scale(.1);
  animation: ha-grow var(--ha-dur, 14s) cubic-bezier(.22,.65,.3,1) infinite;
  animation-delay: calc(var(--i) * -2.33s);
  will-change: transform, opacity;
}
@keyframes ha-grow {
  0%   { transform: scale(.1); opacity: 0; }
  10%  { opacity: 1; }
  35%  { opacity: .8; }
  65%  { opacity: .5; }
  85%  { opacity: .22; }
  100% { transform: scale(1); opacity: 0; }
}
.ha-seed { position: relative; width: 58px; height: 58px; opacity: .85; }
.ha-seed svg { width: 100%; height: 100%; }

/* ---- the Studio's construction: repetition, not nesting -------------------

   One circle at one size, stepped along an axis. Deliberately the coarsest
   possible difference from the two families that nest: Turtle Labs nests and
   closes on its seed, Natural Intelligence nests and opens, and the Studio
   does not nest at all. With six placements and no accent of its own, the
   Studio needs the difference to be legible at a glance rather than in the
   detail.

   --ha-step   the gap between circles, as a share of one circle
   --ha-tilt   the axis they walk along
   --ha-size   how big one circle is  */
.ha-track { position: absolute; inset: 0; display: grid; place-items: center;
            /* Defaults live in each var()'s fallback, for the reason above
               the rings. -24deg is the Studio home cadence, so a placement
               that sets nothing looks like the home page; the family runs
               from -40deg to 90deg, the steep rake on the portfolio to the
               near-vertical axis the rates page needs so the drawing sits
               beside a table rather than behind it. */ }
/* THE RUN NEEDS A WIDTH OR IT CANNOT BE CENTRED.
   The circles are absolutely positioned, so the span they sit in measured
   zero and place-items:center centred nothing: the run started in the middle
   and walked off the right edge. Its width is the first circle plus each
   step after it, which is arithmetic the browser can do as long as it is
   told how many there are. lib/hero-art.js sets --ha-count. */
.ha-track > span { display: block; position: relative; height: var(--ha-size);
                   width: calc(var(--ha-size, 92px) +
                     (var(--ha-count, 5) - 1) * var(--ha-size, 92px)
                     * var(--ha-step, 1.35) * 0.5);
                   transform: rotate(var(--ha-tilt, -24deg)); }
.ha-track i {
  position: absolute; top: 0; display: block;
  width: var(--ha-size, 92px); height: var(--ha-size, 92px); border-radius: 50%;
  border: 2px solid currentColor;
  left: calc(var(--i) * var(--ha-size, 92px) * var(--ha-step, 1.35) * 0.5);
  opacity: 0;
  animation: ha-walk var(--ha-dur, 12s) cubic-bezier(.3,.7,.3,1) infinite;
  animation-delay: calc(var(--i) * -1.4s);
  will-change: opacity, transform;
}
@keyframes ha-walk {
  0%   { opacity: 0; transform: translateX(-14px); }
  14%  { opacity: .9; }
  60%  { opacity: .45; }
  100% { opacity: 0; transform: translateX(14px); }
}
@media (prefers-reduced-motion: reduce) {
  .ha-track i { animation: none; opacity: .5; transform: none; }
}

/* ---- 2. signal -----------------------------------------------------------
   Noise resolving into one line. For a page whose argument is that the
   problem is clarity: six tracks, each flatter and slower than the one above
   it, and a single clean line at the bottom in the spectrum.

   An SVG, because a zigzag is a shape. The first version scaled a 2px bar on
   its Y axis, which makes a bar thicker and not jagged. Only the group's
   transform animates, so it is still one composited layer. */
.ha-signal { position: absolute; inset: 14% 4%; width: 92%; height: 72%; }
.ha-sig-tracks {
  transform-origin: center bottom;
  animation: ha-settle 11s ease-in-out infinite;
  will-change: transform;
}
@keyframes ha-settle {
  0%, 100% { transform: scaleY(1); }
  50%      { transform: scaleY(.46); }
}

/* ---- 3. lattice ----------------------------------------------------------
   A grid of dimensions, most quiet and a few lit. For anything that scores
   or audits: the page says "we measure across n things", and this is n
   things, some of which are the answer.

   The sweep is one gradient moving across a masked grid rather than fifty
   animated cells, so the cost is one layer whatever the grid size. */
.ha-lattice { position: absolute; inset: 8%; display: grid;
              grid-template-columns: repeat(7, 1fr); gap: 10px; }
.ha-lattice i {
  display: block; border-radius: 4px; background: currentColor;
  opacity: .1; aspect-ratio: 1;
}
.ha-lattice i.is-on {
  opacity: 1;
  animation: ha-pulse 6s ease-in-out infinite;
  animation-delay: calc(var(--i) * -.45s);
  will-change: opacity, transform;
}
@keyframes ha-pulse {
  0%, 100% { opacity: .22; transform: scale(.88); }
  50%      { opacity: 1;   transform: scale(1); }
}

/* ---- 4. arc --------------------------------------------------------------
   A score, drawn as the thing a score is: an arc with a gap, filled part of
   the way round. For the audit and the report, where the page is about a
   number out of a hundred.

   Two conic gradients on one square, both masked to the same annulus: the
   track underneath and the fill on top, the fill clipped to its own sweep. */
.ha-arc { position: absolute; inset: 14%; border-radius: 50%; }
.ha-arc::before, .ha-arc::after {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  -webkit-mask: radial-gradient(closest-side, transparent 71%, #000 71.5%);
          mask: radial-gradient(closest-side, transparent 71%, #000 71.5%);
}
.ha-arc::before { background: currentColor; opacity: .12; }
.ha-arc::after {
  background: conic-gradient(from 135deg, var(--ha-spectrum));
  -webkit-mask:
    radial-gradient(closest-side, transparent 71%, #000 71.5%),
    conic-gradient(from 135deg, #000 var(--ha-fill, 74%), transparent 0);
  -webkit-mask-composite: source-in;
  mask:
    radial-gradient(closest-side, transparent 71%, #000 71.5%),
    conic-gradient(from 135deg, #000 var(--ha-fill, 74%), transparent 0);
  mask-composite: intersect;
  animation: ha-sweep 7s cubic-bezier(.3,.7,.3,1) infinite;
  will-change: opacity;
}
@keyframes ha-sweep { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }
.ha-arc-n {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-family: 'Outfit', system-ui, sans-serif; font-weight: 800;
  font-size: clamp(38px, 5vw, 58px); letter-spacing: -0.04em; line-height: 1;
}

/* ---- 5. planes -----------------------------------------------------------
   Layers offset in depth, each a card edge in a different hue. For anything
   made of connected parts: apps, a suite, a stack. What it has to say is
   that these are separate and aligned, which is the whole argument. */
.ha-planes { position: absolute; inset: 14%; display: grid; place-items: center; }
.ha-planes i {
  position: absolute; inset: 0; border-radius: var(--r-lg, 18px);
  /* 1.5px at half opacity was a hairline of a hairline. 3px, and the falloff
     is gentler, so the back planes are still there rather than implied. */
  border: 3px solid currentColor; opacity: calc(.92 - var(--i) * .13);
  transform: translate3d(calc(var(--i) * 16px), calc(var(--i) * -16px), 0)
             scale(calc(1 - var(--i) * .05));
  animation: ha-drift 11s ease-in-out infinite;
  animation-delay: calc(var(--i) * -1.4s);
  will-change: transform;
}
.ha-planes i:nth-child(1) { border-color: #C80018; }
.ha-planes i:nth-child(2) { border-color: #FF8C00; }
.ha-planes i:nth-child(3) { border-color: #00A86B; }
.ha-planes i:nth-child(4) { border-color: #0066B2; }
.ha-planes i:nth-child(5) { border-color: #00A3E0; }
@keyframes ha-drift {
  0%, 100% { transform: translate3d(calc(var(--i) * 16px), calc(var(--i) * -16px), 0)
                        scale(calc(1 - var(--i) * .05)); }
  50%      { transform: translate3d(calc(var(--i) * 22px), calc(var(--i) * -22px), 0)
                        scale(calc(1 - var(--i) * .04)); }
}

/* On a bright ground the spectrum sits back a little. It was written for the
   rings on the group site and it is true of every motif that paints the
   spectrum: at full saturation on a near-white page they come forward of the
   headline, which is the one thing the artwork must not do. Both theme
   selectors, because a token stamped only one way is how the whole estate
   once rendered dark on light machines. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) :is(.ha-rings i, .ha-lattice i.is-on, .ha-arc::after, .ha-planes i) {
    filter: saturate(1.1) brightness(.92);
  }
}
:root[data-theme="light"] :is(.ha-rings i, .ha-lattice i.is-on, .ha-arc::after, .ha-planes i) {
  filter: saturate(1.1) brightness(.92);
}

/* Motion is the point in all five, so with it off each holds a still frame
   that is a composition rather than an empty box. */
@media (prefers-reduced-motion: reduce) {
  .ha-rings i {
    animation: none;
    transform: scale(calc(.16 + var(--i) * .17));
    opacity: calc(.95 - var(--i) * .12);
  }
  .ha-signal i, .ha-lattice i.is-on, .ha-arc::after, .ha-planes i { animation: none; }
  .ha-lattice i.is-on { opacity: 1; transform: none; }
  .ha-arc::after { opacity: 1; }
}

/* ---- the floor under body text -------------------------------------------
   Set 27 September: "keep some minimal visibilty font size across the
   website, if someone can't read it why keep it?"

   A sweep found 456 rules under 15.5px, which is not a page problem and was
   never going to be fixed one page at a time. Two floors, applied to every
   stylesheet in the estate:

     PROSE, a sentence somebody reads, is at least 15.5px.
     SECONDARY PROSE, the line under a thing, is at least 14.5px.

   LABELS ARE NOT PROSE and keep their size. An eyebrow, a chip, a step
   number, a date, a nav item and the text inside a control are words you
   scan rather than sentences you read, and setting them at reading size
   makes them compete with the thing they label. The sweep skipped anything
   whose selector names one, which is why 155 rules moved and not 456.

   These custom properties are the floors, so a new rule can say what it
   means rather than repeating a number. verify/checks/readable-type.js
   measures the rendered page, not this file, because a rule that never
   loads is how the legal documents came to be set at the browser's default.
   ------------------------------------------------------------------------ */
:root {
  --fs-prose: 15.5px;
  --fs-second: 14.5px;
  --fs-label:  11.5px;
}

/* ---- a legal document ----------------------------------------------------
   Long text somebody has to be able to read, cite and link to. It lived in
   ni.css until 27 September and every site in the estate publishes these, so
   the Studio, BrandOS, Shell and the group site rendered them at the
   browser's own sizes. Reported as "text is too small" on the Studio, and it
   was: the rules that size it were never loaded there.

   THE SCROLL OFFSET IS THE OTHER HALF, and it is the part that reads as a
   broken link rather than as a style. A contents link jumps the heading to
   the very top of the viewport, which on every site here is underneath a
   sticky header 65 to 73 pixels tall. The heading goes behind it and the
   reader lands looking at the middle of the section, with the NEXT heading
   in view. Reported as "when I click on Who holds it it scrolls down to the
   next section": it had not, it had scrolled exactly right and hidden the
   answer. 96px clears the tallest header with room to see the heading sit
   down rather than appear flush against the bar.
   ------------------------------------------------------------------------ */
.legal-sec { padding-top: 0; }
.legal-sec h2 { scroll-margin-top: 96px; font-size: clamp(22px, 2.7vw, 28px);
                margin-bottom: 14px; }
/* Anything else a document links to, for the same reason. */
.legal-doc [id], .legal-sec[id] { scroll-margin-top: 96px; }
.legal-lead { font-size: 18px; line-height: 1.7; color: var(--prose, var(--muted));
              max-width: var(--measure, 68ch); margin: 0 0 18px; }
.legal-p { font-size: 17px; line-height: 1.75; color: var(--prose, var(--muted));
           max-width: var(--measure, 68ch); margin: 0 0 16px; }
.legal-sec .bullets { font-size: 17px; line-height: 1.75;
                      color: var(--prose, var(--muted)); max-width: var(--measure, 68ch); }
.legal-sec .bullets li { margin-bottom: 10px; }
.legal-toc { border: 1px solid var(--line-soft, var(--line2, var(--line)));
             border-radius: var(--r-lg, 18px); padding: 22px 24px; }
.legal-toc ol { margin: 0; padding-left: 20px; columns: 2; column-gap: 30px; }
.legal-toc li { margin-bottom: 8px; font-size: 15.5px; }
.legal-toc a { color: var(--muted); text-decoration: none; }
.legal-toc a:hover { color: var(--text, var(--ink)); text-decoration: underline; }
@media (max-width: 640px) { .legal-toc ol { columns: 1; } }

/* ---- a table on a legal page ---------------------------------------------
   Two columns: what the row is about, and the answer. Every legal document in
   the estate uses it, and it was styled in ni.css alone, so the Studio,
   BrandOS and Shell all fell back to the browser's own <th>, which is centred
   and bold. Reported 27 September on Shell's contact page: "Where to write"
   had six labels centred against a common right edge and nothing lined up.

   Here rather than in six stylesheets, which is how .btn and .cta-row each
   ended up with three definitions that disagreed.

   The label column is fixed at 30% so the addresses start on one left edge
   down the whole table, which is the alignment the ragged version lost.
   ------------------------------------------------------------------------ */
.legal-table { overflow-x: auto; margin: 6px 0 18px; max-width: var(--measure, 68ch); }
.legal-table table { border-collapse: collapse; width: 100%; }
.legal-table th, .legal-table td {
  text-align: left; vertical-align: top; padding: 13px 18px 13px 0;
  border-top: 1px solid var(--line-soft, var(--line2, var(--line)));
  font-size: 16px; line-height: 1.6;
}
.legal-table th { color: var(--text, var(--ink)); font-weight: 700;
                  width: 30%; min-width: 150px; }
.legal-table td { color: var(--prose, var(--muted, var(--text))); }
/* Below this the two columns cannot both hold a sentence, so they stack: the
   label above its answer, which still reads as a pair. A 90px label column
   with "Your information, or a request about it" in it does not. */
@media (max-width: 560px) {
  .legal-table th, .legal-table td { display: block; width: auto; padding-right: 0; }
  .legal-table td { border-top: 0; padding-top: 2px; padding-bottom: 14px; }
}

/* ---- the brief ---- */
/* The readback wears the same corner as the recommendation card below it.
   It was the only square panel on the page, reported 27 September: a sharp
   box directly above a rounded one reads as an unstyled element rather than
   as a choice. `overflow: hidden` because the rows carry their own top
   border and a square border crossing a rounded corner is worse than no
   corner at all. */
.brief { border: 1px solid var(--line); background: var(--surface, var(--bg2, var(--card)));
  margin: 0 0 24px; border-radius: var(--r-md, 14px); overflow: hidden; }
.brief-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; padding: 14px 18px;
  border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }
.brief-row:first-child { border-top: 0; }
@media (max-width: 560px) { .brief-row { grid-template-columns: 1fr; gap: 4px; } }
.brief-q { font-size: 14.5px; color: var(--faint); margin: 0; }
.brief-a { font-size: 15.5px; font-weight: 600; margin: 0; }
.brief-empty { padding: 18px; margin: 0; color: var(--muted); }
.brief-rec { margin: 0 0 24px; }
.brief-form { margin: 26px 0 0; }
.brief-form h2 { font-size: 20px; margin: 0 0 14px; }

/* Auto-fit rather than a rigid pair, which is the version crm.css had grown
   further down the file and the better of the two: a field pair that cannot
   fit side by side stacks on its own rather than at one breakpoint chosen
   for one page. */
.two-up { display: grid; gap: 16px;
          grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); }
/* .agree is not repeated here. It is defined twice above already, once as the
   component and once by the control standard, and a third copy at the end of
   the file would silently undo the 22px checkbox of standing rule 7. */

/* The first screen of a guided flow doubles as a landing page, so it carries
   an explanation the later screens do not need. */
.g-intro { margin: 0 0 26px; padding: 18px 20px; border: 1px solid var(--line-soft, var(--line2, var(--line)));
  background: var(--surface, var(--bg2, var(--card))); }
.g-intro-lead { font-size: 15.5px; line-height: 1.6; color: var(--muted); margin: 0 0 12px; }
.g-intro-points { margin: 0; padding-left: 18px; }
.g-intro-points li { font-size: 15.5px; line-height: 1.55; color: var(--muted); margin-bottom: 7px; }

/* Used by the guided chooser on every site that renders one, and defined in
   crm.css and fyp.css only, which is how a button on the Studio came out as
   the browser's own control. */
.linkish { background: none; border: 0; padding: 0; font: inherit;
  color: var(--accent-text, var(--cta-text, var(--cta, var(--accent))));
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }

/* A field pair is laid out by the grid, so the stacked-case margins have no
   business in it. .field + .field carries a top margin for the single column
   arrangement; inside .two-up it put the right-hand field fourteen pixels
   below the left, and two labels that should read as one row did not. */
.two-up > :is(.field, .tl-field),
.tl-form .two-up > :is(.field, .tl-field) { margin: 0; }

/* ---- a fieldset is a group, not a box -----------------------------------
   A bare <fieldset> is drawn by the browser with its own inset border and a
   notch cut for the legend, which is the one control on the estate that
   looks like 1998. It was visible on /audit/start, where a radio group sat
   inside a white-outlined box in the middle of a form where every other
   control follows the standard. The fieldset stays, because a radio group
   needs a group and a legend to name it; only its decoration goes. */
fieldset.field, fieldset.is-group {
  border: 0; padding: 0; margin: 0; min-inline-size: 0;
  display: flex; flex-direction: column; gap: 8px;
}
fieldset.field > legend, fieldset.is-group > legend {
  padding: 0; float: left; width: 100%;
  font-size: 12.5px; font-weight: 700; color: var(--text);
}
/* float:left takes the legend out of the fieldset's special rendering, so
   the gap above has to come back on the first row after it. */
fieldset.field > legend + *, fieldset.is-group > legend + * { clear: both; margin-top: 8px; }

/* A choice row's text starts where its padding starts. The controls block
   centres a choice row's contents, which is right for a row whose whole
   content is one short word and wrong for a stacked list of sentences: on
   /audit/start five options read as centred captions rather than as a list
   to run your eye down. */
.tl-choices .tl-choice { align-items: stretch; text-align: left; }

/* ---- a row of options is one height --------------------------------------
   A grid sizes each row to its own tallest item, so a question whose first
   two options are short and whose last two run to a second line came out as
   two rows of different heights. Reported 25 September: "one option with
   more words will have a bigger box, another with lesser words, smaller
   box." grid-auto-rows makes every row the tallest, which is the whole
   point of laying choices out as a grid. */
.tl-choices { grid-auto-rows: 1fr; }
/* Stretch rather than hug, so the card fills the row it was given. */
.tl-choices > .tl-choice { height: 100%; }

/* Back sits last in the action row and is the quietest thing in it: it is
   the only control there that undoes rather than advances. */
.g-actions .g-back { margin-left: auto; }
@media (max-width: 560px) { .g-actions .g-back { margin-left: 0; width: auto; } }

/* ---- the progress, as steps rather than a bar ---------------------------
   A bar says "somewhere between nothing and everything". Dots say how many
   questions there are and which one this is, which is the thing somebody
   deciding whether to keep going actually wants to know. */
.g-steps { display: flex; align-items: center; gap: 7px; margin: 0 0 6px; }
.g-step-dot { width: 9px; height: 9px; border-radius: 50%; flex: none;
              background: var(--line, rgba(128,128,128,.3)); }
.g-step-dot.is-done { background: var(--cta-text, var(--cta, var(--accent))); }
.g-step-dot.is-now  { background: var(--cta, var(--accent));
                      box-shadow: 0 0 0 4px color-mix(in srgb,
                        var(--cta, var(--accent)) 22%, transparent); }

/* hidden means hidden, for this component.
   The browser applies [hidden] { display: none } as a presentational hint,
   which any author rule with a class in it outranks. .g-invalid sets
   display:flex, so the validation message was drawn on every screen of the
   brief and the audit from the moment the page loaded: a red "Pick one to
   carry on" telling somebody off before they had done anything. Reported
   26 September as "pick one is always there", and it was.

   SCOPED, AND THE FIRST ATTEMPT WAS NOT. It was written as a global
   [hidden] { display: none !important }, which looked like the tidier answer
   and broke the Pathfinder: group.css carries
   .no-js .pf-step[hidden] { display: block }, which is a deliberate reversal
   that shows all three questions to somebody with scripting off. Hiding is
   sometimes meant to be undone, so a blanket rule with !important on it is
   not a tidy-up, it is a decision taken on behalf of every component that
   ever reveals something. */
.g-invalid[hidden] { display: none; }

/* The site's own version of the message the browser would have drawn.
   It appears when a submission is refused and goes away on the next change,
   which is the whole of its job: on a screen whose only controls are a set of
   options and one button, a standing instruction to pick one is noise, and
   the only useful moment for it is the moment somebody did not. */
.g-invalid { display: flex; align-items: center; gap: 9px; margin: 14px 0 0;
             padding: 11px 15px; border-radius: var(--r-md, 14px);
             background: color-mix(in srgb, var(--invalid, #E4553F) 12%, transparent);
             border: 1px solid color-mix(in srgb, var(--invalid, #E4553F) 42%, transparent);
             color: var(--text); font-size: 15.5px; }
.g-invalid::before { content: '!'; flex: none; width: 20px; height: 20px;
                     border-radius: 50%; display: grid; place-items: center;
                     background: var(--invalid, #E4553F); color: #fff; font-weight: 800; font-size: 12px; }
.tl-choices.is-missing > .tl-choice,
.g-opts.is-missing > .g-opt { border-color: color-mix(in srgb, var(--invalid, #E4553F) 55%, transparent); }

/* ---- what the brief recommends -------------------------------------------
   The payoff of six questions, previously four grey lines under a small grey
   label. A card, a rule in the accent, and numbered rows, so the eye lands
   on it rather than sliding past to the form underneath. */
.brief-rec { position: relative; overflow: hidden;
             margin: 26px 0; padding: 24px 22px 20px;
             border-radius: var(--r-md, 14px);
             background: var(--surface, var(--bg2, var(--card)));
             border: 1px solid var(--line, rgba(128,128,128,.3)); }
.brief-rec::before { content: ''; position: absolute; inset: 0 0 auto 0; height: 3px;
  background: linear-gradient(90deg,
    var(--accent-text, var(--cta-text, var(--cta, var(--accent)))), transparent 78%); }
.brief-rec .also-h { font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
                     font-weight: 800; color: var(--faint); margin: 0 0 16px; }
.rec-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.rec-list li { display: flex; gap: 14px; align-items: baseline; }
.rec-n { flex: none; font-family: 'Outfit', sans-serif; font-weight: 800; font-size: 12px;
         letter-spacing: .06em;
         color: var(--accent-text, var(--cta-text, var(--cta, var(--accent)))); }
.rec-body { display: flex; flex-direction: column; gap: 3px; }
.rec-body strong { font-size: 15.5px; color: var(--text, var(--ink)); }
.rec-body span { font-size: 15.5px; line-height: 1.55; color: var(--muted); }
.brief-rec .g-fine { margin-top: 18px; padding-top: 14px;
                     border-top: 1px solid var(--line-soft, var(--line2, var(--line))); }

/* ---- the readback, as findings rather than a receipt --------------------
   Question and answer were two paragraphs of equal weight, so what somebody
   told us looked no more important than what we asked. The question is a
   small numbered label now and the answer is the line the eye lands on. */
.brief-row { align-items: start; padding: 18px 20px; }
.brief-q { display: flex; align-items: baseline; gap: 10px;
           font-size: 12.5px; line-height: 1.45; color: var(--faint);
           letter-spacing: .01em; }
.brief-n { flex: none; font-family: 'Outfit', sans-serif; font-weight: 800;
           font-size: 11px; letter-spacing: .08em;
           color: var(--accent-text, var(--cta-text, var(--cta, var(--accent)))); }
.brief-a { font-size: clamp(16px, 1.7vw, 19px); font-weight: 700; line-height: 1.4;
           letter-spacing: -.015em; color: var(--text, var(--ink)); }
@media (max-width: 560px) { .brief-a { font-size: 17px; } }

/* ------------------------------------------------------------------
   A scale, in a book.

   One question, several buttons, and the buttons have to read as a range
   rather than as a row of separate decisions. They sit on one line at any
   width a phone has, because five numbers wrapping to two lines stops
   looking like a scale and starts looking like a list.
   ------------------------------------------------------------------ */
.rd-choice { border: 0; margin: 0 0 clamp(20px, 2.4vw, 28px); padding: 0; min-inline-size: 0; }
.rd-choice legend { display: block; padding: 0; margin: 0 0 12px; float: left; width: 100%; }

/* A QUESTION IS NOT AN EYEBROW.
   .rd-label is the deck's own field label and those are two words -
   "Principle", "One-line example" - which read correctly as small caps. A
   scale's question is a sentence: "A visitor can tell if this is for them".
   Set in the same 12px letter-spaced green it was shouted, and five of them
   down one page turned the page green, which is the fault CLAUDE.md
   describes as one coloured word reading as a page of colour. The eyebrow
   above it is already doing that job. */
.rd-choice legend .rd-label,
.rd-label.is-ask { font-size: clamp(15px, 1.4vw, 17px); font-weight: 600;
                   letter-spacing: 0; text-transform: none;
                   color: var(--text, var(--ink)); line-height: 1.4; display: block; }
.rd-choice legend + .rd-opts { clear: both; }
.rd-over { display: block; font-size: 12px; letter-spacing: .14em; text-transform: uppercase;
           color: var(--muted); margin-bottom: 4px; }

.rd-opts { display: flex; flex-wrap: wrap; gap: 8px; }

/* A 56px target with a token corner, which is the choice row of standing
   rule 7. The input is not hidden: it is the thing being clicked, and a
   display:none radio takes no focus and no keyboard. */
.rd-opt { display: inline-flex; align-items: center; gap: 10px;
          min-height: 56px; padding: 0 18px; border-radius: var(--r-md);
          border: 1px solid var(--line); background: var(--surface);
          cursor: pointer; flex: 1 1 auto; justify-content: center; }
.rd-opt input { width: 22px; height: 22px; accent-color: var(--sec, var(--cta)); margin: 0; }
.rd-opt:hover { border-color: var(--sec, var(--cta)); }
.rd-opt:has(input:checked) { border-color: var(--sec, var(--cta)); }
.rd-opt:has(input:focus-visible) { outline: 2px solid var(--sec, var(--cta)); outline-offset: 2px; }
.rd-opt span { font-size: 16px; }

/* Two options is a yes/no and wants to look like a pair, not like a scale
   stretched across the measure. */
.rd-opts:has(.rd-opt:nth-child(2):last-child) .rd-opt { flex: 0 1 180px; }

/* A range holds its line at every width. Columns rather than wrapping flex,
   because a row of five that becomes four-and-one is not a scale any more:
   the 5 reads as a different kind of answer from the 1. */
.rd-choice.is-scale .rd-opts { display: grid; grid-auto-flow: column;
                               grid-auto-columns: 1fr; gap: 8px; }

@media (max-width: 560px) {
  .rd-opt { padding: 0 10px; gap: 6px; }
  .rd-choice.is-scale .rd-opts { gap: 6px; }
  /* Under the dot rather than beside it. Side by side, five of them do not
     fit a 390px screen, and shrinking the dot would break the 22px that
     standing rule 7 sets for every tick in the estate. */
  .rd-choice.is-scale .rd-opt { flex-direction: column; gap: 2px; padding: 8px 4px; }
  .rd-choice.is-scale .rd-opt span { font-size: 14px; }
}

/* Which row of a table a box belongs to. Quieter than the column name it
   follows, because it is an address and not a question. */
.rd-row { font-weight: 500; letter-spacing: .08em; opacity: .62; margin-left: 6px; }

/* The team block under a library. A card and not a grid: `.tl-aside` is the
   estate's two-column split, and a single block wearing it laid its heading
   beside its paragraph in one column and left the other empty. */
.team-aside { margin-top: clamp(28px, 3.4vw, 44px); }
.team-aside h2 { margin: 0 0 8px; }
.team-aside p { margin: 0 0 4px; }

/* A row about a person, which has no cover to show.
   `.tl-line` is `56px | 1fr | auto` and that first column is artwork, so a
   row without a picture put the name into 56 pixels and printed it one word
   per line. Reported by looking at the page, which is the only way this kind
   of fault is ever found: the markup was reasonable and the grid was not
   told. */
.tl-line.is-plain { grid-template-columns: minmax(0, 1fr) auto; }
/* A form that follows a list of things needs to leave it, or the first label
   reads as one more row. Rule 13, in the place it turned up next. */
.tl-lines + .tl-form { margin-top: clamp(24px, 3vw, 36px); }
.tl-line.is-plain .tl-line-end form { margin: 0; }

/* ------------------------------------------------------------------
   The set, offered once more at the foot of the store.

   Reported 29 September: the page ended on a waitlist form and a footer,
   with nothing to take the set from. Art beside the words on a wide screen,
   above them on a phone, because the covers are the argument and a reader
   at the bottom of a long page has stopped reading sentences.
   ------------------------------------------------------------------ */
.set-close { padding-top: 0; }
.set-close-card { display: grid; gap: clamp(20px, 3vw, 40px); align-items: center; }
@media (min-width: 900px) {
  .set-close-card { grid-template-columns: 0.9fr 1.1fr; }
  /* The covers lead on a wide screen: they say "five books" faster than the
     heading does, and the heading is the thing that repeats. */
  .set-close-art { order: -1; }
}
.set-close-body > .tl-head { margin-top: 0; }
.set-close-art .tl-stack { margin: 0; }

/* What is added at checkout, beside the price rather than at the last screen.

   The listed figure is the same in seven of eight markets and the tax is not:
   20% in the UK, 17 to 27% across the EU, nothing in India. Quieter than the
   price, because it is a qualification and not the number. */
.tl-price-tax { margin: 6px 0 0; font-size: 13.5px; color: var(--muted);
                max-width: 46ch; }

/* ------------------------------------------------------------------
   THE PROBLEM, as light cards rather than ruled rows.

   Reported 29 September: "revert to the previous card design (light shades
   cards)". The rows are correct and were read as a list; a gap a reader is
   meant to recognise in themselves is a thing, and a thing wants an edge
   around it.

   The hue fills the card here instead of standing beside it as a 3px rule,
   which is what makes this section different from PROVEN PROCESS two below:
   that one keeps its dark cards and wears its hue as an edge and a number.
   Both carry colour, neither is mistaken for the other, which is the ask of
   29SEP-203 and 29SEP-204 together.
   ------------------------------------------------------------------ */
/* 320 and not 280, so five cards fall as three and two rather than four and
   one. A single card alone on a second row reads as one that did not fit;
   the same five in three and two read as a set. */
.tl-rows.is-cards { display: grid; gap: clamp(12px, 1.4vw, 18px); margin-top: 30px;
                    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); }
.tl-rows.is-cards > li {
  padding: clamp(18px, 2vw, 24px);
  border: 1px solid color-mix(in srgb, var(--pc, var(--cta)) 30%, var(--line));
  border-radius: var(--r-md, 14px);
  /* A wash of the hue over the page's own surface, so it lightens in the
     dark theme and tints in the light one without a second set of values. */
  background: color-mix(in srgb, var(--pc, var(--cta)) 9%, var(--surface, var(--bg2)));
}
/* The rule belonged to the row. A card has an edge already, and both at once
   reads as a card with a scar down its side. */
.tl-rows.is-cards > li::before { content: none; }
.tl-rows.is-cards > li:first-child { padding-top: clamp(18px, 2vw, 24px); }
.tl-rows.is-cards > li > strong { display: block; margin-top: 2px; }
.tl-rows.is-cards > li > p { margin: 8px 0 0; color: var(--muted); }

/* PROVEN PROCESS keeps its dark card and takes the hue as a top edge, so the
   two sections are coloured in different places. */
.tl-step { border-top: 3px solid var(--tone, var(--cta, var(--accent))); }
