/* backend/src/main/resources/static/modules_styles/pikiner_sites/components/site-content.css */

/* ==============================================
   CONTENT PATTERNS — pikiner_sites

   The recurring shapes of the two sites' pages: an eyebrow above a
   heading, a trust line under a call to action, feature traits, cards,
   numbered process steps, panels, callouts and contextual link rows.

   These live in components/ rather than in a per-page file because the
   same shapes repeat across the pages of both sites — a numbered process
   appears on the home page and on the migration page alike.
   ============================================== */

/* ==============================================
   THE PAGE IS NORMAL FLOW, DELIBERATELY

   The page wrapper was a flex column, and that quietly doubled the
   rhythm of every page: the starter sets `.section { margin:
   var(--gb-section-gap) 0 }` and relies on adjacent margins collapsing
   to one gap, which is how the token comes out at the 80-160px the
   contract intends. Flex containers do not collapse margins, so each
   join became the sum of two — 300px of empty board between sheets
   against 48px of padding inside them, and the page read as islands.

   Nothing here needed flex: no gap, no ordering, no stretching child.
   If a future layout does need it, it takes the rhythm with it and must
   restate the gap itself.
   ============================================== */

/* ==============================================
   ONE AXIS PER SECTION

   A page of these two sites is a column of reading, and everything in a
   section — the header, the paragraphs, the steps, a criteria pair, a
   table — shares that column's edges. The starter's .section-content is
   --gb-content-max-width (1200px), a LAYOUT width that was doing duty as
   a reading one: paragraphs wrapped at their own measure against the left
   edge while anything with `margin: auto` sat centred in the full 1200,
   so a section had a text axis and a block axis that did not line up.

   The rule belongs here rather than in the starter. .section-content is
   the starter's class and every module renders sections with it —
   narrowing it there would squeeze any module whose sections need the
   full width (side-by-side code panels, card grids) into a reading
   column and make them overflow. This file is delivered only to
   the pages of this module, so the module's own decision reaches its own
   pages and nothing else, without a selector to confine it.

   Sections that need the page rather than the column ask with --wide.
   ============================================== */

.section-content:not(.section-content--wide) {
  max-width: var(--rh-sheet);
}

/* Running text keeps the measure inside the wider sheet, and keeps it on
   the left axis: a paragraph set to the full width of a wide sheet runs
   past the length a line can be and still be read comfortably.

   What is NOT constrained here is deliberate — a table, a card grid, a
   numbered sequence, a FAQ, a panel and a row of links are structures
   rather than prose, and they use the whole sheet they are given.

   Selected by the absence of a class, the same way the list indent is: a
   paragraph carrying a class has been given a job by another rule, and
   the two that are still prose — the callout and the trust line — are
   narrow by their own nature. */
.section-content > p:not(.site-links),
.section-content > ul:not([class]),
.section-content > ol:not([class]) {
  max-width: var(--gb-measure);
}

/* ==============================================
   RHYTHM OF RUNNING TEXT

   The starter's reset zeroes every margin, and the module had restored
   the vertical rhythm only inside the process steps and the cards. In
   the open body of a section — most of the text on both sites —
   paragraphs and lists ran on with no space between them at all:
   measured on the technology page, every <p> and <ul> at 0 / 0. One
   rule for every running-text context, with the values the steps
   already used. Only unclassed paragraphs and lists take part: a
   classed one (link row, trust line, callout) carries its own step.
   ============================================== */

.section-content :is(p, ul, ol) + p:not([class]) {
  margin-top: var(--gb-spacing-md);
}

/* A paragraph that ends in a colon and the list under it are one unit:
   the list keeps close. What follows a list steps back to the text. */
.section-content p + :is(ul, ol):not([class]) {
  margin-top: var(--gb-spacing-sm);
}

.section-content :is(ul, ol):not([class]) + :is(p, ul, ol):not([class]) {
  margin-top: var(--gb-spacing-lg);
}

.section-content li + li {
  margin-top: var(--gb-spacing-sm);
}

/* Two lead paragraphs under a title are two paragraphs: the second
   steps down from the first. Every hero of both sites sets a pair, and
   only two of them had been given the step, each by a rule of its own. */
.section-subtitle + .section-subtitle {
  margin-top: var(--gb-spacing-md);
}

/* A titled list inside running text — "Areas of responsibility" under a
   biography — opens a step below the paragraph before it. */
.section-content :is(p, ul, ol) + :is(h3, h4).site-criteria__title {
  margin-top: var(--gb-spacing-lg);
}

/* The browser hollows the marker of a list nested in another list — a
   <ul> inside a process step, which is an <ol> — and the reader is left
   asking what a hollow circle means. It means nothing: one marker at
   every depth. */
.section-content ul:not([class]) {
  list-style-type: disc;
}

.section-content ol:not([class]) {
  list-style-type: decimal;
}

/* ==============================================
   POSTER AND DOCUMENT

   Two kinds of section. A poster — the hero and the closing call of a
   page — is a short object: a heading, a line or two, the buttons. It is
   read as one block and stands in the middle of the sheet. A document —
   everything between them — is running text with lists, tables and
   cards, and is read from its left margin, where every line starts on
   one vertical. The starter centres every section header; the document
   default here is the left one, and .site-poster on a section restores
   the centre for that section alone.

   Inside a poster only the poster's own parts move to the centre: the
   header, the lead paragraphs before any list or panel, the buttons, the
   link row, the trust line and the row of evidence. A list, a panel, a
   callout with its left rule and a card grid keep the document treatment
   even inside a poster — a closing call that carries requirements or a
   pair of cards is a poster with a document in it. A paragraph that
   introduces a list is a label for it and stays on the list's axis.

   Decided 2026-09-06 by the owner over the earlier left-only rule; the
   three variants were compared on the Migration page at 1440px.
   ============================================== */

.section-header {
  text-align: left;
}

/* The starter centres the subtitle inside the header with auto margins.
   They are what centred it; the width they applied to stays. */
.section-subtitle {
  margin-right: 0;
  margin-left: 0;
}

.site-poster .section-header,
.site-poster > .section-content > :is(h1, h2),
.site-poster > .section-content > p:not(:is(ul, ol, h3, .site-panel, .site-cards, .site-callout) ~ *, :has(+ :is(ul, ol))),
.site-poster > .section-content > .site-trust {
  text-align: center;
}

.site-poster .section-subtitle,
.site-poster > .section-content > p:not(:is(ul, ol, h3, .site-panel, .site-cards, .site-callout) ~ *, :has(+ :is(ul, ol))),
.site-poster > .section-content > .site-trust {
  margin-right: auto;
  margin-left: auto;
}

.site-poster > .section-content > .site-links {
  justify-content: center;
}

/* A callout inside a poster keeps its rule and its left-set text: the
   rule is a left edge, and centred lines beside a left edge read as a
   mistake. The box stands in the middle; the text stands at the rule. */
.site-poster > .section-content > .site-callout {
  text-align: left;
  margin-right: auto;
  margin-left: auto;
}

/* The button group is as wide as its buttons and the traits grid as wide
   as its statements; their margins put them in the middle. */
.site-poster > .section-content > :is(.btn-group, .site-traits) {
  margin-right: auto;
  margin-left: auto;
}

/* A poster that carries a card grid may close with a call below the
   cards: a subheading, a line or two, the buttons. The buttons and the
   link row already stand in the middle; the subheading and the lines
   after the grid join them. The callout keeps its rule and its edge. */
.site-poster > .section-content > .site-cards ~ :is(h3, p:not(.site-callout)) {
  text-align: center;
}

.site-poster > .section-content > .site-cards ~ p:not(.site-callout) {
  margin-right: auto;
  margin-left: auto;
}

/* ==============================================
   SPREAD — a section read as one argument

   The third kind, beside the poster and the document. A spread is a
   stretch of reasoning with a title over it and a way out under it: both
   ends centred, the running text between them justified so that its two
   edges land where the ends do. A document has a left axis it holds to
   and a ragged right; a spread has neither, and that is the whole
   difference between them.

   The text takes the sheet rather than the measure. Every other section
   starts its text 33px in from the paper's edge — the sheet's padding
   and nothing more — and a spread starts there too, so a reader coming
   down the page meets one vertical instead of two. The cost is real: at
   896px the line runs longer than --gb-measure is drawn for, and this is
   the only place on either site where the measure is lifted. It buys the
   single inset, and it was chosen knowing the price.

   Running text is justified wherever the spread holds it, and that
   includes the paragraph inside a card. The cost was measured before it
   was accepted rather than argued about: in a 282px card column the
   widest word space comes to 19px against a natural 4px — 4.75 times a
   normal space, on one line of the first card — where the section's own
   894px paragraphs never exceed 1.56 times. Narrow justified columns
   buy their flush right edge with gaps, and here the flush edge won.

   Hyphenation is the usual remedy and is NOT set. It could not be
   verified: the headless shell the project measures with ships no
   hyphenation dictionary, so `hyphens: auto` changed not one of the
   gaps above. Setting a rule whose effect this project cannot measure
   would be a rule taken on faith.

   What a spread still does not touch is structure. The card grid, a
   panel, a list, a table keep their own layout. Nor does justification
   reach a heading: only paragraphs are named, which is why a card title
   wrapping to two lines is centred over its column rather than stretched
   across it.

   Decided 2026-09-07. Two sections carry it: the editorial column of the
   Pikiner home page and the section of three cards below it.
   ============================================== */

/* The title is centred and stands one gap above the text, however the
   markup spells it — wrapped in a .section-header, or set as a bare
   heading, which is the third form these pages use and the one whose gap
   was coming out at 16px against everyone else's 24. The starter's
   header block carries 2xl because it may hold an eyebrow and a lead as
   well; a spread's title is a title alone. */
.site-spread .section-header,
.site-spread > .section-content > :is(h1, h2) {
  text-align: center;
  margin-bottom: var(--gb-spacing-lg);
}

/* A subheading is a turn inside the section's OWN running text, not the
   label of a box standing in it — which is what separates it from a step
   title, a panel title or a criteria title, and why it takes the
   section's treatment while those keep their left edge. Left alone it
   was the one thing in a centred, justified section still set to the
   left, and it read as something the rules had missed. */
.site-spread > .section-content > .site-subheading {
  text-align: center;
}

/* The measure is lifted, not merely unset: the rule at the top of this
   file caps an unclassed paragraph of a section from .section-content
   (0,2,0), so a selector shorter than this one would lose to it and read
   as though it had applied. */
.site-spread > .section-content > p:not([class]) {
  text-align: justify;
}

/* The same running text, wherever the spread holds it: in a card, in a
   numbered step, in a panel.

   Each fills the box it is in, which is the whole reason the caps are
   lifted here as well. A step carries its own 62ch measure and a panel
   the section measure, and under justification those became the very
   fault this shape exists to remove: measured on the migration section
   at 1440, a step's text stopped 243px short of its column and a
   panel's 191px short of the sheet — with a hard right edge, so the
   text ended in mid-column instead of merely running out. A spread has
   already bought the longer line once, at the top level; buying it
   again inside a step is the same purchase, not a new one.

   The list under a step's paragraph takes the same width for the same
   reason: a 620px list beneath 830px of text is the second width all
   over again, one level down.

   What `:not([class])` leaves out is the callout — a marked sentence
   rather than running text, which keeps its rule, its left edge and its
   own narrower measure, because a rule down the left of a justified
   block would claim the block was quoted rather than emphasised. */
/* Everything a structure holds fills the structure. Two separate
   questions live here and they have different answers: how wide a block
   is, and whether its right edge is flush. Width is answered for
   everything the box contains — paragraph, list, callout alike — because
   a block that stops short of a box the reader can see is the fault this
   shape exists to remove. The callout was the last one still stopping:
   its own 62ch inside a step came to 558px against the step's 894, and
   the 336px left over broke "to the Pikiner / team" across two lines
   with a hole after it, which reads as a typing mistake rather than as a
   measure. */
.site-spread > .section-content > :is(p, ul, ol),
.site-spread :is(.site-card, .site-step, .site-panel, .site-scenario) > :is(p, ul, ol) {
  max-width: none;
}

/* THE PARTS OF A HERO A SPREAD ALSO OWNS

   A hero carries three paragraphs whose class is a role rather than an
   instruction to leave them alone — the eyebrow above the heading and the
   two leads under it, plus the trust line that closes them. In a poster
   they were centred with the rest; under the spread they were the only
   things left ragged and left-set beneath a centred title, which read as
   parts the rules had not reached.

   The eyebrow is a label ON the heading, so it takes the heading's
   centre. The leads and the trust line are running text and take the
   justification, like a FAQ answer and for the same reason: the class
   names the part, it does not exempt it.

   What still keeps its own layout is the traits grid — two columns of
   marked statements are a structure, and structures fill the box a spread
   gives them rather than taking its alignment. */
.site-spread > .section-content > .site-eyebrow {
  text-align: center;
}

/* The traits grid is sized to its widest statement so a poster can centre
   it by its own margins. A spread has nothing to centre it against: it
   fills the box like every other structure, and left as fit-content it
   stopped 137px short of the sheet on the for-Pikiner-clients hero while
   filling it exactly on the home page — the same block, two widths, for
   no reason a reader could see. */
.site-spread > .section-content > .site-traits {
  width: auto;
}

/* The leads belong to the title, all of them: they name the thing before
   the body starts, and they are read with the heading rather than as the
   first paragraph under it. Centred whatever their number and whatever
   their length — one line or three.

   This started as a rule for the lead touching the title alone, with the
   rest justified. Two leads treated two ways looked like an accident
   rather than a distinction, and the simpler rule is also the shorter
   one. The trust line keeps the justification: it closes the leads, it
   does not open them. */
.site-spread > .section-content > .section-subtitle {
  text-align: center;
}

.site-spread > .section-content > .site-trust {
  text-align: justify;
}

/* A SECTION WHOSE WHOLE TEXT IS ONE PARAGRAPH IS READ AS ONE OBJECT

   Justification is for a body: several paragraphs the eye travels down,
   where flush edges hold the column together. A section that carries a
   title, a single paragraph and a way out has no body — the paragraph is
   the sentence the title needs, closer to a hero's lead than to prose,
   and it is centred with the two ends it sits between.

   Selected by there being no second unclassed paragraph, so the rule
   reads the section rather than naming one: 14 sections across the two
   sites are built this way, against 95 with a body of two or more. Two
   or three centred lines are already the site's normal — every hero lead
   is set that way. */
.site-spread
  > .section-content:not(:has(> p:not([class]) ~ p:not([class])))
  > p:not([class]) {
  text-align: center;
}

/* Flush edges are the narrower question: running text only, and in every
   box the spread holds it — including a person's biography beside their
   portrait and the longer one behind its disclosure, which are prose like
   any other. Those two need no width lifted: nothing caps them, and they
   already fill their column.

   A callout is a marked sentence, not running text, and keeps its rule,
   its left edge and a ragged right — a rule down the left of a justified
   block would claim the block was quoted rather than emphasised. The
   role line under a name is excluded the same way every classed
   paragraph is. */
.site-spread
  :is(
    .site-card,
    .site-step,
    .site-panel,
    .site-scenario,
    .site-person__bio,
    .site-bio__body
  )
  > p:not([class]),
.site-spread .site-faq__answer {
  text-align: justify;
}

/* The FAQ answer is named above because it is the one paragraph on these
   sites whose class is a NAME rather than a job. Everywhere else a class
   on a paragraph means the rules should leave it alone — a callout, a
   links row, a trust line, the role under a person's name — and
   `:not([class])` is how that is said. .site-faq__answer is none of
   those: it is the running prose of the section, and most of the text on
   a FAQ page lives in one. */

/* A LONG LIST OF SHORT ENTRIES READS IN TWO COLUMNS

   Past a dozen entries a plain list stops being read and starts being
   scanned, and one column of thirteen short lines wastes the sheet it
   sits on while making the eye travel its whole height. Two columns halve
   the travel and put the whole set in one view.

   Six is the threshold, and it is the floor rather than a preference. It
   started at twelve and came down in steps, each measured the same way:
   the share of entries that wrap to a second line inside a column is
   5-7% at six, seven, eight, ten and twelve alike. The threshold decides
   how many lists get the treatment, not how well they take it, so there
   was never a quality reason to stop higher.

   There is a reason to stop here. At five the rule starts catching lists
   of five entries and fewer — 23 of them across the two sites — which
   split into columns of two and three and read as a broken list rather
   than a scanned one. Six is the last threshold that catches none.

   A LIST WHOSE ENTRIES ARE LINKS IS NOT THIS KIND OF LIST

   The count says how many entries there are; it cannot say how long each
   one is, and that is the rule's blind spot. The one shape where it
   misses is a table of contents — an entry that opens with a link and
   continues into a sentence about where it leads, four lines of it. That
   is read down the page, in order, not scanned across two columns.

   The link is the signal, and it separates the two kinds exactly:
   measured across both sites, 455 lists of six entries or more carry no
   link in an entry, and the 2 that do are the two site maps. So the rule
   asks whether an entry is a label or a destination, rather than naming
   a page.

   The earlier attempt at this was retired — design-rules records "a list
   in two columns in one place" among the rules removed — and it was
   retired for being in ONE place. A rule that reads the list rather than
   naming the section is what that objection asked for.

   The column width is the second half of the rule, and it does two jobs.
   A column can never be narrower than a readable line, so a narrow
   screen returns the list to one column without a media query of its
   own — and so does a narrow BOX. That is why the rule can name any
   prose list a spread holds rather than only the section's own: a list
   inside a step runs the full width of the sheet and splits, while the
   same list inside a card column at 282px or a criteria column at 435px
   cannot fit two readable columns and stays as it was. The container
   decides by its width, and nothing has to enumerate which containers
   qualify. An entry is never split between columns. */
.site-spread
  ul:not([class]):has(> li:nth-child(6)):not(:has(> li > a)) {
  columns: 2 20rem;
  column-gap: var(--gb-spacing-xl);
}

.site-spread
  ul:not([class]):has(> li:nth-child(6)):not(:has(> li > a))
  li {
  break-inside: avoid;
}

/* JUSTIFICATION NEEDS WORDS TO DISTRIBUTE THE SLACK ACROSS

   Below this width it does not have them, and the flush edge is bought
   with holes instead of with spaces. Measured on the migration section
   as the ratio of the widest word space in a paragraph to the narrowest:
   2.63 at 1440 and at 1024, 3.39 at 768, 6.50 at 390 and 27.25 at 320 —
   a line carrying two words and a chasm.

   So the spread keeps its centred ends at every width and gives up only
   the justified edge, on the phone, where a ragged right is what running
   text wants anyway. 768 rather than a narrower cut because it is the
   width this file already turns the layout at, and because 3.39 is the
   last ratio that still reads as setting rather than as damage. */
@media screen and (max-width: 767px) {
  .site-spread > .section-content > p:not([class]),
  .site-spread
    :is(
      .site-card,
      .site-step,
      .site-panel,
      .site-scenario,
      .site-person__bio,
      .site-bio__body
    )
    > p:not([class]),
  .site-spread .site-faq__answer,
  .site-spread > .section-content > .site-trust {
    text-align: left;
  }
}

/* A biography beside a portrait gives up the flush edge earlier than the
   rest, because the threshold is the COLUMN and not the window. Its
   column holds 678px down to 1024 and then falls to 439 while the
   section's own text is still 655 — and the ratios follow the column,
   not the viewport: 3.05 and 3.55 at 678px against 4.46 and 7.69 at
   439px, where the section body at the same viewport is 3.15. One
   breakpoint could not answer for both widths, so this is the second. */
@media screen and (max-width: 1023px) {
  .site-spread :is(.site-person__bio, .site-bio__body) > p:not([class]) {
    text-align: left;
  }
}

/* The two ways out of a section, centred by the two different mechanisms
   their shapes need. .site-links is a flex row, so its centre is a
   main-axis decision rather than a text-align one; .btn-group is a grid
   sized to its buttons, so its own margins place it.

   A document puts both on the left axis and a poster centres them —
   which left the button group of the price section the one control on a
   spread still standing left while the link rows beside it were centred.
   A spread has no left axis to put it on; that is what a spread is.

   The child combinator is the decision, not a shortcut. It centres the
   SECTION's way out and leaves alone a control that belongs to a
   structure standing in the section — the submit button of the enquiry
   form, which sits at the foot of a column of left-set labels, fields
   and a consent line and reads as part of it. Structures keep their own
   layout inside a spread, and a form is a structure; the same reason
   step, panel and criteria titles keep their left edge. */
.site-spread > .section-content > .site-links {
  justify-content: center;
}

.site-spread > .section-content > .btn-group {
  margin-inline: auto;
}

/* A card in a spread is the spread in miniature: the title centred over
   the column, the body justified under it, the two ends of the text
   landing on the same verticals. Centred, not justified — a heading of
   two or three words stretched to the full column would read as broken
   rather than set. */
.site-spread .site-card__title {
  text-align: center;
}

/* THE RUNNING TEXT OF A ROW OF CARDS STANDS ON ONE LINE

   A card title wraps or does not, and the paragraph under it starts
   wherever that leaves it: measured across the three cards here, two
   titles take two lines and one takes one, so its text began 28.6px
   above its neighbours' — one line of the heading — at every width from
   1024 to 1440.

   Pinned the way the closing paragraph of the two preparation panels on
   this page is pinned, and the way a card's button or link row already
   is: the card is a flex column stretched to the row, so auto on the top
   margin pushes the text to the row's bottom line and the three tops
   come level with it.

   Only a paragraph that is the card's whole body — directly under the
   title, with nothing after it — is pinned. On a last paragraph in
   general the auto margin would eat the step above it whenever the row
   left no free space to absorb, and a card with two paragraphs would
   close them up. */
/* THE TEXT OF A ROW OF CARDS STARTS ON ONE LINE

   A card title wraps or does not, a card body runs long or short, and the
   text under each title used to start wherever that left it. The row's
   own grid answers it: the card takes two of the parent's rows as a
   subgrid, so every title in the row shares one row and every body starts
   on the line below it — whatever either of them measures.

   This replaced pinning the body to the bottom of the row with an auto
   top margin. That levelled the tops only while the bodies happened to be
   the same height, and the moment they were not — the three participants
   of the Gabion home page — it held the bottoms and let the tops apart by
   56px. Ten of seventeen card rows on the two sites were doing that.

   The gap between the two rows is the title's own bottom margin and
   nothing else: a row-gap here would add to it rather than replace it,
   and the step came out doubled. */
.site-spread .site-cards:not(:has(> .site-card > :nth-child(3))) > .site-card,
.site-spread
  .site-criteria:not(:has(> .site-criteria__group > :nth-child(3)))
  > .site-criteria__group {
  display: grid;
  grid-template-rows: subgrid;
  grid-row: span 2;
  row-gap: 0;
}

/* ==============================================
   A LIST KEEPS ITS MARKERS INSIDE THE COLUMN

   The reset zeroes padding on ul and ol, and a marker drawn `outside` —
   the browser default — then lands to the LEFT of the axis every other
   line starts on, hanging in the sheet's padding. The list reads as
   though it had escaped the column.

   Measured before this rule: 430 lists across the fourteen pages hung
   their markers outside the axis, against 145 that had been indented by
   hand in a page file or a component, one at a time, at three different
   values.

   A list that carries a class is a shape rather than prose — traits,
   cards, FAQ items, steps, criteria — and dresses itself. Prose lists
   carry no class, which is exactly what selects them here.
   ============================================== */

.section-content :is(ul, ol):not([class]) {
  padding-left: 1.35em;
}

/* ==============================================
   TEXT ACCENTS AROUND A HEADING
   ============================================== */

.site-eyebrow {
  font-size: var(--gb-type-caption-size);
  font-weight: var(--gb-font-weight-medium);
  line-height: var(--gb-type-caption-leading);
  letter-spacing: var(--gb-type-caption-tracking);
  color: var(--gb-text-tertiary);
  margin-bottom: var(--gb-spacing-sm);
}

.site-trust {
  font-size: var(--gb-type-secondary-size);
  line-height: var(--gb-type-secondary-leading);
  color: var(--gb-text-secondary);
  margin-top: var(--gb-spacing-md);
}

/* ==============================================
   SERVICE TRAITS — short claims under the hero

   Four marked-off statements that support the actions above them. They
   were a wrapping row: how many landed on a line depended on the length
   of the wording, the poster then centred each line, and the four marks
   stood on three different verticals — 2 + 1 + 1 on the home page. A grid
   of two equal columns puts the marks on two verticals whatever the
   words, and on one below 768px. Every hero that carries the list has
   four items; a fifth would open a third row, not break the shape. The
   block is as wide as its widest statement twice over, so in a poster it
   is centred by its own margins; in a document it sits on the axis.
   ============================================== */

.site-traits {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gb-spacing-sm) var(--gb-spacing-lg);
  width: fit-content;
  max-width: 100%;
  list-style: none;
  margin: var(--gb-spacing-xl) 0 0;
  padding: 0;
}

.site-traits li {
  font-size: var(--gb-type-secondary-size);
  line-height: var(--gb-type-secondary-leading);
  color: var(--gb-text-secondary);
  text-align: left;
  padding: 0 0 0 var(--gb-spacing-md);
  border-left: 1px solid var(--gb-border-strong);
}

/* ==============================================
   CARDS — parallel ideas of equal weight
   ============================================== */

/* Parallel ideas are set side by side, which is the whole claim the shape
   makes. The rule was missing: .site-cards carried a margin and no display,
   so the cards stacked and read as a sequence — the one thing a card grid
   is meant not to say. auto-fit rather than a fixed count, so a section
   with two cards and a section with four both come out even. */
.site-cards {
  display: grid;
  gap: var(--gb-spacing-lg);
  margin: var(--gb-spacing-xl) 0;
}

/* How many columns is the markup's decision, taken with .grid--3 or
   .grid--4 from the starter; this file used to state a track list of its
   own and quietly won, because module styles are linked last. The effect
   was only visible once the cards held portraits: an 18rem minimum needed
   912px to fit three across 894px of sheet, so a row of three equal people
   came out two-and-one — and on the team card that is a claim about which
   of them outranks the others, which the page says in words that it is
   not making.

   Below desk they stack in one column rather than falling to two: three
   cards in two columns leaves the same orphan, just further down. */
@media screen and (max-width: 1023px) {
  .site-cards {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* CARDS STACKED IN COLUMNS INSTEAD OF LAID IN ROWS

   A grid of cards puts them in rows, and a row is as tall as its tallest
   card — so one long card beside three short ones leaves the short side
   empty for the whole height of the long one. Where the set is one long
   explanation and several short pointers, the reading order is down a
   column rather than across a row, and the markup says so by asking for
   this modifier.

   Declared, not inferred: no property of the markup tells the difference
   between four alternatives of equal weight, which belong in rows, and
   one explanation with three pointers, which belongs in columns. The
   class is how the section states which it is — the same way .site-poster
   and .site-spread state what a section is.

   Columns rather than a grid with placement, so the balance follows the
   content: a card is never split between columns, and the browser fills
   until the two sides are level rather than to a count decided here. */
.site-cards--columns {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.site-cards--columns > .site-card:not(:first-child) {
  grid-column: 2;
}

/* The long card spans the rows the short ones occupy, which is what puts
   its foot on the same line as the last of theirs — and the card's own
   way out is already pinned to its bottom, so the two links meet.

   The span is read from the count: with four cards there are three short
   ones, with three there are two. Balanced columns cannot do this — they
   level the columns by content and leave the long card ending wherever
   its text ends, which is what it did here, 108px above its neighbour. */
.site-cards--columns:has(> .site-card:nth-child(4):last-child)
  > .site-card:first-child {
  grid-row: span 3;
}

.site-cards--columns:has(> .site-card:nth-child(3):last-child)
  > .site-card:first-child {
  grid-row: span 2;
}

@media screen and (max-width: 1023px) {
  .site-cards--columns > .site-card:not(:first-child) {
    grid-column: 1;
  }

  .site-cards--columns > .site-card:first-child {
    grid-row: auto;
  }
}

.site-card {
  display: flex;
  flex-direction: column;
  padding-top: var(--gb-spacing-md);
  border-top: 1px solid var(--gb-border-default);
}

.site-card__title {
  font-size: var(--gb-type-heading-3-size);
  font-weight: var(--gb-font-weight-semibold);
  line-height: var(--gb-type-heading-3-leading);
  color: var(--gb-text-heading);
  margin-bottom: var(--gb-spacing-sm);
}

/* ==============================================
   ROSTER — people read one after another, not compared

   A list rather than a row of cards, and the difference is what it says:
   cards side by side invite comparison and force every card to the height
   of the longest, while a row of the list is as tall as its own text. The
   people on this page are not being compared, and 5.P4.6 says so in words.

   Two columns like the process step above it, and for the same reason:
   a portrait beside a name, a role and several paragraphs is one row of
   two columns, not two columns of everything. Nothing here needs a wrapper
   element — the portrait takes column one, everything else falls into
   column two, exactly as the step's counter does.
   ============================================== */

.site-roster {
  margin: var(--gb-spacing-xl) 0;
}

.site-person {
  display: grid;
  grid-template-columns: 12rem minmax(0, 1fr);
  column-gap: var(--gb-spacing-lg);
  align-items: start;
  padding: var(--gb-spacing-xl) 0;
  border-top: 1px solid var(--gb-border-default);
}

.site-person:first-child {
  padding-top: 0;
  border-top: 0;
}

.site-person:last-child {
  padding-bottom: 0;
}

/* Square, cropped to the head in the file itself, so the browser is never
   asked to letterbox or to guess where the face is. aspect-ratio holds the
   box before the image arrives — the width and height attributes in the
   markup say the same thing to browsers that use them for it — so the row
   does not jump when the portrait loads.

   The hairline is the sheet's own rule and there is no radius: a
   photograph on this board is a print laid on a drawing, not a chip in an
   interface. */
/* The biography is one element rather than a run of them, so the row has
   exactly two cells. Letting the name, the role and the paragraphs fall
   into column two individually makes each of them a grid ROW, and rows are
   shared across columns: the portrait then sets the height of the first
   one and strands the name at the top of it, 140px above its own role.
   (`grid-row: 1 / -1` does not rescue that — the -1 line resolves against
   the explicit grid, and these rows are implicit.) */
.site-person__bio {
  min-width: 0;
}

/* Thirty-two by thirty-five — 192 wide by 210 — which ends the picture on
   the line that would sit under "More" if the control had one below it to
   match the rule above it. That rule stands 8px clear of the text
   (.site-bio padding-top), so its mirror is 8px under it, and the picture
   stops there.

   Four ratios were measured against the biography before this one: square
   was 40px SHORTER than its text, so the control hung past the picture and
   left a band of empty board; 3:4 overshot by 160 and moved the same hole
   under the text; 4:5 overshot by 38; 8:9 by 15.

   Stretching the picture to the row instead of fixing its ratio does not
   work, and the reason is worth keeping: an img carries its own intrinsic
   size, so it contributes to the row's height before it is asked to fill
   it — the row simply became as tall as the picture and the measurement
   looked like a match while nothing had moved.

   height: auto is not decoration. The markup carries width and height
   attributes so the box is reserved before the file arrives, and those
   become presentational hints — with both axes fixed the browser has no
   free axis left and ignores aspect-ratio. */
.site-person__portrait {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 32 / 35;
  object-fit: cover;
  border: 1px solid var(--gb-border-subtle);
}

.site-person__name {
  font-size: var(--gb-type-heading-3-size);
  font-weight: var(--gb-font-weight-semibold);
  line-height: var(--gb-type-heading-3-leading);
  color: var(--gb-text-heading);
  margin-bottom: var(--gb-spacing-xs);
}

/* The role is a label for the name above it, not a line of the biography:
   it takes the caption role and the tracking that marks a label. */
.site-person__role {
  font-size: var(--gb-type-caption-size);
  line-height: var(--gb-type-caption-leading);
  letter-spacing: var(--gb-type-overline-tracking);
  text-transform: uppercase;
  color: var(--gb-text-tertiary);
  margin-bottom: var(--gb-spacing-md);
}

/* ==============================================
   PROCESS STEPS

   The number comes from the list counter, so the markup carries no
   digits and the sequence cannot disagree with itself after an edit.

   Two columns rather than a flex row: a step is a title followed by
   several paragraphs and sometimes a list, and in a flex row every one
   of those becomes a column of its own beside the number. The grid puts
   the counter in the first column and stacks whatever the step contains
   in the second, so a step can grow without its markup needing a
   wrapper element to hold it together.
   ============================================== */

/* Steps are separated by a rule rather than by empty space. A gap wide
   enough to divide a step that runs to three paragraphs and a list is wide
   enough to break the sequence apart; a hairline divides them at any
   length and costs no vertical room. */
.site-steps {
  counter-reset: site-step;
  list-style: none;
  margin: var(--gb-spacing-2xl) 0 var(--gb-spacing-xl);
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
}

.site-step {
  counter-increment: site-step;
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  column-gap: var(--gb-spacing-md);
  align-items: start;
  padding: var(--gb-spacing-xl) 0;
  border-top: 1px solid var(--gb-border-subtle);
}

.site-step:first-child {
  padding-top: 0;
  border-top: 0;
}

.site-step:last-child {
  padding-bottom: 0;
}

.site-step > * {
  grid-column: 2;
}

.site-step::before {
  content: counter(site-step);
  grid-column: 1;
  grid-row: 1;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--gb-text-tertiary);
  font-variant-numeric: tabular-nums;
  font-weight: var(--gb-font-weight-semibold);
  font-size: var(--gb-type-caption-size);
  margin-top: 0.15em;
}

/* THE NUMBER BELONGS TO THE HEADING, NOT TO A COLUMN BESIDE IT

   A step that has a heading carries its number inside it — "1. You show
   us your product" — and the number is written in the template, not
   generated here. The counter column is then empty, so such a step is
   one column and its text starts on the section's own left edge like
   everything else.

   The number being literal is the cost of this, and it is a real one:
   the counter existed so that the markup carried no digits and a
   sequence could not disagree with itself after an edit. Now it can. A
   step inserted into the middle of a list renumbers the ones below it
   BY HAND — 85 titled steps across fourteen lists — and nothing checks
   it. The number is text now, and text is the editor's to keep true.

   Only a step with a heading. The other shape on these sites is the
   compact step — 184 of them against 85 titled — a one-line entry whose
   whole body is a .site-step__label, and there the number beside the
   line IS the shape: it has no heading to move into. Those still take
   the counter, which is why counter-reset and counter-increment stay
   above, and why the switch is made by :has() on the heading rather
   than by a modifier the markup would have to remember.

   The single column also outranks the narrow-screen column pair below
   (0,2,0 against 0,1,0), so a titled step stays one column at every
   width instead of regrowing a counter track that has nothing in it. */
.site-step:has(> .site-step__title) {
  grid-template-columns: minmax(0, 1fr);
}

.site-step:has(> .site-step__title) > * {
  grid-column: 1;
}

.site-step:has(> .site-step__title)::before {
  content: none;
}

.site-step__title {
  font-size: var(--gb-type-heading-3-size);
  font-weight: var(--gb-font-weight-semibold);
  line-height: var(--gb-type-heading-3-leading);
  color: var(--gb-text-heading);
  margin-bottom: var(--gb-spacing-md);
}


/* A step holds a title, several paragraphs and often a list. Left to the
   section width its lines run past the comfortable measure, so the text
   inside a step keeps its own. */
.site-step p,
.site-step ul {
  max-width: 62ch;
  overflow-wrap: break-word;
}

.site-step li::marker {
  color: var(--gb-text-tertiary);
}

/* Only the space is a step's business now. What used to be here — a
   size down, normal weight, secondary colour — was the callout's format
   inside a step alone, and it is the format of every callout on both
   sites now, so it lives with the mark rather than with one of the
   places the mark appears.

   The space stays because a callout after a list is a paragraph after a
   list, which the rhythm puts at 24px, and the general rule cannot
   reach it: it carries a class. */
.site-step .site-callout {
  margin-top: var(--gb-spacing-lg);
}

/* The compact variant is a list of one-line steps. It keeps the plain
   gap it always had: a hairline between entries that are each a single
   line turns the list into a table of rules. */
.site-steps--compact {
  gap: var(--gb-spacing-sm);
  margin: var(--gb-spacing-lg) 0;
}

.site-steps--compact .site-step {
  align-items: center;
  padding: 0;
  border-top: 0;
}

.site-step__label {
  color: var(--gb-text-primary);
}

/* ==============================================
   SUBHEADING — a turn inside a running text section
   ============================================== */

.site-subheading {
  font-size: var(--gb-type-heading-3-size);
  font-weight: var(--gb-font-weight-semibold);
  line-height: var(--gb-type-heading-3-leading);
  color: var(--gb-text-heading);
  margin-top: var(--gb-spacing-xl);
  margin-bottom: var(--gb-spacing-sm);
}

/* ==============================================
   PRICE — the reference figure and the range

   Set apart from the paragraphs around it so the numbers are findable,
   but deliberately not styled as an offer: the control that follows
   leads to a conversation, not to a purchase.
   ============================================== */

.site-price {
  font-size: var(--gb-type-lead-size);
  color: var(--gb-text-primary);
  padding-left: var(--gb-spacing-md);
  border-left: 2px solid var(--gb-border-strong);
  margin-bottom: var(--gb-spacing-lg);
}

/* ==============================================
   TABLE — a comparison the reader scans in two columns

   The wrapper scrolls on narrow screens instead of the page: a table
   that widens the document makes every other section scroll sideways
   with it.
   ============================================== */

.site-table {
  overflow-x: auto;
  margin: var(--gb-spacing-xl) 0;
}

/* A table is framed the way a drawing's title block is: a strong rule
   above the header and a strong rule under the last row, a plain rule
   under the header, hairlines between the rows, and under all of it the
   surface the navigation gives its active item — the one tint the site
   already uses to say "this one, here". The frame and the tint together
   say "table" before a word is read. The table stands on the text axis;
   the cells keep their padding so the text does not touch the tinted
   edge. */
.site-table table {
  width: 100%;
  border-collapse: collapse;
  border-top: 2px solid var(--gb-border-strong);
  border-bottom: 2px solid var(--gb-border-strong);
  text-align: left;
}

/* The tint lies under the rows, not under the caption: a caption is part
   of the table box, and the caption's quiet grey on the tint fell under
   the contrast threshold. On the sheet it reads as it always did. */
.site-table thead,
.site-table tbody {
  background: var(--gb-accent-subtle);
}

.site-table__caption {
  text-align: left;
  color: var(--gb-text-tertiary);
  font-size: var(--gb-type-caption-size);
  letter-spacing: var(--gb-type-caption-tracking);
  padding-bottom: var(--gb-spacing-sm);
}

.site-table th,
.site-table td {
  border-bottom: 1px solid var(--gb-border-subtle);
  padding: var(--gb-spacing-sm) var(--gb-spacing-md);
  vertical-align: top;
}

.site-table th {
  color: var(--gb-text-heading);
  font-weight: var(--gb-font-weight-semibold);
  border-bottom-color: var(--gb-border-default);
}

.site-table tbody tr:last-child :is(th, td) {
  border-bottom: 0;
}

/* ==============================================
   PANEL — a grouped aside inside a section
   ============================================== */

.site-panel {
  margin: var(--gb-spacing-lg) 0;
}

.site-panel__title {
  font-size: var(--gb-type-heading-3-size);
  font-weight: var(--gb-font-weight-semibold);
  line-height: var(--gb-type-heading-3-leading);
  color: var(--gb-text-heading);
  margin-bottom: var(--gb-spacing-sm);
}

/* A message after a form is sent — accepted, failed, throttled, not yet
   open. It is not the page's text, it is the page speaking back, and it
   takes the one mark the site has for "read this apart from the rest":
   the callout's rule on the left. */
.site-panel--status {
  padding-left: var(--gb-spacing-md);
  border-left: 2px solid var(--gb-border-strong);
  margin: var(--gb-spacing-xl) 0;
}

/* ==============================================
   CALLOUT — a sentence the page must not let slide past
   ============================================== */

/* One mark, one format, wherever it stands — in open text, in a step, in
   a panel, in a card. Before this the site set it two ways: 87 at the
   body size in medium weight and primary colour, and 4 inside steps a
   size down, at normal weight, in secondary colour. The quieter one is
   the one that stayed.

   It is set BELOW the text it interrupts rather than above it, which is
   the unusual half of the decision. The rule down the left is what says
   "read this apart"; the type does not have to shout it a second time,
   and a sentence set smaller than the paragraph around it reads as an
   aside from the page rather than as a louder page. */
.site-callout {
  padding-left: var(--gb-spacing-md);
  border-left: 2px solid var(--gb-border-strong);
  margin: var(--gb-spacing-md) 0;
  font-size: var(--gb-type-secondary-size);
  font-weight: var(--gb-font-weight-normal);
  color: var(--gb-text-secondary);
}

/* ==============================================
   CRITERIA — two opposed groups side by side
   ============================================== */

/* Two opposed groups, side by side — and side by side is the argument the
   shape exists to make. The declaration was missing: .site-criteria set a
   margin and nothing else, so "fits" and "does not fit" stacked one above
   the other and read as a sequence rather than as a choice. */
.site-criteria {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--gb-spacing-lg);
  margin: var(--gb-spacing-2xl) 0 var(--gb-spacing-xl);
}


/* The top edge says only where the two columns begin. It used to colour
   the first column with the accent as "the positive case", but the block
   is reused in twenty-seven places and in most of them the first column
   is not a positive case — a burden list, "for the AI", "what depends on
   Gabion" — and in fifteen the columns carry no title to explain the
   colour. A reader met two lines, one coloured, and asked what the
   colour meant. Both edges are neutral now; the titles do the telling. */
.site-criteria__group {
  display: flex;
  flex-direction: column;
  border-top: 2px solid var(--gb-border-strong);
  padding-top: var(--gb-spacing-lg);
}

/* The two groups are read against each other, and each may close with a
   sentence after a list of a different length. That sentence stands on
   the row's bottom line in both columns, not wherever its list ended. */
.section-content .site-criteria__group > ul + p:last-child,
.section-content .site-criteria__group > ol + p:last-child {
  margin-top: auto;
  padding-top: var(--gb-spacing-lg);
}

.site-criteria__group ul {
  margin: 0;
  padding-left: 1.25em;
}

.site-criteria__group li {
  overflow-wrap: break-word;
}

.site-criteria__group li::marker {
  color: var(--gb-text-tertiary);
}

.site-criteria__title {
  font-size: var(--gb-type-heading-3-size);
  font-weight: var(--gb-font-weight-semibold);
  line-height: var(--gb-type-heading-3-leading);
  color: var(--gb-text-heading);
  margin-bottom: var(--gb-spacing-md);
}

/* ==============================================
   SCENARIOS — "if this happens, then this follows"

   Not cards: these are condition-and-consequence pairs read in a fixed
   order, and a grid of equal tiles would invite scanning them as
   interchangeable options. They read as subsections of the running
   text — a title that names the condition, the consequence under it —
   and take the subsection's rhythm: a step above each title, nothing
   else. Each used to carry a 3px rule down its left edge; thirteen of
   them in one section made thirteen tall lines that said nothing the
   title "If …" had not said.
   ============================================== */

.site-scenarios {
  display: flex;
  flex-direction: column;
  gap: var(--gb-spacing-xl);
  margin: var(--gb-spacing-xl) 0;
}

/* Running text inside a scenario or a panel keeps the measure the
   section's own paragraphs keep: the measure rule above reaches only the
   section's direct children, and a paragraph one level down ran the full
   width of the sheet beside paragraphs that did not. */
.site-scenario p,
.site-scenario ul,
.site-scenario ol,
.site-panel p,
.site-panel ul,
.site-panel ol {
  max-width: var(--gb-measure);
}

.site-scenario__title {
  font-size: var(--gb-type-heading-3-size);
  font-weight: var(--gb-font-weight-semibold);
  line-height: var(--gb-type-heading-3-leading);
  color: var(--gb-text-heading);
  margin-bottom: var(--gb-spacing-sm);
}

/* ==============================================
   FAQ — questions that answer on demand

   Built on native <details>/<summary>. Keyboard operation, focus order,
   the expanded/collapsed state announced to a screen reader and opening
   on navigation to a closed item's anchor all come from the element
   itself; a scripted accordion would have to reimplement every one of
   them and would stop working when scripts do not load.

   Each item carries a stable id, so an answer can be linked to directly
   and the address keeps working across edits to the list.
   ============================================== */

.site-faq {
  display: flex;
  flex-direction: column;
  gap: var(--gb-spacing-sm);
  margin: var(--gb-spacing-xl) 0;
}

.site-faq__item {
  border-top: 1px solid var(--gb-border-default);
}

.site-faq__question {
  padding: var(--gb-spacing-md) 0;
  font-weight: var(--gb-font-weight-semibold);
  color: var(--gb-text-heading);
  cursor: pointer;
}

.site-faq__answer {
  padding: 0 0 var(--gb-spacing-md);
  color: var(--gb-text-secondary);
}

/* ==============================================
   BIO — the long version of a team card, on demand

   Native disclosure again, for the same reasons as the FAQ. Kept apart
   from it by name: a card that expands is not a question, and one day
   the two will want to look different.
   ============================================== */

.site-bio {
  margin-top: var(--gb-spacing-md);
  border-top: 1px solid var(--gb-border-subtle);
  padding-top: var(--gb-spacing-sm);
}

.site-bio__toggle {
  font-size: var(--gb-type-secondary-size);
  font-weight: var(--gb-font-weight-medium);
  color: var(--gb-accent-primary);
  cursor: pointer;
}

.site-bio__body {
  margin-top: var(--gb-spacing-sm);
  color: var(--gb-text-secondary);
}

/* ==============================================
   CALLS TO ACTION — a step below the text, on the axis

   The starter's .btn-group carries no margin and the prose here carries
   no margin-bottom, so a button sat flush against the paragraph above
   it; the gap was restated page by page (home, migration) and missing on
   the other twenty-seven groups. --spaced pushed a pair of buttons to the
   opposite edges of the sheet with 300–480px of nothing between them, and
   --centered never took effect: --spaced is declared after it in the
   starter and wins. Neither is used on these pages now. One rule: every
   group starts a step below the text, on the left axis, buttons side by
   side one gap apart and equal in width; below 768px they stack
   full-width.

   Equal width: two buttons in one row are the same size, whatever their
   labels — a primary next to a narrower secondary reads as two objects
   of different rank twice over, once by colour and once by size. A grid
   sized to its content with 1fr columns gives that for free: every
   column takes the width of the widest label, and the group is only as
   wide as its buttons, so it sits on the axis or in the middle with its
   own margins rather than by filling the sheet. Between 560 and 767px two
   large buttons of equal width no longer fit side by side, so the stack
   threshold is the sheet's own narrow one.
   ============================================== */

.btn-group {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  width: fit-content;
  gap: var(--gb-spacing-md);
  margin-top: var(--gb-spacing-xl);
}

/* The starter keeps a button's label on one line. Inside a group the
   column may be narrower than the label — a long label on a narrow
   phone — and then the label wraps rather than pushing the button past
   the sheet; at any width where it fits, nothing changes. */
.btn-group .btn {
  white-space: normal;
}

/* A card ends with its way out — a button, or a row of links where there
   is no button — and the cards of a row are compared side by side: the
   tallest card sets the row, and every way out stands on the row's
   bottom line regardless of how long its column ran. The auto margin
   takes the free space; the padding keeps the step above it in the card
   that has none. Links that follow a button keep their own step and
   travel with it. */
.site-card > .btn-group,
.site-card > .site-links:not(.btn-group + .site-links) {
  margin-top: auto;
}

.site-card > .btn-group {
  padding-top: var(--gb-spacing-xl);
}

.site-card > .site-links:not(.btn-group + .site-links) {
  padding-top: var(--gb-spacing-lg);
}

/* ==============================================
   CONTEXTUAL LINKS — the way out of a section
   ============================================== */

.site-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--gb-spacing-lg);
  margin-top: var(--gb-spacing-lg);
}

.site-link {
  font-size: var(--gb-type-secondary-size);
  font-weight: var(--gb-font-weight-medium);
  color: var(--gb-accent-primary);
  text-decoration: none;
}

.site-link:hover {
  color: var(--gb-accent-hover);
  text-decoration: underline;
}

/* ==============================================
   MOBILE (< 768px)
   ============================================== */

@media screen and (max-width: 767px) {
  /* A smaller portrait, so the biography beside it keeps a readable
     column. */
  .site-person {
    grid-template-columns: 11rem minmax(0, 1fr);
    column-gap: var(--gb-spacing-md);
  }

  /* The counter moves above the text instead of beside it: at this width
     a two-column step leaves the text a column too narrow to read. */
  .site-step {
    grid-template-columns: 2.5rem minmax(0, 1fr);
    column-gap: var(--gb-spacing-sm);
    padding: var(--gb-spacing-lg) 0;
  }

  .site-step::before {
    width: 1.75rem;
    height: 1.75rem;
    font-size: var(--gb-type-caption-size);
  }

  .site-step ul {
    padding-left: 1.15em;
  }



  /* A table on a phone keeps its columns but loses the slack: tighter
     cells and the secondary size let three columns stand in 309px; a
     wider table still scrolls inside its wrapper, not the page. */
  .site-table th,
  .site-table td {
    padding: var(--gb-spacing-xs) var(--gb-spacing-sm);
    font-size: var(--gb-type-secondary-size);
    line-height: var(--gb-type-secondary-leading);
  }

  /* One column: two groups side by side at this width leaves each a
     column too narrow to read. */
  .site-criteria {
    grid-template-columns: minmax(0, 1fr);
    margin-top: var(--gb-spacing-xl);
  }

  .site-criteria__group {
    padding-top: var(--gb-spacing-md);
  }

  .site-criteria__group ul {
    padding-left: 1.1em;
  }
}

/* ==============================================
   NARROW — the roster stacks

   Below this width a portrait beside the text leaves the text too little
   to be read, whatever the portrait's size; the row becomes a portrait
   above a biography. Measured at 480px: the biography squeezed into a
   224px column ran 370px tall against a 220px picture, which put the
   picture 150px clear of its own "More" — the same hole the desk layout
   had, on the other side.
   ============================================== */

@media screen and (max-width: 639px) {
  .site-person {
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--gb-spacing-md);
  }

  /* Full column width. Held at 8rem it was a third of an already small
     screen — small enough that the face stopped carrying, which is the one
     job the photograph has. */
  .site-person__portrait {
    width: 100%;
  }
}

@media screen and (max-width: 767px) {
  .btn-group {
    grid-auto-flow: row;
    width: 100%;
  }

  .site-traits {
    grid-template-columns: minmax(0, 1fr);
    width: 100%;
  }
}
