diff --git a/VIDEO_TRANSCRIPT.md b/VIDEO_TRANSCRIPT.md
index e5e407b..b098a18 100644
--- a/VIDEO_TRANSCRIPT.md
+++ b/VIDEO_TRANSCRIPT.md
@@ -1,118 +1,126 @@
-# Video transcript — the 5:00 pitch (recorded at submission)
+# Video transcript — the 5:00 pitch (plain-English version)
-Shot list assumes the live demo (with the deterministic seed). Timestamps are
-beats, not hard cuts. One take, screen + voice, no intro animation — the first
-10 seconds must carry the thesis.
+Same beats as before, but the spoken lines are plain and simple — short
+sentences, no jargon without a quick explanation. Read it like you talk.
+Timestamps are beats, not hard cuts. One take, screen + voice. The first
+10 seconds carry the whole idea.
-## 0:00–0:20 — the thesis
+## 0:00–0:20 — the big idea
**On screen:** the landing page, hero stamp.
-> "Razorpay's homepage already sells the future: AI agents, shopping in your
-> app, paying on their own. Everyone is building the buyer side of that
-> counter. Somebody has to build the other side — the part a payments company
-> needs before it lets an agent spend. Customs is both sides, in one repo."
+> "AI agents are starting to shop and pay on their own. Almost everyone is
+> building the buying side. Almost nobody is building the selling side —
+> the part a payments company needs before it lets a robot spend money.
+> We built both sides, in one project. It's called Customs."
-## 0:20–1:05 — the buyer side (the brief's named example, live)
+## 0:20–1:05 — the buyer side (live)
**On screen:** Playground. Type "headphones under 5000".
-> "Here's the agent. Every step it takes is visible — the search, the tool
-> calls, the wire format, and the price, recomputed server-side. I'll take the
-> earbuds and check out."
+> "Here is the buying agent. I type: 'headphones under 5000.' Watch every
+> step it takes — the search, the tools it calls, the messages it sends,
+> and the price. The website re-checks the price on its own server, so
+> nobody can trick it with a fake number. Now I say: 'checkout.'"
Type "checkout".
-> "The desk signs a mandate: an amount cap, the item list, an expiry, and my
-> trust tier — Ed25519 over canonical JSON. As the buyer's principal, I approve
-> the envelope, and only then does the agent bind and pay."
+> "Before it pays, the agent gets a permission slip. It says the most it
+> can spend, what it can buy, when it expires, and how much this user is
+> trusted. It is signed with a strong digital signature. I approve it.
+> Only then can the agent pay."
Approve → gate checklist → capture.
-> "The gate re-verified everything at bind time — signature, tier bounds,
-> live prices, quantities. Deterministic code, zero tokens: the LLM never
-> decides money. That's the line the brief draws, and it's the line we hold."
+> "And just before money moves, plain code — not AI — checks everything
+> again: the signature, the spending limit, the prices, the counts. The
+> AI can talk. Only this code can move money. That is the rule, and we
+> never break it."
-## 1:05–1:50 — the refusal beat (failure, handled)
+## 1:05–1:50 — the attack (failure, handled)
**On screen:** red-team panel → "Overspend the tier" → BLOCKED.
-> "Now the interesting part. I attack my own product. An unverified session
-> tries a ₹2,199 mouse against a ₹500 tier — refused, with a reason code, and
-> the attempt is hash-chained into the audit log. Twelve authored attacks,
-> each with its expected verdict. Run `make fuzz` — twelve out of twelve,
-> every time. Not a demo: CI."
+> "Now the best part. I attack my own product. A fake session tries to
+> buy a 2,199-rupee mouse with a 500-rupee limit. Blocked — with a short
+> code that says why. And the attempt is saved in a log that cannot be
+> secretly edited. We wrote twelve attacks like this. All twelve are
+> blocked, every time we test. Not just a demo — it runs in our build
+> check."
**On screen:** "Tamper the signature" → BLOCKED · SIGNATURE_INVALID.
-> "Tamper the mandate after signing — the signature check kills it. And this
-> ledger is tamper-evident: `make audit` flips one historical byte and the
-> chain breaks exactly there."
+> "What if someone edits the permission slip after it is signed? The
+> signature catches it. And if anyone changes one old entry in the log,
+> the chain breaks at that exact spot. Nothing can be changed in
+> secret."
-## 1:50–2:35 — the merchant side (the empty cell)
+## 1:50–2:35 — the merchant side
**On screen:** Control Room. The P&L meter counting up.
-> "Now walk around the counter. The Control Room is what the merchant — and
-> honestly, what Razorpay — needs to see. Agent GMV, revenue at list MDR, the
-> AI serving cost from metered tokens, and the net, live. At one million
-> payments a month this channel nets eight figures — the formula and every
-> assumption ship in the repo, regenerated by command, never hand-written."
+> "Now walk around the counter. This is the merchant's room — what the
+> shop, and honestly what Razorpay, would want to see. How much agents
+> are selling. What it costs to serve them. What is left as profit.
+> Live. At one million payments a month, this channel earns real money
+> — and every number here can be re-made with one command. Nothing was
+> typed in by hand."
**On screen:** approval queue → approve the ₹18,999 hold.
-> "Orders over ten thousand rupees hold for a human, any tier. I approve — it
-> captures, and it's replayable span-by-span. This is the brief's 'one
-> failure handled gracefully', except it's twelve, and they're in CI."
+> "One more rule. Any order above ten thousand rupees waits for a human.
+> I approve this one — it completes, step by step, right in front of
+> you."
-## 2:35–3:20 — the protocol matrix (why this generalizes)
+## 2:35–3:20 — works with any agent protocol
**On screen:** adapter switcher → MCP-style → wire chip expands.
-> "The same gate behind three protocols — direct, MCP-shaped, ACP-shaped.
-> Same tool implementations, same verdicts, different wire overhead — and the
-> ablation measures exactly that: verdict parity everywhere, byte cost
-> published. When ACP and friends settle, the adapter is a transport swap,
-> not a rewrite."
+> "Agent systems speak different languages — direct calls, MCP, ACP. Our
+> gate does not care. The same checks sit behind all three. We measured
+> all three: same decisions, different message sizes — and we published
+> the byte cost. If a new standard wins, we swap the transport. We do
+> not rewrite the product."
-## 3:20–4:05 — the proof layer (built for machine judges)
+## 3:20–4:05 — built to be checked by a machine
**On screen:** terminal — `make triage` scrolling.
-> "Fifteen hundred repos will be submitted. Triage will be machine-assisted,
-> so this repo is built to be judged by a machine: JUDGE.md maps every claim
-> to a file and a regenerate command. `make verify` is the same check CI runs
-> — zero dependencies, plain node. No number in this repo was typed by a
-> human, no link ships until it answers 200, and the engineering log records
-> every incident, each one turned into a test."
+> "Thousands of projects will be judged. Many will be checked by a
+> machine first. So we built this repo to be checked by a machine. One
+> file maps every claim to a file and a command. One command — `make
+> verify` — checks the whole repo with plain node, no setup. No number
+> was typed by a human. No link ships unless it works. And every bug we
+> ever hit became a test — forever."
-## 4:05–4:45 — honesty slide (scope, limits, next)
+## 4:05–4:45 — what we have NOT done (honesty slide)
**On screen:** the honest scope ledger, one card at a time.
-> "What's not done, on purpose, said plainly: the payment rail runs labeled
-> simulation until test keys are attached — the spike that flips it ships
-> ready. x402 is a declared stretch, not a promise. Voice, later. The demo
-> history is a deterministic seed through the real engine — no hand-written
-> ledger lines anywhere. Every limit is labeled in the product, not hidden in
-> a footnote."
+> "Now, what we have not done — said plainly. The payment rail runs in
+> clearly labeled simulation mode until Razorpay test keys are attached.
+> The code to flip it on is ready, and we already tested it against the
+> real API. The demo history comes from a fixed seed through the real
+> engine — no fake rows anywhere. Voice is for later. Every limit is
+> written on the screen, not hidden in a footnote."
## 4:45–5:00 — close
**On screen:** the manifest receipt, stamp lands.
-> "The buyer side everyone is building. The desk nobody has built. Both
-> sides, bounded, metered, replayable — and provable to a machine in sixty
+> "Everyone is building the buyer. Nobody built the desk. Both sides —
+> limited, measured, replayable — and provable to a machine in sixty
> seconds. Customs. Thank you."
---
-**Recording notes**
+**Recording notes (simple)**
-- Use the live seeded state (reset demo before recording) — numbers match the
- committed results.
-- Cursor + typing must be visible; the step chips are the transparency story.
-- Keep the terminal font large enough to read `make triage` output at 720p.
-- The refusal beat (1:05) is the emotional center — don't rush it.
-- Total: 5:00 hard limit. If over, cut the protocol matrix interior, never the
- refusal or the honesty slide.
+- Reset the demo before recording, so the numbers on screen match the
+ numbers in the repo.
+- Keep the cursor and your typing visible — watching the steps happen is
+ the whole trust story.
+- Make the terminal text big enough to read at 720p.
+- Do not rush the attack part at 1:05 — it is the heart of the video.
+- Hard limit: 5:00. If you run long, cut inside the protocol section —
+ never the attack, never the honesty slide.
diff --git a/src/app/globals.css b/src/app/globals.css
index 31856fe..88276ff 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -207,6 +207,21 @@ html.light {
html:not(.light) ::-webkit-calendar-picker-indicator {
filter: invert(1);
}
+ /* the paper grain — the felt world's fiber at a whisper over every
+ surface. One fixed layer, one raster (a 180px fractal-noise tile,
+ desaturated), soft-light blend so it reads on both desks without
+ tinting either. pointer-events: none — it is texture, never a
+ surface, and it never animates. */
+ body::before {
+ content: "";
+ position: fixed;
+ inset: 0;
+ z-index: 80;
+ pointer-events: none;
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.6' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
+ mix-blend-mode: soft-light;
+ opacity: 0.28;
+ }
}
@layer utilities {
@@ -657,7 +672,10 @@ html.light {
.hb-glint {
animation: hb-glint 9.5s ease-in-out infinite;
}
- .hero-bot:hover .hb-eye {
+ /* the hover eye-fill is a day-desk tell — on the night desk the
+ cleared sage reads as the green the desk avoids; dark hover lifts
+ the aurora instead (below) and leaves the eyes alone */
+ html.light .hero-bot:hover .hb-eye {
fill: var(--cleared);
}
@@ -667,18 +685,61 @@ html.light {
and a small warm-up on hover. No CSS transform ever touches this
element — that is the containment contract. */
.hb-aurora {
- opacity: 0.55;
+ /* the night ledger keeps the bot from going green-dominant — but
+ not colorless: the aurora sits at a third strength, a balanced
+ pastel sweep instead of the day desk's saturated wash */
+ opacity: 0.26;
transition: opacity 0.6s ease;
}
html.light .hb-aurora {
opacity: 0.38;
}
.hero-bot:hover .hb-aurora {
- opacity: 0.65;
+ opacity: 0.34;
}
html.light .hero-bot:hover .hb-aurora {
opacity: 0.46;
}
+ /* the felt mat's dye washes follow the same law: all four colors
+ stay present on the night desk — SCREEN-blended so the pastels
+ glow on the dark felt instead of drowning in it, the same trick
+ light does with opacity alone. Colorful, never the green field
+ the day desk wears. The pompoms stay loud. */
+ .hfx-wash {
+ transition: opacity 0.6s ease;
+ }
+ html:not(.light) .hfx-wash {
+ opacity: 0.5;
+ mix-blend-mode: screen;
+ }
+
+ /* the egg vs the mat, night desk: the day body gradient is dark ink
+ on a light field — over the near-black felt it has no contrast, so
+ the mascot dissolves into the fabric. The dark variant lifts the
+ fill and firms the outline, so the orb reads IN FRONT of the mat. */
+ html:not(.light) .hb-body-path {
+ fill: url(#hb-body-dark);
+ stroke-opacity: 0.45;
+ }
+
+ /* the idle yawn — after thirty quiet seconds the desk yawns: the
+ eyes close to happy slits (the whole eye unit squishes, so the
+ glints can never float free), and the smile stretches wide on a
+ slower ease — eyes snap, mouth spreads. Blink is suspended for
+ the yawn; on wake it resumes and the eyes pop open. Transitions
+ and one transform: nothing structural moves. */
+ .hb-smile {
+ transform-box: fill-box;
+ transform-origin: center;
+ transition: transform 0.55s cubic-bezier(0.45, 0, 0.25, 1);
+ }
+ .hero-bot.yawning .hb-smile {
+ transform: scaleX(1.55) translateY(1.6px);
+ }
+ .hero-bot.yawning .hb-eye-unit {
+ animation: none;
+ transform: scaleY(0.3);
+ }
/* the blush warms a touch when visited — the smallest tell of a
happy bot; a paint-only transition, nothing structural moves */
.hb-blush {
@@ -766,6 +827,49 @@ html.light {
}
}
+/* ---------------- the wool mat's pompoms ---------------- */
+/* transform/opacity only — the felt's filters are static paint and
+ rasterize once; the yarn just bobs, each ball on its own clock */
+@keyframes hb-pom-bob {
+ 0%,
+ 100% {
+ transform: translateY(0) rotate(var(--tilt, 0deg));
+ }
+ 50% {
+ transform: translateY(-6px) rotate(calc(var(--tilt, 0deg) + 2.5deg));
+ }
+}
+.hb-pom {
+ transform-box: fill-box;
+ transform-origin: center;
+ animation: hb-pom-bob 5.2s ease-in-out infinite;
+}
+.hb-pom2 {
+ animation-duration: 6.1s;
+ animation-delay: -1.8s;
+}
+.hb-pom3 {
+ animation-duration: 5.7s;
+ animation-delay: -3.1s;
+}
+
+/* the desk agent's thinking yarn — a small ball tilting side to side
+ while it works; transform-only, killed for reduced motion */
+@keyframes agent-yarn-wobble {
+ 0%,
+ 100% {
+ transform: rotate(-9deg) translateY(0);
+ }
+ 50% {
+ transform: rotate(9deg) translateY(-1.5px);
+ }
+}
+.agent-yarn {
+ transform-box: fill-box;
+ transform-origin: center;
+ animation: agent-yarn-wobble 0.85s ease-in-out infinite;
+}
+
/* ---------------- the customs bot's moves ---------------- */
@keyframes hb-float {
from {
@@ -863,7 +967,9 @@ html.light {
.hb-eye-unit,
.hb-eyes,
.hb-glint,
- .hb-shadow {
+ .hb-shadow,
+ .hb-pom,
+ .agent-yarn {
animation: none !important;
}
.pulse-dot::after {
diff --git a/src/components/customs/bits.tsx b/src/components/customs/bits.tsx
index 1d8b68b..420e5c0 100644
--- a/src/components/customs/bits.tsx
+++ b/src/components/customs/bits.tsx
@@ -114,7 +114,7 @@ export function LogoMark({ size = 28, className }: { size?: number; className?:
export type StampKind = "cleared" | "refused" | "held" | "ink" | "sim";
-const STAMP_STYLES: Record
the desk is working…
} + {busy && ( ++ {/* the thinking tell: a small yarn ball unwinding while + the desk works — butter yarn, the held state's warmth */} + + the desk is working… +
+ )} {/* the composer: a sunken well, one arrow — nothing else */} diff --git a/src/components/customs/footer.tsx b/src/components/customs/footer.tsx index 03f9a17..c5f9c9c 100644 --- a/src/components/customs/footer.tsx +++ b/src/components/customs/footer.tsx @@ -5,15 +5,124 @@ * a left column (mark, one copyright line, the theme toggle and the * source pill at its foot) and quiet link columns on the right — * 13px links at half opacity that only reach full ink on approach. - * No paragraphs, no stamps, no second bar. A footer says where things - * are; it does not repeat the site. + * A footer says where things are; it does not repeat the site. + * + * The one decoration: the wool world runs under the footer — a thin + * felt band whose top edge is hand-cut (the hero mat's same recipe, + * condensed to a strip), two pompoms tucked into its corners, and a + * quiet colophon row of verdict stamps that thump in once, in view. + * All of it static paint + transform/opacity, all of it token-colored. */ +import type { CSSProperties } from "react"; import { LogoMark } from "./bits"; import { ThemeToggle } from "./theme"; import type { View } from "./shell"; const GITHUB = "https://github.com/srivtx/customs"; +/* the hand-cut top edge of the felt band — one irregular line, drawn + once, stretched by the layout (preserveAspectRatio none). The wobble + amplitude already varies, so the stretch reads as more hand. */ +const BAND_EDGE = + "M 0 14 C 60 8 120 16 190 12 C 260 8 320 18 400 13 C 470 9 540 17 620 12 C 700 7 770 18 850 13 C 920 9 990 16 1070 11 C 1150 7 1220 18 1300 13 C 1370 9 1450 17 1520 12 C 1560 9 1580 13 1600 11"; + +/** + * YarnPom — a mini yarn ball tucked on the band's edge. Same + * construction as the hero pompoms at pocket scale: shaded sphere, + * wound arcs, one fluff displacement, bobbing on .hb-pom. + */ +function YarnPom({ + id, + light, + base, + dark, + className, + tilt, +}: { + id: string; + light: string; + base: string; + dark: string; + className?: string; + tilt: number; +}) { + return ( + + ); +} + +/** + * FeltBand — the footer's rug edge. A 1600×30 strip: hand-cut top + * edge, pastel washes, grain, running stitch. The pompoms live outside + * the stretched svg so they stay round. + */ +function FeltBand() { + return ( + + ); +} + const SITE: { label: string; view: View }[] = [ { label: "Overview", view: "home" }, { label: "Why it exists", view: "why" }, @@ -37,7 +146,8 @@ const COMMANDS: [string, string][] = [ export function SiteFooter({ onEnter }: { onEnter: (view: View) => void }) { return ( -