Build the PLEA website for this project's Sepolia test run 9 (launch #2234): a static Vite + React + TypeScript + viem site, hash routing, relative base. Change no contracts. The README and ABIs in this repository are the source of truth; read every value (fees, windows, caps, season length, token symbol) from chain. CONTRACTS (Sepolia, chain 11155111; events from block 11948819): PLEA 0xb9efaa7b64232d31db23ee6902972a58f492ee8b, CabalGate 0x78c77e3ddbc5a45daca98cafa2b2896da3104e4a, Seasons 0x8dd85a978dbd7b2cff9247c7e3ad49cf8920f0c6, PleaDistributor 0x2fdf098680cb4b515a8e43f09b3bec5493656a46, PleaLaunch 0x3621606cddc23e69d1115126522b53fbf788761e, Letterbox 0xbc335Ae3DC12D4bF78f8E44A03f4fC62e3afd1d3, Tribunal 0xCD6AAc81882A6Ae51041E8094bCa0feaBd3fC566, PleaHook 0x07B4698506cF15b9a91805Aa90010a47a04228cC. PLEA trades against imdTEST 0x56bcf2a5F47710649A4091219Ef0b56e2d1B58BF (18 decimals, also the oracle fee token): label amounts with its symbol. Trades go through PoolSwapTest 0x9b6b46e2c869aa39918db7f52f5557fe577b6eee (approve it first) on the PoolManager 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543. History: the contracts' events from public Sepolia RPCs, at most 10,000 blocks per request, with fallback; oracle requests from api.imd.fun. DESIGN SYSTEM (exact): github.com/khaed1/claude at commit 91b34fe4b8e5, folder plea/site-design/. Use tokens.css and components.css as they are. components.html is the markup of every component in every state; pages/*.html are the mockups of every page (layout, copy, components); the dashed "Design reference · other states" band at the bottom of each mockup lists that page's other states: build them all, but it is not site content. PAGE-KIT.md holds the rules. sample-data.json is mockup data only. No other colours, fonts or effects. Never IMD's diamond logo; the footer says the site is not an official IMD page. TEST RUN: the status strip starts "Sepolia test run 9 · test tokens, no value". Where a mockup says Ethereum or etherscan.io, use Sepolia and sepolia.etherscan.io; "[after launch]" becomes the addresses above. Letters: drop the Etherscan message-page card (Sepolia has none); keep the wallet's raw data and Write Contract letter(text). Until PleaHook.seeded is true, Buy, Plead and Letters say trading is not open yet, with a countdown to PleaLaunch.START (anyone may seed from then). While PleaDistributor's merkleRoot is zero, Claim shows its empty state. PAGES (exactly as in pages/*.html): Home and how it works; Buy; Plead (the "Sell automatically if approved" box is on by default; the mockup shows it off because its sample plea was sold by hand); a plea's status page at #/p/; Letters (the Etherscan how-to); Objections; Seasons (the leaderboard with the seat multiplier); the Wall; Claim (with delegate.xyz); Profile; the X share card per plea. MUST HAVE: - A colour per verdict state with the state in words on the stamp: APPROVED, DENIED, PENDING (striped, with its countdown), NO VERDICT, LAPSED (dashed), SOLD, OVERTURNED, UPHELD, OBJECTION, SUSTAINED, OVERRULED. Under it "N vs need N" (or the time left), never an amount; an objection is a second small stamp. The stamp lands once when a verdict arrives live; nothing else animates; honour reduced motion. - Every text box (plea, appeal, objection, letter, name) uses text-check.js: a live UTF-8 byte counter "193 / 280 bytes" and the contract's checks, with submit disabled until they pass. Plea text is always rendered as text, never HTML. - Auto-sell box: checked sends Letterbox.plead(amount, text) / appealAuto(originalId, text) (escrow, fees paid in PLEA: the 0.50 oracle fee + the 0.25 tip for whoever delivers it within 7 minutes; 0.35 of a delivered sale to the season prize pot); unchecked is the manual flow (approve the 0.50 fee, submitSell, then executeSell(minOut) inside 7 minutes). Show both cost breakdowns. - Before paying, show the fact score and need = 71 - fact; above 45, disable submit and say the plea cannot pass (CannotPass). Show cooldowns. - Seasons: points with Seasons.pointsFor / holdsSeat (x1.2 rounded down for seat holders), shares from Seasons.shareBps, the 10,000,000 PLEA bonus in the first six seasons, points "held" until the objection window ends, then a Record my points button (nothing records them by itself), close season after CLOSE_GRACE. - Claim: PleaDistributor.claim(index, account, amount, proof) for any typed address from the claims.json file, with isClaimed; delegate.xyz v2 (0x00000000000000447e69651d841bD8D104Bed493): list getIncomingDelegations(connected) and claim for each vault; the PLEA lands on the vault. - Profile: Letterbox.setName / nameOf, "This is your current name" for the wallet's own name; the wallet's ERC-6909 imdTEST claims on the PoolManager (the opening buys may pay that way). - Share card: a 1200 x 675 PNG per plea, in every state, drawn in the browser from the share-card component; "Share on X" (twitter.com/intent/tweet with text and the #/p/ link), "Download image", "Copy image". - After the Cabal dies (CabalGate.DEADMAN without a verdict): the banner on every page, Plead and Objections closed, Sell on Buy. - Cashback earned = the hook's CashbackStacked + CashbackPaidPlain + CashbackPaidClaims events for the wallet. Every buy passes abi.encode(buyer) as hookData. Every write sends an explicit gas limit (estimate x 1.3). Decode the contracts' custom errors into the plain words the mockups use. WALLET: one Connect button top right on every page; everything reads without a wallet; actions say "Connect in the top bar to ...". Switch to Sepolia when needed. Per-button states: wallet, busy, done with a sepolia.etherscan.io link, error with the fix (toasts as in components.html). BUILD: bundle Archivo (variable, wdth 62-125) and IBM Plex Mono 400/500/600 as woff2 files in the build, no font CDN (SIL OFL), and Phosphor Bold icons (MIT). Light theme by default, dark via the toggle, stored in localStorage "plea-theme" and set before first paint. QA before you finish: WCAG 2.1 AA (body text 4.5:1, large 3:1, keyboard focus always visible, targets 44 px, colour never the only signal); every page and every state at 320, 360, 768 and 1280 px, light and dark, in a real browser against the live contracts: no horizontal scroll, no console errors, no axe violations (tools/check.mjs in the design folder does this for a page); compare each with its mockup and fix the differences, then one more round. Refuse: gradients other than the pending stripes, blur, glass, glows, soft shadows, new colours, emoji as icons, modals where a page works, placeholder text.