Pouya's five rulings of 2026-08-31, after 64bce10. One commit, because AGENTS.md
entry (aj) covers all five and the review fixes interleave across them —
recorded here rather than left to be inferred.
1. THE EYEBROW, 13 -> 14px, at the one token definition. `--text-eyebrow` is
deliberately NOT an alias of `--text-sm` despite sharing its value: the two move
for different reasons, and aliasing would mean a change to body-meta type
silently moving every eyebrow. Sweep by rendering, 22 pages x 2 widths, 801 mono
instances: 441 carry `.eyebrow`, 420 now at 14px, 21 held at 11px. Zero of 801
below 4.5:1.
Header check, which Pouya asked for explicitly: nothing header-side consumes the
token, `--header-h` is 81px, and the sticky header measures exactly 81.00px at
every width from 66rem up with the CTA on `.header-inner`'s content edge. Nothing
overflowed, so nothing was improvised.
2. /contact/'s LABELS RAISED, AND THE FLOOR CLAIM REWRITTEN BECAUSE THE RULING'S
PREMISE WAS FALSE. The ruling asked docs/02 to record the tagline as "the ONLY
sub-14px text on the site". It is not: the `--text-xs` 12px rung has ten
declaration sites and 130 elements rendering at 12px. So docs/02 records the
claim that IS verified — `--text-2xs` has one consumer in the repository — and
enumerates the 12px rung as a separate treatment. Writing a false claim into the
spec because the conclusion was handed down is the move the rules forbid.
A third block moved that the ruling did not name: /bio/'s ten <h2>s and its strap
were copies of five of `.eyebrow`'s six declarations at 11px — the same escape the
footer headings were. docs/02 justified holding them by the one-page PDF
constraint; measured, `@media print` sets both to 7pt, so the screen size never
reached the PDF and that was never the reason.
Consolidating them DID change the printed sheet, because the missing sixth
declaration was `font-weight`: 500 grew the PDF 89,496 -> 91,151 bytes. Print
therefore freezes 400, the committed PDF stays byte-identical (10 differing
bytes, all /CreationDate and /ModDate), and unifying is Pouya's call.
3. THE REFLOW DEFECT WAS SITE-WIDE AND /bio/ WAS NOT THE BINDING CAUSE.
`white-space: nowrap` on `SiteHeader .brand-name` held all 22 pages 63px over at
320px/root 32. Eight cause-specific fixes, no `overflow-x` anywhere; all eight
produce byte-identical geometry at root 16. `Pill` is marked in docs/02 as the
backstop it is — the real cause was `PracticeCard`'s rem-based padding, and with
it clamped "Construction" goes from 94x220 in six two-character lines to 158x85 in
two.
Three instrument findings now in docs/02, each of which hid a real defect:
`break-word` does not reduce min-content and `anywhere` does;
`getBoundingClientRect()` reports border boxes, so an element sweep cannot see
text spilling outside its own box; and `mobile: true` emulation expands the
layout viewport, so `scrollWidth - innerWidth` reads 0 while the reader still
scrolls sideways.
4. TTL STAMPED `ENABLED` in §7, with `DISABLED` at first verification recorded
rather than overwritten. Q60 narrowed to its second half and OPEN: no record has
been watched to vanish, and `ENABLED` proves the setting, not the behaviour.
R19's sweep found three stale copies outside §7; all now defer to it. R19 itself
was not edited — it points at §7 rather than carrying state, which is the
property that made it work.
5. §4 GAINS THE STRUCK-UNIVERSAL ROW, citing the committed ontario.ca extract and
cross-referenced to `check:claims`'s `struck-universal-q39`. It bars the claim in
BOTH directions: the commercial half is Pouya's attributed position, not a
verified fact. The row immediately caught two places asserting it flatly —
`SiteHeader.astro` and §9 Q33 — both now attributed. `check:claims` unmodified;
still frozen.
REVIEW: adversarial-reviewer, two rounds, 16 findings, ALL ACCEPTED, NONE
DECLINED. claims-auditor did not run (D20). Eight of round 2's ten were defects
in round 1's own fixes.
Round 1's blocking finding was a defect in my own record: I wrote that every
reflow residual was zero at "root 16 and root 32, 286 measurements". Two ways of
simulating 200% text are NOT equivalent, because media-query `rem` resolves
against the DEFAULT font size, not the root element's. Under the method docs/02
itself prescribes the site is 944px over on 21 pages and 508px on / at 1280, and
304px at 1920 — nav clipped mid-word, Practice/Fees/Contact and the CTA
off-screen, WCAG 1.4.4 with loss of functionality. My 286 excluded exactly the
widths where the defect lives.
NOT FIXED, DELIBERATELY: the cause is `flex-wrap: nowrap` on `.nav-list` above
66rem, a locked step-1 decision, and the standing instruction is to stop and
report rather than improvise a header change. It is now a blocking item on
docs/06's cutover checklist, ticked only by ruling on it — "not by re-measuring
it with the method that reports zero".
Round 2 also caught: a 63,743-byte figure that was the `cmp -l` differing-byte
count rather than the 1,655-byte size delta; a lost-navigation list naming
Med-Arb, which is not in the masthead at any width, while omitting Contact; the
tagline's justification left recorded at 13px, where one clause of it is false at
14px (20px of document overflow at 1216 with a seventh nav item); my own label
raise reintroducing a 38px element overflow at the width just ruled on; two
copies of the eyebrow treatment left at weight 400; three stale residual tables;
and the min-content explanation duplicated six times in src/.
VERIFIED, exit statuses read directly, never through a pipe: build 0 (22 pages),
check 0 (0 errors/0 warnings/0 hints), check:claims 0, og:proof 0, check:intake 0,
lint 0, minifier tripwire clean, TODO in dist 0 with a source sanity check.
Overflow: 374 measurements over 22 pages, every one zero, row counts asserted
before reading, with positive controls (119px at width 200, 319px at root 64).
Lighthouse 0, run three times with identical category scores: perf 99 on / and
100 elsewhere, a11y 100, best practices 100, SEO 100 on all indexable pages,
CLS 0.000. / sits at LCP 2.03s against a 2.0s budget, unchanged by this work.
The zsh `$VAR` word-split trap fired twice more and both times read as a clean
pass; caught only by asserting row counts first. Two of my own instruments were
wrong before they were right: a `grep -F '0.875rem'` state check that could never
match because Lightning CSS writes `.875rem`, and a probe whose inline
`!important` was outranked by a running CSS transition.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Md3GndFqWPzK78xAoebsg5
987 lines
43 KiB
Plaintext
987 lines
43 KiB
Plaintext
---
|
|
/**
|
|
* `/` — Home. Build step 2 (docs/01 §Build order): "proves the design system
|
|
* end to end."
|
|
*
|
|
* JOB (docs/01 §`/`): "establish the unusual stack in under ten seconds, and
|
|
* route each of the four audiences to its surface." Leans in-house counsel.
|
|
*
|
|
* SECTIONS, against docs/01's outline:
|
|
* 1 Hero · 2 Credential row · 3 The approach · 4 Two practices ·
|
|
* 5 Practice areas · 6 Process preview · 7 Latest insights · 8 Contact band
|
|
*
|
|
* SECTION 7 IS BUILT AS OF STEP 7b AND RENDERS NOTHING TODAY. The markup is
|
|
* behind `latest.length > 0`, so with no published article no card, no heading
|
|
* and no link is emitted. D9 means the flip is Pouya's — the schema refuses
|
|
* `draft: false` without `reviewedByPouya: true` — and `SiteHeader` gates the
|
|
* Insights NAV item on the same predicate at two pieces. Do not "finish" this by
|
|
* hardcoding a placeholder card.
|
|
*
|
|
* ⚠️ **THE STEP-2 REASONING FOR DEFERRING THIS SECTION WAS THAT AN UNRENDERED
|
|
* COMPONENT STILL SHIPS ITS CSS. THAT IS TRUE, AND IT IS NOW MEASURED RATHER
|
|
* THAN ARGUED:** importing `ArticleCard` puts **10 rules, 1,496 bytes, 4.4% of
|
|
* `dist/index.html`** into this page for a block that renders nothing. Astro
|
|
* bundles a component's scoped styles on IMPORT, not on render, and
|
|
* `inlineStylesheets: 'auto'` inlines them here.
|
|
*
|
|
* It is kept anyway, and the reason is also a measurement: `npm run lighthouse -- /`
|
|
* returns **performance 99, LCP 2.03 s, CLS 0.000 — identical before and after
|
|
* the 1,498-byte growth.** So the cost is real in bytes and absent in the metric,
|
|
* on the one page already at `docs/04`'s LCP budget. The dead weight clears
|
|
* itself the moment an article publishes, which is the same event that makes the
|
|
* section visible.
|
|
*
|
|
* EVERY FACTUAL CLAIM ON THIS PAGE TRACES TO AGENTS.md §4, and the ones that
|
|
* carry risk are constants from src/data/site.ts rather than typed here.
|
|
* Notably: arbitration is scoped to COMMERCIAL matters throughout (§4
|
|
* Offerings, Q39 2026-08-27), the held designations come from `CREDENTIALS`,
|
|
* and nothing claims or implies licensure (D13).
|
|
*/
|
|
import { Picture, getImage } from 'astro:assets';
|
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
|
import Button from '../components/Button.astro';
|
|
import ContactBand from '../components/ContactBand.astro';
|
|
import CredentialRow from '../components/CredentialRow.astro';
|
|
import Eyebrow from '../components/Eyebrow.astro';
|
|
import InfinityMark from '../components/InfinityMark.astro';
|
|
import PracticeCard from '../components/PracticeCard.astro';
|
|
import ProcessStep from '../components/ProcessStep.astro';
|
|
import ArticleCard from '../components/ArticleCard.astro';
|
|
import SectionHeading from '../components/SectionHeading.astro';
|
|
import portrait from '../assets/pouya-lajevardi.jpg';
|
|
import ogDefault from '../assets/og-portrait.jpg';
|
|
import { getCollection } from 'astro:content';
|
|
import { homeGraph } from '../data/schema';
|
|
import {
|
|
ASYMMETRY_LINE,
|
|
CREDENTIAL_ROW,
|
|
CREDENTIAL_ROW_ARB,
|
|
PRACTICE_AREAS,
|
|
PORTRAIT,
|
|
PROCESS,
|
|
PROCESS_FRAMING,
|
|
SITE,
|
|
} from '../data/site';
|
|
|
|
/**
|
|
* FOUR SLOTS, NOT THREE, and it is now a §4 substitution-principle choice
|
|
* rather than a condition.
|
|
*
|
|
* docs/01 §`/` item 2 says "Three slots"; §4's substitution principle
|
|
* authorises `Q.Arb` as a fourth "where one exists", and this layout has one.
|
|
* It was MANDATORY here until 2026-08-29 under §4's paired-disclosure
|
|
* condition, because the page says *arbitrator* in its second sentence and the
|
|
* stage had to appear beside the offering. Q.Arb is held and that condition is
|
|
* dissolved; the slot stays because a fourth held credential earns its place,
|
|
* not because anything requires it.
|
|
*/
|
|
const credentials = [...CREDENTIAL_ROW, CREDENTIAL_ROW_ARB];
|
|
|
|
/**
|
|
* docs/04 lists `image` on the Person node. This generates it with EXACTLY the
|
|
* transform SEO.astro already applies to the same source — jpeg, 1200 x 630 —
|
|
* so Astro's asset cache returns the same hashed file rather than emitting a
|
|
* second copy for the crawler's benefit. Verified by asset count: dist holds
|
|
* one `og-portrait.*.jpeg` with this line present, not two.
|
|
*
|
|
* JPEG on purpose, for the same reason SEO.astro uses it: link-preview and
|
|
* structured-data consumers are not browsers and several still do not decode
|
|
* WebP, let alone AVIF.
|
|
*/
|
|
const ldImage = await getImage({
|
|
src: ogDefault,
|
|
format: 'jpeg',
|
|
width: 1200,
|
|
height: 630,
|
|
});
|
|
const graph = homeGraph(new URL(ldImage.src, Astro.site).href);
|
|
|
|
/* THE THREE MOST RECENT, and the same `!data.draft` predicate the rest of the
|
|
site uses — see the `draft` field in `src/content.config.ts`. Sorted here
|
|
rather than trusting the loader's order: `glob()` returns files in directory
|
|
order, which is alphabetical by filename and has nothing to do with date. */
|
|
const latest = (await getCollection('insights', ({ data }) => !data.draft))
|
|
.sort((a, b) => b.data.publishDate.getTime() - a.data.publishDate.getTime())
|
|
.slice(0, 3);
|
|
---
|
|
|
|
<BaseLayout
|
|
title={`${SITE.name} · ${SITE.tagline}`}
|
|
description="Mediation and commercial arbitration in Toronto. Construction, technology, energy, insurance and shareholder disputes, read as contracts and as engineering."
|
|
imageAlt={PORTRAIT.alt}
|
|
jsonLd={graph}
|
|
preloadSerifItalic
|
|
>
|
|
{/* ---- 1. Hero ------------------------------------------------------- */}
|
|
<section class="hero">
|
|
<div class="wrap hero-inner">
|
|
<div class="hero-copy">
|
|
{
|
|
/* docs/01 specifies this exact string as the hero eyebrow. The
|
|
masthead no longer repeats it on this page — see SiteHeader. */
|
|
}
|
|
<Eyebrow dot>{SITE.tagline}</Eyebrow>
|
|
|
|
{
|
|
/* docs/03 §Approved headline options, option 1 — the recommended one.
|
|
Option 3 is struck there: "every side" asserts having acted as
|
|
party, as counsel and as neutral, and §4 verifies one of the three.
|
|
The italic is the single flourish docs/02 allows in a headline. */
|
|
}
|
|
<h1 class="display hero-h">
|
|
A mediator who reads the contract, the code, and <em class="it"
|
|
>the room</em
|
|
>.
|
|
</h1>
|
|
|
|
{
|
|
/* THREE CONSTRAINTS ON THIS PARAGRAPH. Trimmed to them under D19; the
|
|
history is in the `AGENTS.md` Change Log.
|
|
|
|
1. ARBITRATION STAYS OFFERING-SHAPED. §4 verifies that he ACCEPTS
|
|
arbitral appointments and separately verifies completed sole
|
|
mediations; there is no completed-arbitration row. A present
|
|
indicative beside "mediate" invites a track record §4 does not
|
|
hold.
|
|
2. NO COMPARATIVE CLAIM ABOUT OTHER NEUTRALS — Q41(b), closed
|
|
2026-08-27, and struck from `docs/03`'s positioning statement too.
|
|
3. THE TAIL IS POUYA'S WORDING, VERBATIM: *"built for disputes that
|
|
turn on the contract, the code, and the engineering documents"*.
|
|
Its echo of the `<h1>`'s triad is deliberate. */
|
|
}
|
|
<p class="hero-lede">
|
|
I mediate from Toronto, and I accept commercial arbitration
|
|
appointments. I also practise as a machine-learning and infrastructure
|
|
engineer, so the matters I take are the ones that turn on the
|
|
contract, the code, and the engineering documents: the change order,
|
|
the model card, the System Impact Assessment, and the regulatory
|
|
overlay around them.
|
|
</p>
|
|
|
|
<div class="hero-cta">
|
|
<Button href="/contact/" variant="primary"
|
|
>Request a consultation →</Button
|
|
>
|
|
<Button href="/process/" variant="ghost">How I work</Button>
|
|
</div>
|
|
</div>
|
|
|
|
{
|
|
/* `widths` + `sizes` rather than `densities`: the portrait is fluid, and
|
|
a density ladder would size it from one assumed CSS width.
|
|
|
|
⚠️ THE OLD CEILING ARGUMENT WAS WRONG, AND IT WAS WRONG BY IGNORING
|
|
THE RANGE WHERE THIS IMAGE IS WIDEST. It read: "The largest real
|
|
render is ~476px (content 1280 - 96 gutter - 64 gap, x 0.425), so 960
|
|
is the 2x ceiling." That arithmetic describes the TWO-COLUMN layout,
|
|
which only engages at 66rem. Below 66rem the hero is a single column
|
|
and the portrait is the full content width. Measured, both pages:
|
|
|
|
viewport slot DPR-2 needs picked result
|
|
640 592 1184 760/960 1.23-1.56x upscale
|
|
768 672 1344 960 1.40x
|
|
900 804 1608 960 1.68x
|
|
1024 928 1856 960 1.93x
|
|
|
|
At 768/DPR-1 a 760w file EXISTS and is not chosen, so part of the loss
|
|
was purely the wrong `sizes` (60vw declared against a ~88vw slot).
|
|
Reconfirmed with the HTTP cache cleared — an earlier probe reported a
|
|
2.81x OVERSIZED fetch at 390/DPR-1 which was a cache artefact, not a
|
|
defect.
|
|
|
|
TWO FIXES, BOTH REQUIRED. (1) `.hero-portrait` is capped at 30rem in
|
|
the single-column range, so the widest real slot is 480 CSS px
|
|
everywhere — which makes 960 exactly right for DPR 2 rather than
|
|
accidentally short. (2) A 1440 rung, because 480 x 3 = 1440 and the
|
|
desktop slot at DPR 3 already needed 1287-1428; the ≥66rem range was
|
|
upscaling ~1.34-1.49x at DPR 3 before this and nobody had measured it.
|
|
The master is 1600, so 1440 exists.
|
|
|
|
THE CAP CHANGES HOW THIS PAGE LOOKS between 640px and 1055px — the
|
|
portrait was 592-928px wide there and is now 480. That is a visible
|
|
design change to a reviewed page, made on payload grounds; raising the
|
|
cap is a one-line change but the ladder has to grow with it.
|
|
|
|
`width` AND `height` ARE PASSED ALONGSIDE `widths`, AND THAT IS NOT
|
|
REDUNDANT. With `widths` alone, Astro emits the UNTOUCHED 1600px
|
|
master as the <img src> fallback — measured 254,626 bytes sitting in
|
|
dist as the declared fallback for a 476px slot. Passing width/height
|
|
pins it to the 960 variant instead (78,665 B), and the 1600px file
|
|
stops being generated at all. Same defect InfinityMark's comment
|
|
records for a missing `width`, in the one shape that survives passing
|
|
`widths`. Verified by reading the emitted `src` attribute and that
|
|
file's real dimensions — not the build log, which reported "before:
|
|
349kB" for every variant either way.
|
|
|
|
What a browser actually takes is the AVIF: 5.6 / 7.3 / 11.1 / 14.8 /
|
|
21.5 kB across the first five widths [measured 2026-08-27]; 1080 and
|
|
1440 are added 2026-08-28 for DPR 3 (26.6 / 48.8 kB).
|
|
|
|
AND THE 1440 RUNG OVERSHOT, SO 1080 EXISTS TO CORRECT IT. Adding 1440
|
|
for DPR 3 removed a 1.07x upscale at 390/DPR3 and replaced it with a
|
|
**48,799 B fetch where the old one was 21,526 B** — +27 KB on a phone,
|
|
to fix a 7% softness nobody can see. A browser takes the smallest
|
|
candidate at or above what it needs, and with no rung between 960 and
|
|
1440 the only choices were "slightly soft" or "+27 KB". 1080 makes
|
|
1026 (390 x DPR 3) exact and cheap. This was a defect in the fix for
|
|
the defect above, found by measuring the fix rather than the source.
|
|
|
|
AND 1080 ALONE MISSED THE TWO LARGEST CURRENT PHONES, WHICH
|
|
MAKES THIS THE THIRD ITERATION OF THIS LADDER. 1080 was tuned to
|
|
390 CSS px x DPR 3 (= 1026), and `sizes` resolves to
|
|
`calc(100vw - 3rem)` up to 528px, so every phone wider than 390
|
|
overshoots to the next rung: iPhone 14 Plus (428@3, needs 1140)
|
|
and 15/16 Pro Max (430@3, needs 1146) both took **1440 —
|
|
48,799 B**, against 27,594 for the device the rung was tuned
|
|
for. +21,205 B, 13% of page weight. A 1200 rung closes it at
|
|
1.05x. Measured after: no rung more than 1.06x oversized on the
|
|
phone axis, and still no upscaling anywhere — 0 upscaling across 11
|
|
real device profiles on both pages.
|
|
|
|
TWO RESIDUAL OVER-FETCHES, LEFT DELIBERATELY, so neither reads as an
|
|
oversight later. **320@2** needs 544 and takes 640 (1.18x): there is no
|
|
rung between 480 and 640, and 480 would be a 1.13x UPSCALE, so the
|
|
oversize is the better half of that trade. **1056@2 needs 760.4 and
|
|
takes 960 (1.26x, +6.8 KB)** — a knife-edge, and worth stating because
|
|
it looks like a `sizes` error and is not: the declared `36vw` is
|
|
accurate to the measured 36.0% track, and 380.2 x 2 = 760.4 misses the
|
|
760 rung by four tenths of a pixel. Declaring 35vw to duck under it
|
|
would make `sizes` less truthful across the whole band in exchange for
|
|
a 0.05% upscale at this width. The declaration stays honest and one
|
|
viewport over-fetches.
|
|
|
|
⚠️ `fetchpriority="high"` IS GONE, AND THIS PAGE HAD IT WHILE
|
|
`/about/` WITHHELD IT ON THE IDENTICAL MEASUREMENT. The old comment
|
|
read "eager + fetchpriority=high because this is the LCP candidate on
|
|
the page docs/04 budgets hardest" — true only from 768px up. Measured,
|
|
cache cleared per device:
|
|
|
|
320x568 @2 portrait visible 0px LCP — 11,058 B
|
|
360x780 @3 portrait visible 0px LCP — 21,526 B
|
|
390x844 @3 portrait visible 0px LCP P.hero-lede 27,594 B
|
|
430x932 @3 portrait visible 30px LCP P.hero-lede 48,799 B
|
|
1280x900 @1 portrait visible 595px LCP IMG.portrait 7,257 B
|
|
|
|
So on mobile — the axis the ≥95 budget is actually measured on — it
|
|
promoted 27-49 KB of image the reader cannot see above the Geist face
|
|
that paints the real LCP element. `/about/` already withheld it for
|
|
exactly this reason and this page did the opposite; the inconsistency
|
|
is the finding.
|
|
|
|
`loading="eager"` STAYS: the portrait is the LCP element from 768px up,
|
|
and eager costs nothing where it is off-screen. If the desktop LCP ever
|
|
needs protecting explicitly, the right instrument is
|
|
`<link rel="preload" imagesrcset imagesizes>` in <head>, which honours
|
|
`sizes` and therefore self-cancels on phones — not a blanket attribute
|
|
that cannot. */
|
|
}
|
|
<div class="hero-portrait">
|
|
<Picture
|
|
src={portrait}
|
|
width={960}
|
|
height={960}
|
|
widths={[380, 480, 640, 760, 960, 1080, 1200, 1440]}
|
|
sizes="(min-width: 80rem) 476px, (min-width: 66rem) 36vw, (min-width: 33rem) 480px, calc(100vw - 3rem)"
|
|
formats={['avif', 'webp']}
|
|
fallbackFormat="jpeg"
|
|
alt={PORTRAIT.alt}
|
|
loading="eager"
|
|
decoding="sync"
|
|
class="portrait-img"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ---- 2. Credential row --------------------------------------------- */}
|
|
<section class="wrap credential-band" aria-label="Credentials">
|
|
<CredentialRow slots={credentials} />
|
|
</section>
|
|
|
|
{
|
|
/* NO `aria-labelledby` ON THE CONTENT SECTIONS, and that is the fix for an
|
|
inconsistency rather than a removal of information.
|
|
|
|
Four of them carried one and this one did not, which put five named
|
|
regions plus header / nav / main / footer / footer-nav in a screen
|
|
reader's region list — eleven entries for a marketing page — with the only
|
|
unnamed content section being the odd one out. A `<section>` without an
|
|
accessible name is not exposed as a region at all, and the visible `<h2>`s
|
|
already give heading navigation, which is how a reader moves through a
|
|
page like this.
|
|
|
|
THE ONE EXCEPTION IS THE CREDENTIAL BAND, which has no visible heading, so
|
|
`aria-label` is the only thing that can name it. The rule is: name a region
|
|
only where it has no heading of its own. */
|
|
}
|
|
{/* ---- 3. The approach ----------------------------------------------- */}
|
|
<section class="section section-inverse approach reveal">
|
|
<div class="wrap approach-inner">
|
|
<div class="approach-copy">
|
|
<SectionHeading eyebrow="The approach" level={2}>
|
|
<span slot="heading">Two directions at once.</span>
|
|
</SectionHeading>
|
|
{
|
|
/* THIS PARAGRAPH USED TO OPEN "Law and engineering are not blended
|
|
here", and `claims-auditor` flagged it as Q37's struck parallel
|
|
relocated from the credential label into prose — a degree and a
|
|
practice under one noun, one day after Pouya struck exactly that
|
|
construction. It is a fair reading and the fix is the same fix:
|
|
make the two halves asymmetric. What a neutral does with a contract
|
|
is read it; what an engineer does is engineering. Neither sentence
|
|
now sets "Law" beside "engineering" as two instances of one thing.
|
|
|
|
`docs/01` §`/` item 3 and `docs/03` §Home both specify this section
|
|
as "the 'two directions at once' argument — law and engineering
|
|
converging on the same dispute", so the ARGUMENT is unchanged and
|
|
still delivered; only the construction that carried the implication
|
|
is gone.
|
|
|
|
**Q41(a) CLOSED 2026-08-27: Q37's reasoning DOES extend to prose,
|
|
and prose has to do more than avoid the parallel.** Pouya: *"The
|
|
implication test applies everywhere, not just to labels. Prose has
|
|
more room, so it is easier to satisfy: state the asymmetry
|
|
explicitly rather than relying on a parallel construction to carry
|
|
it."* Avoiding the pair was therefore only half the fix — a reader
|
|
can still supply the missing symmetry from silence. So the second
|
|
paragraph now names both halves for what they are: training on one
|
|
side, current work on the other. "Training I hold" is the opposite
|
|
of a licence claim, which is the point of saying it out loud.
|
|
|
|
`docs/01`'s and `docs/03`'s own phrase "law and engineering" is the
|
|
struck construction; both now carry a note not to lift it into copy.
|
|
The argument it names is Pouya's and stands. */
|
|
}
|
|
<div class="prose approach-prose">
|
|
<p>
|
|
Any dispute I take gets read twice: once against the documents, and
|
|
once as engineering. The two readings are not blended here. They run
|
|
at the same time.
|
|
</p>
|
|
{
|
|
/* FROM A CONSTANT, NOT TYPED. It was typed here and then typed
|
|
again on `/about/`, and the two copies had ALREADY diverged inside
|
|
one session — a comma here, full stops there. Q41(a) makes this
|
|
the sentence responsible for foreclosing the licence implication,
|
|
so it is the worst string on the site to let drift. See
|
|
ASYMMETRY_LINE in src/data/site.ts. */
|
|
}
|
|
<p>{ASYMMETRY_LINE}</p>
|
|
{
|
|
/* TWO COMPARATIVES CAME OUT OF THESE PARAGRAPHS ON 2026-08-28, and
|
|
both had SURVIVED the sweep that closed Q41(b) the day before:
|
|
|
|
"The second half of each pair usually arrives as a separate
|
|
expert report." — an empirical claim about how disputes are
|
|
usually run, i.e. about a population of other matters.
|
|
"it is why the technical half is not something a party has to
|
|
commission and wait for" — the same claim in counterfactual
|
|
form, which is harder to spot and says more.
|
|
|
|
Pouya's ruling on Q41(b) is the test: *"assert his capability, not
|
|
the field's incapability."* Both worked by asserting the field's.
|
|
What replaces them says only what he does, which is the stronger
|
|
claim anyway — and it is shorter. */
|
|
}
|
|
<p>
|
|
A construction claim is a contract question and a scheduling
|
|
question. A software dispute is a licence question and an
|
|
architecture question. A grid connection is a regulatory question
|
|
and a load question.
|
|
</p>
|
|
<p>
|
|
I read both halves of each pair myself. That is the whole of it.
|
|
</p>
|
|
<p class="approach-metaphor">
|
|
My mark is an infinity loop, and it is the argument in one line:
|
|
disputes are loops. The work is redrawing the loop into a line.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{
|
|
/* Decorative: the paragraph beside it says what it is, so an
|
|
accessible name here would be read twice. Measured on this ground —
|
|
the ribbon's champagne half carries the silhouette against ink, the
|
|
maroon half against cream; it reads on both. */
|
|
}
|
|
<div class="approach-mark">
|
|
{
|
|
/* width=232 and loading=lazy are both measured, not defaults.
|
|
`size` caps at 9rem tall, so the mark renders at up to 144 x 225.5
|
|
CSS px — five times the header's 50.1px, and the component's default
|
|
64px ladder tops out at 192px. Measured upscale before this: 1.17x
|
|
at DPR 1, 2.35x at DPR 2, 3.52x at DPR 3. 232 with densities
|
|
[1,2,3] gives 232/464/696, and 696 covers the 676 device px a DPR-3
|
|
screen asks for. Lazy because this sits roughly a screen and a half
|
|
down; the component defaults to eager for the masthead. */
|
|
}
|
|
<InfinityMark
|
|
size="clamp(4rem, 14vw, 9rem)"
|
|
width={232}
|
|
loading="lazy"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ---- 4. Two practices --------------------------------------------- */}
|
|
<section class="section practices reveal">
|
|
<div class="wrap">
|
|
{
|
|
/* The id goes on a span INSIDE the h2 via the `heading` slot, so
|
|
`aria-labelledby` names the visible heading. The first version passed
|
|
`title=` and additionally rendered a `.visually-hidden` span carrying
|
|
the id, which put the same words in the accessibility tree twice —
|
|
found in the heading-outline dump, not by reading the source. */
|
|
}
|
|
{
|
|
/* THE WRAPPER IS THE FIX, NOT DECORATION. This was
|
|
`<SectionHeading class="section-head">`, and the page's rule compiled
|
|
to `.section-head[data-astro-cid-<page>]` while the rendered root
|
|
carried SectionHeading's own cid — so it never matched. Measured:
|
|
`margin-block-end: 0px` and a 0px gap to the cards on all three call
|
|
sites, with `.display`'s 0.98 line-height putting the glyphs over the
|
|
card's top edge. `astro check` 0 errors, `eslint` clean, and the source
|
|
looked right. Fourth instance of this on the project; SectionHeading no
|
|
longer accepts a `class` at all, and passing one is now a build
|
|
error. */
|
|
}
|
|
<div class="section-head">
|
|
<SectionHeading eyebrow="What I do" level={2}>
|
|
<span slot="heading">Two processes.</span>
|
|
</SectionHeading>
|
|
</div>
|
|
|
|
<div class="pair">
|
|
<article class="feature">
|
|
<h3 class="feature-title">
|
|
<a href="/mediation/">Mediation</a>
|
|
</h3>
|
|
{
|
|
/* NO FEE CLAIM ON THIS CARD — a constraint, not an omission.
|
|
There are two ways to get it wrong in one line and both have
|
|
shipped once: "at one published rate" reads as ONE price where
|
|
D14's card sets two, and an unqualified "preparation included"
|
|
sells an uncapped allowance where `docs/07` caps it at stated
|
|
hours. A card this size cannot state either properly.
|
|
`/mediation/` and `/fees/` carry the card. */
|
|
}
|
|
<p class="feature-body">
|
|
Sole mediator, Q.Med through ADRIC and ADRIO, with multiple
|
|
completed sole mediations. Half day or full day, in person or by
|
|
video.
|
|
</p>
|
|
<span class="feature-arrow" aria-hidden="true">→</span>
|
|
</article>
|
|
|
|
<article class="feature">
|
|
<h3 class="feature-title">
|
|
<a href="/arbitration/">Arbitration</a>
|
|
</h3>
|
|
{
|
|
/* COMMERCIAL MATTERS ONLY (Q39) — family arbitration is not offered,
|
|
and that scope is Pouya's own choice, not a conclusion this record
|
|
draws about the law. */
|
|
}
|
|
<p class="feature-body">
|
|
I accept sole, party-appointed and co-arbitration appointments in
|
|
commercial matters, on a documents-only, expedited or full-hearing
|
|
track.
|
|
</p>
|
|
<span class="feature-arrow" aria-hidden="true">→</span>
|
|
</article>
|
|
</div>
|
|
|
|
{
|
|
/* Med-Arb has its own §4 Offerings row (Q35, 2026-08-27), so it is named
|
|
here as a present offering. docs/01 item 4 asked for it as "the
|
|
long-term arc"; there is no arc, and that item is amended. */
|
|
}
|
|
<p class="pair-note">
|
|
<strong>Med-Arb</strong> combines the two: one neutral mediates, then arbitrates
|
|
whatever has not settled. I accept those appointments in commercial matters.
|
|
The page on it meets the procedural-fairness objection head on rather than
|
|
around it —
|
|
<a href="/med-arb/">how med-arb works →</a>
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ---- 5. Practice areas -------------------------------------------- */}
|
|
{
|
|
/* docs/01: "the most important block on the page for search, because it
|
|
distributes authority to the pages that can actually rank." */
|
|
}
|
|
<section class="section section-alt areas reveal">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<SectionHeading
|
|
eyebrow="Practice areas"
|
|
level={2}
|
|
lede="Six areas, chosen because the disputes in them turn on documents I can read without an intermediary."
|
|
>
|
|
<span slot="heading">Where the work is.</span>
|
|
</SectionHeading>
|
|
</div>
|
|
|
|
{
|
|
/* reveal-stagger is capped at six children by design (docs/02) and there
|
|
are exactly six. A seventh would land with the sixth, not break. */
|
|
}
|
|
<div class="area-grid reveal-stagger">
|
|
{
|
|
PRACTICE_AREAS.map((area) => (
|
|
<PracticeCard
|
|
href={`/practice/${area.slug}/`}
|
|
chip={area.chip}
|
|
title={area.name}
|
|
level={3}
|
|
>
|
|
{area.blurb}
|
|
</PracticeCard>
|
|
))
|
|
}
|
|
</div>
|
|
|
|
{
|
|
/* THIS SAID "All six areas, and what else is offered", which asserted
|
|
offerings beyond the six, none of which had a §4 row.
|
|
|
|
**Q42 CLOSED 2026-08-27, and one of the four candidates came out.**
|
|
Early neutral evaluation, dispute-system design and pre-dispute
|
|
technical advisory now have Offerings rows. **Settlement counsel does
|
|
not, and never will** — Pouya struck it as his own error in `docs/01`:
|
|
*"Settlement counsel acts FOR a party in negotiation. That is a
|
|
partisan role, and putting it on a site that (a) sells neutrality and
|
|
(b) asserts no licensure under D13 is wrong twice over."*
|
|
|
|
THIS LINE STILL READS "All six practice areas" and that is unchanged
|
|
on purpose. The three rowed processes are `/practice/`'s "also
|
|
offered" strip at step 5, not a claim `/` makes in a link label — a
|
|
six-card grid followed by "and what else is offered" is the
|
|
Med-Arb-in-the-footer shape whether or not the rows exist. */
|
|
}
|
|
<p class="areas-more">
|
|
<a href="/practice/">All six practice areas →</a>
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ---- 6. Process preview ------------------------------------------- */}
|
|
<section class="section process reveal">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<SectionHeading
|
|
eyebrow="How it runs"
|
|
level={2}
|
|
lede="Five steps, from the first call to the conclusion — including what happens if the matter does not settle."
|
|
>
|
|
<span slot="heading">From first call to conclusion.</span>
|
|
</SectionHeading>
|
|
</div>
|
|
|
|
<ol class="process-strip" role="list">
|
|
{
|
|
PROCESS.map((step, i) => (
|
|
<ProcessStep n={i + 1} title={step.title} timing={step.timing}>
|
|
{step.body}
|
|
</ProcessStep>
|
|
))
|
|
}
|
|
</ol>
|
|
|
|
{
|
|
/* NOT OPTIONAL, AND NOT A DISCLAIMER — Q43, Pouya 2026-08-27. It is the
|
|
condition on which the five timings may be published at all: *"Published
|
|
as typical, they are honest and useful; published as commitments, the
|
|
first matter that slips makes the page false."* It sits directly under
|
|
the numbers rather than in the section lede above them, because a
|
|
reader who scans the strip and skips the lede has read a commitment.
|
|
`/process/` renders the same constant at step 6. */
|
|
}
|
|
<p class="process-framing">{PROCESS_FRAMING}</p>
|
|
|
|
<p class="process-more">
|
|
<a href="/process/">What happens if the matter does not settle →</a
|
|
>
|
|
</p>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ---- 7. Latest insights -------------------------------------------- */}
|
|
{
|
|
latest.length > 0 && (
|
|
<section class="section section-alt reveal">
|
|
<div class="wrap">
|
|
<div class="section-head">
|
|
<SectionHeading
|
|
eyebrow="Insights"
|
|
level={2}
|
|
lede="Notes on process, regulatory change, and the technical record underneath commercial disputes."
|
|
>
|
|
<span slot="heading">Recently written.</span>
|
|
</SectionHeading>
|
|
</div>
|
|
<div class="grid-autofit insights-grid" style="--grid-min: 20rem">
|
|
{latest.map((entry) => (
|
|
<ArticleCard
|
|
href={`/insights/${entry.id}/`}
|
|
title={entry.data.title}
|
|
description={entry.data.description}
|
|
date={entry.data.publishDate}
|
|
topics={entry.data.topics}
|
|
readingTime={entry.data.readingTime}
|
|
level={3}
|
|
/>
|
|
))}
|
|
</div>
|
|
<p class="insights-more">
|
|
<a href="/insights/">Everything written →</a>
|
|
</p>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|
|
|
|
{/* ---- 8. Contact band ---------------------------------------------- */}
|
|
{
|
|
/* A COMPONENT SINCE 2026-08-28. It was ~20 lines of markup plus ~20 of CSS
|
|
here and the same again on `/about/`, and the two had already drifted
|
|
(`.contact-body` at 52ch here, 46ch there) inside the session that wrote
|
|
the second one. Seventeen pages remain. docs/01 item 8's missing booking
|
|
link is documented in the component, once. */
|
|
}
|
|
<ContactBand />
|
|
</BaseLayout>
|
|
|
|
<style>
|
|
/* --- 7. Latest insights --------------------------------------------- */
|
|
|
|
/* `.grid-autofit` (global.css) carries the columns and the `min()` guard. */
|
|
.insights-grid {
|
|
gap: var(--space-5);
|
|
}
|
|
.insights-more {
|
|
margin-block-start: var(--space-6);
|
|
font-size: var(--text-base);
|
|
}
|
|
|
|
/* --- 1. Hero -------------------------------------------------------- */
|
|
|
|
.hero {
|
|
padding-block: var(--space-8) var(--space-9);
|
|
}
|
|
.hero-inner {
|
|
display: grid;
|
|
gap: var(--space-7);
|
|
align-items: center;
|
|
}
|
|
.hero-copy {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-5);
|
|
}
|
|
.hero-h {
|
|
/* --text-6xl is the display ceiling. At 360px it is 52px and the headline
|
|
runs four lines; `text-wrap: balance` (global.css) keeps them even. */
|
|
font-size: var(--text-6xl);
|
|
max-inline-size: 22ch;
|
|
/* `anywhere`, not `break-word` — one word here held the whole hero column
|
|
open. `docs/02` §Reflow, instrument finding 1. */
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.hero-lede {
|
|
max-inline-size: var(--width-prose);
|
|
font-size: var(--text-lg);
|
|
line-height: var(--leading-relaxed);
|
|
color: var(--text-secondary);
|
|
}
|
|
.hero-cta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: var(--space-4);
|
|
margin-block-start: var(--space-2);
|
|
}
|
|
/* The 4:5 crop is CSS, not a build step: Astro's image service does not crop,
|
|
and cropping offline would mean committing a second derived binary on top
|
|
of the ~3.16 MB of brand and portrait masters already in the repo. The cost
|
|
is that a browser downloads the full square and shows 80% of it; the source
|
|
is 1600 x 1600 and the largest variant generated is 960 wide. */
|
|
.hero-portrait {
|
|
aspect-ratio: 4 / 5;
|
|
overflow: hidden;
|
|
border-radius: var(--radius-lg);
|
|
background: var(--bg-raised);
|
|
/* THE CAP IS THE FIX FOR THE UPSCALE, not a style preference. Uncapped, the
|
|
single-column slot ran to 928 CSS px at a 1024px viewport, which is 1856
|
|
device px at DPR 2 against a 960w ceiling — 1.93x. 30rem makes 480 the
|
|
widest real slot on any page, so the ladder's 960 covers DPR 2 exactly
|
|
and the new 1440 rung covers DPR 3. See the <Picture> comment above.
|
|
`margin-inline: auto` because a 480px box in a 928px column would
|
|
otherwise sit hard against the inline start. */
|
|
max-inline-size: 30rem;
|
|
margin-inline: auto;
|
|
}
|
|
/* The <picture> wrapper is the box that gets sized, NOT the <img> — the exact
|
|
defect CLAUDE.md records for <Button> and then for <Picture> inside
|
|
InfinityMark. `class="portrait-img"` lands on the <img>, so the <img> rule
|
|
below is reached via Astro's cid on the emitted element, and the wrapper is
|
|
targeted by the bare `picture` selector, which the markup does carry. */
|
|
.hero-portrait :global(picture) {
|
|
display: block;
|
|
block-size: 100%;
|
|
}
|
|
.portrait-img {
|
|
inline-size: 100%;
|
|
block-size: 100%;
|
|
object-fit: cover;
|
|
/* Above centre: the subject's head is in the upper half of a square crop. */
|
|
object-position: 50% 22%;
|
|
}
|
|
|
|
@media (min-width: 66rem) {
|
|
.hero-portrait {
|
|
/* Above 66rem the grid track is already 390-476px, so the cap is inert —
|
|
released anyway so the track, not this number, governs the two-column
|
|
layout. Keeping it would silently become the constraint if the track
|
|
ever widened. */
|
|
max-inline-size: none;
|
|
}
|
|
.hero {
|
|
padding-block: var(--space-9);
|
|
}
|
|
.hero-inner {
|
|
/* 1.15 / 0.85 — the copy column carries a 22ch headline and a 68ch lede;
|
|
an even split starves the headline and leaves the portrait oversized. */
|
|
grid-template-columns: 1.15fr 0.85fr;
|
|
gap: var(--space-8);
|
|
}
|
|
}
|
|
|
|
/* --- 2. Credential row ---------------------------------------------- */
|
|
|
|
.credential-band {
|
|
/* No .section wrapper: the row owns its own padding-block and border, and
|
|
stacking --section-y on top would put 160px of air around a 4-line band. */
|
|
padding-block-end: var(--space-4);
|
|
}
|
|
|
|
/* --- 3. The approach ------------------------------------------------ */
|
|
|
|
.approach-inner {
|
|
display: grid;
|
|
gap: var(--space-7);
|
|
align-items: center;
|
|
}
|
|
/* NO `display: flex; gap` ANY MORE, AND THAT IS A CORRECTION TO A FIX.
|
|
global.css gained `:where(.prose) > p + p { margin-block-start }` on
|
|
2026-08-28 because a bare `.prose` block had no paragraph spacing at all.
|
|
The new rule's comment asserted "with `:where()` the flex container's gap
|
|
governs and this contributes nothing" — **false, and it was measured false
|
|
immediately after being written.** `:where()` controls SPECIFICITY, not
|
|
whether a declaration applies: nothing here was overriding the margin, so
|
|
flex `gap` and the margin both applied and this block's paragraph gaps went
|
|
**24px -> 48px**. Spacing now comes from the one global rule, which is the
|
|
point of having it. */
|
|
.approach-prose {
|
|
margin-block-start: var(--space-6);
|
|
font-size: var(--text-lg);
|
|
line-height: var(--leading-relaxed);
|
|
}
|
|
.approach-metaphor {
|
|
padding-block-start: var(--space-5);
|
|
border-block-start: 1px solid var(--rule);
|
|
font-family: var(--font-serif);
|
|
font-size: var(--text-xl);
|
|
line-height: var(--leading-snug);
|
|
color: var(--text-inverse-2);
|
|
}
|
|
.approach-mark {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
@media (min-width: 66rem) {
|
|
.approach-inner {
|
|
grid-template-columns: 1fr auto;
|
|
gap: var(--space-9);
|
|
}
|
|
/* Mark second in the DOM and second visually. No `order` anywhere on this
|
|
page: reordering flex or grid items puts focus order out of step with
|
|
visual order, which is what WCAG 2.4.3 and docs/02 both forbid, and it
|
|
already cost a header rebuild at step 1. */
|
|
}
|
|
|
|
/* --- 4. Two practices ---------------------------------------------- */
|
|
|
|
/* NO `.section-head` RULE — it moved to `global.css`, where `.prose` lives.
|
|
It was byte-identical in both pages, and every one of the seventeen
|
|
remaining pages needs it for the same reason (a parent cannot style a child
|
|
component's root, so the wrapper must be page-owned). */
|
|
.pair {
|
|
display: grid;
|
|
gap: var(--space-5);
|
|
}
|
|
.feature {
|
|
position: relative;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: var(--space-4);
|
|
/* CLAMPED, not a flat --space-7. At a 200% default font size (root 32px)
|
|
`3rem` is 96px a side — 192px of horizontal padding inside a 342px
|
|
container, which was most of the 83px residual overflow
|
|
`adversarial-reviewer` measured. The vw term keeps it at 48px on any real
|
|
viewport and lets it collapse when the rem is doubled. */
|
|
padding: clamp(var(--space-5), 4vw, var(--space-7));
|
|
background: var(--bg-raised);
|
|
border-radius: var(--radius-lg);
|
|
transition: background-color var(--dur-hover) var(--ease);
|
|
}
|
|
.feature:hover {
|
|
background: var(--cream-2);
|
|
}
|
|
.feature-title {
|
|
font-family: var(--font-serif);
|
|
font-size: var(--text-3xl);
|
|
line-height: var(--leading-tight);
|
|
letter-spacing: var(--tracking-tight);
|
|
/* `overflow-wrap: break-word` (global.css) permits a break at layout time
|
|
but does NOT reduce min-content size, so "Arbitration" at a 60px
|
|
--text-3xl held the card open. `anywhere` does reduce it. Only reachable
|
|
at a large default font size; at every real size the word never breaks. */
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.feature-title a {
|
|
color: var(--text);
|
|
text-decoration: none;
|
|
}
|
|
/* One link per card, hit area the whole card, accessible name the heading —
|
|
same pattern and same reasoning as PracticeCard. */
|
|
.feature-title a::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: var(--radius-lg);
|
|
}
|
|
.feature:hover .feature-title a {
|
|
color: var(--accent);
|
|
}
|
|
.feature:has(a:focus-visible) {
|
|
outline: 2px solid var(--focus-ring);
|
|
outline-offset: var(--focus-offset);
|
|
}
|
|
.feature-title a:focus-visible {
|
|
outline: none;
|
|
}
|
|
.feature-body {
|
|
flex: 1 1 auto;
|
|
max-inline-size: 46ch;
|
|
color: var(--text-secondary);
|
|
/* `anywhere`, not the `break-word` `global.css` already gives this `<p>` —
|
|
one token held this card's grid track open. `docs/02` §Reflow, instrument
|
|
finding 1. */
|
|
overflow-wrap: anywhere;
|
|
}
|
|
.feature-arrow {
|
|
font-size: var(--text-xl);
|
|
line-height: 1;
|
|
color: var(--accent);
|
|
}
|
|
.pair-note {
|
|
max-inline-size: var(--width-prose);
|
|
margin-block-start: var(--space-6);
|
|
color: var(--text-secondary);
|
|
}
|
|
@media (min-width: 56rem) {
|
|
.pair {
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: var(--space-6);
|
|
}
|
|
}
|
|
|
|
/* --- 5. Practice areas --------------------------------------------- */
|
|
|
|
.area-grid {
|
|
display: grid;
|
|
/* auto-fit with an 18rem floor: 1 up on a phone, 2 up on a tablet, 3 up on
|
|
a desktop, with no breakpoint of its own. The cards size themselves to
|
|
the cell (see PracticeCard) — a parent cannot reach a child's root.
|
|
|
|
`min(18rem, 100%)`: a bare rem floor is a hard minimum, so at a large
|
|
default font size (root 32px) 18rem becomes 576px and the track will not
|
|
shrink. Measured at root 200%: this grid overflowed a 390px viewport by
|
|
234px. See CredentialRow for the same guard and the full reasoning. */
|
|
grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr));
|
|
gap: var(--space-5);
|
|
}
|
|
/* THESE TWO LINKS STAND ALONE ON THEIR OWN LINE, so docs/02's 44px touch
|
|
floor applies to them in full. Measured before this rule: 250.8 x 18 and
|
|
287.9 x 18 — the paragraph's line box and nothing more.
|
|
|
|
`inline-flex` + min-block-size rather than padding, so the 44px IS the hit
|
|
area rather than visual air, and `inline-size: fit-content` keeps the
|
|
target the width of the words instead of the whole measure.
|
|
|
|
The INLINE link in `.pair-note` ("how med-arb works") is deliberately NOT
|
|
given this treatment: it sits mid-sentence at 164 x 21, and WCAG 2.5.8
|
|
exempts a target "in a sentence or its size is otherwise constrained by the
|
|
line-height of non-target text". Padding it out would break the paragraph's
|
|
leading to satisfy a rule that does not apply to it. A deviation from
|
|
docs/02's flat wording, taken deliberately and recorded rather than left to
|
|
look like an oversight.
|
|
|
|
The card heading links measure 26-39px tall and are NOT a finding: each
|
|
card's whole box is the link's hit area via `::after { inset: 0 }`. Verified
|
|
by hit-testing nine points per card at 390 / 768 / 1280px — 24 cards, 9/9
|
|
inside the link every time. The width sweep flagged them because it
|
|
measured the <a>'s own box, which is not the target. */
|
|
.areas-more,
|
|
.process-more {
|
|
margin-block-start: var(--space-6);
|
|
font-size: var(--text-sm);
|
|
}
|
|
.areas-more a,
|
|
.process-more a {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
inline-size: fit-content;
|
|
min-block-size: 44px;
|
|
}
|
|
|
|
/* --- 6. Process preview -------------------------------------------- */
|
|
|
|
.process-strip {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
|
|
gap: var(--space-5);
|
|
margin: 0;
|
|
}
|
|
/* --space-5 rather than --space-6: this belongs to the strip above it, not to
|
|
the link below.
|
|
|
|
THE RATIO IN THIS COMMENT WAS WRONG WHEN FIRST WRITTEN. It said
|
|
"--text-meta on cream measures 3.07:1". It does not: `#6e6359` on `#faf7f2`
|
|
measures **5.47:1** and passes AA. 3.07:1 is `--muted` on **ink**, which is
|
|
exactly what `tokens.css` says and what this comment misread. So the
|
|
*reason* given was false even though the *choice* is right —
|
|
--text-secondary (10.76-11.75:1) is correct here because this sentence is a
|
|
CONDITION on the numbers above it, not metadata about them, and a condition
|
|
has to read like body copy. Corrected 2026-08-28 on
|
|
`adversarial-reviewer`'s measurement. */
|
|
.process-framing {
|
|
margin-block-start: var(--space-5);
|
|
max-inline-size: var(--width-prose);
|
|
font-size: var(--text-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* NO CONTACT-BAND RULES HERE. `.contact-inner`, `.contact-h`, `.contact-body`
|
|
and `.contact-action` moved to `ContactBand.astro` with the markup they
|
|
style. They were left behind after the extraction — dead CSS shipping to
|
|
every visitor, and worse, the **46ch / 52ch divergence the extraction
|
|
existed to end was still sitting on disk in both pages**, so the next reader
|
|
would have found two different values and no rendered difference. Deleted
|
|
2026-08-28. */
|
|
</style>
|