D19 into §3 and swept to all six destinations Pouya named: both agent briefs
(scope + severity filter), /build Phases 2–4 (non-gating questions, scope, stop
signal, two-round cap), /wrap, and CLAUDE.md (comments record decisions, not
history). Sweep verified by command, not recalled.
Rulings applied:
Q46(a) PUBLISHABLE — three §4 Offerings rows flipped; gate 1 records
[Pouya's stated basis], never [verified]. Step 5 unblocked.
Q48 closed, not site-relevant.
Q49 one §4 row, "Mediator"; read as declining Q49(b), so worksFor stays out.
Q50 DEVIATES — ships as name + slogan, not the concatenation. Flagged.
Q51 OBA sections stay; the regulator/voluntary distinction recorded.
Q52 docs/reference/deploy-credential-verification.md — 18 read-only AWS
calls, re-run rather than transcribed, access key ID redacted.
Q53 memberOf emitted on /about/'s Person node.
Two review rounds. The headline finding was this session's own: the Q53 sweep
was asserted and never run, leaving six in-scope records saying memberOf was
withheld — including §12 R10, which is read aloud every session. Round 2 then
found that round 1's simplification had put memberOf on / as well; the per-page
opt-in is restored, because Pouya's ruling turns on /about/'s visible HTML.
Also fixed: MEMBERSHIP_ORGS had orphaned BOUTIQUE's D16 JSDoc; /'s title now
derives from the constants; §7's deploy row stated and retracted three facts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0148NztQskLKKApP5SzAA78e
947 lines
42 KiB
Plaintext
947 lines
42 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 NOT BUILT, DELIBERATELY, and this is the only spec item this
|
|
* page does not deliver. `src/content/insights/` is empty: the collection ships
|
|
* at build step 7, which is also where `ArticleCard` and the drafted slate
|
|
* arrive (docs/01 §Build order; docs/03 §Launch article slate, D9). Rendering
|
|
* the section now means importing a component with nothing to render — its
|
|
* scoped CSS ships to every visitor for an empty block — and a props surface
|
|
* with no call site, which is already an open finding against InfinityMark.
|
|
* SiteHeader gates the Insights NAV item on the same collection, so the page
|
|
* and the nav appear together. Do not "finish" this by hardcoding placeholders.
|
|
*
|
|
* 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 Q.Arb stage is stated on the page and not
|
|
* only in the footer, 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 SectionHeading from '../components/SectionHeading.astro';
|
|
import portrait from '../assets/pouya-lajevardi.jpg';
|
|
import ogDefault from '../assets/og-portrait.jpg';
|
|
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 THE FOURTH IS NOT DECORATIVE.
|
|
*
|
|
* docs/01 §`/` item 2 says "Three slots". §4 Offerings is the higher authority
|
|
* on claims and attaches a PAIRED-DISCLOSURE CONDITION to offering arbitration
|
|
* at all: the site "makes the first while stating the second plainly", and
|
|
* "neither half may be dropped". This page says *arbitrator* in its second
|
|
* sentence, so the stage of the arc belongs on this page rather than only in
|
|
* the site footer. docs/03 authorises the fourth slot; docs/03 has been amended
|
|
* to record that on `/` it is required. Q.Arb reads as commenced — never as
|
|
* held or nearing completion (§4 Forbidden).
|
|
*/
|
|
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);
|
|
---
|
|
|
|
<BaseLayout
|
|
title={`${SITE.name} · ${SITE.tagline}`}
|
|
description="Commercial mediation and 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>
|
|
|
|
{
|
|
/* TWO CORRECTIONS FROM `claims-auditor`, 2026-08-27, both about
|
|
implication rather than assertion — which is where D13 says the risk
|
|
actually lives.
|
|
|
|
(a) This read "I mediate and arbitrate commercial disputes". §4
|
|
verifies that he ACCEPTS arbitral appointments, and separately
|
|
verifies "multiple completed sole mediations" — there is no
|
|
counterpart row for a completed arbitration. Present-indicative
|
|
"arbitrate" beside "mediate" invites the reader to supply a track
|
|
record for both. The offering-shaped form is what the register
|
|
actually holds, and it is already the form the arbitration card
|
|
below uses.
|
|
|
|
(b) "facts most neutrals take on faith" is a COMPARATIVE assertion
|
|
about a population of third parties, and **Q41(b) CLOSED 2026-08-27:
|
|
it is not restored, and the reason is not only compliance.** Pouya:
|
|
*"That is an unverifiable empirical claim about other practitioners,
|
|
and comparative claims must be factual and verifiable. It is also
|
|
weaker copy: assert his capability, not the field's incapability."*
|
|
It is struck from `docs/03`'s core positioning statement too — the
|
|
approved-copy defence is gone, because the approved copy changed.
|
|
|
|
His replacement wording is used verbatim: *"built for disputes that
|
|
turn on the contract, the code, and the engineering documents"*. The
|
|
interim ("the documents rather than the pleadings") is also gone; it
|
|
said nothing about other neutrals but it still worked by contrast.
|
|
|
|
ON THE ECHO OF THE HEADLINE, because it is deliberate and one edit
|
|
from being reversed if he reads it as a stumble. The `<h1>` ends
|
|
"the contract, the code, and the room"; this sentence re-runs the
|
|
triad and swaps the third term for "the engineering documents". Two
|
|
of three words repeat forty words apart. Read as a rhyme it does the
|
|
work of the whole positioning statement in one move; read as an
|
|
oversight it looks careless. Judged the first, flagged as the
|
|
second. */
|
|
}
|
|
<p class="hero-lede">
|
|
I mediate commercial disputes 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>
|
|
{
|
|
/* THE FEE CLAIM IS GONE, and it was wrong on two counts —
|
|
`claims-auditor`, 2026-08-27, checked against `docs/07-fees.md`.
|
|
"at one published rate" reads as ONE PRICE for half and full day;
|
|
D14's card sets TWO ($2,000 half, $4,000 full). And "preparation
|
|
time included" was unqualified where `docs/07` bundles a CAPPED
|
|
allowance and says in terms: *"must be stated on the page —
|
|
'including 2 hours of preparation' ... Do not quietly fold it into
|
|
the hours figure. At these rates, saying preparation is included
|
|
is the selling point, not a footnote."*
|
|
|
|
A home card is the wrong place to state it properly, and stating
|
|
it improperly misdescribes money. `/mediation/` (step 4) and
|
|
`/fees/` (step 9) carry the card. "Published" was also
|
|
forward-looking: `/fees/` does not exist yet. */
|
|
}
|
|
<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>
|
|
{
|
|
/* docs/03's model sentence, and BOTH halves are required: §4
|
|
Offerings — "neither half may be dropped". Pouya's instruction:
|
|
being open about the stage is the differentiator, so it is not
|
|
hedged into vagueness and not dropped. Commercial matters only
|
|
(Q39): family arbitration is not offered. */
|
|
}
|
|
<p class="feature-body">
|
|
I accept sole, party-appointed and co-arbitration appointments in
|
|
commercial matters. The Q.Arb pathway commenced in August 2026;
|
|
C.Med-Arb is the endpoint.
|
|
</p>
|
|
<span class="feature-arrow" aria-hidden="true">→</span>
|
|
</article>
|
|
</div>
|
|
|
|
{
|
|
/* docs/01 §`/` item 4: "Med-Arb named here as the long-term arc,
|
|
linking to /med-arb/." It has had its own §4 Offerings row since
|
|
2026-08-27 (Q35), so it is named as offered rather than only as an
|
|
aspiration — but the arc is what docs/01 asks this page to carry. */
|
|
}
|
|
<p class="pair-note">
|
|
<strong>Med-Arb</strong> combines the two: one neutral mediates, then arbitrates
|
|
whatever has not settled. I accept those appointments, and C.Med-Arb is the
|
|
designation endpoint. 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: NOT BUILT AT STEP 2. See the header note. -- */}
|
|
|
|
{/* ---- 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>
|
|
/* --- 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;
|
|
}
|
|
.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);
|
|
}
|
|
.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>
|