feat: build step 2 — the home page; close Q35, Q37, Q39; Q39's answer corrected the register
Pouya's rulings and the page are one commit, not two, because they are not separable: Q37 changed the credential label the page renders, Q39 scoped the arbitration copy, and Q35 gave Med-Arb the row that lets the footer link stand. Splitting them would produce a commit where the register says one thing and the page says another. RULINGS Q39 — ANSWERED, and my assertion was FALSE as a universal. Pouya checked rather than defended. Family arbitration in Ontario is gated: prescribed training, 14h on screening for domestic violence and power imbalances, 30h of Ontario family law for arbitrators outside the bar, 10h ongoing per two years. claims-auditor produced that counter-example from inside the repo on 2026-08-26 and it was right. The source is now IN the repo per R14 — docs/reference/ontario-family-arbitration-training.md, retrieved with its provenance — and §4 states which half it establishes: the family gate directly, the commercial half only by absence. So "commercial arbitration in Ontario requires no licence and no designation" is recorded as Pouya's stated position, unstamped. What disposes of the question is the scope exclusion: he does not do family arbitration, so it is NOT OFFERED and the gate never bears on the practice. The false universal survived in three more live places, found by grep, not recall: §4's NOT-NEGOTIABLE boundary bullet (the boundary paragraph breaching its own boundary), §9's Q33 closure row, and a comment in SiteHeader.astro. Q35 — ANSWERED, all four items, and the answer supplied a gate that was missing. Med-Arb gets an Offerings row and stays in the footer sitemap. The six subject-matter labels pass test 1. §4 gains "Subject-matter areas — the publication gate": competence to accept an appointment, AND a page that frames it as positioning without claiming history. Nothing in the nav or footer is unrowed any more. Q37 — ANSWERED. "Law and engineering" becomes "Legal training and engineering practice". A degree is not a practice; the parallel was doing the implying. Q38/R13 — the committed SVG does not close it; the walk-back went too far. It renders faithfully BECAUSE it is the raster. R13 stays open. R1 — surfaced and acknowledged; the interim licensure framing is now carried by a shipped page rather than a spec, which raises the stakes. BUILD STEP 2 Seven of docs/01's eight home sections, six new components, zero JavaScript. Section 7 (Latest insights) ships at step 7 with the collection it lists — recorded in docs/01 and in the page, not just here. Four credential slots, not three: §4's paired-disclosure condition requires the Q.Arb stage on any page that offers arbitration. No booking link (R6). The masthead tagline is suppressed on / (it duplicated the hero eyebrow). The step-1 proof sheet is deleted, and five live references to it were found by grep. WHAT THE REVIEWS FOUND — 24 findings across the two passes claims-auditor FAILED it on ten, every one implication or scope rather than fabrication, which is where D13 says the risk lives. The four that mattered: "I mediate and arbitrate" asserted a track record §4 does not hold for arbitration; the JSON-LD asserted arbitration twice and stated the stage nowhere (a crawler-only claim is still a claim); "at one published rate with preparation time included" misdescribed money against docs/07's two day rates and capped prep allowance; and "Law and engineering are not blended here" was Q37's struck parallel relocated into prose one day after Pouya struck it. It also found the Canadian Tax Foundation missing from §9 Q8 — a stale second copy of a fact that would have bitten at step 3. adversarial-reviewer found fourteen, three blocking. The first was class="section-head" on <SectionHeading> never matching — the parent-cannot- style-a-child defect for the FOURTH time, written into a diff where I had just added fresh warnings about it to two other components. Measured: 0px margin, 0px gap, headings over the card edges, with astro check and eslint both clean. I had looked at a screenshot of that section and passed over it. Fixed with a page-owned wrapper (48px, measured) and the prop is deleted from six components so it is now a build error. Also: the credential row was never "two up on a phone" and its comment said it was; PROCESS was hardcoded in the page against the reason written in site.ts; 83px of residual overflow at a 200% default font size, now 3px. Seven more I found myself first, including <Picture widths> declaring the untouched 1600px master as the <img src> fallback (254,626 B for a 476px slot, and the build log said "before: 349kB" either way), and a prop named `as` silently turning off type-checking for a whole component. VERIFICATION — run, not asserted. Full figures in AGENTS.md entry (w). npx tsc 6.0.3 · check 0/0/0 · lint clean · build clean · audit 0 1 <script> and it is JSON-LD; non-JSON-LD scripts 0; no JS bundle; identical page with script execution disabled (444 nodes, 6,578 chars) Phase 5 minifier check: no `animation` shorthand beside animation-timeline overflow 0 at 14 widths, AND every mark measured at 1.5654-1.5657 vs 1.5657 one h1, no heading skips, focus order == DOM order across 44 focusables 31 painted contrast pairs at 3 widths, 0 failures 72/72 hit-test points across 8 cards resolve to the card's link print 0 hidden, reduced-motion 0 hidden Lighthouse NOT RUN — tool unavailable until step 7 (R11) HTML validator NOT RUN, and 4 of 12 srcsets carry a duplicate 1x descriptor Opened for Pouya: Q40 (one OG image for nineteen pages), Q41 (does Q37 reach prose; may the comparative be restored; what LAT pre-hearing mediation means), Q42 (the four "also offered" processes have no row), Q43 (the process timings are published commitments with no row). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0148NztQskLKKApP5SzAA78e
This commit is contained in:
co-authored by
Claude Opus 5
parent
8a2f513577
commit
165d259f5c
@@ -0,0 +1,780 @@
|
||||
---
|
||||
/**
|
||||
* `/` — 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 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 {
|
||||
CONTACT,
|
||||
CREDENTIAL_ROW,
|
||||
CREDENTIAL_ROW_ARB,
|
||||
PRACTICE_AREAS,
|
||||
PORTRAIT,
|
||||
PROCESS,
|
||||
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} · Mediation & Arbitration · Toronto`}
|
||||
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}
|
||||
>
|
||||
{/* ---- 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. It comes from `docs/03`'s core
|
||||
positioning statement, so it is approved copy — but `docs/03`'s own
|
||||
compliance checklist requires "any comparative claim is factual and
|
||||
verifiable", and §4 has no row for it. Replaced with a
|
||||
non-comparative distinction that is checkable and says the same
|
||||
thing: documents rather than pleadings. Q41 asks Pouya whether the
|
||||
original may be restored; changing approved copy back is his call,
|
||||
and shipping an unverifiable comparative in the meantime is not. */
|
||||
}
|
||||
<p class="hero-lede">
|
||||
I mediate commercial disputes from Toronto, and I accept arbitration
|
||||
appointments. I also practise as a machine-learning and infrastructure
|
||||
engineer, so the matters I take are the ones that turn on the
|
||||
documents rather than the pleadings: 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 largest
|
||||
real render is ~476px (content 1280 - 96 gutter - 64 gap, x 0.425),
|
||||
so 960 is the 2x ceiling.
|
||||
|
||||
`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 five widths [measured 2026-08-27].
|
||||
|
||||
eager + fetchpriority=high because this is the LCP candidate on the
|
||||
page docs/04 budgets hardest. */
|
||||
}
|
||||
<div class="hero-portrait">
|
||||
<Picture
|
||||
src={portrait}
|
||||
width={960}
|
||||
height={960}
|
||||
widths={[380, 480, 640, 760, 960]}
|
||||
sizes="(min-width: 80rem) 476px, (min-width: 66rem) 42vw, (min-width: 40rem) 60vw, 92vw"
|
||||
formats={['avif', 'webp']}
|
||||
fallbackFormat="jpeg"
|
||||
alt={PORTRAIT.alt}
|
||||
loading="eager"
|
||||
decoding="sync"
|
||||
fetchpriority="high"
|
||||
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. Whether Q37's reasoning formally extends to prose is
|
||||
Pouya's to say — Q41. */
|
||||
}
|
||||
<div class="prose approach-prose">
|
||||
<p>
|
||||
Any dispute I take has two readings: what the documents say, and
|
||||
what the engineering says. They are not blended here. They run at
|
||||
the same time.
|
||||
</p>
|
||||
<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. The second half of each pair usually arrives as
|
||||
a separate expert report.
|
||||
</p>
|
||||
<p>
|
||||
I do both readings myself. That is the whole of it, and it is why
|
||||
the technical half is not something a party has to commission and
|
||||
wait for.
|
||||
</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. The only candidates — early neutral
|
||||
evaluation, settlement counsel, dispute-system design, pre-dispute
|
||||
technical advisory (`docs/01` §`/practice/`) — have NO row in §4
|
||||
"Offered now", and §4 states: "An offering may not be published until
|
||||
it has a row." `claims-auditor` called this the Med-Arb-in-the-footer
|
||||
defect in a new place, and it is exactly that. Reworded to what is
|
||||
rowed; the four candidates need rows before `/practice/` can offer
|
||||
them at step 5. */
|
||||
}
|
||||
<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. Counsel want the shape of the downside before they commit a client's day."
|
||||
>
|
||||
<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>
|
||||
|
||||
<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 ---------------------------------------------- */}
|
||||
{
|
||||
/* docs/01 item 8 is "Intake CTA and booking link". THERE IS NO BOOKING LINK
|
||||
and that is not an omission: booking is parked (AGENTS.md R6) and
|
||||
CONTACT.bookingUrl is null, so /contact/ ships the intake form with a
|
||||
reserved slot for an embed. Stated rather than silently dropped. */
|
||||
}
|
||||
<section class="section section-accent contact-band">
|
||||
<div class="wrap contact-inner">
|
||||
<div>
|
||||
<Eyebrow dot>Next step</Eyebrow>
|
||||
<h2 class="display contact-h">Start with a call.</h2>
|
||||
<p class="contact-body">
|
||||
Tell me the shape of the matter and who is involved, and I will tell
|
||||
you whether I am the right neutral for it. {CONTACT.responseTime}
|
||||
</p>
|
||||
</div>
|
||||
<div class="contact-action">
|
||||
<Button href="/contact/" variant="gold"
|
||||
>Request a consultation →</Button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</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 <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 {
|
||||
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;
|
||||
}
|
||||
.approach-prose {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-5);
|
||||
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 ---------------------------------------------- */
|
||||
|
||||
.section-head {
|
||||
margin-block-end: var(--space-7);
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
/* --- 8. Contact band ----------------------------------------------- */
|
||||
|
||||
.contact-inner {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-6);
|
||||
}
|
||||
.contact-h {
|
||||
margin-block: var(--space-4) var(--space-4);
|
||||
font-size: var(--text-4xl);
|
||||
}
|
||||
.contact-body {
|
||||
max-inline-size: 52ch;
|
||||
}
|
||||
.contact-action {
|
||||
/* `flex: none` is `0 0 auto`, so the button could not shrink below its
|
||||
max-content width — 425px at a 200% default font size, in a 342px
|
||||
container. `0 1 auto` plus `min-inline-size: 0` lets it. It still never
|
||||
shrinks at a normal font size, because there is room. */
|
||||
flex: 0 1 auto;
|
||||
min-inline-size: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user