/* SMALL TYPE GROWS WITH THE SCREEN — 2026-08-08.
 *
 * The client's two observations were one fault: "有些字太小了" on a 4K screen,
 * and "保持长宽不变，整体缩小就整体缩小". Measured: this stylesheet had 349
 * vw-clamps and EVERY ONE of them stops growing before 3840 — the design is
 * fluid to about 1400-1900 and frozen after that — while the small labels
 * (eyebrows, dates, roles, filter captions, cookie buttons, review bylines)
 * were flat pixel values that never grew at all. On a 4K screen the big type
 * is frozen and the small type is a third the size it should be, which is why
 * the proportions read as broken.
 *
 * A global `zoom` was tried first and rejected with evidence: at 3840 with
 * zoom 2.29 the layout width stays 3840 and the page overflows sideways by
 * 1600px, because `zoom` does not shrink the layout viewport and every vw in
 * here still resolves against the full width. A vw-driven layout and a global
 * zoom cannot both be true.
 *
 * So the values grow instead. Each small size becomes
 * `clamp(<old>, <old/16.8>vw, <old*1.8>)`: the vw term equals the old value at
 * exactly 1680, so **nothing below 1680 changes by a single pixel** — the
 * laptop rendering the client already approved is untouched — and above it the
 * label scales with the screen to 1.8x, reaching its cap around 3020.
 */
/* MINIMUM TYPE SIZE: 12px — set 2026-08-08 at the client's request.
 *
 * "有些字太小了，可能丧失可读性。对于字的大小方面，这个应该是有明确要求的."
 *
 * The rule is: NO RENDERED TEXT ON THIS SITE IS SMALLER THAN 12px. Measured
 * before it existed, nine public pages carried text at 9, 9.5, 10, 10.5 and 11
 * — 79 declarations in all — and most of it was the letter-spaced small-caps
 * this design leans on: eyebrows, dates, roles, filter labels, the cookie
 * buttons, the review attributions. Small caps at 10px with 0.12em of tracking
 * looks composed on a designer's screen and is genuinely hard to read on a
 * laptop at arm's length, which is where a client reads it.
 *
 * 12px is the floor, not a target: anything already larger is untouched. If a
 * label no longer fits its column at 12px, the column is what changes.
 */
/* ============================================================
   Editorial routes — Legal Insights, Case Studies, profiles, privacy

   These pages did not exist before: everything on this site linked out to
   `yingke.com.au`, which is being taken down. They are built from the same
   vocabulary the three original routes settled on, so the site reads as one
   thing rather than as a core plus a content annexe:

     - a pigment ground with a progressive frost on the left, from four static
       images (one sharp, three pre-blurred) — never a live blur;
     - hairlines that run from a label out to the margin;
     - tracked small-caps labels in the rust accent;
     - one serif, and no second typeface for emphasis;
     - grained paper below the opening, never a flat fill.
   ============================================================ */

.editorial-page {
  /* The rail was fixed at 1400px, which is right at 1440 and mean at 1920: the page
     kept its narrow middle band and left the outer 260px either side plus the whole
     right third of the column empty. It now grows with the window up to 1680. */
  --rail: clamp(1400px, 96vw, 1680px);
  --gutter: max(var(--gutter-min), calc((100vw - var(--rail)) / 2));

  /* ---- the measure ----------------------------------------------------------
   *
   * The one number a page of prose is built on: how many characters land on a line.
   * 45 to 75 is the comfortable band and 66 is the number Bringhurst gives as ideal
   * for a single column, and the reason is mechanical rather than a matter of taste.
   * The eye reads in jumps and, at the end of a line, has to sweep back to the start
   * of the next one. Past about 75 characters that return sweep starts landing on the
   * wrong line, so you re-read or skip; under about 45 the returns come so often the
   * rhythm never settles. This site was running 76 at 768px and 86 from 1920 up.
   *
   * It was 86 rather than 66 because the cap was a *width* — 62rem — while the type
   * scaled with the window. The column grew 1.80x between 768 and 1920 and the type
   * only 1.58x, so the character count drifted upward with the screen. A width cannot
   * hold a character count; only something tied to the font can.
   *
   * `ch` is the obvious candidate and is a trap here twice over. It is the advance of
   * "0", which in these faces is 1.23x the average character — measured, Iowan Old
   * Style at 26px: 1ch is 14.46px against an average of 11.77px — so `68ch` fits 84
   * characters, not 68. And it resolves against each element's own font-size, which is
   * why an earlier attempt at `58ch` gave the paragraphs 548px and the headings 813px.
   *
   * So the measure is derived instead. The average character is 0.4527 of the font
   * size in this face, so 66 characters is 29.9 times the font size, and the body type
   * is clamp(16.5px, 1.45vw, 22px). Multiplying through gives the line below — a plain
   * length, identical everywhere it is read, which the grid track needs as much as the
   * prose does. Change the type scale and this has to be recomputed with it; that is
   * the cost of holding the count fixed, and it is worth paying.
   *
   * The cap is 22px. It was 28, reached by working backwards from the picture: figures
   * are flush with the prose by a standing rule of the client's, so the measure is also
   * the picture's width, and bigger type at a fixed 66 characters is the only way to a
   * bigger picture that does not break the flush rule. That reasoning was sound and the
   * result was wrong. 28px is what the rest of this site uses for a *lead* — the
   * homepage's opening statement is 28px and its running text is 21px — so the articles
   * were setting every paragraph at the size everything else reserves for one sentence.
   * The client read it as 太大 twice before I stopped optimising for the picture.
   *
   * 22px, a shade above the site's 21px body because this is sustained reading. The
   * measure follows it down to 658px, and so does the figure, which the client has
   * already said is the right trade.
   *
   * Result: 66 characters at every width from 1024 up, and 47 on a phone, where a
   * short line is right anyway. */
  --measure: clamp(493px, 43.4vw, 658px);

  /* The contents rail beside the prose, and the space between the two. The gap used
     to be clamp(36px, 10vw, 150px), derived so that the leftover width either side of
     the text came out equal — arithmetic that only held at one measure and stopped
     holding the moment the measure changed. The block is centred as a block now, so
     the gap is free to be what it should have been all along: enough air to read as a
     separation, and no more. */
  --rail-col: clamp(196px, 18vw, 264px);
  --rail-gap: clamp(36px, 5vw, 108px);

  /* Everything on an article page lines up inside this: the rail, the gap and the
     measure. The h1 stands on the same left edge the contents rail does — that
     alignment is the page's spine, and centring the article block without bringing the
     hero with it would have left the two 138px apart at 1440. */
  --block: calc(var(--rail-col) + var(--rail-gap) + var(--measure));

  /* ---- the list rows ----------------------------------------------------------
   *
   * One column, and the roles inside it separated by size — which is how Apple's
   * newsroom does it, measured rather than assumed: at both 1440 and 1920 the h1, the
   * standfirst and the body all sit in the *same* 653px column, and the column does not
   * grow with the window. What changes between the roles is the type: 48px gives 29
   * characters a line, 24px gives 55, in one width.
   *
   * I had just done the opposite — given the title its own wider measure and made the
   * deck a fraction of it — because the titles were wrapping to four lines. That fixed
   * the symptom by breaking the structure. The column is sized by the role that is
   * *read* (the deck, at ~66 characters), and the role that is *scanned* takes whatever
   * that width gives it at its own size, which for a 30px title is 45 characters —
   * inside the 30-45 the display convention asks for, and enough that the longest entry
   * on the site (133 characters) lands on three lines instead of four.
   *
   * The deck's type had drifted to 16.5px, smaller than body copy anywhere else on this
   * site, which is the other half of why these rows read as thin. At 20px it is the
   * size the homepage and the articles use, and the column follows from it: the average
   * character is 0.45 of the font size, so 66 characters is 29.7 times it. */
  --item-deck: clamp(15px, 1.32vw, 20px);
  --item-measure: clamp(446px, 39.2vw, 594px);

  /* And the row those two sit in.
   *
   * The row used to span the whole rail with the middle track at 1fr, so the text
   * stopped at its measure and the "Read" link stayed pinned to the far edge: 694px of
   * nothing between them at 1440, 965px at 1920 — 51% and 57% of the row. At that
   * distance the link is not part of the entry it belongs to, it is a column of its own
   * standing at the edge of the page.
   *
   * The row is now only as wide as its three parts need, and centred. --item-go is the
   * "Read" plus its arrow with room for the 4px the arrow travels on hover; it was a
   * 190px track holding 63px of content, right-aligned, so a further 105px of the gap
   * was inside the link's own column. */
  --item-go: 84px;
  --item-gap: clamp(20px, 2.6vw, 46px);
  --list-block: calc(128px + var(--item-gap) + var(--item-measure) + var(--item-gap) + var(--item-go));
  --wall: #f5f3ec;
  --ink: #1b1c19;
  --body: #35332e;
  --muted: #625d55;
  --accent: #52281b;
  --label: #6f5529;
  --rule: rgba(28, 26, 24, 0.16);
  --grain: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.45'/%3E%3C/svg%3E");
  background-color: var(--wall);
  background-image: var(--grain);
  background-size: 190px 190px;
  background-attachment: fixed;
  background-blend-mode: soft-light;
}

/* ---- opening ---- */

.ed-opening {
  --field-focus: 100% center;
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.ed-opening__field,
.ed-opening__frost,
.ed-opening__settle,
.ed-opening__masthead {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.ed-opening__field {
  z-index: -3;
  background: var(--field) var(--field-focus) / cover no-repeat;
}

.ed-opening__field::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0.15;
  mix-blend-mode: soft-light;
  background-image: var(--grain);
  background-size: 180px 180px;
}

.ed-opening__frost { z-index: -2; }

.ed-opening__frost span {
  position: absolute;
  inset: 0;
  display: block;
  background-position: var(--field-focus);
  background-repeat: no-repeat;
  background-size: cover;
}

.ed-opening__frost span:nth-child(1) {
  background-image: var(--field-soft);
  mask-image: linear-gradient(90deg, #000 0%, #000 44%, transparent 70%);
  -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 44%, transparent 70%);
}
.ed-opening__frost span:nth-child(2) {
  background-image: var(--field-softer);
  mask-image: linear-gradient(90deg, #000 0%, #000 30%, transparent 56%);
  -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 30%, transparent 56%);
}
.ed-opening__frost span:nth-child(3) {
  background-image: var(--field-softest);
  mask-image: linear-gradient(90deg, #000 0%, #000 16%, transparent 42%);
  -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 16%, transparent 42%);
}

.ed-opening__frost::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    rgba(248, 246, 239, 0.42) 0%,
    rgba(248, 246, 239, 0.28) 26%,
    rgba(248, 246, 239, 0.1) 54%,
    rgba(248, 246, 239, 0) 76%);
}

/* Nav legibility must not depend on which artwork sits behind it. */
.ed-opening__masthead {
  z-index: -1;
  height: calc(var(--header-height) + 34px);
  inset: 0 0 auto;
  background:
    linear-gradient(90deg, rgba(247, 245, 239, 0) 34%, rgba(247, 245, 239, 0.5) 78%, rgba(247, 245, 239, 0.62) 100%),
    linear-gradient(180deg, rgba(247, 245, 239, 0.84) 0%, rgba(247, 245, 239, 0.62) 46%, rgba(247, 245, 239, 0) 100%);
  /* The horizontal component has no vertical falloff of its own, so on its own it
     paints a uniformly opaque band the full height of this element and then stops
     dead — a white stripe straight across the artwork. Masking the whole layer
     vertically makes both components fade out together. */
  mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.62) 48%, transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, 0.62) 48%, transparent 100%);
}

.ed-opening__settle {
  z-index: -1;
  background: linear-gradient(180deg, rgba(245, 243, 236, 0) 64%, rgba(245, 243, 236, 0.7) 86%, var(--wall) 100%);
}

@supports not ((mask-image: linear-gradient(#000, transparent)) or (-webkit-mask-image: linear-gradient(#000, transparent))) {
  .ed-opening__frost span { display: none; }
  .ed-opening__frost::after {
    background: linear-gradient(90deg,
      rgba(248, 246, 239, 0.93) 0%, rgba(248, 246, 239, 0.8) 26%,
      rgba(248, 246, 239, 0.48) 48%, rgba(248, 246, 239, 0.1) 70%);
  }
}

.ed-hero {
  min-height: clamp(360px, 24.4792vw, 470px);
  position: relative;
  display: grid;
  align-content: center;
  padding: calc(var(--header-height) + clamp(44px, 4.2vw, 66px)) 0 clamp(40px, 3.02083vw, 58px);
  @media (max-width: 1180px) {
    min-height: clamp(360px, 30vw, 470px);
    padding: calc(var(--header-height) + clamp(44px, 4.2vw, 66px)) 0 clamp(40px, 3.8vw, 58px);
  }
}

.ed-hero__inner { margin: 0 var(--gutter); }

/* On a page that carries an article, the hero's text sits on the article's own left
   edge rather than on the page's. The painting behind it stays full-bleed; only the
   type moves. Scoped with `:has()` because the index pages share this hero and have no
   article under it to line up with. */
.editorial-page:has(.ed-article) .ed-hero__inner {
  width: min(100% - var(--gutter) * 2, var(--block));
  margin-inline: auto;
}

/* Same treatment for the index pages, against their own narrower block: the h1 has to
   stand on the same left edge the entries do, or the page has two spines. */
.editorial-page:has(.ed-items) .ed-hero__inner {
  width: min(100% - var(--gutter) * 2, var(--list-block));
  margin-inline: auto;
}

/* the site's label-plus-rule device */
.ed-eyebrow {
  margin: 0 0 clamp(20px, 2.2vw, 30px);
  display: flex;
  align-items: center;
  gap: clamp(14px, 1.25vw, 24px);
  color: var(--accent);
  font-family: var(--sans);
  font-size: clamp(10.5px, 0.625vw, 18.9px);
  font-weight: 700;
  letter-spacing: 0.24em;
  line-height: 1.2;
  text-transform: uppercase;
  @media (max-width: 1180px) {
    gap: clamp(14px, 1.6vw, 24px);
  }
}

.ed-eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(82, 40, 27, 0.4), rgba(82, 40, 27, 0.08) 62%, rgba(82, 40, 27, 0));
}

.ed-hero h1 {
  max-width: 19ch;
  margin: 0;
  color: var(--ink);
  font-family: var(--serif);
  /* 82px is a display size, and an article title is a sentence — "Discretionary Trust
     Tax Reforms: Preparing for the 2028 Changes" came out four lines deep and taller
     than the artwork behind it. The same fault the Contact page's section heading had:
     a treatment that works on one word applied to twenty. */
  font-size: clamp(34px, 3.6vw, 56px);
  font-weight: 400;
  letter-spacing: -0.03em;
  line-height: 0.96;
}

.ed-hero__lede {
  max-width: 54ch;
  margin: clamp(20px, 2vw, 28px) 0 0;
  color: var(--body);
  font-family: var(--serif);
  font-size: clamp(17px, 1.09375vw, 21px);
  /* 1.15 — the client's number, asked for three times and reaffirmed with a
     reason: it is what makes a law firm read as approachable rather than
     institutional. The site-wide pass set it on the marketing routes; the
     editorial ones kept the looser reading measures they were drawn with
     (1.45-1.62), which is why the pages with the MOST prose were the ones
     still not obeying it. Measured before changing: article body 1.62,
     privacy 1.62, profile 1.45, index summaries 1.55. */
  line-height: 1.15;
  @media (max-width: 1180px) {
    font-size: clamp(17px, 1.35vw, 21px);
  }
}

/* ---- Case Studies: matters handled by type ------------------------------
 *
 * One pigment, many weights.  Every segment starts from the page's own
 * `--accent` and is mixed only toward its own `--wall`; there is no dashboard
 * palette and no new hue.  The order therefore reads as a measured tonal
 * hierarchy rather than as eight unrelated categories shouting at once. */
.ed-case-mix {
  padding: clamp(56px, 4.6vw, 88px) var(--gutter) clamp(62px, 5vw, 96px);
  border-bottom: 1px solid var(--rule);
}

.ed-case-mix__inner {
  width: min(100%, 1040px);
  margin-inline: auto;
}

.ed-case-mix__intro { max-width: 620px; }

/* THE EYEBROW IS GONE — "MATTERS HANDLED" above the heading, removed at the
   client's request. Its rules go with it rather than being left for a class
   nothing emits: a stylesheet that describes elements the site does not build
   is a map of a place that is not there. */

/* A STEP BELOW THE PAGE'S OWN TITLE, DELIBERATELY. `.ed-hero h1` — "Case
   Studies" — is clamp(34px, 3.6vw, 56px); this was clamp(32px, 2.5vw, 48px),
   close enough at the narrow end that the two read as the same rank and the
   section looked like a second page title. At 1440 it is now 29px against the
   title's 52px, and at the phone floor 27 against 34. A section heading inside
   a page is not the page. */
.ed-case-mix__intro h2 {
  margin: 0;
  color: var(--ink);
  font-family: var(--serif);
  font-size: clamp(27px, 2vw, 38px);
  font-weight: 400;
  letter-spacing: -0.03em;
  line-height: 1;
  text-wrap: balance;
}

.ed-case-mix__layout {
  margin-top: clamp(38px, 4vw, 68px);
  display: grid;
  grid-template-columns: minmax(290px, 0.82fr) minmax(0, 1.5fr);
  gap: clamp(42px, 5.2vw, 88px);
  align-items: center;
}

.ed-case-mix__visual {
  width: min(100%, 380px);
  position: relative;
  justify-self: center;
  aspect-ratio: 1;
}

.ed-case-mix__svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.ed-case-mix__track,
.ed-case-mix__segment {
  fill: none;
  stroke-width: 18;
}

/* THE RAMP IS THE LOGO'S GOLD, NOT THE ROUTE'S OXBLOOD.
 *
 * Every tone in this chart is one weight of a single ink mixed into the paper,
 * so that ink decides what the whole figure looks like. It was `--accent`,
 * #52281b — a dark red-brown that belongs to the editorial furniture (eyebrows,
 * rules, focus outlines) and reads as oxblood at full strength. The client:
 * 「和 logo 保持一致，现在太深红了」.
 *
 * `--gold` is #a07b45, the brand token, and the mark itself is #90724e at 68%
 * over paper — within a few percent of each other. Taking the token rather than
 * sampling the artwork keeps one name for the brand colour instead of a second
 * hex that would have to be found and changed again if the mark ever moves.
 *
 * SCOPED, NOT GLOBAL. `--accent` is used twenty times in this stylesheet and
 * still is; only the five declarations that paint this chart read the new
 * variable. The legend's focus outline deliberately stays on `--accent`: it is
 * an accessibility affordance, and a lighter gold would weaken it. */
.ed-case-mix { --case-mix-ink: var(--gold, #a07b45); }

.ed-case-mix__track { stroke: color-mix(in srgb, var(--case-mix-ink) 8%, var(--wall)); }

/* ONE CATEGORY'S COLOUR, IN TWO STEPS.
 *
 * `--case-tone-shade` darkens the brand gold toward the page's own ink; the
 * result is then set against the paper by `--case-tone-weight`. Both numbers
 * come from `caseTone()` in render-section-index.ts, which is where the reason
 * for the ramp's length is written down. Doing it in two nested mixes rather
 * than emitting eight hex values keeps the palette a consequence of ONE colour:
 * change `--case-mix-ink` and the whole ring follows, which is how it survived
 * the move off the oxblood. */
.ed-case-mix__segment,
.ed-case-mix__dot {
  --case-tone: color-mix(in srgb, var(--ink) var(--case-tone-shade, 0%), var(--case-mix-ink));
}

.ed-case-mix__segment {
  stroke: var(--case-mix-ink);
  stroke: color-mix(in srgb, var(--case-tone) var(--case-tone-weight), var(--wall));
  stroke-linecap: butt;
  transform: translate(0, 0);
  opacity: 0.94;
  /* It is painted, not pointed at — see `.ed-case-mix__hit` below. */
  pointer-events: none;
  will-change: transform, opacity;
  transition:
    stroke-dasharray 700ms cubic-bezier(0.22, 0.61, 0.36, 1) var(--case-delay),
    transform 280ms cubic-bezier(0.22, 0.61, 0.36, 1),
    opacity 280ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* The inline head bootstrap adds `.js` before styles are parsed. That lets a
   motion-capable browser begin at zero without ever flashing the SSR final
   ring first; no-script and reduced-motion visitors keep the complete
   presentation attribute emitted by the server. */
.js .ed-case-mix__segment { stroke-dasharray: 0 100; }
.js .ed-case-mix.is-revealed .ed-case-mix__segment {
  stroke-dasharray: var(--case-dash);
}

/* THE RING THE POINTER ACTUALLY TOUCHES. Unpainted, unmoving, and eight units
   wider than the arc it stands in for, so the band is a forgiving target on a
   380px circle. `pointer-events: stroke` is what makes a stroke of `none`
   hit-testable; without it this element is invisible to the pointer as well as
   to the eye. See render-section-index.ts for why the moving arc and the
   pointed-at arc had to become two circles. */
.ed-case-mix__hit {
  fill: none;
  stroke: none;
  stroke-width: 26;
  pointer-events: stroke;
}

/* THE INACTIVE ARCS GO MUCH FURTHER DOWN THAN THEY USED TO — 0.38 before.
 *
 * Every tone in this figure is one ink at a different weight, so two adjacent
 * categories can be a few percent apart and the client could not tell which arc
 * they were pointing at: 「因为颜色是渐变的不是很明显」. Dimming is the only
 * contrast lever available here that the compositor can run on its own — stroke
 * width and dash length are both geometry, and the audit forbids changing them
 * on hover for exactly that reason.
 *
 * 0.24 rather than the 0.14 this was first set to. At 0.14 the other categories
 * vanish into the track and the figure stops being a chart for as long as the
 * pointer is on it — you can no longer see that the lit arc is one part of a
 * whole, which is the only thing a donut is for. At 0.24 the divisions are still
 * legible as tints and the live arc is four times any of them; the old contrast
 * was two and a half. Both were rendered and compared before choosing. */
.ed-case-mix[data-active] .ed-case-mix__segment { opacity: 0.24; }

/* data-active is the chart's only hover/focus state. The admin caps a
   collection at fifty readable categories, so this static selector map keeps
   the public script out of every segment's style/class list: one attribute
   write on the container and the compositor owns the rest. */
.ed-case-mix[data-active="0"] .ed-case-mix__segment[data-case-segment="0"],
.ed-case-mix[data-active="1"] .ed-case-mix__segment[data-case-segment="1"],
.ed-case-mix[data-active="2"] .ed-case-mix__segment[data-case-segment="2"],
.ed-case-mix[data-active="3"] .ed-case-mix__segment[data-case-segment="3"],
.ed-case-mix[data-active="4"] .ed-case-mix__segment[data-case-segment="4"],
.ed-case-mix[data-active="5"] .ed-case-mix__segment[data-case-segment="5"],
.ed-case-mix[data-active="6"] .ed-case-mix__segment[data-case-segment="6"],
.ed-case-mix[data-active="7"] .ed-case-mix__segment[data-case-segment="7"],
.ed-case-mix[data-active="8"] .ed-case-mix__segment[data-case-segment="8"],
.ed-case-mix[data-active="9"] .ed-case-mix__segment[data-case-segment="9"],
.ed-case-mix[data-active="10"] .ed-case-mix__segment[data-case-segment="10"],
.ed-case-mix[data-active="11"] .ed-case-mix__segment[data-case-segment="11"],
.ed-case-mix[data-active="12"] .ed-case-mix__segment[data-case-segment="12"],
.ed-case-mix[data-active="13"] .ed-case-mix__segment[data-case-segment="13"],
.ed-case-mix[data-active="14"] .ed-case-mix__segment[data-case-segment="14"],
.ed-case-mix[data-active="15"] .ed-case-mix__segment[data-case-segment="15"],
.ed-case-mix[data-active="16"] .ed-case-mix__segment[data-case-segment="16"],
.ed-case-mix[data-active="17"] .ed-case-mix__segment[data-case-segment="17"],
.ed-case-mix[data-active="18"] .ed-case-mix__segment[data-case-segment="18"],
.ed-case-mix[data-active="19"] .ed-case-mix__segment[data-case-segment="19"],
.ed-case-mix[data-active="20"] .ed-case-mix__segment[data-case-segment="20"],
.ed-case-mix[data-active="21"] .ed-case-mix__segment[data-case-segment="21"],
.ed-case-mix[data-active="22"] .ed-case-mix__segment[data-case-segment="22"],
.ed-case-mix[data-active="23"] .ed-case-mix__segment[data-case-segment="23"],
.ed-case-mix[data-active="24"] .ed-case-mix__segment[data-case-segment="24"],
.ed-case-mix[data-active="25"] .ed-case-mix__segment[data-case-segment="25"],
.ed-case-mix[data-active="26"] .ed-case-mix__segment[data-case-segment="26"],
.ed-case-mix[data-active="27"] .ed-case-mix__segment[data-case-segment="27"],
.ed-case-mix[data-active="28"] .ed-case-mix__segment[data-case-segment="28"],
.ed-case-mix[data-active="29"] .ed-case-mix__segment[data-case-segment="29"],
.ed-case-mix[data-active="30"] .ed-case-mix__segment[data-case-segment="30"],
.ed-case-mix[data-active="31"] .ed-case-mix__segment[data-case-segment="31"],
.ed-case-mix[data-active="32"] .ed-case-mix__segment[data-case-segment="32"],
.ed-case-mix[data-active="33"] .ed-case-mix__segment[data-case-segment="33"],
.ed-case-mix[data-active="34"] .ed-case-mix__segment[data-case-segment="34"],
.ed-case-mix[data-active="35"] .ed-case-mix__segment[data-case-segment="35"],
.ed-case-mix[data-active="36"] .ed-case-mix__segment[data-case-segment="36"],
.ed-case-mix[data-active="37"] .ed-case-mix__segment[data-case-segment="37"],
.ed-case-mix[data-active="38"] .ed-case-mix__segment[data-case-segment="38"],
.ed-case-mix[data-active="39"] .ed-case-mix__segment[data-case-segment="39"],
.ed-case-mix[data-active="40"] .ed-case-mix__segment[data-case-segment="40"],
.ed-case-mix[data-active="41"] .ed-case-mix__segment[data-case-segment="41"],
.ed-case-mix[data-active="42"] .ed-case-mix__segment[data-case-segment="42"],
.ed-case-mix[data-active="43"] .ed-case-mix__segment[data-case-segment="43"],
.ed-case-mix[data-active="44"] .ed-case-mix__segment[data-case-segment="44"],
.ed-case-mix[data-active="45"] .ed-case-mix__segment[data-case-segment="45"],
.ed-case-mix[data-active="46"] .ed-case-mix__segment[data-case-segment="46"],
.ed-case-mix[data-active="47"] .ed-case-mix__segment[data-case-segment="47"],
.ed-case-mix[data-active="48"] .ed-case-mix__segment[data-case-segment="48"],
.ed-case-mix[data-active="49"] .ed-case-mix__segment[data-case-segment="49"] {
  transform: translate(var(--push-x), var(--push-y));
  opacity: 1;
}

/* THE ROW ON THE RIGHT ANSWERS THE ARC ON THE LEFT.
 *
 * It did not before: pointing at a slice dimmed the other slices and the list
 * beside them did nothing at all, so the chart could say THAT a category was
 * live without saying WHICH one — the client's second suggestion,
 * 「或者在右边做一个对应的效果？」. The relationship already runs both ways in
 * the script, so this costs one more selector map and no new state.
 *
 * ONE VARIABLE, NOT THREE MAPS. The map sets `--case-row: 1` on the live row
 * and the row's children read it, so the dot, the label and the percentage each
 * respond without needing fifty selectors of their own. Custom properties
 * inherit; that is the whole mechanism. */
.ed-case-mix[data-active="0"] .ed-case-mix__legend-item[data-case-legend="0"],
.ed-case-mix[data-active="1"] .ed-case-mix__legend-item[data-case-legend="1"],
.ed-case-mix[data-active="2"] .ed-case-mix__legend-item[data-case-legend="2"],
.ed-case-mix[data-active="3"] .ed-case-mix__legend-item[data-case-legend="3"],
.ed-case-mix[data-active="4"] .ed-case-mix__legend-item[data-case-legend="4"],
.ed-case-mix[data-active="5"] .ed-case-mix__legend-item[data-case-legend="5"],
.ed-case-mix[data-active="6"] .ed-case-mix__legend-item[data-case-legend="6"],
.ed-case-mix[data-active="7"] .ed-case-mix__legend-item[data-case-legend="7"],
.ed-case-mix[data-active="8"] .ed-case-mix__legend-item[data-case-legend="8"],
.ed-case-mix[data-active="9"] .ed-case-mix__legend-item[data-case-legend="9"],
.ed-case-mix[data-active="10"] .ed-case-mix__legend-item[data-case-legend="10"],
.ed-case-mix[data-active="11"] .ed-case-mix__legend-item[data-case-legend="11"],
.ed-case-mix[data-active="12"] .ed-case-mix__legend-item[data-case-legend="12"],
.ed-case-mix[data-active="13"] .ed-case-mix__legend-item[data-case-legend="13"],
.ed-case-mix[data-active="14"] .ed-case-mix__legend-item[data-case-legend="14"],
.ed-case-mix[data-active="15"] .ed-case-mix__legend-item[data-case-legend="15"],
.ed-case-mix[data-active="16"] .ed-case-mix__legend-item[data-case-legend="16"],
.ed-case-mix[data-active="17"] .ed-case-mix__legend-item[data-case-legend="17"],
.ed-case-mix[data-active="18"] .ed-case-mix__legend-item[data-case-legend="18"],
.ed-case-mix[data-active="19"] .ed-case-mix__legend-item[data-case-legend="19"],
.ed-case-mix[data-active="20"] .ed-case-mix__legend-item[data-case-legend="20"],
.ed-case-mix[data-active="21"] .ed-case-mix__legend-item[data-case-legend="21"],
.ed-case-mix[data-active="22"] .ed-case-mix__legend-item[data-case-legend="22"],
.ed-case-mix[data-active="23"] .ed-case-mix__legend-item[data-case-legend="23"],
.ed-case-mix[data-active="24"] .ed-case-mix__legend-item[data-case-legend="24"],
.ed-case-mix[data-active="25"] .ed-case-mix__legend-item[data-case-legend="25"],
.ed-case-mix[data-active="26"] .ed-case-mix__legend-item[data-case-legend="26"],
.ed-case-mix[data-active="27"] .ed-case-mix__legend-item[data-case-legend="27"],
.ed-case-mix[data-active="28"] .ed-case-mix__legend-item[data-case-legend="28"],
.ed-case-mix[data-active="29"] .ed-case-mix__legend-item[data-case-legend="29"],
.ed-case-mix[data-active="30"] .ed-case-mix__legend-item[data-case-legend="30"],
.ed-case-mix[data-active="31"] .ed-case-mix__legend-item[data-case-legend="31"],
.ed-case-mix[data-active="32"] .ed-case-mix__legend-item[data-case-legend="32"],
.ed-case-mix[data-active="33"] .ed-case-mix__legend-item[data-case-legend="33"],
.ed-case-mix[data-active="34"] .ed-case-mix__legend-item[data-case-legend="34"],
.ed-case-mix[data-active="35"] .ed-case-mix__legend-item[data-case-legend="35"],
.ed-case-mix[data-active="36"] .ed-case-mix__legend-item[data-case-legend="36"],
.ed-case-mix[data-active="37"] .ed-case-mix__legend-item[data-case-legend="37"],
.ed-case-mix[data-active="38"] .ed-case-mix__legend-item[data-case-legend="38"],
.ed-case-mix[data-active="39"] .ed-case-mix__legend-item[data-case-legend="39"],
.ed-case-mix[data-active="40"] .ed-case-mix__legend-item[data-case-legend="40"],
.ed-case-mix[data-active="41"] .ed-case-mix__legend-item[data-case-legend="41"],
.ed-case-mix[data-active="42"] .ed-case-mix__legend-item[data-case-legend="42"],
.ed-case-mix[data-active="43"] .ed-case-mix__legend-item[data-case-legend="43"],
.ed-case-mix[data-active="44"] .ed-case-mix__legend-item[data-case-legend="44"],
.ed-case-mix[data-active="45"] .ed-case-mix__legend-item[data-case-legend="45"],
.ed-case-mix[data-active="46"] .ed-case-mix__legend-item[data-case-legend="46"],
.ed-case-mix[data-active="47"] .ed-case-mix__legend-item[data-case-legend="47"],
.ed-case-mix[data-active="48"] .ed-case-mix__legend-item[data-case-legend="48"],
.ed-case-mix[data-active="49"] .ed-case-mix__legend-item[data-case-legend="49"] {
  --case-row: 1;
  background: color-mix(in srgb, var(--case-mix-ink) 9%, transparent);
  border-bottom-color: color-mix(in srgb, var(--case-mix-ink) 55%, var(--rule));
}

.ed-case-mix__center {
  position: absolute;
  inset: 29%;
  display: grid;
  place-content: center;
  text-align: center;
  pointer-events: none;
}

/* IT IS A LABEL, AND IT IS SET LIKE ONE — `.ed-eyebrow`'s voice, not the
 * heading's.
 *
 * Two wrong turns are recorded here because the second was caused by the first.
 * The client asked for the type of "our legal service"; that string is not in
 * this repository, so the request was read as meaning the heading beside it,
 * and the centre was matched to `.ed-case-mix__intro h2`. Both are `--serif` —
 * measuring them showed the only difference had been size — so the change moved
 * a number and settled nothing. The client's correction named the actual
 * difference: 一个是有衬线，一个是无衬线.
 *
 * "OUR LEGAL SERVICES" is the eyebrow above the page title on /case-studies, and
 * the firm typed those words into it themselves through the in-page editor —
 * which is why grepping the source for them found nothing and the live page
 * found them at once. It is `--sans`, 700, uppercase, widely tracked: a label.
 * So is this. `Top 8` names the set the ring is a picture of; that is the same
 * job the eyebrow does for the page.
 *
 * Tracking is 0.18em against the eyebrow's 0.24em, and that is not a
 * disagreement: the eyebrow sets at 10.5px where 0.24em is 2.5px of air, and
 * this sets at two and a half times that, where the same ratio would pull the
 * word apart. The letters are meant to sit at the same visual rhythm, not at the
 * same number.
 *
 * What went with the four-digit number it replaced: `-0.045em`, tracking for
 * digits; the tabular lining figures, which lined four numerals up under a
 * caption; and the caption itself. "TOTAL MATTERS" was the label for a number,
 * and the words that replaced it are their own label. */
.ed-case-mix__center strong {
  color: var(--ink);
  font-family: var(--sans);
  font-size: clamp(22px, 1.7vw, 30px);
  font-weight: 700;
  letter-spacing: 0.18em;
  /* The tracking is added to the RIGHT of the last letter too, so a centred
     line sets visibly left of centre. Half of it back, which is what a
     typesetter does by hand. */
  text-indent: 0.18em;
  line-height: 1.1;
  text-transform: uppercase;
}

.ed-case-mix__legend {
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  column-gap: clamp(24px, 3vw, 48px);
  list-style: none;
}

.ed-case-mix__legend li { min-width: 0; }

.ed-case-mix__legend-item {
  /* 0 at rest, 1 on the live row. Every response in this row is a function of
     it, so there is one place to change how strongly the row answers. */
  --case-row: 0;
  width: 100%;
  min-height: 64px;
  /* The wash on the live row is drawn on this box, so it lines up with the rule
     under it exactly. That is the argument against bleeding it into the gutter
     with a negative margin: the band and the rule would stop agreeing. */
  padding: 12px 2px;
  display: grid;
  grid-template-columns: 10px minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  border: 0;
  border-bottom: 1px solid var(--rule);
  border-radius: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: default;
  transition: background-color 220ms ease, border-color 220ms ease;
}

.ed-case-mix__legend-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

.ed-case-mix__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--case-mix-ink);
  background: color-mix(in srgb, var(--case-tone) var(--case-tone-weight), var(--wall));
  /* The three responses to `--case-row`, and none of them is a new element or a
     second state: the dot swells, the label darkens, the figure turns gold. */
  transform: scale(calc(1 + 0.5 * var(--case-row)));
  transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.ed-case-mix__name {
  min-width: 0;
  color: color-mix(in srgb, var(--ink) calc(var(--case-row) * 100%), var(--body));
  font-family: var(--serif);
  font-size: clamp(15px, 1vw, 17px);
  line-height: 1.15;
  text-wrap: pretty;
  transition: color 220ms ease;
}

/* THE ONE NUMBER IN THE ROW, so it carries the weight the count used to.
   `--muted` was right when the percentage was the quieter half of a pair; alone
   it is the value the row exists to state. Tabular figures keep the decimal
   points in a column down the list, and 4.2ch is wide enough for "100%". */
.ed-case-mix__percent {
  min-width: 4.2ch;
  color: color-mix(in srgb, var(--case-mix-ink) calc(var(--case-row) * 100%), var(--ink));
  font-family: var(--sans);
  font-size: clamp(12px, 0.7143vw, 14px);
  font-variant-numeric: lining-nums tabular-nums;
  font-weight: 700;
  line-height: 1;
  text-align: right;
  white-space: nowrap;
  transition: color 220ms ease;
}

.ed-case-mix__footnote {
  margin: clamp(24px, 2.4vw, 36px) 0 0;
  padding-top: 14px;
  border-top: 1px solid var(--rule);
  color: var(--muted);
  font-family: var(--sans);
  font-size: clamp(12px, 0.7143vw, 14px);
  font-variant-numeric: lining-nums tabular-nums;
  letter-spacing: 0.04em;
  line-height: 1.35;
}

@media (max-width: 820px) {
  .ed-case-mix__layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 34px;
  }
  .ed-case-mix__visual { width: min(100%, 350px); }
}

@media (max-width: 560px) {
  .ed-case-mix { padding: 46px 20px 58px; }
  .ed-case-mix__intro h2 { font-size: 27px; }
  .ed-case-mix__layout { margin-top: 32px; }
  .ed-case-mix__visual { width: min(100%, 330px); }
  .ed-case-mix__legend { grid-template-columns: minmax(0, 1fr); }
  .ed-case-mix__legend-item { min-height: 58px; }
  .ed-case-mix__center strong { font-size: 22px; }
}

@media (prefers-reduced-motion: reduce) {
  .ed-case-mix__segment { transition: none; }
  .js .ed-case-mix__segment { stroke-dasharray: var(--case-dash); }
  /* The row still answers — it just answers at once. Removing the response
     rather than its duration would take the information away from the visitor
     who asked for less motion. */
  .ed-case-mix__legend-item,
  .ed-case-mix__dot,
  .ed-case-mix__name,
  .ed-case-mix__percent { transition: none; }
}

/* Legal Insights uses the same opening component as News and Case Studies.
 * A low-opacity copy of the pre-blurred field takes the digital edge off the
 * artwork's clear side without changing the shared opening's dimensions, crop
 * or settle ramp. */
.insights-page .ed-opening::after {
  content: "";
  position: absolute;
  z-index: -2;
  inset: 0;
  pointer-events: none;
  background: var(--field-soft) var(--field-focus) / cover no-repeat;
  opacity: 0.22;
}

/* The year on its own line, on all three indexes. This was scoped to
   `.insights-page`; the client asked for the same date on News and Case
   Studies, and the renderer stopped branching on section at the same time. */
.ed-item__date-year {
  display: block;
  margin-top: 3px;
}

/* ---- index listing ---- */

.ed-list {
  padding: clamp(52px, 3.95833vw, 76px) var(--gutter) clamp(96px, 7.29167vw, 140px);
  position: relative;
  isolation: isolate;
  @media (max-width: 1180px) {
    padding: clamp(52px, 5vw, 76px) var(--gutter) clamp(96px, 9vw, 140px);
  }
}

.ed-list::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(1400px 700px at 88% -6%, rgba(118, 92, 110, 0.12), transparent 70%),
    radial-gradient(1100px 560px at 6% 22%, rgba(150, 118, 80, 0.08), transparent 68%);
  /* The first ellipse is centred *above* this box, so without a mask it paints
     at near-peak from row 0 and butts the opening's finished paper — the same
     hard-line defect the header scrim had, measured at 6–7/255 right where the
     artwork is supposed to settle into the page. The wash has to arrive, not
     start. Fixed lengths, not percentages: this section is 21 rows tall, so a
     percentage ramp would be hundreds of pixels on the index and a few dozen on
     a short one. */
  mask-image: linear-gradient(180deg, transparent 0, #000 210px, #000 calc(100% - 140px), transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 210px, #000 calc(100% - 140px), transparent 100%);
}

/* The count and its rule sit on the list, not on the page — one rule for both so the
   hairline above the entries can never end somewhere the entries do not. */
.ed-count,
.ed-items {
  max-width: var(--list-block);
  margin-inline: auto;
}

/* THE LIST'S TOP RULE. It used to be `.ed-count`'s bottom border, and the count
   line went at the client's direction — so the rule that closes the top of the
   list moved here rather than disappearing with it. The margin is what the
   count's own bottom margin used to contribute. */
.ed-items {
  margin-top: clamp(26px, 2.6vw, 36px);
  border-top: 1px solid rgba(28, 26, 24, 0.5);
}

.ed-count {
  margin: 0 auto clamp(26px, 2.6vw, 36px);
  padding-bottom: 15px;
  border-bottom: 1px solid rgba(28, 26, 24, 0.5);
  color: var(--label);
  font-family: var(--sans);
  font-size: clamp(10px, 0.5952vw, 18px);
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.ed-items { display: grid; }

/* An entry is a row, not a card. Rows keep one left edge down the whole page,
   which is the discipline the rest of the site is built on; a card grid would
   introduce a second rhythm for no reason. */
.ed-item {
  padding: clamp(26px, 2.6vw, 36px) 0;
  display: grid;
  grid-template-columns: clamp(118px, 7.6vw, 148px) minmax(0, 1fr) var(--item-go);
  gap: var(--item-gap);
  align-items: baseline;
  border-bottom: 1px solid var(--rule);
  color: inherit;
  text-decoration: none;
  transition: background-color 320ms ease;
}

.ed-item { position: relative; z-index: 0; }

/* The hover wash used to be painted on the row's own content box, so its edge
   fell exactly on the "1" of the date and exactly on the arrow — a lit panel with
   its contents pressed against both ends.
   A first fix widened the plate past the row with a negative inset. That worked
   visually and cost 18px of row `scrollWidth`, which the layout audit correctly
   flagged at all 11 viewports; an audit that has to be taught to ignore things
   stops being worth running.
   So the plate stays inside the row and loses its edges instead: the tint fades
   out over the last 30px at each end, so it is at full strength under the type
   and there is no line for anything to sit flush against. Same principle as the
   header scrim and the section washes — nothing on this page ends on a hard
   boundary. */
.ed-item::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  background-color: transparent;
  transition: background-color 320ms ease;
  mask-image: linear-gradient(90deg, transparent 0, #000 30px, #000 calc(100% - 30px), transparent 100%);
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 30px, #000 calc(100% - 30px), transparent 100%);
}

.ed-item:hover::before,
.ed-item:focus-visible::before { background-color: rgba(255, 253, 247, 0.66); }

/* THE PINNED MARK. A row lifted above the date order has to say why, or it
   reads as the sort having gone wrong. Set in the section's accent above the
   title, at the label size the rest of this page uses, so it is a caption on
   the row rather than a badge stuck to it.

   The row also carries `.ed-item--pinned`, which draws nothing on its own — it
   is there so the treatment can grow without the renderer changing. */
.ed-item__pin {
  margin: 0 0 6px;
  display: block;
  color: var(--accent);
  font-family: var(--sans);
  font-size: clamp(11px, 0.65vw, 15px);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.ed-item__date {
  color: var(--muted);
  font-family: var(--sans);
  /* Three sizes in one day, and the middle one is the answer. It began at
     clamp(10px, 0.5952vw, 18px); "日期大一些" took it to clamp(14px, 0.8333vw,
     25.2px); "有点太大了，看起来不太精致" brings it back to here. 12px is the
     floor rather than 10 because 10 is under the threshold the screen matrix
     treats as a finding on sight, and 0.7143vw is the ramp the rest of this
     file uses for 12px-floored labels. The date also reads larger than its size
     now that it sets on two lines. */
  font-size: clamp(12px, 0.7143vw, 20px);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.ed-item h2 {
  /* The same column as the deck below it — see the note on --item-measure. The title is
     wider than it was because the column is, not because it was given one of its own. */
  max-width: var(--item-measure);
  margin: 0;
  color: var(--ink);
  font-family: var(--serif);
  font-size: clamp(21px, 1.5625vw, 30px);
  font-weight: 400;
  letter-spacing: -0.022em;
  line-height: 1.15;
  text-wrap: pretty;
  @media (max-width: 1180px) {
    font-size: clamp(21px, 1.9vw, 30px);
  }
}

/* The deck. Same column as the title, body-sized like the rest of the site. */
.ed-item p {
  max-width: var(--item-measure);
  margin: 10px 0 0;
  color: var(--body);
  font-family: var(--serif);
  font-size: var(--item-deck);
  /* 1.15 — the client's number, asked for three times and reaffirmed with a
     reason: it is what makes a law firm read as approachable rather than
     institutional. The site-wide pass set it on the marketing routes; the
     editorial ones kept the looser reading measures they were drawn with
     (1.45-1.62), which is why the pages with the MOST prose were the ones
     still not obeying it. Measured before changing: article body 1.62,
     privacy 1.62, profile 1.45, index summaries 1.55. */
  line-height: 1.15;
}

.ed-item__go {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--accent);
  font-family: var(--sans);
  font-size: clamp(9.5px, 0.5655vw, 17.1px);
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  white-space: nowrap;
}

.ed-item__go svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  transition: transform 340ms cubic-bezier(0.22, 1, 0.36, 1);
}

/* The arrow rests 4px inside the rail so its hover travel lands *on* the rail
   every hairline on the page shares, instead of overshooting it. */
.ed-item__go { padding-right: 4px; }

.ed-item:hover .ed-item__go svg,
.ed-item:focus-visible .ed-item__go svg { transform: translate(4px, -4px); }

/* ---- News: one editorial feed -------------------------------------------
 *
 * This is intentionally not an `.ed-item` variation. Insights and Case
 * Studies keep that shared list byte-for-byte; News alone uses its article
 * body image and lets text-only entries close up naturally. */
.news-page { --news-block: min(100%, 1180px); }

.news-page .ed-hero__inner {
  width: min(100% - var(--gutter) * 2, 1180px);
  margin-inline: auto;
}

.news-feed {
  padding: clamp(54px, 4.6vw, 88px) var(--gutter) clamp(96px, 7.3vw, 140px);
  position: relative;
  isolation: isolate;
}

.news-feed::before {
  content: "";
  position: absolute;
  z-index: -1;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(1100px 520px at 92% 2%, rgba(118, 92, 110, 0.1), transparent 72%),
    radial-gradient(900px 480px at 4% 42%, rgba(150, 118, 80, 0.07), transparent 70%);
  mask-image: linear-gradient(180deg, transparent 0, #000 190px, #000 calc(100% - 140px), transparent 100%);
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 190px, #000 calc(100% - 140px), transparent 100%);
}

.news-feed__inner {
  width: var(--news-block);
  margin-inline: auto;
}

.news-feed__items { border-top: 1px solid rgba(28, 26, 24, 0.5); }

.news-entry {
  padding: clamp(30px, 3vw, 48px) 0;
  display: grid;
  grid-template-columns: clamp(220px, 24vw, 330px) minmax(0, 1fr);
  gap: clamp(28px, 4.2vw, 68px);
  align-items: center;
  border-bottom: 1px solid var(--rule);
  color: inherit;
  text-decoration: none;
}

.news-entry__picture {
  display: block;
  aspect-ratio: 1.55;
  overflow: hidden;
  background: color-mix(in srgb, var(--accent) 7%, var(--wall));
}

.news-entry__picture img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  filter: saturate(0.82) contrast(0.97);
  transition: transform 520ms cubic-bezier(0.22, 1, 0.36, 1), filter 320ms ease;
}

.news-entry__body {
  min-width: 0;
  max-width: 760px;
  display: block;
}

.news-entry--text .news-entry__body { grid-column: 1 / -1; max-width: 820px; }

.news-entry__meta {
  margin-bottom: 14px;
  display: flex;
  align-items: center;
  gap: 18px;
  color: var(--muted);
  font-family: var(--sans);
  font-size: clamp(12px, 0.7143vw, 20px);
  font-weight: 700;
  letter-spacing: 0.14em;
  line-height: 1.2;
  text-transform: uppercase;
}

.news-entry__pin {
  padding-left: 18px;
  position: relative;
  color: var(--accent);
}

.news-entry__pin::before {
  content: "";
  width: 1px;
  height: 1em;
  position: absolute;
  left: 0;
  top: 0.08em;
  background: var(--rule);
}

.news-entry h2 {
  margin: 0;
  color: var(--ink);
  font-family: var(--serif);
  font-size: clamp(26px, 2.2vw, 38px);
  font-weight: 400;
  letter-spacing: -0.025em;
  line-height: 1.04;
  text-wrap: pretty;
  overflow-wrap: anywhere;
  transition: color 240ms ease;
}

.news-entry p {
  margin: 14px 0 0;
  color: var(--body);
  font-family: var(--serif);
  font-size: clamp(17px, 1.18vw, 20px);
  line-height: 1.15;
  overflow-wrap: anywhere;
}

.news-entry:hover h2,
.news-entry:focus-visible h2 { color: var(--accent); }

.news-entry:hover .news-entry__picture img,
.news-entry:focus-visible .news-entry__picture img {
  transform: scale(1.018);
  filter: saturate(0.9) contrast(0.98);
}

/* THE PINNED ROW LOOKS LIKE EVERY OTHER ROW, and that is the point.
 *
 * It used to take a full-width 2.3:1 picture stacked over a 58px headline. The
 * client, looking at the result: 「跟下面那个一样大小会更好一点，就一张小图在左边，
 * 然后右边一个标题」. They are right about why it read badly — a headline set at
 * 58px is a headline written for six words, and the firm's own titles run to
 * fifteen, so the treatment meant to signal importance was instead a wall of
 * type under a letterbox.
 *
 * `.news-entry--feature` therefore has no layout of its own any more. It is
 * still emitted, and deliberately: it is what tells the first pinned picture to
 * load EAGERLY rather than lazily — see render-news-index.ts — because that
 * image is the first thing on the screen and the page's largest paint. A class
 * that carries a loading decision and no appearance is worth keeping; a class
 * that carried an appearance nobody wanted is not.
 *
 * What still marks the row as chosen is the word in its meta line: "In focus",
 * beside the date. A label, not a size. */

/* One or two entries should not leave a full archive's empty vertical tail. */
.news-feed--compact { padding-bottom: clamp(68px, 5.4vw, 96px); }
.news-feed--compact .news-feed__items { max-width: 1000px; }

.news-feed__empty {
  margin: 0;
  padding: clamp(34px, 3.2vw, 52px) 0;
  border-top: 1px solid rgba(28, 26, 24, 0.5);
  border-bottom: 1px solid var(--rule);
  color: var(--muted);
  font-family: var(--serif);
  font-size: clamp(18px, 1.25vw, 21px);
  line-height: 1.2;
}

@media (max-width: 820px) {
  .news-entry {
    grid-template-columns: minmax(190px, 0.75fr) minmax(0, 1.25fr);
    gap: 28px;
  }
}

@media (max-width: 680px) {
  .news-feed { padding: 38px 20px 74px; }
  .news-entry {
    padding: 26px 0;
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
  }
  .news-entry__picture { aspect-ratio: 1.6; }
  .news-entry__body,
  .news-entry--text .news-entry__body { grid-column: 1; max-width: none; }
  .news-entry__meta { margin-bottom: 11px; letter-spacing: 0.11em; }
  .news-entry h2 { font-size: 27px; }
  .news-entry p { margin-top: 11px; font-size: 17px; }
  /* The three feature overrides that stood here went with the treatment above:
     on a phone every row was already one column, so the pinned one differed
     only by a wider crop and a larger headline, and neither is wanted now. */
}

@media (prefers-reduced-motion: reduce) {
  .news-entry__picture img { transition: none; }
}

/* ---- article ---- */

.ed-article {
  padding: clamp(56px, 4.375vw, 84px) var(--gutter) clamp(90px, 6.875vw, 132px);
  position: relative;
  isolation: isolate;
  @media (max-width: 1180px) {
    padding: clamp(56px, 5.4vw, 84px) var(--gutter) clamp(90px, 8.6vw, 132px);
  }
}

/* An article is prose plus a margin, not prose alone.
 *
 * It was one 620px measure pinned to the left gutter, which left ~850px of blank
 * paper down the whole right side of every one of the 27 article pages — for the
 * entire scroll, because the images were capped to the text measure too and could
 * not relieve it. The reviewer called it "a column that reads as having slipped
 * off the left edge"; the client put it less kindly.
 *
 * The prose does not move: it keeps the left rail the h1 above it stands on, which
 * is the page's spine and also the part of the artwork where the frost is
 * strongest. What changes is that the space beside it now carries the section, the
 * date and the article's own contents — and that figures and the takeaways summary
 * are released from the reading measure, so the wide column gets used by the two
 * things that want width.
 *
 * `:has()` so the privacy page, which shares this class and has no margin element,
 * stays a single column instead of reserving an empty track. Same guard as
 * `.pf-main`. */
/* The block is centred as a block, and that is what puts white space on both sides.
 *
 * It used to be a grid stretched across the whole rail, with the prose in a 1fr track
 * that took everything the margin column and the gap did not. So the leftover always
 * fell on the right, and how much fell there was an accident of three unrelated
 * clamps: at 1280 and 1440 the text ended 88 and 104px from the edge of the window
 * with 400-odd on the other side, which is a column pushed against a wall rather than
 * a column with margins.
 *
 * Now the second track *is* the measure, the whole thing is only as wide as it needs
 * to be, and the auto margins split what is over. The two outer margins are therefore
 * equal at every width by construction rather than by arithmetic that has to be
 * redone whenever a number moves — 178px either side at 1440, 392 at 1920, 706 at
 * 2560. box-sizing is border-box site-wide, so the gutters are inside the max-width
 * and stay the minimum edge padding on the way down. */
.ed-article:has(> .ed-margin) {
  display: grid;
  grid-template-columns: var(--rail-col) minmax(0, 1fr);
  gap: 0 var(--rail-gap);
  max-width: calc(var(--block) + var(--gutter) * 2);
  margin-inline: auto;
  align-items: start;
}

/* Single column — the pages that have no contents rail, and every page below 1080.
   Same treatment: as wide as the measure, centred, gutters inside. */
.ed-article:not(:has(> .ed-margin)) {
  max-width: calc(var(--measure) + var(--gutter) * 2);
  margin-inline: auto;
}

.ed-article:has(> .ed-margin) > .ed-article__body { grid-column: 2; grid-row: 1; }
.ed-article:has(> .ed-margin) > .ed-foot { grid-column: 2; grid-row: 2; }

/* The margin is on the *left*, and the prose to its right.
 *
 * The first version of this had them the other way round, and the client asked
 * whether the text would not be more comfortable on the right. It is, for three
 * reasons that all point the same way:
 *
 *   1. With the margin on the right, the two content blocks hugged opposite edges
 *      and left a ~550px hole between them. Adjacent, they read as one composition.
 *   2. The prose sits at x=363 instead of x=40, so its centre is 639 against a page
 *      centre of 720 — reading happens near the middle of a wide screen rather than
 *      hard against its left edge.
 *   3. It is what the rest of the site already does. `.pf-main` on the profiles is
 *      rail-then-body, and `.ed-item` on the listings is date-then-content. The
 *      article was the only editorial layout putting its labels on the right.
 *
 * The page's left rail stays occupied at x=40 — by the margin now instead of the
 * prose — so the article still shares the spine the nav, the hero eyebrow and the
 * footer stand on. And figures and the takeaways run out to x=1400, so the right
 * side carries content rather than being the new empty half.
 *
 * A side benefit: visual order now matches document order, which it did not before.
 *
 * Spans both rows and sticks, so it stays with the reader on a 1,800-word piece
 * instead of scrolling away in the first screen. */
.ed-margin {
  grid-column: 1;
  grid-row: 1 / span 2;
  position: sticky;
  top: calc(var(--header-height) + clamp(24px, 2.4vw, 38px));
}

.ed-margin__rows { margin: 0; }

.ed-margin__row {
  padding: 10px 0;
  display: grid;
  gap: 4px;
  border-bottom: 1px solid var(--rule);
}

.ed-margin__row:first-child { border-top: 1px solid rgba(28, 26, 24, 0.4); }

.ed-margin__row dt,
.ed-margin__label {
  margin: 0;
  color: var(--label);
  font-family: var(--sans);
  font-size: clamp(9px, 0.5357vw, 16.2px);
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.ed-margin__row dd {
  margin: 0;
  color: var(--body);
  font-family: var(--serif);
  font-size: 14.5px;
  line-height: 1.4;
}

.ed-margin__contents { margin-top: clamp(26px, 2.6vw, 36px); }
.ed-margin__label { margin-bottom: 11px; }

/* Numbered, because the sections are read in order, but the numerals are set as
   quiet tracked figures rather than a browser list marker. */
.ed-margin__contents ol {
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: ed-contents;
  display: grid;
  gap: 9px;
}

.ed-margin__contents li {
  counter-increment: ed-contents;
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  gap: 8px;
}

/* ---- the reading timeline ----------------------------------------------------
 *
 * Only under `.is-live`, which motion.js adds once it has found a heading for every
 * entry. Without script, or on a list whose anchors do not resolve, none of this
 * applies and the contents are the plain list they have always been.
 *
 * The hairline and the fill are the same geometry — same left, same top, same bottom —
 * so the fill can only ever be a portion of the track it runs inside. The nodes sit on
 * that line, and motion.js drives both from one measurement, so a node lights at the
 * exact moment the line reaches it. */
.ed-margin__contents.is-live ol {
  position: relative;
}

/* Both are real elements created by motion.js, and both are given their top and their
   height from the measured node centres — so the track runs from the first node to the
   last, and the fill can only ever be a portion of that same run. Written as one rule so
   the two cannot be given different geometry by an edit to one of them. */
.ed-contents__track,
.ed-contents__progress {
  position: absolute;
  left: 3px;
  width: 1px;
}

.ed-contents__track {
  background: var(--rule);
}

.ed-contents__progress {
  background: var(--label);
  transform: scaleY(0);
  transform-origin: 50% 0;
  will-change: transform;
}

.ed-margin__contents.is-live li {
  position: relative;
  padding-left: 17px;
}

/* ::before is the numeral, so the node is ::after. 0.42em puts its middle on the first
   line's centre at this size rather than on the line's top. */
.ed-margin__contents.is-live li::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0.42em;
  width: 7px;
  height: 7px;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--wall);
  transition:
    background-color 420ms cubic-bezier(0.22, 1, 0.36, 1),
    border-color 420ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 420ms cubic-bezier(0.22, 1, 0.36, 1);
}

.ed-margin__contents.is-live li.is-read::after {
  border-color: var(--label);
  background: var(--label);
}

/* The section being read. Bigger rather than a different colour: a second colour on a
   rail this small is noise, and scale reads at 7px where hue does not. */
.ed-margin__contents.is-live li.is-here::after {
  transform: scale(1.34);
}

.ed-margin__contents.is-live li.is-here > a {
  color: var(--ink);
}

/* The state is information and stays; only the easing of it goes. */
@media (prefers-reduced-motion: reduce) {
  .ed-margin__contents.is-live li::after { transition: none; }
}

.ed-margin__contents li::before {
  content: counter(ed-contents, decimal-leading-zero);
  color: var(--label);
  font-family: var(--sans);
  font-size: clamp(9px, 0.5357vw, 16.2px);
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1.9;
}

.ed-margin__contents a,
.ed-margin__row dd a {
  color: var(--body);
  font-family: var(--serif);
  font-size: 14px;
  line-height: 1.35;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color 200ms ease, border-color 200ms ease;
}

.ed-margin__contents a:hover,
.ed-margin__row dd a:hover {
  color: var(--accent);
  border-bottom-color: rgba(82, 40, 27, 0.34);
}

/* The reading measure lives on the text, not on the container.
   68ch of this serif measured 78-86 characters a line — its `0` is narrow, so the
   `ch` unit under-reports; 58ch lands at 66-72, the range long-form prose is
   actually set to. But capping the *container* at that measure also capped the
   figures and the takeaways summary, which is why the note below about images
   being "pulled to the measure's edge" was untrue: `width: min(100%, 100%)` is a
   no-op, and every photograph sat inside the text column. Those two now take the
   full width of the column they are in. */
.ed-article__body {
  color: var(--body);
  font-family: var(--serif);
  /* Scaled with the measure. A wider column at the old 19px would have run to 110
     characters a line; at 26px a 992px measure is about 80, which is where the rest of
     this site sits. Bigger type on a bigger screen is also the point — the client's
     word for the old setting was 小气. */
  font-size: clamp(16.5px, 1.45vw, 22px);
  /* 1.15 — the client's number, asked for three times and reaffirmed with a
     reason: it is what makes a law firm read as approachable rather than
     institutional. The site-wide pass set it on the marketing routes; the
     editorial ones kept the looser reading measures they were drawn with
     (1.45-1.62), which is why the pages with the MOST prose were the ones
     still not obeying it. Measured before changing: article body 1.62,
     privacy 1.62, profile 1.45, index summaries 1.55. */
  line-height: 1.15;
}

/* One measure, on the container, so nothing inside it can disagree with anything
   else — prose, headings, figures and the takeaways summary all share both edges.
 *
 * Two earlier attempts were wrong in opposite directions and both are worth
 * recording. First the measure was `58ch` on the container, which also capped the
 * figures. Then it moved onto the text elements so figures could break out to the
 * full column — 1,037px against 552px of prose. That looked like it was using the
 * width and was really just filling space I had left over: it gave one generic
 * stock photograph per article twice the presence of the writing, and it made the
 * figure share the text's left edge and the page's right edge, which is neither
 * one alignment nor the other.
 *
 * A length rather than `ch`, because `ch` resolves against each element's own
 * font-size: a shared `58ch` gave paragraphs 548px and the 27px headings 813px, so
 * the headings overhung the prose they introduced by 265px. `rem` resolves
 * identically everywhere and still tracks the reader's root font size. */
/* One measure, on the container, so nothing inside it can disagree with anything
 * else: prose, headings, figures and the takeaways summary all share both edges.
 *
 * This was briefly moved onto the text children so figures could break out and fill
 * the column. That fixed "the image is tiny" and broke something the client had
 * already asked for twice — the image and the text being the same width. It is the
 * more basic rule of the two, and it wins.
 *
 * So the way to a big image is a big measure, not a released figure. The measure is
 * now the derived one at the top of this file, which holds the line at 66 characters
 * rather than letting it drift into the eighties — and the figure, being flush with
 * the prose, comes down with it: 992px to 777px at 1920. That is the price of a line
 * that can actually be read, and it is still a wide picture on a big screen.
 */
.ed-article__body {
  max-width: min(100%, var(--measure));
}

.ed-article__body > * { margin: 0 0 1.05em; }
.ed-article__body > *:last-child { margin-bottom: 0; }

/* Scaled with the body, or the hierarchy collapses: at the old cap a heading was 27px
   against 26px of body text — the same size, to the eye. Margins are in em so they
   follow the size rather than needing their own scale.
 *
 * One scale, three steps: body 1, h3 1.18, h2 1.46. Every one of the three clamps has
 * its floor, its slope and its cap in that ratio, so all three reach their cap at about
 * the same window width — 1600px — and the hierarchy holds at every size in between
 * rather than only at the two ends.
 *
 * The h3 was clamp(18px, 1.4vw, 22px) against a body reaching 26px: a subheading
 * *smaller than the text it introduces*, in 13 places across 6 articles, and getting
 * worse every time the body grew. The same failure the paragraph above was written
 * about, one level further down and unnoticed because h3 is rarer than h2. */
.ed-article__body h2,
.ed-article__body h3 {
  margin: 1.9em 0 0.6em;
  color: var(--ink);
  font-size: clamp(21px, 1.66667vw, 32px);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.2;
  @media (max-width: 1180px) {
    font-size: clamp(21px, 2.12vw, 32px);
  }
}

.ed-article__body h3 { font-size: clamp(19.5px, 1.35417vw, 26px);
 @media (max-width: 1180px) {
   font-size: clamp(19.5px, 1.71vw, 26px);
 }
}
.ed-article__body h2:first-child,
.ed-article__body h3:first-child { margin-top: 0; }

.ed-article__body ul,
.ed-article__body ol { padding-left: 1.35em; }
.ed-article__body li { margin: 0 0 0.5em; }

.ed-article__body strong { font-weight: 600; }

/* Full width of the measure, which is the same width as the text. The generator still
   writes each image's natural size onto the figure and its intrinsic width/height onto
   the img — that stops the layout shifting as they load — but nothing caps the figure
   below the measure any more. Every editorial photograph is at least 1024px, and the
   measure tops out at 992, so none of them is ever upscaled. */
.ed-article__body figure {
  margin: 2.6em 0;
  width: 100%;
}

.ed-article__body img {
  width: 100%;
  height: auto;
  display: block;
  filter: saturate(0.86) contrast(0.97);
}

/* ---- article footer + neighbours ---- */

/* The same token as the article body, not a copy of the number it happened to have.
   In `ch` this hairline never agreed with the text above it: 631px against the body's
   642.5px at 1440, 624px at 1024. Then it was pinned to 34.5rem, which was the body's
   value at the time and stopped being it the moment the measure grew — 552px of rule
   under 893px of type at 1440. Always out, always at the one place on the page where a
   rule and a column of type are supposed to end together. */
.ed-foot {
  max-width: min(100%, var(--measure));
  margin: clamp(54px, 3.95833vw, 76px) 0 0;
  padding-top: clamp(24px, 2.4vw, 32px);
  border-top: 1px solid rgba(28, 26, 24, 0.42);
  @media (max-width: 1180px) {
    margin: clamp(54px, 5vw, 76px) 0 0;
  }
}

/* `.ed-foot p` below is (0,1,1) and later in the file, so it beat every
   declaration here: the label rendered 15px serif grey with no margin, which put
   the word NOTE hard against the first line of the disclaimer and made it read
   as the sentence's first word. Same specificity class, so it wins on order. */
.ed-foot .ed-foot__label {
  margin: 0 0 10px;
  color: var(--label);
  font-family: var(--sans);
  font-size: clamp(9.5px, 0.5655vw, 17.1px);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.ed-foot p {
  margin: 0;
  color: var(--muted);
  font-family: var(--serif);
  font-size: 15px;
  /* 1.15 — the client's number, asked for three times and reaffirmed with a
     reason: it is what makes a law firm read as approachable rather than
     institutional. The site-wide pass set it on the marketing routes; the
     editorial ones kept the looser reading measures they were drawn with
     (1.45-1.62), which is why the pages with the MOST prose were the ones
     still not obeying it. Measured before changing: article body 1.62,
     privacy 1.62, profile 1.45, index summaries 1.55. */
  line-height: 1.15;
}

.ed-near {
  padding: clamp(52px, 3.95833vw, 76px) var(--gutter) clamp(90px, 6.66667vw, 128px);
  border-top: 1px solid var(--rule);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(28px, 3.33333vw, 64px);
  @media (max-width: 1180px) {
    padding: clamp(52px, 5vw, 76px) var(--gutter) clamp(90px, 8.6vw, 128px);
    gap: clamp(28px, 3.4vw, 64px);
  }
}

.ed-near a {
  display: block;
  color: inherit;
  text-decoration: none;
}

.ed-near__dir {
  margin: 0 0 12px;
  display: flex;
  align-items: center;
  gap: 12px;
  color: var(--accent);
  font-family: var(--sans);
  font-size: clamp(9.5px, 0.5655vw, 17.1px);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.ed-near__dir::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(82, 40, 27, 0.32), rgba(82, 40, 27, 0));
}

.ed-near h2 {
  margin: 0;
  color: var(--ink);
  font-family: var(--serif);
  font-size: clamp(19px, 1.30208vw, 25px);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.2;
  @media (max-width: 1180px) {
    font-size: clamp(19px, 1.6vw, 25px);
  }
}

.ed-near a:hover h2 { color: var(--accent); }
/* `margin: 0` because this is a `<p>` sitting beside an `<h2>` in a two-column
   block: the UA's 17px top margin pushed it 5px off its neighbour's first
   baseline, which is the one alignment the block exists to make. */
.ed-near__empty { margin: 0; color: var(--muted); font-family: var(--serif); font-size: 17px; }

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

@media (max-width: 1180px) { .editorial-page { --gutter: 28px; } }

/* Below 1080 there is not enough width for a prose measure and a margin side by
   side, so the margin stops being a margin and becomes a header block: the two
   facts on one line, the contents beneath. It is first in the document precisely
   so that it lands *above* the article when stacked — a contents list after the
   text it indexes would be useless, and screen readers get the same sensible
   order either way. */
@media (max-width: 1080px) {
  /* No rail beside the prose any more, so the block is the measure alone — and the
     hero follows it, because --block is what both are built from. */
  .editorial-page { --block: var(--measure); }

  .ed-article:has(> .ed-margin) {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    max-width: calc(var(--block) + var(--gutter) * 2);
  }

  .ed-article:has(> .ed-margin) > .ed-article__body,
  .ed-article:has(> .ed-margin) > .ed-foot {
    grid-column: 1;
    grid-row: auto;
  }

  /* Reset the desktop placement too, or the single column keeps a span-2 row. */
  .ed-margin { grid-column: 1; }

  .ed-margin {
    grid-column: 1;
    grid-row: auto;
    position: static;
    max-width: 40rem;
    margin-bottom: clamp(34px, 2.5vw, 48px);
    @media (max-width: 1180px) {
      margin-bottom: clamp(34px, 3.6vw, 48px);
    }
}

  .ed-margin__rows {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 clamp(20px, 3vw, 40px);
  }

  .ed-margin__row:nth-child(2) { border-top: 1px solid rgba(28, 26, 24, 0.4); }

  .ed-margin__contents ol {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 9px clamp(20px, 3vw, 40px);
  }
}

@media (max-width: 560px) {
  .ed-margin__rows,
  .ed-margin__contents ol { grid-template-columns: minmax(0, 1fr); }
  .ed-margin__row:nth-child(2) { border-top: 0; }
}

@media (max-width: 960px) {
  /* The link's column is dropped here, so the block must stop reserving it — otherwise
     the list stays 84px plus a gap wider than anything inside it. */
  .editorial-page {
    --list-block: calc(108px + var(--item-gap) + var(--item-measure));
  }

  .ed-item { grid-template-columns: 124px minmax(0, 1fr); }
  .ed-item__go { display: none; }
  .ed-hero h1 { max-width: 100%; }

  /* The frost ramp is tuned for a hero whose type occupies the left ~40% and
     whose artwork owns the right. Below 960 the title cap is lifted because a
     44px serif cannot live in a narrow column — but the ramp was left where it
     was, so long titles crossed the 44% mark onto full-strength pigment and
     measured 2.42:1, and the lede reached bare paint at 3.97:1. Both are AA
     failures on the largest type on the page.
     The type is not squeezed to fit the frost; the frost is widened to cover the
     type, which is the only one of the two that preserves the design. */
  .ed-opening__frost span:nth-child(1) {
    mask-image: linear-gradient(90deg, #000 0%, #000 80%, transparent 100%);
    -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 80%, transparent 100%);
  }
  .ed-opening__frost span:nth-child(2) {
    mask-image: linear-gradient(90deg, #000 0%, #000 56%, transparent 86%);
    -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 56%, transparent 86%);
  }
  .ed-opening__frost span:nth-child(3) {
    mask-image: linear-gradient(90deg, #000 0%, #000 32%, transparent 64%);
    -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 32%, transparent 64%);
  }
  .ed-opening__frost::after {
    background: linear-gradient(90deg,
      rgba(248, 246, 239, 0.5) 0%,
      rgba(248, 246, 239, 0.4) 40%,
      rgba(248, 246, 239, 0.24) 72%,
      rgba(248, 246, 239, 0.08) 100%);
  }
}

@media (max-width: 680px) {
  .editorial-page { --gutter: 20px; }
  /* `cover` crops hard horizontally at phone width, so the focal point decides
     whether the hero shows the painting or an empty corner of it. The two
     artworks are mirrored — measured chroma puts the insights field's pigment at
     78-100% of its width and the case-studies field's at 0-60% — so one shared
     value cannot serve both. The old flat `16% center` landed on the insights
     field's empty left third, and every insights page, every profile and the
     privacy page rendered a blank cream panel where the hero art should be.
     Each page names its own narrow focus next to the artwork it belongs to. */
  .ed-opening { --field-focus: var(--field-focus-narrow, 88% center); }
  .ed-opening__frost span:nth-child(1),
  .ed-opening__frost span:nth-child(3) { display: none; }
  .ed-opening__frost span:nth-child(2) { mask-image: none; -webkit-mask-image: none; }
  .ed-opening__frost::after {
    background: linear-gradient(180deg, rgba(248, 246, 239, 0.38), rgba(248, 246, 239, 0.26) 62%, rgba(248, 246, 239, 0.12));
  }
  .editorial-page { --list-block: var(--item-measure); }
  .ed-item { grid-template-columns: minmax(0, 1fr); gap: 9px; }
  .ed-near { grid-template-columns: minmax(0, 1fr); }

  /* SECTION INDEX PHONE RHYTHM — 2026-08-10.
   *
   * The title, lede and entry copy already sit on a coherent 34 / 17 / 21 /
   * 15px ladder. The fault measured on all three indexes was the other end:
   * 10–10.5px labels beside that ladder, plus a 120px pause after the lede.
   * Scope this to pages with the shared listing template so articles, profiles
   * and Privacy can be judged independently. */
  .editorial-page:has(.ed-items) .ed-hero {
    min-height: 340px;
    padding: calc(var(--header-height) + 40px) 0 34px;
  }

  .editorial-page:has(.ed-items) .ed-eyebrow {
    margin-bottom: 18px;
    font-size: var(--phone-type-label);
    letter-spacing: 0.16em;
  }

  .editorial-page:has(.ed-items) .ed-list {
    padding-top: 36px;
  }

  .editorial-page:has(.ed-items) .ed-count {
    margin-bottom: 24px;
    padding-bottom: 12px;
    font-size: var(--phone-type-label);
    letter-spacing: 0.12em;
  }

  .editorial-page:has(.ed-items) .ed-item {
    padding-block: 22px;
    gap: 8px;
  }

  /* Back to the label scale with the desktop size. This went to
     --phone-type-body (15px) when the date was enlarged; leaving it there while
     the desktop came back to 12 would have made the phone's date larger than
     the desktop's, which is the wrong way round for the same element. */
  .editorial-page:has(.ed-items) .ed-item__date {
    font-size: var(--phone-type-label);
    letter-spacing: 0.1em;
  }

  .editorial-page:has(.ed-items) .ed-item p {
    margin-top: 8px;
  }

  /* ARTICLE DETAIL PHONE HIERARCHY — 2026-08-10.
   *
   * The 34px title, 17px standfirst and 16.5px reading text already form a
   * comfortable ladder. What broke it on every article was the 9–10.5px
   * metadata around them, plus rail links whose one-line boxes were shorter
   * than a useful touch target. Privacy and the error documents reuse this
   * markup; their pass is separate, so the body classes are part of the
   * boundary rather than an accidental inheritance. */
  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-eyebrow {
    margin-bottom: 18px;
    font-size: var(--phone-type-label);
    letter-spacing: 0.14em;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-article {
    padding-top: 44px;
    padding-bottom: 72px;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-margin__row dt,
  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-margin__label,
  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-margin__contents li::before {
    font-size: var(--phone-type-label);
    letter-spacing: 0.12em;
    line-height: 1.25;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-margin__row dd,
  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-margin__contents a,
  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-margin__row dd a {
    font-size: var(--phone-type-body);
    line-height: 1.35;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-margin__contents {
    margin-top: 24px;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-margin__contents a {
    min-height: 24px;
    display: flex;
    align-items: center;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-margin__row dd a {
    min-height: 24px;
    display: inline-flex;
    align-items: center;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-takeaways__label,
  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-foot .ed-foot__label,
  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-near__dir {
    font-size: var(--phone-type-label);
    letter-spacing: 0.14em;
    line-height: 1.25;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-foot {
    margin-top: 48px;
    padding-top: 22px;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-foot a {
    min-height: 24px;
    display: inline-block;
    line-height: 24px;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-near {
    padding-top: 44px;
    padding-bottom: 72px;
    gap: 24px;
  }

  .editorial-page:not(.privacy-page):not(.error-page):has(.ed-article) .ed-near__dir {
    margin-bottom: 10px;
  }

  /* PROFILE PHONE HIERARCHY — 2026-08-10.
   *
   * Names, roles, biographies and the 300px portrait already work at phone
   * size. The split was in the surrounding 9–10.5px labels and in contact
   * links whose clickable box was only the 20px line of text. Reallocate the
   * row's existing vertical rhythm to the value so links get a genuine 44px
   * target without turning every contact row into a padded card. */
  .profile-page .pf-hero {
    padding-top: calc(var(--header-height) + 38px);
    padding-bottom: 38px;
  }

  .profile-page .ed-eyebrow {
    margin-bottom: 18px;
    font-size: var(--phone-type-label);
    letter-spacing: 0.14em;
  }

  .profile-page .pf-role {
    margin-top: 12px;
    line-height: 1.3;
  }

  .profile-page .pf-main {
    padding-top: 40px;
    padding-bottom: 72px;
    gap: 26px;
  }

  .profile-page .pf-row {
    padding: 8px 0 0;
    gap: 0;
  }

  .profile-page .pf-row dt,
  .profile-page .pf-label {
    font-size: var(--phone-type-label);
    letter-spacing: 0.12em;
    line-height: 1.25;
  }

  .profile-page .pf-row dd {
    min-height: 44px;
    display: flex;
    align-items: center;
    font-size: var(--phone-type-body);
    line-height: 1.35;
  }

  .profile-page .pf-row dd a {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
  }

  .profile-page .pf-block li,
  .profile-page .pf-plain {
    font-size: var(--phone-type-body);
    line-height: 1.3;
  }

  .profile-page .ed-near {
    padding-top: 44px;
    padding-bottom: 72px;
    gap: 24px;
  }

  .profile-page .ed-near__dir {
    margin-bottom: 10px;
    font-size: var(--phone-type-label);
    letter-spacing: 0.14em;
    line-height: 1.25;
  }

  /* PRIVACY + ERROR DOCUMENT PHONE HIERARCHY — 2026-08-10.
   *
   * These documents share the article skeleton but not the article batch's
   * body classes. Their 34px headings and 16.5px prose already read well;
   * their 9–10.5px labels and sub-20px navigation links did not. Error pages
   * also have no metadata rows, so the contents nav must not reserve the
   * margin that normally separates it from those rows. */
  .privacy-page .ed-hero,
  .error-page .ed-hero {
    min-height: 340px;
    padding: calc(var(--header-height) + 40px) 0 34px;
  }

  .privacy-page .ed-eyebrow,
  .error-page .ed-eyebrow {
    margin-bottom: 18px;
    font-size: var(--phone-type-label);
    letter-spacing: 0.14em;
  }

  .privacy-page .ed-article,
  .error-page .ed-article {
    padding-top: 44px;
    padding-bottom: 72px;
  }

  .privacy-page .ed-margin__row dt,
  .privacy-page .ed-margin__label,
  .privacy-page .ed-margin__contents li::before,
  .error-page .ed-margin__label,
  .error-page .ed-margin__contents li::before {
    font-size: var(--phone-type-label);
    letter-spacing: 0.12em;
    line-height: 1.25;
  }

  .privacy-page .ed-margin__row dd,
  .privacy-page .ed-margin__contents a,
  .error-page .ed-margin__contents a {
    font-size: var(--phone-type-body);
    line-height: 1.35;
  }

  .privacy-page .ed-margin__contents {
    margin-top: 24px;
  }

  .error-page .ed-margin__contents {
    margin-top: 0;
  }

  .privacy-page .ed-margin__contents a,
  .error-page .ed-margin__contents a {
    min-height: 24px;
    display: flex;
    align-items: center;
  }
}

/* A summary block, which is what the source's runs of <h6> actually were. On the
   page's own paper with hairlines above and below — not a tinted callout box,
   because a coloured panel would be the only one on the site. */
.ed-takeaways {
  margin: 2.2em 0;
  padding: clamp(20px, 1.8vw, 26px) 0;
  border-top: 1px solid rgba(28, 26, 24, 0.4);
  border-bottom: 1px solid var(--rule);
}

/* One column while the block is at the reading measure, two once it is not.
   The summary is released from the measure again so it can use the width — and at
   full width a single line runs to 110 characters, which is exactly why the
   two-column version existed the first time. Splitting it puts each line back at
   about 55. A grid rather than `columns: 2`, because this ul is a grid container and
   the column properties do nothing on one. */
.ed-takeaways__label {
  margin: 0 0 14px;
  color: var(--label);
  font-family: var(--sans);
  font-size: clamp(9.5px, 0.5655vw, 17.1px);
  font-weight: 700;
  letter-spacing: 0.17em;
  text-transform: uppercase;
}

.ed-takeaways ul {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 11px;
}

.ed-takeaways li {
  margin: 0;
  padding-left: 20px;
  position: relative;
  font-size: 0.94em;
  /* 1.15 — the client's number, asked for three times and reaffirmed with a
     reason: it is what makes a law firm read as approachable rather than
     institutional. The site-wide pass set it on the marketing routes; the
     editorial ones kept the looser reading measures they were drawn with
     (1.45-1.62), which is why the pages with the MOST prose were the ones
     still not obeying it. Measured before changing: article body 1.62,
     privacy 1.62, profile 1.45, index summaries 1.55. */
  line-height: 1.15;
}

.ed-takeaways li::before {
  content: "";
  width: 5px;
  height: 5px;
  position: absolute;
  top: 0.62em;
  left: 2px;
  border-radius: 50%;
  background: var(--accent);
}

/* ---- profile ---- */

.pf-hero {
  position: relative;
  padding: calc(var(--header-height) + clamp(46px, 4.4vw, 70px)) 0 clamp(44px, 3.33333vw, 64px);
  @media (max-width: 1180px) {
    padding: calc(var(--header-height) + clamp(46px, 4.4vw, 70px)) 0 clamp(44px, 4.2vw, 64px);
  }
}

.pf-hero__inner {
  margin: 0 var(--gutter);
  display: grid;
  grid-template-columns: clamp(200px, 22vw, 292px) minmax(0, 1fr);
  gap: clamp(30px, 3.54167vw, 68px);
  align-items: end;
  @media (max-width: 1180px) {
    gap: clamp(30px, 4vw, 68px);
  }
}

/* The portrait sits in the frosted half, so it reads as part of the ground
   rather than as a photo pasted over it. */
.pf-portrait {
  position: relative;
  aspect-ratio: 0.86;
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.5);
  box-shadow: 0 18px 40px rgba(24, 20, 18, 0.14), inset 0 1px 0 rgba(255, 255, 255, 0.7);
}

.pf-portrait img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
  object-position: center top;
  filter: saturate(0.86) contrast(0.97);
}

.pf-id h1 {
  /* Capped so a long name cannot run out of the frosted half and into the
     pigment, where it would lose contrast. */
  max-width: 17ch;
  margin: 0;
  color: var(--ink);
  font-family: var(--serif);
  font-size: clamp(38px, 3.54167vw, 68px);
  font-weight: 400;
  letter-spacing: -0.032em;
  line-height: 0.98;
  @media (max-width: 1180px) {
    font-size: clamp(38px, 4.4vw, 68px);
  }
}

/* Some of these are genuine four-appointment stacks — the longest is 200
   characters. At 22px on a 30ch measure that is seven lines in the same face as
   the name above it, so the role out-massed the h1 and the page lost its
   hierarchy. A wider measure and a smaller size put the worst case at three
   lines and keep the name the largest thing on the page; the short roles, which
   are most of them, are unaffected. */
.pf-role {
  max-width: 42ch;
  margin: clamp(12px, 1.2vw, 18px) 0 0;
  color: var(--body);
  font-family: var(--serif);
  font-size: clamp(16px, 1.15vw, 19px);
  line-height: 1.4;
}

.pf-main {
  padding: clamp(50px, 4.0625vw, 78px) var(--gutter) clamp(80px, 6.04167vw, 116px);
  display: grid;
  grid-template-columns: clamp(200px, 22vw, 292px) minmax(0, 1fr);
  gap: clamp(30px, 3.54167vw, 68px);
  align-items: start;
  @media (max-width: 1180px) {
    padding: clamp(50px, 5vw, 78px) var(--gutter) clamp(80px, 7.6vw, 116px);
    gap: clamp(30px, 4vw, 68px);
  }
}

.pf-rail { display: grid; gap: clamp(24px, 1.77083vw, 34px);
 @media (max-width: 1180px) {
   gap: clamp(24px, 2.4vw, 34px);
 }
}

/* The blocks were direct children of the rail and are now wrapped, so the wrapper
   has to carry the rail's own gap or the two blocks would close up against each
   other. Same expression, deliberately: they are one rhythm, not two.
   `:empty` because the container is emitted even for a profile with no blocks, to
   give the admin somewhere to add the first one — and an empty grid would otherwise
   still take a gap and open a hole under the contact rows. */
.pf-blocks { display: grid; gap: clamp(24px, 1.77083vw, 34px);
 @media (max-width: 1180px) {
   gap: clamp(24px, 2.4vw, 34px);
 }
}
.pf-blocks:empty { display: none; }

.pf-rows { margin: 0; display: grid; }

/* Label above value, not beside it. The 66px label column plus its 14px gutter
   left the value 131px at 961px wide — against a 208px email address, which
   `overflow-wrap: anywhere` then broke mid-domain into `carrie.fan@yingkelaw` /
   `yer.com.au`, each half carrying its own underline so it read as two broken
   links. At 1440 the same arithmetic left 3.6px between the address and the end
   of its own hairline, where the rows below it have 99px.
   Stacked, the value gets the rail's full width at every size: no mid-word
   break, nothing flush to the rule, and the hairlines stop running a third of
   their length past their own content in the 768-960 band. */
.pf-row {
  padding: 11px 0;
  display: grid;
  gap: 5px;
  border-bottom: 1px solid var(--rule);
}

.pf-row:first-child { border-top: 1px solid rgba(28, 26, 24, 0.4); }

.pf-row dt {
  color: var(--label);
  font-family: var(--sans);
  font-size: clamp(9px, 0.5357vw, 16.2px);
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.pf-row dd {
  margin: 0;
  color: var(--body);
  font-family: var(--serif);
  font-size: 14.5px;
  line-height: 1.4;
  /* `anywhere` was breaking addresses mid-domain. `break-word` keeps whole words
     together and only splits one that cannot fit on a line by itself, which for
     a stacked 292px rail is nothing we ship. */
  overflow-wrap: break-word;
}

.pf-row dd a { color: inherit; text-decoration: none; border-bottom: 1px solid rgba(82, 40, 27, 0.32); }
.pf-row dd a:hover { color: var(--accent); border-bottom-color: var(--accent); }

.pf-label {
  margin: 0 0 9px;
  color: var(--label);
  font-family: var(--sans);
  font-size: clamp(9px, 0.5357vw, 16.2px);
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
}

.pf-block ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 8px; }

.pf-block li,
.pf-plain {
  margin: 0;
  color: var(--body);
  font-family: var(--serif);
  font-size: 14.5px;
  /* 1.15 — the client's number, asked for three times and reaffirmed with a
     reason: it is what makes a law firm read as approachable rather than
     institutional. The site-wide pass set it on the marketing routes; the
     editorial ones kept the looser reading measures they were drawn with
     (1.45-1.62), which is why the pages with the MOST prose were the ones
     still not obeying it. Measured before changing: article body 1.62,
     privacy 1.62, profile 1.45, index summaries 1.55. */
  line-height: 1.15;
}

.pf-body { max-width: 66ch; }

/* There was no link rule for prose anywhere in either stylesheet, so every
   inline link in every article body and every profile rendered as plain body
   text — same colour, no underline, no weight. On the six profiles whose bio is
   the short fallback, "contact our Sydney office" is the only actionable thing
   on the page and it was invisible. Matches the rail's link treatment, which
   already did this correctly. */
.ed-article__body a,
.pf-body a,
.ed-foot a {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid rgba(82, 40, 27, 0.34);
  transition: color 200ms ease, border-color 200ms ease;
}

.ed-article__body a:hover,
.pf-body a:hover,
.ed-foot a:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* A profile with no rail is a single column, not a column plus a 292px hole.
   Six of the 37 have no rail data; the grid was reserving the track anyway and
   indenting the bio 390px into open paper. */
.pf-main:not(:has(> .pf-rail)) { grid-template-columns: minmax(0, 1fr); }
.pf-body h2 { font-size: clamp(19px, 1.25vw, 24px);
 @media (max-width: 1180px) {
   font-size: clamp(19px, 1.5vw, 24px);
 }
}

@media (max-width: 960px) {
  .pf-hero__inner,
  .pf-main { grid-template-columns: minmax(0, 1fr); }
  .pf-portrait { width: min(100%, 300px); }
  .pf-rail { max-width: 520px; }
}

/* Legislation names run long and contain no break opportunities — "Competition
   and Consumer (Industry Codes–Franchising) Regulation 2014" pushed the document
   past the viewport at narrow widths. Prose is allowed to break inside a word
   only as a last resort, which is what `anywhere` means here. */
.ed-article__body,
.ed-article__body li,
.ed-article__body p,
.ed-takeaways li,
.ed-item h2,
.ed-item p,
.ed-near h2 {
  overflow-wrap: anywhere;
}
