/* ═══════════════════════════════════════════════════════════════════════
 * Page sections.
 * ───────────────────────────────────────────────────────────────────────
 * One block of rules per StreamField block in core/blocks.py, in the same
 * order, so a section's markup, its Python and its CSS are found together.
 *
 * Mobile first, as in simplelaw.css. The desktop rules are collected in
 * one `min-width: 1025px` query at the foot of each section rather than
 * scattered, so the phone layout can be read straight through.
 *
 * Every section is rendered inside a gridlayout cell (`.gl-cell`), which
 * is a column flex container. Sections therefore do not set their own
 * background or full-bleed behaviour — the editor chooses those in the
 * layout canvas, and tokens.css maps them onto the grid's --gl-* hooks.
 * What lives here is the section's *internal* layout only.
 *
 * Seeded sections are placed with pad "none" so the vertical rhythm comes
 * from --space-section below rather than from the grid's padding; the two
 * would otherwise compound.
 * ═══════════════════════════════════════════════════════════════════════ */

/* ── Section width ─────────────────────────────────────────────────────
   base.html puts the grid inside the page column, which is what makes a
   section with a background an inset panel rather than a full-width
   stripe. The cost was that every section was then stuck at that width
   with no way out — the design runs its card rows much closer to the
   screen edge than a reading column allows.

   Chosen per section in the layout editor (SECTION_WIDTHS in
   core/blocks.py). `margin-inline: calc(50% - 50vw)` is the standard
   break-out: 50% is half this element's own width, so the sum is exactly
   the distance from its edge to the viewport's — the same arithmetic
   gridlayout uses for a full-bleed background, and it carries the same
   condition, that the cell is full width and therefore centred.

   --section-edge-gap is the distance from the section's *content* edge to
   the viewport edge in whichever mode is active. It is declared here so
   the hero's bleeding image can read it instead of recomputing the same
   sum a second way and drifting out of step with it. */

/* Declared on the grid, not on `.section`, so every block root inherits
   it whatever it calls itself. The CTA band's root is `.cta-band` and not
   `.section` at all — declaring these on `.section` left it with an
   undefined variable and no padding. */
.gl-grid {
    /* Distance from a section's content edge to the viewport edge, in
       whichever width mode is active. The hero's bleeding image reads
       this to cancel exactly as much as the section is holding. */
    --section-edge-gap: calc(
        (100vw - min(100vw, var(--container))) / 2 + var(--container-gutter)
    );
    /* How much horizontal padding the break-out itself needs. Zero in the
       default mode, because there the section has not broken out of
       anything and the page column already provides the inset. */
    --section-breakout-pad: 0px;
}

.section--width-wide {
    /* max(), so the wide measure collapses to a plain gutter on a screen
       narrower than it rather than producing a negative padding. */
    --section-edge-gap: max(
        var(--container-gutter), calc((100vw - var(--container-wide)) / 2)
    );
    --section-breakout-pad: var(--section-edge-gap);
}

.section--width-full {
    /* Edge to edge, but never flush: text touching the screen edge is
       unreadable, and on a phone it sits under the rounded corners. */
    --section-edge-gap: var(--container-gutter);
    --section-breakout-pad: var(--section-edge-gap);
}

.section--width-wide,
.section--width-full {
    margin-inline: calc(50% - 50vw);
    padding-inline: var(--section-breakout-pad);
}

/* ── Backgrounds follow the width ──────────────────────────────────────
   A section's background is painted by its *cell*, not by the section —
   that is what lets an editor pick "Background: primary" in the canvas
   and have it apply to any block. But the cell stays at the page column
   while a widened section breaks out of it, so the CTA band's navy panel
   would end up narrower than the words inside it.

   `:has()` lets the cell follow what its section asked for. The selector
   reads "a cell whose direct child is a widened section", which is
   exactly the case, because grid.html renders the block as the cell's
   only child.

   Skipped for a cell the editor has already set to full bleed: there the
   background is meant to span the whole viewport and this would pull it
   back in, overriding an explicit choice with an inferred one. */

.gl-cell:not(.gl-cell--bleed-full):has(> .section--width-wide) {
    --cell-edge-gap: max(
        var(--container-gutter), calc((100vw - var(--container-wide)) / 2)
    );
}

.gl-cell:not(.gl-cell--bleed-full):has(> .section--width-full) {
    --cell-edge-gap: var(--container-gutter);
}

.gl-cell:not(.gl-cell--bleed-full):has(> .section--width-wide)::before,
.gl-cell:not(.gl-cell--bleed-full):has(> .section--width-full)::before {
    inset-inline: calc(50% - 50vw + var(--cell-edge-gap));
}

/* ── Corners follow the section too ────────────────────────────────────
   The "Corners" setting on every section block (SECTION_RADII in
   core/blocks.py, D68). gridlayout rounds a painted cell with one
   site-wide `--gl-radius`, and its ::before inherits the cell's
   border-radius — so redefining the variable on the one cell is all it
   takes, and gridlayout itself stays untouched (D4, D50).

   Same `:has()` route as the width above, for the same reason: the cell
   paints, the section chooses. */

/* One rule per step in RADIUS_STEPS (core/blocks.py, D68 revised). "Site
   default" has no rule on purpose: it leaves gridlayout's --gl-radius as
   tokens.css set it. core.tests checks every step here has its rule. */
.gl-cell:has(> .section--radius-0)  { --gl-radius: 0; }
.gl-cell:has(> .section--radius-4)  { --gl-radius: 4px; }
.gl-cell:has(> .section--radius-8)  { --gl-radius: 8px; }
.gl-cell:has(> .section--radius-12) { --gl-radius: 12px; }
.gl-cell:has(> .section--radius-16) { --gl-radius: 16px; }
.gl-cell:has(> .section--radius-24) { --gl-radius: 24px; }

/* The large radii only where the band is a band. Stacked on a narrow
   screen a section is taller than it is wide, and big round corners —
   a pill most of all, which becomes a lozenge — cut into the first and
   last lines of text. So below the desktop breakpoint everything above
   24 px is held at the large radius. */
.gl-cell:has(> .section--radius-32),
.gl-cell:has(> .section--radius-48),
.gl-cell:has(> .section--radius-64),
.gl-cell:has(> .section--radius-96),
.gl-cell:has(> .section--radius-pill) { --gl-radius: var(--radius-lg); }

@media (min-width: 1025px) {
    .gl-cell:has(> .section--radius-32)   { --gl-radius: 32px; }
    .gl-cell:has(> .section--radius-48)   { --gl-radius: 48px; }
    .gl-cell:has(> .section--radius-64)   { --gl-radius: 64px; }
    .gl-cell:has(> .section--radius-96)   { --gl-radius: 96px; }
    .gl-cell:has(> .section--radius-pill) { --gl-radius: var(--radius-pill); }
}

/* Shared: every section's outer wrapper. */
.section {
    padding-block: var(--space-section);
}

/* ── Alignment ─────────────────────────────────────────────────────────
   Driven by the two dropdowns on every section block, which gridlayout's
   canvas renders in the layout editor (core/blocks.py). Nothing here is
   hardcoded per section any more: the previous `.section__head--centred`
   baked `text-align: center` into two block templates, which silently
   beat the cell-level align control gridlayout already provided — so that
   control appeared to do nothing at all.

   Left unset, a block emits no class and simply inherits the cell's
   alignment, so gridlayout's own control is back in charge.

   Revised (D102): five dropdowns now, not two — the small label, the card
   and item titles and the buttons have their own (Abhijith: "Make them
   all adjustable separately"). And the heading group's class is
   `heading-align-*`, no longer the same `align-*` the section root uses
   for its text: sharing the names meant a rule meant for the paragraphs
   also matched inside the heading group, and whichever came later in this
   file won. The root keeps `align-*`. */

.align-start  { text-align: start; }
.align-center { text-align: center; }
.align-end    { text-align: end; }

/* The heading group: eyebrow and headline, and in the heroes also the
   text and buttons that sit inside it (D102). */
.heading-align-start  { text-align: start; }
.heading-align-center { text-align: center; }
.heading-align-end    { text-align: end; }

/* What is *not* body text (D102). A paragraph tag is not always prose:
   the small label (.eyebrow), a card or item title or caption written as
   a <p> (.item-title), the button rows and the service hero's subheading
   are all <p>s, and each has an alignment of its own. Every rule below
   that aligns body text excludes them with the same list:

       :not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading)

   Keep the lists identical — `test_the_non_prose_lists_agree` checks.
   Before this, "Justified" text pulled the eyebrow to the left under a
   centred headline, and would have justified a two-line label. */

/* Nor do they need the 68ch reading measure every <p> is given
   (simplelaw.css): a label, a title and a row of buttons are short, and
   with the cap a centred one was centred inside a box still pinned to the
   left — the label sat off-centre over its headline, and centred buttons
   off-centre on a tablet. Uncapped, their alignment alone places them.
   The service hero's subheading keeps its measure, as a heading-like line
   of prose; its own rules below centre the box with the text. */
:is(.eyebrow, .item-title, .hero__actions, .cta-band__actions) {
    max-width: none;
}

/* The section's own text alignment, applied to the body text directly
   (D102), so that it holds even inside a container with an alignment of
   its own: the heroes keep their text inside the heading group, and the
   "What we do" cards centre theirs. Before, a hero set to "Heading:
   Centre, Text: Left" centred its paragraph. "Inherit" emits no class and
   adds no rule, so the cell's and the design's alignment still apply. */
.align-start :is(p, .rich-text li):not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    text-align: start;
}

.align-center :is(p, .rich-text li):not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    text-align: center;
}

.align-end :is(p, .rich-text li):not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    text-align: end;
}

/* A paragraph's box is capped at 68ch (simplelaw.css), so centred or
   right-aligned text also needs its box moved, or it is aligned inside a
   box pinned to the left. The box follows whichever setting aligns the
   text, at the same priority: the section's own text alignment first;
   then, for text it leaves to inherit, the heading group it sits in (the
   heroes keep their text there) and the layout editor's own alignment,
   both under `:where()` and the heading group's listed last so it beats
   the cell around it. Paragraphs only: list items have no measure. */
.align-start p:not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    margin-inline: 0;
}

.align-center p:not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    margin-inline: auto;
}

.align-end p:not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    margin-inline: auto 0;
}

:where(.gl-cell--align-center) p:not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    margin-inline: auto;
}

:where(.gl-cell--align-end) p:not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    margin-inline: auto 0;
}

:where(.heading-align-start) p:not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    margin-inline: 0;
}

:where(.heading-align-center) p:not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    margin-inline: auto;
}

:where(.heading-align-end) p:not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    margin-inline: auto 0;
}

/* Justification is prose-only, and deliberately not applied to the
   container. A justified heading stretches its first line across the full
   column — a heading is short enough that this is nearly always its only
   line, so the result is a few enormous word gaps and nothing else. The
   design justifies paragraphs, never headlines. */
/* Revised (D102): not every <p> — see the list of what is not body text
   above. Rich-text list items have their own rule, next. */
.align-justify p:not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    text-align: justify;
    /* Without hyphenation, justified text opens rivers of white space
       down the column. `hyphens` needs the element's language, which
       base.html sets on <html>. */
    hyphens: auto;
    -webkit-hyphens: auto;
}

/* Paragraphs only — not list items. An earlier version justified `li` too,
   on the reasoning that a bulleted list in rich text is prose. It is, but
   `li` is also what the sector cards and the nav are built from, so a
   justified section stretched the card titles into "Corporates     and
   commercial organisations". A rich-text list opts back in by its own
   container instead. */
.align-justify .rich-text li {
    text-align: justify;
    hyphens: auto;
    -webkit-hyphens: auto;
}

/* Paragraphs cap their own measure at 68ch, so centring the container
   leaves them pinned to its left edge unless their own box is centred
   too. Same for the heading group, which the design also narrows. */
/* Revised (D102): the heading group's rules below name `heading-align-*`,
   its class since the root and the heading group stopped sharing names. */
/* Revised (D102, after review): this rule is now one of the box rules
   above — `.align-center p:not(…)` — because here it reached every <p>,
   button rows and captions included. In a column flex container (the
   stacked call to action band, a team card) auto margins shrink the item
   to its content and centre it, so "Button alignment" and "Card and item
   title alignment" could not move them. */

.section__head.heading-align-center,
.section__head-text.heading-align-center {
    margin-inline: auto;
    max-width: 46rem;
}

/* Revised (D102): under `:where()`, so the section's own text alignment
   (the box rules above) decides where the intro's box goes when it is
   set; this still centres it when the text is left to inherit. */
:where(.section__head.heading-align-center) .section-intro {
    margin-inline: auto;
}

/* ── The smaller parts' own alignment (D102) ──────────────────────────
   Each is a dropdown on the sections that have the part, and each emits a
   class on the section root only when set. Left on its automatic value it
   emits nothing and the part behaves as before. */

/* The small label. Automatic: inside the heading group, so it goes with
   the heading. */
.label-align-start  .eyebrow { text-align: start; }
.label-align-center .eyebrow { text-align: center; }
.label-align-end    .eyebrow { text-align: end; }

/* Card and item titles. Automatic: the section's own design — most follow
   the text; "What we do" and "How we work" centre theirs. Justified is
   offered here (Abhijith, 7 C), with hyphenation as for the text. */
.title-align-start   .item-title { text-align: start; }
.title-align-center  .item-title { text-align: center; }
.title-align-end     .item-title { text-align: end; }

.title-align-justify .item-title {
    text-align: justify;
    hyphens: auto;
    -webkit-hyphens: auto;
}

/* A chosen title alignment has to have room to show. The "What we do"
   cards centre their contents as a column (`align-items: center`), which
   shrinks the title to its own words; and an icon list item puts its
   label beside the icon in a row. Both give the title the rest of the
   width. ("How we work" offers no title alignment: each step is only as
   wide as its title, and the design centres the steps themselves.) */
:is(.title-align-start, .title-align-center, .title-align-end, .title-align-justify)
    .area-card > .item-title {
    align-self: stretch;
}

/* The "What we do" card's text spans the card too, always (D102, after
   review). Centred as a column item it was only as wide as its words, so
   "Text alignment: Left" could not move a one-line text, and the box rules
   for right-aligned text pushed the whole box to the card's edge. Spanning
   the card, the alignment acts on the text and the box stays put; the
   design's centring is unchanged, because the card centres its text.
   No box margins either: in a flex column an auto margin beats `stretch`
   and shrinks the item again, and a box that spans the card needs none.
   `p.` and its place after the box rules are what let this outweigh them. */
.area-card > p.area-card__text {
    align-self: stretch;
    margin-inline: 0;
}

:is(.title-align-start, .title-align-center, .title-align-end, .title-align-justify)
    .icon-item > .item-title {
    flex-grow: 1;
}

/* The buttons. They are a flex row (simplelaw.css), so `text-align` never
   moved them — under a centred headline they stayed at the left. They are
   placed with `justify-content` instead, from lowest priority to highest:

   1. the layout editor's own alignment for the section (centre or right);
   2. the section's text alignment, where the heading is left to inherit it;
   3. the heading's alignment — the default, "Same as the heading" (the
      call to action band's buttons sit outside its heading group, so its
      template gives them the heading's class itself);
   4. the section's own "Button alignment".

   1 is under `:where()`, so it never outweighs the others; 2–4 weigh the
   same and the later rule wins, which is why they are in this order.
   On a wide screen the call to action band puts its buttons beside its
   text, shrunk to fit, and none of this moves them there. */
:where(.gl-cell--align-center) :is(.hero__actions, .cta-band__actions) {
    justify-content: center;
}

:where(.gl-cell--align-end) :is(.hero__actions, .cta-band__actions) {
    justify-content: flex-end;
}

.align-start  :is(.hero__actions, .cta-band__actions) { justify-content: flex-start; }
.align-center :is(.hero__actions, .cta-band__actions) { justify-content: center; }
.align-end    :is(.hero__actions, .cta-band__actions) { justify-content: flex-end; }

.heading-align-start:is(.hero__actions, .cta-band__actions),
.heading-align-start :is(.hero__actions, .cta-band__actions) {
    justify-content: flex-start;
}

.heading-align-center:is(.hero__actions, .cta-band__actions),
.heading-align-center :is(.hero__actions, .cta-band__actions) {
    justify-content: center;
}

.heading-align-end:is(.hero__actions, .cta-band__actions),
.heading-align-end :is(.hero__actions, .cta-band__actions) {
    justify-content: flex-end;
}

.button-align-start  :is(.hero__actions, .cta-band__actions) { justify-content: flex-start; }
.button-align-center :is(.hero__actions, .cta-band__actions) { justify-content: center; }
.button-align-end    :is(.hero__actions, .cta-band__actions) { justify-content: flex-end; }

/* ── The layout editor's own "Justify" (D102) ─────────────────────────
   gridlayout's justify class sets `text-align: justify` on the whole cell
   (grid.css), so everything in it inherited it: the headline, the small
   label and the card titles all stretched ("Train.        Comply.").
   Here it means what the section's own "Justified" means — body text
   only, with the same list of what is not body text; everything else
   falls back to left unless its own setting says otherwise. A section
   whose own "Text alignment" is set keeps it: those rules above weigh
   more than this one, whose cell class is under `:where()`. */
.gl-cell.gl-cell--align-justify {
    text-align: start;
    hyphens: manual;
    -webkit-hyphens: manual;
}

:where(.gl-cell--align-justify) :where(p, .rich-text li):not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    text-align: justify;
    hyphens: auto;
    -webkit-hyphens: auto;
}

/* …except for body text inside a heading group with an alignment of its
   own: the heroes keep their text there, and it goes with the heading
   whatever the layout editor says — as it already did for the editor's
   Left, Centre and Right, which it only inherits. Same weight as the rule
   above and after it, so it wins; both weigh less than the section's own
   text alignment (`.align-*`), which still beats them. (D102, after
   review: before, the editor's Justify justified a centred hero's text.) */
:where(.heading-align-start) :where(p, .rich-text li):not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    text-align: start;
    hyphens: manual;
    -webkit-hyphens: manual;
}

:where(.heading-align-center) :where(p, .rich-text li):not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    text-align: center;
    hyphens: manual;
    -webkit-hyphens: manual;
}

:where(.heading-align-end) :where(p, .rich-text li):not(.eyebrow, .item-title, .hero__actions, .cta-band__actions, .service-hero__subheading) {
    text-align: end;
    hyphens: manual;
    -webkit-hyphens: manual;
}

/* WCAG 1.4.8 advises against justified text, and a phone column is
   narrow enough that the word spacing genuinely breaks reading. The
   editor's choice is honoured everywhere it is readable and quietly
   relaxed where it is not — gridlayout does the same for its own justify
   class, and the two should not disagree. */
/* Revised (D62, D102): the relaxation is gone. Abhijith: "Put the option
   to make it justified properly anyways, the editor can choose to make it
   left if it looks awkward." Justified now means justified on every
   screen, phones included, and the help text under "Text alignment" says
   to choose Left where a phone's short lines open wide gaps. `hyphens:
   auto` above still narrows them in browsers that hyphenate English.
   gridlayout's own justify class (grid.css) was changed to match, so the
   two still agree. */

/* Heading left, supporting paragraph right — the "Who We Work With" and
   "How We Work" pattern. Stacked on a phone, side by side above it, with
   the short rule the design draws before the paragraph. */
.section__head--split {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.section__head--split .section-intro {
    margin-top: 0;
    font-size: var(--text-base);
}

/* ── Hero ──────────────────────────────────────────────────────────── */

.hero {
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
    padding-block: var(--space-section);
}

/* ── How tall the hero is ───────────────────────────────────────────────
   Chosen per hero (HERO_HEIGHTS in core/blocks.py). A *minimum* height, so
   content that needs more room — a long intro on a phone — still gets it
   and nothing is clipped.

   "screen" subtracts the sticky header, so the hero ends exactly at the
   bottom of the first screen rather than a header's height below it.
   `svh`, not `vh`: on a phone `vh` is measured with the browser's address
   bar hidden, so a 100vh hero is taller than what the visitor first sees. */
.hero--height-screen {
    min-height: calc(100svh - var(--header-height));
}

.hero--height-tall {
    min-height: calc(75svh - var(--header-height) * 0.75);
}

.hero__heading {
    margin-top: 0;
    font-size: var(--text-4xl);
}

.hero__intro {
    margin-top: var(--space-5);
    color: var(--colour-text-muted);
    font-size: var(--text-lg);
}

.hero__actions {
    margin-top: var(--space-6);
}

.hero__media img {
    width: 100%;
    border-radius: var(--radius-lg);
}

/* ── How the hero image fills its half ─────────────────────────────────
   Chosen per section in the layout editor (HERO_IMAGE_FITS in
   core/blocks.py). The default keeps the previous behaviour: a fixed 4:3
   slot inside the section's own vertical padding.

   The other two drop that padding on the media side only — the text keeps
   its breathing room — and let the picture take the band's full height.
   `align-self: stretch` is what gives the media column a height to fill;
   without it a flex item is only as tall as its content and `height: 100%`
   resolves against nothing. */

.hero--media-fill,
.hero--media-bleed {
    padding-block: 0;
}

.hero--media-fill .hero__body,
.hero--media-bleed .hero__body {
    padding-block: var(--space-section);
}

.hero--media-fill .hero__media,
.hero--media-bleed .hero__media {
    align-self: stretch;
    /* A floor, so a short hero does not squash the picture into a strip
       on a wide screen where the text beside it is only a few lines. */
    min-height: 26rem;
}

.hero--media-fill .hero__media img,
.hero--media-bleed .hero__media img {
    height: 100%;
    object-fit: cover;
}

/* ── Filling the band to its painted edge ──────────────────────────────
   Two bugs, both visible as tint showing below the picture.

   1. The band is painted by the cell, and gridlayout extends that paint
      half a grid gap beyond the cell above and below (grid.css,
      `.gl-cell--bleed-full::before`), so that two stacked bands meet with
      no hairline of page between them. The picture stopped at the cell's
      own edge, leaving a strip of tint under it. The media box now reaches
      out by the same half gap. It reads --gl-gap, the variable grid.css
      itself uses, so the two stay equal at every breakpoint.

   2. The picture's own height decided the hero's height: `height: 100%`
      on an image in an auto-height row lets the image's natural size size
      the row, so a portrait photo made the hero over 1200px tall.
      Absolutely positioning it takes it out of that calculation — the
      text beside it decides the height, and the picture only fills. */
.hero--media-fill .hero__media,
.hero--media-bleed .hero__media {
    position: relative;
    /* Stacked under the text on a phone there is no text beside it to
       take a height from, so it gets a shape of its own.
       Revised (D101): above or below the text is now the editor's
       choice — see "Picture on phones and tablets" below. */
    aspect-ratio: 4 / 3;
    min-height: 0;
    margin-bottom: calc(var(--gl-gap) / -2);
}

.hero--media-fill .hero__media > *,
.hero--media-bleed .hero__media > * {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
}

@media (min-width: 1025px) {
    .hero {
        flex-direction: row;
        align-items: center;
        gap: var(--space-8);
    }

    /* The design gives the text a little under half the width and the
       image the rest. `min-width: 0` on a flex child that contains long
       words is what stops the headline forcing the row wider than the
       page. */
    .hero__body {
        flex: 1 1 48%;
        min-width: 0;
    }

    .hero__media {
        flex: 1 1 52%;
        min-width: 0;
    }

    /* Run the picture off the right edge of the page, as the design does.
       It cancels exactly as much padding as the section is currently
       holding, whatever width mode the editor picked — which is why
       --section-edge-gap exists rather than this repeating the sum. A
       hero already set to "Full width" therefore only has a gutter left
       to cancel, and does not overshoot.

       Only above the tablet breakpoint: below it the image sits under the
       text and there is no side to bleed towards.
       Revised (D101): stacked, the picture is now above or below the text,
       the editor's choice ("Picture on phones and tablets"); either way it
       is not beside the text, so there is still no side to bleed towards. */
    .hero--media-bleed .hero__media {
        margin-right: calc(-1 * var(--section-edge-gap));
    }

    /* Beside the text the picture takes the band's full height, so it
       reaches up by the half gap too, and has no ratio of its own. The
       26rem floor above still stops a short hero squashing it. */
    .hero--media-fill .hero__media,
    .hero--media-bleed .hero__media {
        aspect-ratio: auto;
        min-height: 26rem;
        margin-top: calc(var(--gl-gap) / -2);
    }

    .hero--media-bleed .hero__media img {
        /* Square off the edge that now runs past the page. */
        border-start-end-radius: 0;
        border-end-end-radius: 0;
    }
}

/* ── "Run to the page edge" puts the picture behind the section (D85) ──
   Revised after review: "move the picture to the background so that its
   leftmost edge is not abruptly cut off". The rules above give the
   picture a column of its own and `object-fit: cover` it, which crops
   whatever does not fit the column — and the design's hero pictures are
   composites whose left edge is a diagonal cut with accent shapes, so the
   crop sliced it off with a hard vertical line.

   For this fit the picture is now a backdrop to the whole band instead:
   positioned behind the text, anchored to the right-hand page edge, as
   tall as the band and as wide as its own proportions make it. Nothing is
   cropped — if a very wide picture would not fit the band, `contain`
   scales it down rather than cutting it. The text sits above it.

   The rules above still apply to the other two fits, and still set up
   this one's padding and painted-edge reach; what follows overrides only
   the picture's box. Both heroes use it — the home page's and the About
   page's — because they share these classes (D77). */

/* Stacked on a phone the picture stays under the text, not behind it: a
   picture behind a phone-width column of text is not readable. It keeps
   its own proportions there rather than a 4:3 crop, so it is not cut
   either.
   Revised (D101): above or below the text is now the editor's choice —
   see "Picture on phones and tablets" below. */
.hero--media-bleed .hero__media {
    aspect-ratio: auto;
}

/* `> picture > img` as well: pictures are wrapped in <picture> for their
   WebP version (D98, core/templatetags/image_tags.py). */
.hero--media-bleed .hero__media > img,
.hero--media-bleed .hero__media > picture > img {
    position: static;
    height: auto;
}

/* The placeholder has no proportions of its own to keep, so it keeps the
   4:3 slot until a picture is chosen. */
.hero--media-bleed .hero__media > .placeholder {
    position: static;
    aspect-ratio: 4 / 3;
}

@media (min-width: 1025px) {
    .hero--media-bleed {
        position: relative;
        /* Keeps the text's z-index local to the hero. */
        isolation: isolate;
    }

    /* The text keeps the width it had beside the picture, and sits above
       it. Without flex-grow held at 0 it would take the whole band now
       the picture has left the row. */
    .hero--media-bleed .hero__body {
        position: relative;
        z-index: 1;
        flex: 0 1 48%;
    }

    /* The picture's box is the whole painted band: from the left edge of
       the section to the right-hand page edge, and half a grid gap past
       the cell at top and bottom, where gridlayout extends the band's
       paint. The right offset cancels whatever the section is not already
       holding — zero once it has broken out to the page edge ("Wide",
       "Full width"), the whole distance in the default page column. */
    .hero--media-bleed .hero__media {
        position: absolute;
        top: calc(var(--gl-gap) / -2);
        bottom: calc(var(--gl-gap) / -2);
        left: 0;
        right: calc(var(--section-breakout-pad) - var(--section-edge-gap));
        min-height: 0;
        margin: 0;
        /* A backdrop: clicks go to the section, and in the layout editor's
           preview a click on the picture still selects the section. */
        pointer-events: none;
    }

    /* As tall as the band, its width following from its proportions, held
       to the right. `max-width` and `contain` together are what stop a
       very wide picture being cropped: it is scaled to fit instead. */
    .hero--media-bleed .hero__media > img,
    .hero--media-bleed .hero__media > picture > img {
        position: absolute;
        inset: 0 0 0 auto;
        width: auto;
        max-width: 100%;
        height: 100%;
        object-fit: contain;
        object-position: right center;
        border-radius: 0;
    }

    /* No picture yet: the placeholder keeps to the right-hand side, the
       share the picture's column had, rather than filling the band behind
       the text. */
    .hero--media-bleed .hero__media > .placeholder {
        position: absolute;
        inset: 0 0 0 auto;
        width: 52%;
        height: 100%;
        aspect-ratio: auto;
    }
}

/* ── Brand frame (D100) ────────────────────────────────────────────────
   The design's hero picture, drawn rather than supplied: the photograph
   seen through the navy rounded rectangle, the pale blue one behind it,
   both turned and running off the page edge. Chosen per hero as the
   picture fit "Brand frame" (core/includes/brand_frame.html).

   The two supplied rectangles are one shape — about 1.23 : 1, corners 11%
   of the short side — and every size, angle and place below was measured
   from the design's hero (688 px export: the window's edges at 34.3° and
   55.4°, 89.6° apart; the blue's at 10.9° and 11.0°; and the predicted
   edges checked against the slivers of blue at the band's top corner and
   left edge). They are in hero-heights, `--frame-unit`, so the frame
   scales with the band:

     window  1.284 × 1.043, corners 0.113, turned 34.4°, centre 0.208 in
             from the outer page edge and 0.524 down
     accent  the same shape, turned 11°, centre 0.212 in and 0.480 down
     photo   the band from the window's sharp corner (1.0325 in) to the
             outer edge, full height

   The unit is the band's height, held to half the frame box's width so a
   narrow desktop does not run the picture under the text; then it is
   centred in the band and simply a little smaller.

   Sized with container units against `.brand-frame` (Chrome 105, Firefox
   110, Safari 16), and animated through registered custom properties
   (`@property`: Chrome 85, Firefox 128, Safari 16.4): one property turns
   the window and, the other way, the photograph inside it, about the same
   centre, so the photograph stays upright and still. A browser without
   them shows the frame finished, without the turn. */

@property --frame-turn { syntax: "<angle>"; inherits: true; initial-value: 0deg; }
@property --frame-grow { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --frame-show { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --frame-accent-turn { syntax: "<angle>"; inherits: true; initial-value: 0deg; }
@property --frame-accent-grow { syntax: "<number>"; inherits: true; initial-value: 1; }
@property --frame-accent-show { syntax: "<number>"; inherits: true; initial-value: 1; }

.brand-frame {
    /* The measured geometry (see above). */
    --frame-long: 1.284;
    --frame-short: 1.043;
    --frame-round: 0.113;
    --frame-window-x: 0.208;
    --frame-window-y: 0.524;
    --frame-accent-x: 0.212;
    --frame-accent-y: 0.48;
    --frame-photo: 1.0325;
    --frame-window-angle: 34.4deg;
    --frame-accent-angle: 11deg;
    /* Patch 08: which way each shape turns into place (+1 clockwise),
       for the turning effect's extra turn. Both clockwise in the home
       design, as their angles above are. */
    --frame-window-sense: 1;
    --frame-accent-sense: 1;
    /* At most this wide a unit; the stacked layouts give it the whole box. */
    --frame-cap: 50cqw;

    /* Patch 07: the frame style's numbers (above, and the "services"
       style below) are the `--frame-*-base` ones from here on, and what
       the shapes use is worked out from them: mirrored for a picture on
       the left (`--frame-dir`), and moved by the hero's "Adjust the
       frame" fields, which arrive as `--frame-adjust-*` in the frame's
       style attribute (core/templatetags/image_tags.py, frame_vars). An
       adjustment left empty is not set, and its fallback here is "no
       change". */
    --frame-dir: 1;
    --frame-size: var(--frame-adjust-size, 1);
    --frame-window-x-at: calc(var(--frame-window-x) + var(--frame-adjust-x, 0));
    --frame-window-y-at: calc(var(--frame-window-y) + var(--frame-adjust-y, 0));
    --frame-accent-x-at: calc(var(--frame-accent-x) + var(--frame-adjust-x, 0));
    --frame-accent-y-at: calc(var(--frame-accent-y) + var(--frame-adjust-y, 0));
    --frame-window-turn: calc(var(--frame-dir) * (var(--frame-window-angle) + var(--frame-adjust-tilt, 0deg)));
    --frame-accent-turned: calc(var(--frame-dir) * (var(--frame-accent-angle) + var(--frame-adjust-tilt, 0deg)));
    --frame-long-at: calc(var(--frame-long) * var(--frame-size));
    --frame-short-at: calc(var(--frame-short) * var(--frame-size));
    /* How far the photograph reaches in from the page edge: the style's
       measured value, or — where the browser can work it out (CSS
       trigonometry: Chrome 111, Firefox 108, Safari 15.4) — the window's
       own reach after any adjustment: its centre plus its turned half
       width and half height. `max(a, -a)` is the absolute value. */
    --frame-photo-at: calc(var(--frame-photo) + var(--frame-adjust-x, 0) + (var(--frame-size) - 1) * 0.83);

    /* Where the animation leaves things: finished. "Turn into frame" and
       "Turn out of frame" (gridlayout, D97/D99) move these. */
    --frame-turn: var(--frame-window-turn);
    --frame-grow: 1;
    --frame-show: 1;
    --frame-accent-turn: var(--frame-accent-turned);
    --frame-accent-grow: 1;
    --frame-accent-show: 1;

    position: absolute;
    inset: 0;
    overflow: hidden;
    container-type: size;
}

/* The photograph's reach worked out exactly, where CSS can (see above). */
@supports (width: calc(1px * cos(1deg))) {
    .brand-frame {
        --frame-photo-at: calc(
            var(--frame-window-x-at)
            + var(--frame-long-at) / 2 * max(cos(var(--frame-window-turn)), -1 * cos(var(--frame-window-turn)))
            + var(--frame-short-at) / 2 * max(sin(var(--frame-window-turn)), -1 * sin(var(--frame-window-turn)))
        );
    }
}

/* The Services page design's frame (patch 07), measured from its hero
   (Services – 1, the 1930 px wide page, hero 970 px tall) in the same
   hero-height units, as if its picture were on the right; the service
   hero mirrors it, as the design draws it. The photograph's window is
   the same shape and size as the home design's, turned the other way and
   less (its edges at 20.7° and 110.7°, and its long sides 1.039 hero
   heights apart — the shape's 1.043); the blue is the same shape too,
   turned 28.6° the other way (edges at 61.4° and −28.6°, both straight
   for over half the hero). The blue's two hidden edges are behind the
   window or off the page, so its place along them is taken at the
   smaller turn. Checked against the design: the window's sharp corner
   falls at 0.51 across and 0.24 down, the blue's straight edge meets the
   band's top 0.31 in, and a sliver of blue shows in the outer bottom
   corner. */
.brand-frame--services {
    --frame-window-x: 0.235;
    --frame-window-y: 0.503;
    --frame-accent-x: 0.224;
    --frame-accent-y: 0.604;
    --frame-photo: 1.02;
    --frame-window-angle: -20.7deg;
    --frame-accent-angle: 28.6deg;
    /* Patch 08: the window turns anticlockwise into place, the blue
       clockwise, as the angles above do. */
    --frame-window-sense: -1;
    --frame-accent-sense: 1;
}

/* Declared on the children, not on .brand-frame: container units resolve
   where they are used, and on the frame itself they would measure the
   frame's own container instead of the frame. */
.brand-frame__accent,
.brand-frame__window {
    --frame-unit: min(100cqh, var(--frame-cap));
    position: absolute;
    width: calc(var(--frame-long-at) * var(--frame-unit));
    height: calc(var(--frame-short-at) * var(--frame-unit));
    border-radius: calc(var(--frame-round) * var(--frame-size) * var(--frame-unit));
}

.brand-frame__accent {
    left: calc(100cqw - (var(--frame-accent-x-at) + var(--frame-long-at) / 2) * var(--frame-unit));
    top: calc(50cqh + (var(--frame-accent-y-at) - 0.5 - var(--frame-short-at) / 2) * var(--frame-unit));
    background: var(--colour-accent);
    opacity: var(--frame-accent-show);
    transform: rotate(var(--frame-accent-turn)) scale(var(--frame-accent-grow));
}

/* Navy: it is what shows while the photograph loads, or if it fails. */
.brand-frame__window {
    left: calc(100cqw - (var(--frame-window-x-at) + var(--frame-long-at) / 2) * var(--frame-unit));
    top: calc(50cqh + (var(--frame-window-y-at) - 0.5 - var(--frame-short-at) / 2) * var(--frame-unit));
    overflow: hidden;
    background: var(--colour-navy);
    opacity: var(--frame-show);
    transform: rotate(var(--frame-turn)) scale(var(--frame-grow));
}

/* The photograph (or the placeholder's box): the band from the window's
   corner to the outer edge, placed inside the window and turned back
   about the window's own centre — the exact inverse of the window's turn
   and growth — so on the page it never moves. Two classes, so the
   heroes' own `img` rules (width 100%, rounded corners) do not reach it.

   It runs a few pixels past the page edge and the band's top and bottom
   (`--frame-overshoot`, clipped by the frame anyway): measured to the
   exact edge, rounding left the last column of pixels navy at some
   widths (1366 px). The turning point moves with it. */
.brand-frame .brand-frame__photo {
    --frame-unit: min(100cqh, var(--frame-cap));
    --frame-overshoot: 4px;
    position: absolute;
    left: calc((var(--frame-window-x-at) + var(--frame-long-at) / 2 - var(--frame-photo-at)) * var(--frame-unit));
    top: calc(-50cqh - (var(--frame-window-y-at) - 0.5 - var(--frame-short-at) / 2) * var(--frame-unit) - var(--frame-overshoot));
    width: calc(var(--frame-photo-at) * var(--frame-unit) + var(--frame-overshoot));
    max-width: none;
    height: calc(100cqh + 2 * var(--frame-overshoot));
    border-radius: 0;
    object-fit: cover;
    transform-origin:
        calc((var(--frame-photo-at) - var(--frame-window-x-at)) * var(--frame-unit))
        calc(50cqh + (var(--frame-window-y-at) - 0.5) * var(--frame-unit) + var(--frame-overshoot));
    transform: scale(calc(1 / var(--frame-grow))) rotate(calc(-1 * var(--frame-turn)));
    /* The image's focal point, when one is set (patch 07, question 2):
       it decides which part of the photograph the window keeps. */
    object-position: var(--frame-focus, 50% 50%);
}

/* "Turn into frame, photo turning too" (patch 07, question 3): the
   photograph is held at the finished angle against the window instead of
   the page, so it turns and grows with the window and lands upright as
   the window does.

   The photograph only covers the part of the window that is inside the
   hero when finished; the window's outer corners are off the hero then,
   and bare (navy). While the window is still turning and growing, those
   corners swing into view — so the photograph also starts enlarged about
   the window's centre and settles to its own size as the window does
   (the enlargement follows --frame-grow, 1 when finished, so the last
   frame is exactly the still frame's). */
.brand-frame.gl-fx-frame-turn .brand-frame__photo {
    transform:
        rotate(calc(-1 * var(--frame-window-turn)))
        scale(calc(1 + (1 - var(--frame-grow)) * var(--frame-turn-zoom)));
}

/* How much the photograph is enlarged per unit the window is short of
   its full size: 2.35 times at the start (--frame-grow 0.55), 1.15 at
   0.95. The least that keeps the window's corners covered at every point
   of the turn — checked frame by frame, counting navy pixels, at 1920 and
   1366 for both frame styles; 2 still let the home design's frame, which
   turns further, show a sliver. */
.brand-frame {
    --frame-turn-zoom: 3;
}

/* And the window's navy — there for a photograph still loading — is left
   out while it turns, so if a thin edge of the window is still bare at
   some moment (the page edge, where the photograph stops), it shows the
   pale band rather than a dark line. The class goes when the animation
   ends, when nothing of the window is bare. */
.brand-frame.gl-fx-frame-turn .brand-frame__window {
    background: transparent;
}

/* ── Patch 08: the turning effect turns much further ─────────────────
   Abhijith: "I want the image and the shapes to rotate" — the turn from
   level was 21°–34°, and the growth from 55 % read as a zoom. Now the
   shapes, and the photograph with them, start a quarter, half or full
   turn further back than level (the editor's "How far it turns",
   carried as a gl-turn-* class by reveal.js), each turning its own way
   into place, and grow only from 85 %. The plain "Turn into frame" is
   unchanged. */
.brand-frame.gl-fx-frame-turn {
    --frame-turn-extra: 90deg;
}

.brand-frame.gl-fx-frame-turn.gl-turn-half {
    --frame-turn-extra: 180deg;
}

.brand-frame.gl-fx-frame-turn.gl-turn-full {
    --frame-turn-extra: 360deg;
}

/* Where it starts (and, leaving, where it goes): further back than level
   by the extra turn, each shape on its own side. It replaces the shared
   start rule below ("The frame's animation") for the turning effect —
   by one more class, since that rule comes later in this file. */
.gl-cell--anim.gl-anim-armed .gl-anim-unit.gl-fx-frame-turn.brand-frame,
.gl-anim-exiting.gl-anim-out .gl-anim-unit.gl-fx-frame-turn.brand-frame {
    --frame-turn: calc(-1 * var(--frame-dir) * var(--frame-window-sense) * var(--frame-turn-extra));
    --frame-accent-turn: calc(-1 * var(--frame-dir) * var(--frame-accent-sense) * var(--frame-turn-extra));
    --frame-grow: 0.85;
    --frame-accent-grow: 0.85;
}

/* Easing in and out, not gridlayout's entrance curve (0.2, 0.7, 0.2, 1):
   that one does four fifths of the movement in the first fifth of the
   time, which suits a fade but turned a half turn into a jolt — at 2 s,
   only 0.2 s in, it was already 2° from level of 214°. Measured the
   same way, this one is still turning through the middle second. The
   exit keeps gridlayout's accelerating curve. */
.gl-cell--anim.gl-anim-armed.gl-anim-in .gl-anim-unit.gl-fx-frame-turn {
    transition-timing-function: cubic-bezier(0.45, 0, 0.25, 1);
}

/* Covering the window as it turns. Turned any distance from where it
   finishes, the parts of the window the photograph does not cover — off
   the hero when finished — come into view; turned a quarter or more,
   nearly all of it can. So the photograph is enlarged about the window's
   centre by as much as covering the whole window takes, and comes back
   to its own size over the last 40° or so of the turn (3 × |sin(Δ/2)|
   reaches 1 at 39° and stays there to 321°; Δ is the angle still to
   turn). Checked frame by frame with the window's navy left showing, for
   a quarter, half and full turn in both frame styles at 1920 and 1366:
   no bare window in any frame. At 2 × it was 60°, and a sliver showed
   at the page edge with a degree to go.

   How much covering the whole window takes is worked out from the frame
   itself: the window's half width and half height as turned, against
   the photograph's reach from the window's centre to each of its edges
   — the outer page edge (the nearest), the far edge, the top and the
   bottom. Held at 6 at most, which only a frame pushed against the page
   edge by an adjustment would need more than. CSS trigonometry again;
   without it, the enlargement of patch 07 above applies. */
@supports (width: calc(1px * cos(1deg))) {
    .brand-frame {
        --frame-half-w: calc(
            var(--frame-long-at) / 2 * max(cos(var(--frame-window-turn)), -1 * cos(var(--frame-window-turn)))
            + var(--frame-short-at) / 2 * max(sin(var(--frame-window-turn)), -1 * sin(var(--frame-window-turn)))
        );
        --frame-half-h: calc(
            var(--frame-long-at) / 2 * max(sin(var(--frame-window-turn)), -1 * sin(var(--frame-window-turn)))
            + var(--frame-short-at) / 2 * max(cos(var(--frame-window-turn)), -1 * cos(var(--frame-window-turn)))
        );
        --frame-cover-zoom: min(6, 1.05 * max(
            var(--frame-half-w) / var(--frame-window-x-at),
            var(--frame-half-w) / (var(--frame-photo-at) - var(--frame-window-x-at)),
            var(--frame-half-h) / var(--frame-window-y-at),
            var(--frame-half-h) / (1 - var(--frame-window-y-at))
        ));
    }

    .brand-frame.gl-fx-frame-turn .brand-frame__photo {
        --frame-turn-left: calc(var(--frame-turn) - var(--frame-window-turn));
        transform:
            rotate(calc(-1 * var(--frame-window-turn)))
            scale(calc(1 + (var(--frame-cover-zoom) - 1) * min(1, 3 * max(
                sin(var(--frame-turn-left) / 2),
                -1 * sin(var(--frame-turn-left) / 2)
            ))));
    }
}

.brand-frame__photo > .placeholder {
    width: 100%;
    height: 100%;
    aspect-ratio: auto;
    border-radius: 0;
}

/* Mirrored, for the service hero, whose picture runs to the left-hand
   page edge: the same measurements from the left, the turns reversed. */
.brand-frame--mirrored {
    /* Patch 07: the turns are reversed through --frame-dir, so that a
       frame style and the tilt adjustment are mirrored too. This rule
       used to set the two angles to −34.4° and −11°, the home design's
       turned the other way — which is what --frame-dir now gives. */
    --frame-dir: -1;
}

.brand-frame--mirrored .brand-frame__accent {
    left: calc((var(--frame-accent-x-at) - var(--frame-long-at) / 2) * var(--frame-unit));
}

.brand-frame--mirrored .brand-frame__window {
    left: calc((var(--frame-window-x-at) - var(--frame-long-at) / 2) * var(--frame-unit));
}

.brand-frame--mirrored .brand-frame__photo {
    left: calc((var(--frame-long-at) / 2 - var(--frame-window-x-at)) * var(--frame-unit) - var(--frame-overshoot));
    transform-origin:
        calc(var(--frame-window-x-at) * var(--frame-unit) + var(--frame-overshoot))
        calc(50cqh + (var(--frame-window-y-at) - 0.5) * var(--frame-unit) + var(--frame-overshoot));
}

/* ── The frame's animation: "Turn into frame" / "Turn out of frame" ────
   gridlayout marks the frame as one piece of the Images group
   (`gl-fx-frame`, reveal.js) and leaves the frame's own box where it is
   (grid.css); what moves is inside it. Away from its finished state the
   window is level, small and clear, and the blue level, small and clear;
   both turn into place as they grow and appear. The timings are the
   section's, carried in the gridlayout variables. */
.gl-cell--anim.gl-anim-armed .gl-anim-unit.gl-fx-frame,
.gl-anim-exiting.gl-anim-out .gl-anim-unit.gl-fx-frame,
.gl-cell--anim.gl-anim-armed .gl-anim-unit.gl-fx-frame-turn,
.gl-anim-exiting.gl-anim-out .gl-anim-unit.gl-fx-frame-turn {
    --frame-turn: 0deg;
    --frame-grow: 0.55;
    --frame-show: 0;
    --frame-accent-turn: 0deg;
    --frame-accent-grow: 0.6;
    --frame-accent-show: 0;
}

.gl-cell--anim.gl-anim-armed.gl-anim-in .gl-anim-unit.gl-fx-frame,
.gl-anim-exiting .gl-anim-unit.gl-fx-frame,
.gl-cell--anim.gl-anim-armed.gl-anim-in .gl-anim-unit.gl-fx-frame-turn,
.gl-anim-exiting .gl-anim-unit.gl-fx-frame-turn {
    transition-property:
        --frame-turn, --frame-grow, --frame-show,
        --frame-accent-turn, --frame-accent-grow, --frame-accent-show;
}

/* Playing in: back to the finished values. Spelled out, because the rule
   holding the start (above) still matches — the cell keeps
   `gl-anim-armed` while `gl-anim-in` plays. */
.gl-cell--anim.gl-anim-armed.gl-anim-in .gl-anim-unit.gl-fx-frame,
.gl-cell--anim.gl-anim-armed.gl-anim-in .gl-anim-unit.gl-fx-frame-turn {
    --frame-turn: var(--frame-window-turn);
    --frame-grow: 1;
    --frame-show: 1;
    --frame-accent-turn: var(--frame-accent-turned);
    --frame-accent-grow: 1;
    --frame-accent-show: 1;
}

/* ── The heroes' "Brand frame" fit ───────────────────────────────────
   The hero's picture box, as for "run to the page edge" (D85): behind
   the text on a wide screen, the whole painted band from the section's
   left to the right-hand page edge; under the text on a phone.
   Revised (D101): above or below the text is now the editor's choice —
   see "Picture on phones and tablets" below. */
.hero--media-frame {
    padding-block: 0;
}

.hero--media-frame .hero__body {
    padding-block: var(--space-section);
}

/* Stacked: a box of its own shape, running to the right-hand page edge
   and down to the band's painted edge; the frame fills it.
   Revised (D101): up to the band's top edge instead when the picture is
   above the text — see "Picture on phones and tablets" below. */
.hero--media-frame .hero__media {
    position: relative;
    aspect-ratio: 1.15 / 1;
    margin-right: calc(-1 * var(--section-edge-gap));
    margin-bottom: calc(var(--gl-gap) / -2);
}

.hero--media-frame .brand-frame {
    --frame-cap: 100cqw;
}

@media (min-width: 1025px) {
    .hero--media-frame {
        position: relative;
        isolation: isolate;
    }

    .hero--media-frame .hero__body {
        position: relative;
        z-index: 1;
        flex: 0 1 48%;
    }

    .hero--media-frame .hero__media {
        position: absolute;
        top: calc(var(--gl-gap) / -2);
        bottom: calc(var(--gl-gap) / -2);
        left: 0;
        right: calc(var(--section-breakout-pad) - var(--section-edge-gap));
        margin: 0;
        aspect-ratio: auto;
        min-height: 0;
        /* A backdrop: a click in the layout editor's preview still
           selects the section. */
        pointer-events: none;
    }

    .hero--media-frame .brand-frame {
        --frame-cap: 50cqw;
    }
}

/* ── What We Do ────────────────────────────────────────────────────── */

.what-we-do__cards {
    display: grid;
    /* auto-fit + minmax is the whole responsive rule: cards are at least
       15rem wide and the browser fits as many per row as there is room
       for — one on a phone, two on a tablet, four on a desktop — with no
       breakpoint of its own to keep in sync. */
    grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
    gap: var(--space-4);
    margin-top: var(--space-7);
}

.area-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-6) var(--space-5);
    background: var(--colour-card);
    border-radius: var(--radius-lg);
    text-align: center;
}

.area-card__icon img {
    width: 4.5rem;
    height: 4.5rem;
    object-fit: contain;
}

.area-card__title {
    margin: 0;
    font-size: var(--text-2xl);
}

.area-card__text {
    margin: 0;
    color: var(--colour-text-muted);
}

/* The arrow at the foot of each card in the design. Rendered only when the
   card actually links somewhere — an arrow that goes nowhere is a promise
   the page does not keep. `margin-top: auto` pins it to the bottom so the
   arrows line up across cards of different text lengths. */
.area-card__link {
    margin-top: auto;
    padding-top: var(--space-2);
    font-size: var(--text-xl);
    line-height: 1;
    text-decoration: none;
}

/* The whole card is the click target, not just the arrow: the link is
   stretched over the card with a pseudo-element. The card needs a
   stacking context of its own for that, hence position: relative. */
.area-card {
    position: relative;
}

.area-card__link::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
}

/* ── Why organisations work with us ────────────────────────────────── */

.value-props__items {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: var(--space-6) var(--space-5);
    margin-top: var(--space-7);
}

.value-prop {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.value-prop__icon img {
    width: 3.5rem;
    height: 3.5rem;
    object-fit: contain;
}

.value-prop__title {
    margin: 0;
    font-size: var(--text-2xl);
}

.value-prop__text {
    margin: 0;
    color: var(--colour-text-muted);
    font-size: var(--text-sm);
}

@media (min-width: 1025px) {
    /* The vertical rules between columns in the design. Drawn as a left
       border on every item except the first, so the count of rules always
       matches the count of items with no extra markup. */
    .value-prop + .value-prop {
        padding-left: var(--space-5);
        border-left: 1px solid var(--colour-border);
    }
}

/* ── Who we work with ──────────────────────────────────────────────
   The card grid's shape is the editor's (D69). SectorsBlock resolves the
   rows and columns chosen for each breakpoint and writes them onto
   .sectors__body as custom properties; nothing here decides a count.

   The cards and the photo share one grid: the <ul> is `display: contents`,
   so its <li>s become grid items beside the photo. That is what lets the
   photo span every card row and take whole card columns. (Chromium and
   Safari keep list semantics on a `display: contents` list; the photo is
   outside the list either way, so the count a screen reader announces is
   the cards alone.)

   On desktop `grid-auto-rows: 1fr` makes every row as tall as the
   tallest, so a short card title does not leave a stubby card beside a
   long one. Desktop only: below it the photo has a row of its own under
   the cards, and 1fr would stretch every card row to the photo's height. */

.sectors__body {
    display: grid;
    grid-template-columns: repeat(var(--sectors-cols-phone, 1), minmax(0, 1fr));
    gap: var(--space-4);
    margin-top: var(--space-6);
}

.sectors__cards {
    display: contents;
}

.sector-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-5);
    background: var(--colour-card);
    border-radius: var(--radius-lg);
}

.sector-card__icon img {
    width: 3.5rem;
    height: 3.5rem;
    object-fit: contain;
}

.sector-card__title {
    margin: 0;
    font-size: var(--text-base);
    font-weight: var(--weight-bold);
    color: var(--colour-navy);
}

/* Below desktop the photo goes under the cards, full width, whatever side
   the editor put it on: beside a one- or two-column grid it would be a
   sliver. `auto` rows so it does not inherit the cards' 1fr height. */
.sectors__media {
    grid-column: 1 / -1;
    grid-row: auto;
    position: relative;
    min-width: 0;
}

.sectors__media img,
.sectors__media .placeholder {
    width: 100%;
    border-radius: var(--radius-lg);
}

/* Block, so the image sits flush without an inline baseline gap. Not the
   placeholder, which is a flex box that centres its label. */
.sectors__media img {
    display: block;
}

/* How tall the photograph is allowed to be, chosen per section in the
   layout editor (SECTORS_IMAGE_HEIGHTS in core/blocks.py).

   "match" (the default) and "tall" crop the photo to its box, so the box
   decides the height and the photo only fills it. It used to be the other
   way round — the photo's own height, capped at 32rem, stretched the
   cards beside it, so "match the cards" in fact made the cards match the
   photo. Absolutely positioning the image takes it out of the row-height
   calculation, and then the cards alone decide. */
.sectors--image-match .sectors__media,
.sectors--image-tall .sectors__media {
    /* Stacked under the cards there is nothing to match, so it gets a
       landscape shape of its own. */
    aspect-ratio: 16 / 9;
}

.sectors--image-match .sectors__media > *,
.sectors--image-tall .sectors__media > * {
    position: absolute;
    inset: 0;
    height: 100%;
    aspect-ratio: auto;
    object-fit: cover;
}

.sectors--image-auto .sectors__media img {
    height: auto;
}

@media (min-width: 641px) {
    .sectors__body {
        grid-template-columns: repeat(var(--sectors-cols-tablet, 2), minmax(0, 1fr));
    }
}

@media (min-width: 1025px) {
    .section__head--split {
        flex-direction: row;
        align-items: flex-start;
        justify-content: space-between;
        gap: var(--space-8);
    }

    .section__head--split .section__head-text {
        flex: 1 1 50%;
        min-width: 0;
    }

    .section__head--split .section-intro {
        flex: 1 1 42%;
        min-width: 0;
        /* The short horizontal rule the design draws to the left of the
           paragraph. A border on a pseudo-element rather than an <hr>,
           because it is decoration and has no place in the document
           outline. */
        position: relative;
        padding-left: var(--space-7);
    }

    .section__head--split .section-intro::before {
        content: "";
        position: absolute;
        left: 0;
        top: 0.7em;
        width: var(--space-5);
        height: 1px;
        background: var(--colour-text-muted);
    }

    /* The card columns plus the photo's own columns. The track count is
       worked out in Python, because repeat() cannot add two variables. */
    .sectors__body {
        grid-template-columns: repeat(var(--sectors-track-desktop, 3), minmax(0, 1fr));
        grid-auto-rows: 1fr;
    }

    /* Beside the cards: its own columns, every card row. Placed on both
       axes, so the auto-placed cards flow around it whichever side it is
       on, and its place in the source does not matter. */
    .sectors__body--photo-left .sectors__media,
    .sectors__body--photo-right .sectors__media {
        grid-column: var(--sectors-photo-start) / span var(--sectors-photo-cols);
        grid-row: 1 / span var(--sectors-rows-desktop);
        aspect-ratio: auto;
    }

    .sectors--image-tall .sectors__media {
        min-height: 44rem;
    }

    /* Natural height beside the cards: top-aligned rather than stretched,
       and the rows grow if the photo is taller than the cards. */
    .sectors--image-auto .sectors__media {
        align-self: start;
    }
}

/* ── How we work ───────────────────────────────────────────────────── */

.process__steps {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-4);
    margin-top: var(--space-7);
}

.process__step {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    text-align: center;
}

.process__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 5rem;
    height: 5rem;
    background: var(--colour-card);
    border-radius: var(--radius-pill);
}

.process__icon img {
    width: 2.5rem;
    height: 2.5rem;
    object-fit: contain;
}

.process__title {
    margin: 0;
    font-size: var(--text-2xl);
}

/* The arrow between steps. Decorative, and marked aria-hidden in the
   template, so it is never announced as "right arrow" between each pair.
   It points down while the steps are stacked and right once they are in a
   row — the connector always follows the reading direction. */
.process__arrow {
    color: var(--colour-navy);
    font-size: var(--text-xl);
    line-height: 1;
    transform: rotate(90deg);
}

@media (min-width: 1025px) {
    .process__steps {
        flex-direction: row;
        justify-content: space-between;
        gap: var(--space-4);
    }

    .process__step {
        flex: 0 1 auto;
    }

    .process__arrow {
        transform: none;
    }
}

/* ── Call to action band ───────────────────────────────────────────────
   The rounded navy panel above the footer. The colour comes from the
   editor's "Background: primary" choice in the canvas, not from here, so
   this rule set is only the internal layout. */

.cta-band {
    /* The band's own inner padding, kept in a variable so the rule below
       can add it to the break-out rather than replace it. Setting
       `padding` here directly is what put the text 16px outside its own
       navy panel: the shorthand beat the width rule's `padding-inline`,
       so the words sat at the section's break-out edge while the colour
       started further in. */
    --cta-inset: var(--space-6);

    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    padding-block: var(--space-7);
    padding-inline: calc(var(--section-breakout-pad) + var(--cta-inset));
}

.cta-band .eyebrow {
    color: inherit;
    opacity: 0.85;
}

.cta-band__heading {
    margin: 0;
    /* Inherits white from the grid's --gl-on-primary. Stated explicitly
       because h2 sets navy, which would otherwise win. */
    color: inherit;
    font-size: var(--text-3xl);
}

@media (min-width: 1025px) {
    .cta-band {
        --cta-inset: var(--space-8);

        flex-direction: row;
        align-items: center;
        justify-content: space-between;
        gap: var(--space-8);
    }

    .cta-band__actions {
        flex: 0 0 auto;
    }

    /* Fully round ends eat into the corners, so a pill band pushes its
       text and button further in to clear the curve. The design sets the
       text about 105px in from the end of a 1750px band — roughly 6vw. */
    .cta-band.section--radius-pill {
        --cta-inset: clamp(var(--space-8), 6vw, 7rem);
    }
}

/* ── Page hero (D77) ───────────────────────────────────────────────────
   The About page's hero. Its layout is the home hero's — it carries the
   same `hero` classes, so height, photo fit and the bleed to the page edge
   all come from the Hero rules above. What is here is only what differs:
   rich text instead of a one-line intro, a large first letter, and a
   short rule under the text. Both are always on (D77). */

.page-hero__body {
    margin-top: var(--space-5);
    color: var(--colour-text-muted);
    font-size: var(--text-lg);
}

/* The first letter, raised rather than dropped: it sits on the first
   line's baseline at about twice the height of the text, in navy — read
   off the About design, where it is the only navy letter in the
   paragraph. First paragraph only; a body that opens with a heading or a
   list has no first letter to enlarge, and gets none. */
.page-hero__body > p:first-child::first-letter {
    font-size: 2.1em;
    line-height: 1;
    font-weight: var(--weight-semibold);
    color: var(--colour-navy);
}

/* The short rule the design draws under the text. A pseudo-element, not an
   <hr>: it is decoration, and an <hr> would announce a thematic break to a
   screen reader. It follows the heading group's alignment. */
.page-hero .hero__body::after {
    content: "";
    display: block;
    width: 4.5rem;
    height: 2px;
    margin-top: var(--space-7);
    background: var(--colour-text-muted);
}

/* Revised (D102, after review): the heading group's alignment can also
   come from the section's text alignment or the layout editor's, when
   "Heading alignment" is left to inherit — the rule followed neither, and
   stayed at the left under a centred hero. These follow them, weakest
   first: the editor's (under `:where()`), then the section's text
   alignment, then the heading group's own class, listed last to win. */
:where(.gl-cell--align-center) .page-hero .hero__body::after {
    margin-inline: auto;
}

:where(.gl-cell--align-end) .page-hero .hero__body::after {
    margin-inline: auto 0;
}

/* Not `.align-justify`: it aligns only the text itself, so the heading
   group still takes the layout editor's alignment. */
.page-hero.align-start .hero__body::after {
    margin-inline: 0;
}

.page-hero.align-center .hero__body::after {
    margin-inline: auto;
}

.page-hero.align-end .hero__body::after {
    margin-inline: auto 0;
}

/* Revised (D102): `heading-align-*`, the heading group's class now. */
.page-hero .hero__body.heading-align-start::after {
    margin-inline: 0;
}

.page-hero .hero__body.heading-align-center::after {
    margin-inline: auto;
}

/* Both margins: a centring rule above may have set the end one too. */
.page-hero .hero__body.heading-align-end::after {
    margin-inline: auto 0;
}

/* ── Approach (D77) ────────────────────────────────────────────────────
   "How we think about this work": the principles side by side under a
   split head (the shared .section__head--split rules above). */

.approach__items {
    display: grid;
    /* As many columns as fit at 16rem or more: three on a desktop, fewer
       as the screen narrows, one on a phone — no breakpoint to keep in
       step with anything. */
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: var(--space-7) var(--space-6);
    margin-top: var(--space-7);
}

.approach-item {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

/* The design's icons are drawn larger than the home page's and are not on
   a disc, so they get their own size: a fixed height, width to suit. */
.approach-item__icon img {
    width: auto;
    height: 7rem;
    object-fit: contain;
}

.approach-item__title {
    margin: 0;
    font-size: var(--text-xl);
}

.approach-item__text {
    color: var(--colour-text-muted);
}

@media (min-width: 1025px) {
    /* The vertical rules between principles, as on "Why us": a border on
       every item after the first, so the rules always match the items. */
    .approach-item + .approach-item {
        padding-left: var(--space-6);
        border-left: 1px solid var(--colour-border);
    }
}

/* ── Team (D79) ────────────────────────────────────────────────────────
   Heading and paragraph over a card per person. The columns are fixed
   (D79): one on a phone, two on a tablet, four from 1025px, and more
   people start another row. */

.team {
    /* Inner padding for when the section sits on a painted panel, added to
       the break-out rather than replacing it — the lesson the CTA band
       learned (see .cta-band above). Zero without a background, where the
       text should line up with the sections around it. */
    --team-inset: 0px;

    padding-inline: calc(var(--section-breakout-pad) + var(--team-inset));
}

/* Any background at all: gridlayout adds `gl-cell--bg-<name>` only when
   the editor has chosen one. */
.gl-cell[class*="gl-cell--bg-"] > .team {
    --team-inset: var(--space-5);
}

.team__cards {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-7) var(--space-5);
    margin-top: var(--space-7);
}

.team-card {
    /* The stacking context the stretched button and the details panel are
       positioned against. */
    position: relative;
    display: flex;
    flex-direction: column;
}

/* The photo's frame: large round corners and a soft blue glow, measured
   off the About design. It clips the photo and the details panel alike. */
.team-card__frame {
    position: relative;
    overflow: hidden;
    border-radius: var(--radius-xl);
    background: var(--colour-white);
    box-shadow:
        0 0 0 1px var(--colour-border),
        0 6px 24px rgba(122, 147, 255, 0.18);
}

/* The placeholder already takes the 7:10 shape (.placeholder--portrait in
   simplelaw.css) and must stay a flex box, which is what centres its
   label — so only the photo is set here. */
.team-card__photo img {
    display: block;
    width: 100%;
    aspect-ratio: 7 / 10;
    object-fit: cover;
    transition: transform 400ms ease;
}

.team-card__name {
    margin: var(--space-5) 0 0;
    padding-inline: var(--space-5);
    font-size: var(--text-2xl);
}

.team-card__role {
    margin: var(--space-1) 0 0;
    padding-inline: var(--space-5);
    color: var(--colour-text-muted);
}

/* The name as a button: it looks like the heading it sits in, and its
   ::after is stretched over the whole card, so tapping the photo opens the
   card as well as tapping the name. The resets are spelled out rather than
   `all: unset`, which would also wipe the site's focus ring. */
.team-card__toggle {
    margin: 0;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    color: inherit;
    text-align: inherit;
    cursor: pointer;
}

.team-card__toggle::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
}

/* ── The details panel ──
   Over the photo, hidden until revealed. `visibility` as well as opacity,
   so a hidden panel's links cannot be tabbed to or clicked; its change is
   delayed on the way out so the fade can finish first. Above the stretched
   button (z-index 2), so its links take the click when it is showing. */
.team-card__details {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-6) var(--space-5);
    overflow-y: auto;
    background: color-mix(in srgb, var(--colour-navy) 94%, transparent);
    color: var(--colour-text-invert);
    opacity: 0;
    visibility: hidden;
    transform: translateY(1.5rem);
    transition:
        opacity 250ms ease,
        transform 250ms ease,
        visibility 0s linear 250ms;
}

.team-card__bio {
    font-size: var(--text-sm);
}

/* Full rich text (D80) may bring headings and links into a small navy
   panel: they take its white rather than the page's navy, and a heading is
   sized for a card, not a page. */
.team-card__bio :is(h2, h3, h4, h5, h6) {
    margin: 0 0 var(--space-2);
    font-size: var(--text-base);
    color: inherit;
}

.team-card__details a {
    color: inherit;
}

.team-card__social {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    /* Pinned to the bottom of the panel, whatever the length of the bio. */
    margin-top: auto;
}

/* Text labels for now, as in the footer: no icon set has been supplied
   (D57). Outlined pills so they read as links on the navy. */
.team-card__social a {
    display: inline-block;
    padding: var(--space-1) var(--space-3);
    border: 1px solid currentColor;
    border-radius: var(--radius-pill);
    font-size: var(--text-sm);
    text-decoration: none;
}

/* Revised (D103): a link whose platform has an icon in the footer shows
   the icon, as uploaded and at the site's icon size (.social-icon in
   simplelaw.css), with no pill around it. As wide as the icon, so the
   first lines up with the bio above it; 2.5rem tall, an easy target to
   tap. A platform with no icon keeps the pill above. */
.team-card__social a.social-icon-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 2.5rem;
    padding: 0;
    border: 0;
}

.team-card__social a.social-icon-link:hover {
    background: none;
}

.team-card__social a:hover {
    background: var(--colour-white);
    color: var(--colour-navy);
}

/* Revealed: opened with a click or tap, or holding keyboard focus.
   `:focus-visible` rather than `:focus-within`, so a mouse click on the
   name — which focuses the button — does not also pin the panel open and
   make a second click look as if it did nothing. */
.team-card.is-open .team-card__details,
.team-card:has(:focus-visible) .team-card__details {
    opacity: 1;
    visibility: visible;
    transform: none;
    transition-delay: 0s;
}

/* And on hover, but only where the device can hover. A phone emulates
   :hover on tap and then leaves it stuck, which would fight the button. */
@media (hover: hover) {
    .team-card--has-details:hover .team-card__details {
        opacity: 1;
        visibility: visible;
        transform: none;
        transition-delay: 0s;
    }

    .team-card--has-details:hover .team-card__photo img {
        transform: scale(1.04);
    }
}

/* Escape pressed (team-cards.js): hidden whatever else says otherwise,
   until the pointer or focus leaves the card. Last, and no less specific
   than the rules above, so it wins. */
.team-card.is-dismissed .team-card__details {
    opacity: 0;
    visibility: hidden;
    transform: translateY(1.5rem);
}

@media (min-width: 641px) {
    .team__cards {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1025px) {
    .team__cards {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    /* The design's panel holds its text about 3rem in from its edge. */
    .gl-cell[class*="gl-cell--bg-"] > .team {
        --team-inset: var(--space-7);
    }
}

/* ── Text section ──────────────────────────────────────────────────────
   The generic heading-plus-rich-text section that predates the design.
   Still used by StandardPage, so it keeps its rules. */

.text-section__heading {
    margin-bottom: var(--space-4);
}

/* ═══════════════════════════════════════════════════════════════════════
 * The Services pages (step 6): the service hero, the service cards, and
 * the three general pieces every service page is made of — the numbered
 * list, the highlight panel and the icon list. Read off the Services and
 * service page designs (Services – 1 to 5).
 * ═══════════════════════════════════════════════════════════════════════ */

/* ── Service hero ──────────────────────────────────────────────────────
   The home and About heroes put their picture on the right; this one puts
   it on the left, behind the band and running to the page's left edge
   (Services, question 4). It follows D85's rule for that: the picture is
   never cropped — as tall as the band, as wide as its own proportions
   make it, scaled down with `contain` if it would not fit.

   Stacked on a phone the picture comes first, above the text, at its own
   proportions: behind a phone-width column of text it would not be
   readable.
   Revised (D101): above is now the default of the editor's "Picture on
   phones and tablets", which can put it below — see that block below. */

.service-hero__media img {
    display: block;
    width: 100%;
    height: auto;
}

.service-hero__subheading {
    margin: var(--space-4) 0 0;
    font-family: var(--font-heading);
    font-size: var(--text-xl);
    font-weight: var(--weight-semibold);
    line-height: 1.3;
    color: var(--colour-navy);
}

/* The subheading is a heading-like line, so it follows the heading's
   alignment, never the section's "Justified": a justified two-line
   subheading stretches its first line into wide word gaps. `.align-justify
   p` (above) would otherwise reach it, because it is a paragraph. */
/* Revised (D102): `.align-justify p` no longer reaches it — the subheading
   is on the list of what is not body text — so this rule no longer sets
   `text-align: start`, which forced it left even where the heading it
   follows was centred by the layout editor's own alignment. */
.align-justify .service-hero__subheading {
    hyphens: manual;
    -webkit-hyphens: manual;
}

/* Revised (D102): `heading-align-*`, the heading group's class now. */
.service-hero__body.heading-align-center .service-hero__subheading {
    text-align: center;
    /* Its 68ch box too (D102), as for paragraphs above. */
    margin-inline: auto;
}

.service-hero__body.heading-align-end .service-hero__subheading {
    text-align: end;
    margin-inline: auto 0;
}

.service-hero__text {
    margin-top: var(--space-5);
    color: var(--colour-text-muted);
    font-size: var(--text-lg);
}

/* The Services page sets its first letter large, as the About hero does
   (same size and weight, so the two read as one family). A switch here,
   because the service pages' designs do not. */
.service-hero__text--first-letter > p:first-child::first-letter {
    font-size: 2.1em;
    line-height: 1;
    font-weight: var(--weight-semibold);
    color: var(--colour-navy);
}

@media (min-width: 1025px) {
    .service-hero {
        position: relative;
        /* Keeps the text's z-index local to the hero. */
        isolation: isolate;
        padding-block: 0;
    }

    /* The text takes the right-hand 48%, as the design draws it, and sits
       above the picture. */
    .service-hero__body {
        position: relative;
        z-index: 1;
        flex: 0 1 48%;
        margin-inline-start: auto;
        padding-block: var(--space-section);
    }

    /* The picture's box is the band from the left-hand page edge to where
       the text begins, reaching half a grid gap past the cell at top and
       bottom, where gridlayout extends the band's paint (see D63). The
       left offset cancels whatever the section is not already holding —
       the mirror of the About hero's right offset. */
    .service-hero__media {
        position: absolute;
        top: calc(var(--gl-gap) / -2);
        bottom: calc(var(--gl-gap) / -2);
        left: calc(var(--section-breakout-pad) - var(--section-edge-gap));
        right: 48%;
        /* A backdrop: a click in the layout editor's preview still selects
           the section. */
        pointer-events: none;
    }

    .service-hero__media > img,
    .service-hero__media > picture > img {
        position: absolute;
        inset: 0 auto 0 0;
        width: auto;
        max-width: 100%;
        height: 100%;
        object-fit: contain;
        object-position: left center;
    }

    /* No picture yet: the placeholder fills the picture's share of the
       band, so the layout around it is the real one. */
    .service-hero__media > .placeholder {
        position: absolute;
        inset: 0;
        height: 100%;
        aspect-ratio: auto;
        border-radius: 0;
    }
}

/* The service hero's "Brand frame" fit (D100): mirrored, running to the
   left-hand page edge. Stacked, the picture comes first, above the text.
   Revised (D101): by default; "Picture on phones and tablets" below can
   put it under the text, and it now reaches the band's edge either way. */
.service-hero--media-frame .service-hero__media {
    position: relative;
    aspect-ratio: 1.15 / 1;
    margin-left: calc(-1 * var(--section-edge-gap));
    margin-top: calc(var(--gl-gap) / -2);
}

.service-hero--media-frame .brand-frame {
    --frame-cap: 100cqw;
}

@media (min-width: 1025px) {
    /* The box is the one the service hero already gives its picture, from
       the left-hand page edge to where the text begins; the frame may
       take nearly all of it. */
    .service-hero--media-frame .service-hero__media {
        position: absolute;
        aspect-ratio: auto;
        margin: 0;
    }

    .service-hero--media-frame .brand-frame {
        --frame-cap: 96cqw;
    }
}

/* ── Picture side (D100, patch 07, question 1c) ───────────────────────
   Every hero chooses which side its picture is on ("Picture side"). The
   rules above draw each hero type on its design's side — the home and
   About heroes' picture on the right, the service hero's on the left —
   and these put it on the other: the text column, the picture's box and
   its run to the page edge all swap. The brand frame is mirrored with it
   in the templates (brand-frame--mirrored). Stacked on a phone the order
   stays as it was; only the run to the page edge changes side.
   Revised (D101): still true of the picture side; the stacked order is
   now a setting of its own, "Picture on phones and tablets", below. */

/* The home and About heroes, picture on the left. */
.hero--picture-left.hero--media-frame .hero__media {
    margin-right: 0;
    margin-left: calc(-1 * var(--section-edge-gap));
}

@media (min-width: 1025px) {
    /* The text goes to the right; for the two fits whose picture sits
       behind the band, the text is the only item in the row, and
       row-reverse puts it at the right as well. */
    .hero--picture-left {
        flex-direction: row-reverse;
    }

    .hero--picture-left.hero--media-bleed .hero__media,
    .hero--picture-left.hero--media-frame .hero__media {
        left: calc(var(--section-breakout-pad) - var(--section-edge-gap));
        right: 0;
        margin: 0;
    }

    .hero--picture-left.hero--media-bleed .hero__media > img,
    .hero--picture-left.hero--media-bleed .hero__media > picture > img,
    .hero--picture-left.hero--media-bleed .hero__media > .placeholder {
        inset: 0 auto 0 0;
        object-position: left center;
        border-radius: 0;
    }
}

/* The service hero, picture on the right. */
.service-hero--picture-right.service-hero--media-frame .service-hero__media {
    margin-left: 0;
    margin-right: calc(-1 * var(--section-edge-gap));
}

@media (min-width: 1025px) {
    .service-hero--picture-right .service-hero__body {
        margin-inline-start: 0;
    }

    .service-hero--picture-right .service-hero__media {
        left: 48%;
        right: calc(var(--section-breakout-pad) - var(--section-edge-gap));
    }

    .service-hero--picture-right .service-hero__media > img,
    .service-hero--picture-right .service-hero__media > picture > img {
        inset: 0 0 0 auto;
        object-position: right center;
    }
}

/* ── Picture on phones and tablets (D101) ─────────────────────────────
   Below 1025px every hero stacks, and which comes first used to be fixed
   by each template's source order: the home and About heroes' picture
   under the text, the service hero's above it. It is now the editor's
   ("Picture on phones and tablets"), above by default on all three, and
   moved with `order` so the source order — the order a screen reader
   reads — stays as it was.

   The fits that reach the band's painted edge (fill the height, shaped
   picture, brand frame; the service hero has only the last two) now
   reach whichever edge the picture is next to: the top when it is above
   the text, the bottom when it is below. The text keeps the section's
   padding on its outer side only, so picture and text are the hero's
   own gap apart, whichever way round — as the service hero always was.
   "Inside the section" is left alone: padding above and below is what
   that fit is.

   Inside `max-width: 1024px` so that none of it reaches the side-by-side
   layout, whose own rules set these same margins and padding.

   The space between the band's edge and the picture is the editor's too
   ("Space at the picture's edge", under "Phones and tablets"): one class
   per step in PICTURE_GAP_STEPS (core/blocks.py), each setting
   --hero-picture-gap, which the margins below add to the half-gap reach.
   `test_every_picture_gap_has_a_rule` keeps the two lists in step. */
.hero--picture-gap-0  { --hero-picture-gap: 0px; }
.hero--picture-gap-16 { --hero-picture-gap: 16px; }
.hero--picture-gap-32 { --hero-picture-gap: 32px; }
.hero--picture-gap-48 { --hero-picture-gap: 48px; }
.hero--picture-gap-64 { --hero-picture-gap: 64px; }

@media (max-width: 1024px) {
    .hero--picture-position-above .hero__media,
    .hero--picture-position-above .service-hero__media {
        order: -1;
    }

    .hero--picture-position-below .hero__media,
    .hero--picture-position-below .service-hero__media {
        order: 1;
    }

    /* The service hero kept the section's padding when stacked, which is
       why its picture stopped short of the band's top edge. The padding
       moves to the text, as on the other two heroes' edge-reaching fits. */
    .service-hero {
        padding-block: 0;
    }

    /* The text's padding, on the side away from the picture only. */
    .hero--picture-position-above:is(.hero--media-fill, .hero--media-bleed, .hero--media-frame) .hero__body,
    .hero.hero--picture-position-above .service-hero__body {
        padding-block: 0 var(--space-section);
    }

    .hero--picture-position-below:is(.hero--media-fill, .hero--media-bleed, .hero--media-frame) .hero__body,
    .hero.hero--picture-position-below .service-hero__body {
        padding-block: var(--space-section) 0;
    }

    /* The picture reaches the band's painted edge on its own side. Half a
       grid gap, as everywhere else: gridlayout paints the band that far
       past the cell (see "Filling the band to its painted edge"). */
    .hero--picture-position-above:is(.hero--media-fill, .hero--media-bleed, .hero--media-frame) .hero__media,
    .hero.hero--picture-position-above .service-hero__media {
        margin-top: calc(var(--gl-gap) / -2 + var(--hero-picture-gap, 0px));
        margin-bottom: 0;
    }

    /* Below the text, `margin-top: auto` keeps the picture on the band's
       bottom edge even when the hero is taller than its content ("Fill
       the screen" on a tall tablet); the spare room goes between the text
       and the picture instead of under the picture. */
    .hero--picture-position-below:is(.hero--media-fill, .hero--media-bleed, .hero--media-frame) .hero__media,
    .hero.hero--picture-position-below .service-hero__media {
        margin-top: auto;
        margin-bottom: calc(var(--gl-gap) / -2 + var(--hero-picture-gap, 0px));
    }
}

/* ── Service cards ─────────────────────────────────────────────────────
   One card per live service page (Services, question 1). The grid's shape
   is the editor's, per breakpoint (question 2): ServiceCardsBlock works
   out how many tracks the grid has and how many each card spans, so that
   a short last row stretches to fill the width — five services in rows of
   three are a 6-track grid, 2 tracks per card, then 3 each in the last row.

   The whole card is the link's target (a stretched ::before on "Explore
   the service"), so it can be clicked anywhere, while only one link is
   announced per card. */

.service-cards__grid {
    display: grid;
    grid-template-columns: repeat(var(--cards-tracks-phone, 1), minmax(0, 1fr));
    gap: var(--space-5);
    margin: var(--space-7) 0 0;
    padding: 0;
    list-style: none;
}

.service-card {
    grid-column: span var(--card-span-phone, 1);
    position: relative;
    display: flex;
    flex-direction: column;
    background: var(--colour-white);
    border: 1px solid var(--colour-border);
    border-radius: var(--radius-lg);
    overflow: hidden;
}

/* A fixed height rather than a ratio: the design keeps the photo strip the
   same height across a row of three and a row of two wider cards. */
.service-card__media {
    height: 12rem;
}

.service-card__media img,
.service-card__media .placeholder {
    display: flex;
    width: 100%;
    height: 100%;
    object-fit: cover;
    aspect-ratio: auto;
    border: 0;
    border-radius: 0;
}

/* The icon on its pale disc, half over the photo. */
.service-card__icon {
    position: relative;
    z-index: 1;
    display: grid;
    place-items: center;
    width: 4rem;
    height: 4rem;
    margin: -2rem 0 0 var(--space-5);
    background: var(--colour-surface);
    border-radius: var(--radius-pill);
}

.service-card__icon img {
    width: 2.25rem;
    height: 2.25rem;
    object-fit: contain;
}

.service-card__body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4) var(--space-5) var(--space-5);
}

.service-card__title {
    margin: 0;
    font-size: var(--text-xl);
    color: var(--colour-navy);
}

.service-card__summary {
    margin: 0;
    color: var(--colour-text-muted);
}

.service-card__link {
    margin-top: auto;
    padding-top: var(--space-4);
    color: var(--colour-navy);
    font-size: var(--text-sm);
    font-weight: var(--weight-semibold);
    text-decoration: none;
}

/* The design's "»", drawn rather than typed, so it is not read out. */
.service-card__link::after {
    content: "\00a0\00a0»";
}

/* The whole card is the link's target. */
.service-card__link::before {
    content: "";
    position: absolute;
    inset: 0;
}

.service-card:hover .service-card__link,
.service-card__link:focus-visible {
    text-decoration: underline;
}

.service-cards__empty {
    margin-top: var(--space-6);
    color: var(--colour-text-muted);
}

@media (min-width: 641px) {
    .service-cards__grid {
        grid-template-columns: repeat(var(--cards-tracks-tablet, 2), minmax(0, 1fr));
    }

    .service-card {
        grid-column: span var(--card-span-tablet, 1);
    }
}

@media (min-width: 1025px) {
    .service-cards__grid {
        grid-template-columns: repeat(var(--cards-tracks-desktop, 3), minmax(0, 1fr));
    }

    .service-card {
        grid-column: span var(--card-span-desktop, 1);
    }
}

/* ── Numbered list ─────────────────────────────────────────────────────
   Compact (short items beside small numbers) or large (a title and a
   paragraph per item, between rules). The number is drawn from the
   list's order; the <ol> already announces it, so the drawn one is
   aria-hidden (core/blocks/numbered_list.html). */

.numbered-list__items {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    margin: var(--space-5) 0 0;
    padding: 0;
    list-style: none;
}

.numbered-item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
}

.numbered-item__number {
    flex: none;
    display: grid;
    place-items: center;
    width: 1.75rem;
    height: 1.75rem;
    background: var(--colour-surface);
    border-radius: var(--radius-pill);
    font-family: var(--font-heading);
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    color: var(--colour-navy);
}

.numbered-item__body {
    flex: 1;
    min-width: 0;
}

.numbered-item__title {
    margin: 0;
    font-size: var(--text-lg);
    color: var(--colour-navy);
}

.numbered-item__text {
    /* Full width of the item, as the design runs it to the same edge as
       the rules between items — not the 68ch reading measure every other
       paragraph keeps (simplelaw.css). The section's own width already
       bounds it. */
    max-width: none;
    margin: 0;
    font-size: var(--text-lg);
    color: var(--colour-text-muted);
}

.numbered-list--large .numbered-list__items {
    gap: 0;
}

.numbered-list--large .numbered-item {
    padding-block: var(--space-6);
}

/* The rules between items: a border on every item after the first, so
   their count always matches the items. */
.numbered-list--large .numbered-item + .numbered-item {
    border-top: 2px solid var(--colour-border);
}

.numbered-list--large .numbered-item__number {
    width: 2.75rem;
    height: 2.75rem;
    font-size: var(--text-xl);
}

/* The title sits level with the larger number; the paragraph below it. */
/* Revised (D102, after review): grid rather than flex. As a flex
   container its text was an anonymous item only as wide as its words, so
   a title alignment had nothing to move; a grid item spans the row, and
   `align-items: center` still sets the title level with the number. */
.numbered-list--large .numbered-item__title {
    min-height: 2.75rem;
    display: grid;
    align-items: center;
}

.numbered-list--large .numbered-item__text {
    margin-top: var(--space-3);
}

/* ── Highlight panel ───────────────────────────────────────────────────
   The pale box beside a numbered list: "What you walk away with",
   "Formats we offer". The box is the section's own (see the template);
   the heading is the design's small uppercase label, in navy. */

.highlight-panel__box {
    padding: var(--space-7) var(--space-6);
    background: var(--colour-surface);
    border-radius: var(--radius-xl);
}

.highlight-panel__heading {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--colour-navy);
}

.highlight-panel__items {
    display: flex;
    flex-direction: column;
    gap: var(--space-5);
    margin: var(--space-6) 0 0;
    padding: 0;
    list-style: none;
}

.highlight-item {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
}

/* The Simple Law mark as the bullet: a narrow glyph about a capital's
   height, in navy, level with the first line. */
.highlight-item .mark {
    flex: none;
    width: 0.45rem;
    height: auto;
    margin-top: 0.3em;
    fill: currentColor;
    color: var(--colour-navy);
}

.highlight-item__body {
    min-width: 0;
}

.highlight-item__title {
    margin: 0;
    font-size: var(--text-sm);
    font-weight: var(--weight-bold);
    color: var(--colour-navy);
}

.highlight-item__text {
    margin: 0;
    font-size: var(--text-lg);
    color: var(--colour-text-muted);
}

/* An item with a title ("Simple Law Awareness") reads as a title and a
   line of description, so its text is set smaller, as the design does. */
.highlight-item__title + .highlight-item__text {
    margin-top: var(--space-1);
    font-size: var(--text-sm);
}

/* ── Icon list ─────────────────────────────────────────────────────────
   A heading, a small label *under* it, and short items each beside an
   icon on a pale disc. One column on a phone, two on a tablet, three from
   1025px (Services, question 11). */

.icon-list__label {
    margin: var(--space-3) 0 0;
}

.icon-list__items {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-6) var(--space-5);
    margin: var(--space-7) 0 0;
    padding: 0;
    list-style: none;
}

.icon-item {
    display: flex;
    align-items: center;
    gap: var(--space-4);
}

.icon-item__disc {
    flex: none;
    display: grid;
    place-items: center;
    width: 4.5rem;
    height: 4.5rem;
    background: var(--colour-surface);
    border-radius: var(--radius-pill);
}

.icon-item__disc img {
    width: 2.75rem;
    height: 2.75rem;
    object-fit: contain;
}

.icon-item__label {
    margin: 0;
    font-family: var(--font-heading);
    font-size: var(--text-xl);
    font-weight: var(--weight-semibold);
    line-height: 1.2;
    color: var(--colour-navy);
}

@media (min-width: 641px) {
    .icon-list__items {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 1025px) {
    .icon-list__items {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}
