/* claim.css — the pre-auth claim funnel (site/claim/).
 *
 * ⭐⭐ PORT LAW — THIS IS A DELIBERATE PORT OF `#perch-setup-view`, NOT A SECOND DESIGN.
 * Source of truth: `app/site/styles.css` (the `#perch-setup-view` block) + `app/site/index.html`'s
 * `#ps-step-search`. A matching note sits at BOTH of those, because the drift is silent otherwise and two
 * claim screens that look different is exactly the report this file was rewritten to answer.
 *
 * ⚠ WHY A PORT AND NOT AN IMPORT: `site/` (adperch.ai) and `app/` (app.adperch.ai) are SEPARATE Netlify
 * deployments with separate stylesheets. Neither can `@import` the other. So the vocabulary is copied, and
 * the copy is declared — a fork nobody wrote down is how the two screens diverged in the first place.
 *
 * ⭐ THE TOKENS ALREADY AGREE, WHICH IS WHAT MAKES THE PORT FAITHFUL RATHER THAN APPROXIMATE. Checked
 * 2026-07-31: `--accent-sage: #7d9471`, `--accent-gold`, `--text-tertiary: #8a7e72`, `--border-subtle` and
 * `--font-serif` are byte-identical in both stylesheets, so this file uses the VARIABLES and inherits any
 * future change. ⚠ The one exception is `--surface`, which `site/styles.css` does NOT define — every use
 * below carries an explicit fallback. Do not "tidy" those fallbacks away.
 *
 * ⚠ Rule #11: earthtone + grayscale. The dark hero band is the app's own "claim console" register carried
 * across — NOT glassmorphism, and not a new direction invented for a marketing page.
 */

.container-narrow { max-width: 42rem; }

/* ⛔ START THE CONTENT HIGHER — THE SEARCH BOX WAS BELOW THE FOLD ON A LAPTOP, and it is the one control
   this page exists for. `.section`'s 4rem/4rem is tuned for marketing pages that open on a headline and
   want air; this page opens on an ACTION, and air above an action is just distance from it.
   ⚠ Scoped to these two sections rather than changed in styles.css — .section is shared by every marketing
   page and this is a claim-funnel judgement, not a site-wide one. Same reasoning as the .hero trim at
   styles.css:346, which solved the identical problem for the homepage. */
.claim-find { padding: 1.5rem 0 var(--space-6); position: relative; }

/* ⛔ THE ROOFTOP TEXTURE AND THE MARGIN PHRASES MOVED TO site/styles.css (2026-09-04) — /funds carries
   both now, and this stylesheet is loaded by /claim ALONE. A second copy is what the port law forbids;
   the rules and their whole reasoning travelled together, unedited. */


.claim-profile { padding: 1.5rem 0 var(--space-6); }

/* ⚠ THE SAME TRIM AS THE OTHER TWO STEPS, AND IT IS NOT COSMETIC. `.section`'s default is 4rem/4rem, tuned
   for marketing pages that open on a headline and want air; this page opens on an ACTION and the file trims
   both other steps to 1.5rem for exactly that reason. Without this the request step would carry noticeably
   more top padding than the step it replaces — a visible jump on a change that is meant to be a swap.
   ⛔ MISSED ON THE FIRST PUSH: scripts/check-claim-form.mjs's sibling class-coverage check printed
   "⛔ unstyled: claim-request-host" and I committed past it. The check was right and the output was read
   past — the same failure as reading a green line and stopping. */
.claim-request-host { padding: 1.5rem 0 var(--space-6); }

/* ⭐⭐ THE WHOLE PAGE FITS ONE VIEWPORT, AND THE CARD SITS IN IT RATHER THAN AT THE TOP OF IT.
   ⛔ THE PROBLEM WAS NEVER THAT ELEMENTS DID NOT FIT — every one of them does. It is that the page had no
   opinion about the space it left over: nav, card, then whatever was left, then a footer floating wherever
   the content happened to end. On a tall laptop that reads as "the page ended" (five separate reports).
   ⭐ A column flex on the page makes the leftover space DELIBERATE instead of residual: `main` grows, the
   footer is pinned to the bottom of the viewport when content is short, and the search card can be centred
   in the space it actually owns.
   ⚠ SCOPED TO THIS PAGE VIA `.claim-page` ON <body> — `.page-share` (styles.css:1272) already does exactly
   this for /share, so the pattern is the site's own rather than invented here. ⛔ Do NOT lift it into
   styles.css: the marketing pages are long-scroll and a min-height flex would do nothing for them but add
   a rule that looks load-bearing.
   ⚠ `100dvh` NOT `100vh` — on mobile Safari `vh` includes the retracting toolbar, so a `100vh` column is
   taller than the visible viewport and pushes the footer below the fold on exactly the device where the
   fold matters most. `dvh` is the whole point of this block on a phone. */
body.claim-page { min-height: 100dvh; display: flex; flex-direction: column; }
body.claim-page > main { flex: 1 0 auto; display: flex; flex-direction: column; justify-content: center; }
body.claim-page > footer.site-footer { flex: 0 0 auto; }

/* ⚠ ONLY WHILE THE PAGE IS SHORT. Once a fund is found the profile is long, and centring a tall section
   inside a flex column can clip its top on a short viewport (the classic centred-overflow trap — there is
   no scroll above the start of a centred flex item). `justify-content` reverts to the top for the profile
   step, which is the state that can exceed the viewport. */
body.claim-page.claim-step-profile > main { justify-content: flex-start; }

@media (max-width: 640px) {
  /* ⛔ Centring is a DESKTOP affordance here. On a phone the card plus the footer already fill the screen,
     and vertical centring only adds a gap above the one control this page exists for. */
  body.claim-page > main { justify-content: flex-start; }
}

/* ⛔⛔ THE FOOTER WAS THE OVERFLOW, NOT THE SPACING. The dvh column made the leftover space deliberate but
   did not make the CONTENT shorter, and the footer is the tallest block on this page: slogan band (~28px
   + 2rem margin) over "Learn more" over the utility links over the copyright — four stacked rows, ~272px
   before the page's own content.
   ✅ TWO COLUMNS: the slogan LEFT, the link stack RIGHT. That is most of the height back in one move,
   because the slogan currently occupies a full band OF ITS OWN above LEARN MORE and now shares a row
   with it. Arithmetic: ~60px of stacked band removed.
   ⭐ IT ALSO READS BETTER, WHICH IS WHY IT IS NOT A HACK: a slogan beside its own site's links is a
   signature; a slogan stacked above them is a headline for a footer, which is not a thing a footer needs.

   ⚠ SCOPE, DECIDED RATHER THAN DRIFTED (2026-08-01): THIS PAGE ONLY, via `body.claim-page`.
   ⛔⛔ THAT LINE ONCE READ `**` + `/claim ONLY` + `**` AND IT CLOSED THIS COMMENT SEVEN LINES EARLY.
   `*` `*` `/` CONTAINS `*` `/`. Everything after it became top-level garbage, Chrome discarded the
   NEXT construct — the @media block below — and the two-column footer never applied. Three reports of
   "still not fixed", six server-side checks that all passed, and a CDN theory, for a markdown bold
   marker written inside a CSS comment.
   ⛔⛔ NEVER WRITE A CLOSE-COMMENT SEQUENCE INSIDE A COMMENT — not even to NAME it, not in backticks, not
   as an example. THIS VERY WARNING CONTAINED ONE ON ITS FIRST WRITING and closed the comment here, seven
   hours after the bug it describes.
   ⚠⚠ THREE TIMES NOW, NOT TWO — RECOUNTED 2026-08-01. This line said "for the second time" and became
   false when the AMENDMENT below broke the same rule again; the count is relative to a set, so growing
   the set falsified it without a word of it changing (CLAUDE.md #24). ⛔ IF IT HAPPENS A FOURTH TIME,
   UPDATE THIS NUMBER IN THE SAME EDIT — and read the fourth occurrence as proof that the warning is not
   the control. Banked: audits/bug-classes.md, THE-RULE-IS-NOT-LEARNABLE-BY-BEING-TOLD.
   ⭐ The token is asterisk-then-slash. It cannot be quoted, escaped or backticked out of meaning something
   to the parser; a comment is not a string, and there is no escape mechanism inside one.
   ⚠ AND A BALANCED BRACE COUNT DOES NOT MEAN A VALID STYLESHEET — the orphaned text carried no braces, so
   every depth check this file was subjected to came back clean while it was broken, twice.
   ⭐ THE INSTRUMENT MUST BE EXTERNAL: `npx csstree-validator`, one line, and it caught it both times.
   A parser you wrote closes on the same token your mistake used, so it agrees with you forever.

   ⚠⚠ AMENDED 2026-08-01 — THE VALIDATOR IS NECESSARY AND NOT SUFFICIENT, AND ITS GAP IS THIS FILE'S
   BUSIEST REGION. It caught both historical breaks because the orphan landed at TOP LEVEL. Injected
   INSIDE the `@media (min-width: 641px)` block below, immediately before a rule, the SAME break returns
   COMPLETELY CLEAN from csstree-validator — witnessed, not reasoned: probe file, orphan before the
   `.footer-home` rule, validator silent, brace count balanced 133/133, and the rule that followed was
   swallowed exactly as Chrome swallowed the @media block.
   ⭐ ONLY THE PARSE-ERROR HOOK SAW IT. Run this ALONGSIDE the validator, never instead of it — they fail
   on different shapes and neither covers the other:

     node -e "const c=require('css-tree'),f=require('fs');const e=[];
       c.parse(f.readFileSync('site/claim/claim.css','utf8'),{onParseError(x){e.push(x.message+' @'+x.line)}});
       console.log(e.length?e:'clean')"

   ⛔⛔ WHY THE AMENDMENT MATTERS MORE THAN THE COMMAND: this warning told the next person to write comments
   in the @media block and then handed them a witness that is blind there. **A warning that points at a
   check which can't fail for the case it warns about** is the same class as the warning itself — and it
   arrived the same way the seam count did, by being TRUE OF THE INCIDENT and carried forward without
   re-deriving its SCOPE. The instrument was right about the bug it was born from; nobody asked where else
   it looks. ⚠ Re-derive an instrument's scope when you move it to a new location, not just its verdict.
   ⛔⛔ AND THE AMENDMENT ITSELF BROKE THE RULE ON ITS FIRST WRITING — THE THIRD TIME IN THIS COMMENT, by
   the author who had just finished proving the gap. The paragraph above ended with a close-comment token,
   orphaning these seven lines and the @media block below exactly as before. ⭐ IT WAS CAUGHT IN SECONDS,
   BEFORE A COMMIT, BY RUNNING BOTH CHECKS — which is the entire argument for running them: the rule is
   evidently not learnable by being told, so the instrument is not a formality, it is the control. ⚠ The
   count in the line above ("for the second time") is now the thing that is stale — this is instance three.
   ⭐⭐ THE RULES THEMSELVES HAVE LEFT THIS FILE — PROMOTED TO `site/styles.css` 2026-08-01, ALL 12 PAGES.
   Only this warning stays, because it is about writing comments HERE, not about the footer.
   ⚠⚠ AND THE REASON THE SCOPE CAME OFF IS NOT THE REASON IT WENT ON — WORTH READING BEFORE RE-SCOPING
   ANYTHING. The divergence was justified: /claim is the one page with a fits-in-one-viewport requirement,
   so a tall footer was BINDING there and merely suboptimal elsewhere. That justification was correct and
   it is not what changed.
   ⭐ What changed is the CHANGE ITSELF. The sweep removes a stacked row and deletes a link that pointed at
   the page you were already on — **that is a better footer, not a viewport fix.** "The footer is too tall"
   was always true everywhere; the constraint was only ever binding on one page. A fix scoped to where the
   pain was measurable is right; keeping it there once it turned out to be an improvement is not.
   ⚠ So the promotion is not "the old reasoning was wrong" — it is that the work outgrew the reason for
   its scope. ⛔ Do not read this as licence to unscope other `body.claim-page` rules: the dvh flex column
   above IS a viewport fix and stays exactly where it is. */

/* ⛔ THE max-height GATE THAT SAT HERE IS DELETED (2026-08-01), AND THE REASON IS DESIGN, NOT THE
   MEASUREMENT. The two-column footer above is width-gated and always applies on desktop; this block
   additionally tightened the card and footer padding below 820px tall.
   ⭐ A CONDITIONAL LAYOUT MEANS TWO FOOTERS TO EYEBALL ON ONE PAGE, and the two-column footer was never a
   small-screen accommodation — slogan left, links right, one row IS the right footer for this page. A
   thing that is right unconditionally should not be written as if it were a concession.
   ⚠ AND A HEIGHT GATE IS HAZARDOUS IN A WAY A WIDTH GATE IS NOT: the reporter had DevTools docked
   (innerHeight 688) while their real viewport is ~900, so a 820px threshold fires while debugging and not
   in use. ⭐ A gate that cannot fire on the device the report came from is the same family as a check that
   cannot pass — it reports success by never running.
   ⏳ If /claim still scrolls, the remaining height is NOT in the footer. Measure with the snippet in
   `wiki/reference/measure-claim-fit.md` rather than tuning this file a third time. */

.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ── the claim console hero — ported from #perch-setup-view .claim-hero ─────────────────────────────── */
.claim-hero {
  background: radial-gradient(120% 130% at 72% 18%, #1f1812 0%, #15110d 55%, #100d0a 100%);
  border: 1px solid #2b231c;
  border-radius: 16px;
  padding: 30px 32px;
  margin-bottom: 22px;
}
/* ── THE CARD GOES WIDE — SEARCH LEFT, GLOBE RIGHT ──────────────────────────────────────────────────
   The measurement settled this page: it never scrolled (FITS with 0px to spare), main is 407px and the
   card 242px of it. The complaint was that it READS EMPTY, and unused space cannot be fixed by removing
   height — only by putting something in it.
   The globe is the ARGUMENT rather than decoration: the page argues accuracy, and a cold VC asks whether
   there is anything here for them. Nothing on the page answered that until now.
   Two auto tracks rather than 1fr for the globe: a 1fr track EATS THE REMAINDER, which is how the footer
   ended up left-aligned in an over-wide row earlier today. The text column takes what it needs and the
   globe takes a fixed share. */
/* ⛔⛔ THE CARD WIDENS WITH THE GLOBE, NOT AROUND IT. Adding a 260px column to a 42rem container took
   260 + 28 out of the TEXT side and left the search input ~170px of content width — the placeholder
   truncated to "Your fu". ⭐ The one control this page exists for was narrowed to make room for an
   illustration, which is the wrong direction on any page and an absurd one here.
   ⚠ THE GLOBE DID NOT TAKE TOO MUCH SPACE; THE CARD NEVER GREW. There is a wide empty margin either side
   at desktop widths — the container was still sized for a single-column card.
   ⚠ Scoped to .claim-find so the PROFILE step keeps the narrow measure: a fund profile is prose and long
   line lengths hurt it, while a search row is a control and wants the room. */
@media (min-width: 761px) {
  .claim-find .container-narrow { max-width: 62rem; }
}
.claim-hero-wide { display: grid; grid-template-columns: minmax(0, 1fr) 260px; gap: 28px; align-items: center; }
.claim-hero-say { min-width: 0; }
.claim-hero-globe { width: 260px; height: 260px; }
.claim-hero-globe canvas { width: 100%; height: 100%; display: block; }

/* Below the two-column threshold the globe is the first thing to go. It is the argument, not the control,
   and a phone that shows a globe above the search box has buried the one thing this page exists for.
   Not display:none on the canvas alone — the whole cell goes, so the grid collapses to one column. */
@media (max-width: 760px) {
  .claim-hero-wide { grid-template-columns: 1fr; }
  .claim-hero-globe { display: none; }
}

/* ── THE MARGIN PHRASES — ON THE PAGE, NOT IN THE CARD ─────────────────────────────────────────────
   ⛔⛔ OUTSIDE THE CARD, DELIBERATELY, AND IT IS A PRIVACY DECISION BEFORE IT IS A DESIGN ONE. A category
   and a location composited into ONE PICTURE is a different object from either alone — "hydrogen storage
   at pre-seed" beside a single dot in a small city is closer to identifying than either is. The Terms
   clause authorises PUBLISHING a pair; it does not contemplate COMPOSITING one with a map.
   ⭐ Rendering them on the page background removes that risk rather than managing it, and there is nothing
   left to balance against the marks.

   ⚠ TRACKING, NOT OPACITY, IS THE LEVER. Widely-tracked small type RECEDES; low-opacity text reads as
   FAILING TO LOAD, which is a different and worse thing. The target is ambient texture that resolves only
   if you look for it — not text someone has turned down.
   ⚠⚠ "AND NOT FAST … 40s per drift" STOOD HERE AND WAS RE-RULED 2026-08-01 AFTER A LIVE LOOK. At that
   cadence a visitor never saw it change at all, so it read as FIXED FAINT TEXT — the ambient intent
   produced a static result. ✅ The rule now: **something moves every 2-3 seconds, somewhere.** Not every
   split second — visibly alive without being busy. The per-phrase dwell is still long (~24-40s); it is the
   FIELD that turns over quickly, because turnover is dwell ÷ slot count.
   ⛔ THE CHECK: if it competes with the search input or the headline, it is too loud. */

.claim-eyebrow {
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 11px; text-transform: uppercase; letter-spacing: .2em;
  color: rgba(239, 233, 223, .5);
}
.step-h {
  font-family: var(--font-serif); font-weight: 500; font-size: 30px; letter-spacing: -.01em;
  margin: 9px 0 8px; color: #f3ece0;
}
.step-sub { color: rgba(239, 233, 223, .62); font-size: 14px; line-height: 1.55; margin: 0; }

/* ── the search field — the hero action ─────────────────────────────────────────────────────────────── */
.search-field { position: relative; margin-top: 22px; }
.search-ic {
  position: absolute; left: 16px; top: 50%; transform: translateY(-50%);
  width: 18px; height: 18px; color: rgba(239, 233, 223, .45);
  pointer-events: none; transition: color .15s;
}
.search-field:focus-within .search-ic { color: var(--accent-sage); }
.search-field input {
  width: 100%; box-sizing: border-box;
  /* 16px minimum — below it iOS zooms on focus. A documented fix, not a taste call. */
  font-size: 16px;
  padding: 15px 16px 15px 46px;
  /* reserve room for the submit button so a long fund name never runs under it */
  padding-right: 104px;
  border: 1px solid rgba(239, 233, 223, .18); border-radius: 11px;
  background: rgba(245, 242, 237, .06); color: #f3ece0;
  font-family: inherit;
  transition: border-color .15s, box-shadow .15s, background .15s;
}
.search-field input::placeholder { color: rgba(239, 233, 223, .4); }
.search-field input:focus {
  outline: none; border-color: var(--accent-sage);
  background: rgba(245, 242, 237, .1);
  box-shadow: 0 0 0 3px rgba(125, 148, 113, .25);
}
.search-go {
  position: absolute; right: 7px; top: 50%; transform: translateY(-50%);
  border: 1px solid var(--accent-sage); background: var(--accent-sage); color: #14140f;
  font: inherit; font-size: 13.5px; font-weight: 600;
  border-radius: 8px; padding: 8px 15px; cursor: pointer; transition: filter .15s;
}
.search-go:hover { filter: brightness(1.08); }
.search-go:focus-visible { outline: 2px solid var(--accent-sage); outline-offset: 2px; }

/* ── results ────────────────────────────────────────────────────────────────────────────────────────── */
.results { margin-top: 14px; display: flex; flex-direction: column; gap: 8px; list-style: none; padding: 0; }
.fund-row {
  text-align: left; width: 100%;
  background: var(--surface, #fff); border: 1px solid var(--border-subtle);
  border-radius: 10px; padding: 13px 15px; cursor: pointer;
  display: flex; align-items: center; gap: 10px; font: inherit; color: inherit;
  transition: border-color .12s, box-shadow .12s;
}
.fund-row:hover { border-color: var(--accent-sage); box-shadow: 0 2px 10px rgba(125, 148, 113, .12); }
.fund-row:focus-visible { outline: 2px solid var(--accent-sage); outline-offset: 2px; }
.fr-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fr-name { font-weight: 600; }
.fr-meta { color: var(--text-tertiary); font-size: 12.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.results-empty { color: var(--text-tertiary); font-size: 13.5px; padding: 12px 2px; }

.linkish {
  background: none; border: none; color: var(--accent-sage); font: inherit;
  cursor: pointer; text-decoration: none; padding: 0;
}
.linkish:hover { text-decoration: underline; }

/* ── step 2 · the profile ───────────────────────────────────────────────────────────────────────────── */
.claim-back { margin: 0 0 1.25rem; }

.cp-ident { margin-bottom: 1.5rem; }
.cp-name { margin: 0 0 .2rem; font-family: var(--font-serif); font-weight: 500; }
.cp-meta { margin: 0; color: var(--text-tertiary); font-size: .95rem; }

/* ⭐ The thesis is the largest thing on the page, in prose, because it is the whole argument. */
.cp-thesis { margin: 0 0 2rem; }
.cp-thesis-label,
.cp-similar-label,
.cp-gaps-label {
  margin: 0 0 .5rem;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 11px; letter-spacing: .2em; text-transform: uppercase; color: var(--text-tertiary);
}
.cp-thesis-body { margin: 0; font-size: 1.2rem; line-height: 1.62; }

/* The empty case — DESIGNED, not degraded. Same weight as the thesis it replaces, so the page reads as
   complete-and-honest rather than as a page with its middle missing. */
.cp-empty { margin: 0 0 2rem; }
.cp-empty-lede { margin: 0 0 .5rem; font-size: 1.2rem; line-height: 1.55; }
.cp-empty-body { margin: 0; color: var(--text-tertiary); line-height: 1.6; }

.cp-have { margin: 0 0 2rem; }
.cp-have-list { margin: 0; padding: 0; list-style: none; display: grid; gap: .4rem; }
.cp-have-list li { line-height: 1.55; }

.cp-similar { margin: 0 0 2rem; }
.cp-similar-list { margin: 0; padding: 0; list-style: none; display: grid; gap: .45rem; }
.cp-similar-item {
  display: flex; justify-content: space-between; gap: 1rem; align-items: baseline;
  padding-bottom: .45rem; border-bottom: 1px solid var(--border-subtle);
}
.cp-similar-item:last-child { border-bottom: 0; }
.cs-name { font-weight: 500; }
.cs-meta { font-size: .85rem; color: var(--text-tertiary); text-align: right; white-space: nowrap; }
.cp-similar-foot { margin: .6rem 0 0; font-size: .85rem; color: var(--text-tertiary); }

/* ⛔ Gaps are INVITATIONS, so they carry no warning colour, no error iconography, no meter. Anything that
   reads as a fault turns "what only you can answer" into a list of things we failed at. */
.cp-gaps { margin: 0 0 2rem; }
.cp-gaps-list { margin: 0; padding: 0; list-style: none; display: grid; gap: .4rem; }
.cp-gaps-list li { line-height: 1.55; }

.cp-cta { margin: 2.25rem 0 1.5rem; }
.cp-claim-foot { margin: .6rem 0 0; font-size: .875rem; color: var(--text-tertiary); line-height: 1.5; }

.cp-sources {
  margin: 0; padding-top: 1.25rem;
  border-top: 1px solid var(--border-subtle);
  font-size: .85rem; line-height: 1.6; color: var(--text-tertiary);
}

@media (max-width: 34rem) {
  .claim-hero { padding: 24px 20px; }
  .step-h { font-size: 25px; }
  .cp-similar-item { flex-direction: column; gap: .1rem; }
  .cs-meta { text-align: left; white-space: normal; }
}

/* ── supply — the silhouettes ────────────────────────────────────────────────────────────────────────
   ⭐ The same visual argument as unclaimed funds on the globe: drawn as OUTLINES, literally not filled in.
   There the fund is the unfilled thing; here the founders are, and claiming is what fills them.
   ⛔ THREE, ALWAYS. The count is fixed in the markup and must stay fixed — silhouettes that tracked the
   pool would BE the count the band exists to withhold. */
.cp-supply { margin: 0 0 2rem; }
.cp-supply-label {
  margin: 0 0 .6rem;
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 11px; letter-spacing: .2em; text-transform: uppercase; color: var(--text-tertiary);
}
/* ⛔⛔ `.cp-supply-row` AND `.cp-ghost` ARE GONE WITH THEIR MARKUP (ruled Bret 2026-09-04). Three dashed,
   diagonally-striped tiles that nothing ever filled — the shape of something withheld, which is what they
   were built to be, and which read from the chair as A LOADER THAT NEVER FINISHED.
   ⭐⭐ AND THE AUTHOR SAW THE RISK AND AIMED AT THE WRONG HALF OF IT. The rule below this one carried
   `transition: none` with the note "nothing animates: a pulsing silhouette reads as loading, not withheld"
   — correct that MOTION says loading, and motion was never the tell. A dashed outline with a stripe fill
   is the universal skeleton idiom whether or not it moves, and stillness reads as a skeleton that has
   STALLED rather than as a deliberate absence.
   ⚠ THE LESSON IS NOT "PLACEHOLDERS ARE BAD": it is that a placeholder promises a resolution, and this one
   had no code path that could ever deliver it. **An absence says "there is nothing here"; a permanent
   skeleton says "this page is failing."**
   ⚖️ 2026-10-18 (the counts ruling): the /claim count line is GONE — the founder-supply band was a public count of
   founders. On /claim the block stays hidden; on /funds it carries the sign-in line, which names no number. */
.cp-supply-line { margin: 0; line-height: 1.55; }


/* ── THE PERCH ITSELF ────────────────────────────────────────────────────────────────────────────────
   ⭐⭐ PORT LAW: banner + row vocabulary from app/site/styles.css's .perch-banner / .frow blocks, built by
   renderVcFund. Reciprocal notes sit at both. Separate deployments, so the copy is unavoidable and is
   therefore DECLARED — a fork nobody wrote down is how the two claim screens diverged before.
   ⚠ Padding is trimmed from the app's (24px 32px) because there is no app chrome around this one. */
.perch-banner { display: flex; flex-wrap: wrap; align-items: stretch; gap: 14px; padding: 0 0 4px; }
.perch-banner-avatar {
  width: 38px; height: 38px; flex: 0 0 38px; border-radius: 50%;
  background: var(--accent-terracotta); color: var(--bg-primary);
  font-size: 15px; font-weight: 600;
  display: inline-flex; align-items: center; justify-content: center;
}
.perch-banner-id { min-width: 0; flex: 1 1 auto; }
.perch-banner-name {
  font-family: var(--font-serif); font-weight: 500; font-size: 24px; line-height: 1.1;
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; margin: 0;
}
.perch-banner-facts { display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.perch-banner-sub { font-size: 13px; color: var(--text-tertiary); margin-top: 3px; }
.perch-pill {
  font-size: 11.5px; font-weight: 600; padding: 3px 10px; border-radius: 999px; white-space: nowrap;
  background: var(--bg-secondary); color: var(--text-secondary); text-transform: capitalize;
}
.perch-def { max-width: 64ch; margin: 12px 0 22px; font-size: 13.5px; line-height: 1.55; color: var(--text-secondary); }

.perch-card {
  background: var(--surface, #fff); border: 1px solid var(--border-subtle);
  border-left: 3px solid var(--accent-sage);
  border-radius: 10px; padding: 4px 18px; margin-bottom: 2rem;
}
.frow { display: flex; align-items: baseline; gap: 14px; padding: 11px 0; border-top: 1px solid var(--border-subtle); }
.frow:first-of-type { border-top: none; }
.frow-block { display: block; }
.frow .flabel {
  flex: 0 0 150px; font-size: 12px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--text-secondary);
}
.frow-block .flabel { display: block; margin-bottom: 5px; }
.frow .fval { flex: 1 1 auto; font-size: 14px; line-height: 1.55; }

/* ⛔ A GAP IS AN INVITATION, NOT AN ERROR — no warning colour, no icon, no meter. It is quieter than a real
   value because it IS absence, and it must never read as a fault in our filing. */
.fgap { color: var(--text-tertiary); font-style: italic; }

@media (max-width: 34rem) {
  .frow { display: block; }
  .frow .flabel { display: block; margin-bottom: 4px; }
}

/* ── moment 2 · the freehand request ─────────────────────────────────────────────────────────────────
   ⚠ Quieter than the search above it. This is the fallback path, and a form that competes with the
   primary action turns a zero-result into a fork. */
.cp-request { margin-top: 1.25rem; padding-top: 1.25rem; border-top: 1px solid var(--border-subtle); }
.cp-request-lede { margin: 0 0 1rem; line-height: 1.55; }
.cp-request-form { display: grid; gap: .35rem; }
.cp-request-form label {
  font-size: 12px; font-weight: 600; letter-spacing: .05em; text-transform: uppercase;
  color: var(--text-secondary); margin-top: .5rem;
}
.cp-opt { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--text-tertiary); }
.cp-request-form input {
  font: inherit; font-size: 16px;   /* 16px — below it iOS zooms on focus */
  padding: .6rem .75rem;
  border: 1px solid var(--border-subtle); border-radius: 8px;
  background: var(--surface, #fff); color: inherit;
}
.cp-request-form input:focus-visible { outline: 2px solid var(--accent-sage); outline-offset: 1px; }
.cp-request-form button { margin-top: 1rem; justify-self: start; }
.cp-request-msg { margin: .75rem 0 0; line-height: 1.55; }

/* ── THE INTENT CHECKBOX AND THE ROLE FIELD (2026-08-03) ──────────────────────────────────────────────
   ⚠ `.fr-check` OVERRIDES THE FORM'S LABEL RULE ABOVE, WHICH IS UPPERCASE 12px FOR FIELD LABELS. This is
   not a field label — it is a sentence the person is agreeing to, and rendering a consent statement as a
   shouty caption makes it read as a heading nobody has to accept. Same size as the body copy, sentence case,
   normal weight.
   ⭐ It is a `<label>` wrapping its own input, so the whole sentence is the hit target — a 16px checkbox on
   a phone is not. */
.fr-check {
  display: grid; grid-template-columns: auto 1fr; gap: .6rem; align-items: start;
  margin-top: .9rem; cursor: pointer;
  font-size: .95rem; font-weight: 400; text-transform: none; letter-spacing: 0;
  line-height: 1.5; color: var(--text-primary);
}
.fr-check input { width: 1.05rem; height: 1.05rem; margin: .15rem 0 0; accent-color: var(--accent-terracotta); }

/* The role field is revealed by the checkbox, so it carries the grid the form's own `gap` would otherwise
   apply to a wrapper that is `hidden` — a hidden element still occupies a grid track in some engines. */
.fr-role { display: grid; gap: .35rem; }

/* ⛔ THE HONEYPOT MUST BE INVISIBLE TO PEOPLE AND PRESENT TO BOTS — so it is moved off-screen, never
   display:none. A hidden input is trivially skipped by anything parsing the DOM; an off-screen one is not.
   ⚠ aria-hidden + tabindex="-1" keep it out of the accessibility tree and the tab order, so a screen-reader
   user is not asked to fill a trap. */
.hp-field {
  position: absolute !important; left: -9999px !important;
  width: 1px; height: 1px; opacity: 0; pointer-events: none;
}

/* ── THE CLAIM BAND ────────────────────────────────────────────────────────────────────────────────────
   ⭐ DARK GROUND, SAGE BUTTON. `--accent-sage` is the VC AGENT COLOUR, so the palette is already saying
   "this one is yours" before the copy does — using any other accent would say it in a colour that means
   something else on this site.
   ⚠ WEIGHT, NOT OPACITY, ON THE DARK GROUND (banked rule): dim light-text on near-black is a WEIGHT problem.
   `.cp-band-foot` sits at 400 on `--text-on-dark` rather than a dimmed copy of it — the same ruling that
   took `opacity: .82` off the blog figure's destination box earlier today.
   ⚠ Two instances of this band is not the two-primaries failure: that ruling was about two different VERBS
   competing for one decision. This is one verb, offered twice on a long page. */
/* ⛔⛔ THE CHARCOAL BOX IS GONE (ruled 2026-09-04). It wrapped the second Claim in a dark panel on a paper
   page — a band that announced itself as a separate region and made one verb, offered twice, look like two
   different things. The button now stands on the paper ground like any other control on this page.
   ⚠ `margin` SURVIVES, `padding` and `background` DO NOT: the block still needs air around it, it just
   does not need a container. */
.cp-band {
  margin: var(--space-4) 0; text-align: center;
}
/* ⛔ TERRACOTTA, MATCHING THE ARRIVAL BUTTON — it was SAGE, which is the VC AGENT COLOUR, so the page's
   one ask wore the colour that means "this is the investor side" rather than the colour that means "this
   is AdPerch asking". Claim is always terracotta, on both instances and on both doors.
   ⚠ `--accent-terracotta` (#c65a36) IS THE MARKETING TOKEN AND IS CORRECT HERE, on paper. The showcase's
   arrival button uses the perch vocabulary's #e07a54 because it sits on the dark broadsheet. Two grounds,
   two values, one ruling. */
.cp-band-btn {
  font: 600 1rem/1 var(--font-sans); letter-spacing: .01em;
  background: var(--accent-terracotta); color: #fff; border: 0;
  border-radius: 8px; padding: .85rem 1.6rem; cursor: pointer;
}
.cp-band-btn:hover { filter: brightness(1.06); }
.cp-band-btn:focus-visible { outline: 2px solid var(--text-on-dark); outline-offset: 3px; }
.cp-band-foot {
  font: 400 .875rem/1.55 var(--font-sans); color: var(--text-on-dark);
  margin: .85rem auto 0; max-width: 46ch;
}

/* ⭐ The reason a disabled claim button is disabled — shown only while it refuses. Quiet, not an alarm: the
   visitor has not done anything wrong, they simply have not asserted the one thing this door asks for. */
.fr-gate-why { margin: .5rem 0 0; font-size: .85rem; line-height: 1.5; color: var(--text-tertiary); }

/* ⚠ TIGHT UNDER ITS FIELD — `margin: 0`, deliberately NOT .fr-gate-why's `.5rem`. The form is
   `display: grid; gap: .35rem`, so the grid gap already provides the spacing; adding a margin on top of it
   would float the hint away from the input it describes and toward the NEXT label, which carries its own
   `margin-top: .5rem`. Same visual register as .fr-gate-why, different spacing role — so it is its own rule
   rather than a grouped selector. */
.fr-hint { margin: 0; font-size: .85rem; line-height: 1.5; color: var(--text-tertiary); }

/* ⭐ THE FORM SUBMIT — charcoal fill, sage label. Deliberately the INVERSE of .cp-band-btn above, which is
   sage fill with dark ink inside the dark .cp-band. Ruled by Bret 2026-08-08: the band INVITES, the form
   submit COMMITS. Two buttons sharing the label "Claim this fund" in opposite treatments on one page is the
   intended hierarchy, NOT a drift to harmonise later.
   ⚠ SCOPED TO #fr-go ON PURPOSE. The shared rule .btn-primary[data-role="vc"] lives in /styles.css and is
   live on four marketing pages; this is one button on one page, so it is not widened to reach here.
   ⛔ #a9c48a, NEVER var(--accent-sage). --accent-sage is #7d9471 and is a BACKGROUND token — on charcoal it
   measures ~3.5:1 and fails AA at body size. #a9c48a is the pair the tree already validates for exactly
   this combination (styles.css:190, documented ~7.8:1; measured 7.84:1) and is what the VC role word uses.
   ⚠ WORTH KNOWING: #fr-go carries no data-role, and base .btn-primary sets GEOMETRY ONLY — the light gold
   comes from [data-role="founder"]. So this button had no background rule anywhere and was rendering as the
   browser default. This gives it a fill for the first time rather than replacing one.

   ⛔⛔ AND THE ID IS NOT UNIQUE ACROSS THE PAGES THIS STYLESHEET SERVES. claim.css is loaded by BOTH
   /claim/ and /funds/ (funds/index.html:36, deliberately shared — "the two doors are one product"), and
   /funds/index.html:167 carries its OWN <button id="fr-go">, labelled "Ask us to add it" and never disabled.
   A bare #fr-go rule would silently restyle the ADD door too — a different verb, not the button asked about.
   ⭐ SO THIS IS SCOPED ON :has(#fr-claim) — the attestation checkbox, which exists ONLY on the claim door
   (/funds/ has zero occurrences of it, and the two doors run different scripts: claim.js vs funds.js). It
   discriminates on the actual semantic difference: this is the door that requires you to attest.
   ⚠ THE COUPLING IS DELIBERATE BUT REAL: if the attestation checkbox is ever removed from the claim door,
   this button silently loses its charcoal. It degrades to the previous look rather than breaking, and the
   same is true if :has() is unsupported — but it is a hidden dependency, so it is named here.
   ⭐ NARROW ON PURPOSE, SO WIDENING IS A DELETION. If Bret rules that the add door should match, the fix is
   to delete `:has(#fr-claim)` from both selectors — a subtraction, which is the safer direction to move. */
.cp-request-form:has(#fr-claim) #fr-go { background: var(--accent-charcoal); color: #a9c48a; }

/* ⛔⛔ THE DISABLED STATE IS THE EXPLANATION, NOT A DETAIL. claim.js:51-52, in its own words: "required on
   both inputs does the refusing; this only keeps the BUTTON honest about it. Both are needed: the attribute
   is the enforcement, the disabled state is the explanation." A charcoal button that looked the same in both
   states would delete that explanation and be worse than the unstyled one it replaced.
   ⭐ GHOST, NOT FADED — the charcoal fill ARRIVING is the affordance. No fill means the door has not opened,
   and it pairs with #fr-gate-why, which is visible on exactly the same condition.
   ⛔⛔ THE BASE opacity: 0.6 IS REPLACED, NOT INHERITED, AND THAT IS AN ACCESSIBILITY FIX RATHER THAN A
   PREFERENCE. Fading charcoal plus #a9c48a to 60 percent over the page composites to #7d7772 behind #c7d6b2
   — MEASURED 2.88:1, which FAILS AA. Opacity fades an element toward its backdrop and drags the label down
   with the fill, so the one pair whose contrast was proven stops being the pair on screen. Explicit colors
   are the only way a disabled label stays legible: #5a5048 on the page is 7.03:1, and the same value serves
   as the ring (a UI boundary needs 3:1, and no parchment tint got past 2.03:1 against this background).
   ⚠ INSET box-shadow, NOT border. Base .btn-primary is border: 0, so adding a real border would grow the box
   and the button would visibly JUMP SIZE at the moment the attestation is ticked. The ring is drawn inside
   the existing box, so the two states occupy identical space.
   ⚠ cursor: not-allowed replaces the base progress, which reads as "working" while the gate is simply shut.
   See the note below — this button has TWO disabled reasons and CSS cannot tell them apart. */
.cp-request-form:has(#fr-claim) #fr-go:disabled {
  background: transparent;
  color: var(--text-secondary);
  box-shadow: inset 0 0 0 1.5px var(--text-secondary);
  opacity: 1;
  cursor: not-allowed;
}

/* ⚠ FLAGGED, NOT FIXED — #fr-go is disabled for TWO UNRELATED REASONS and they render identically:
     claim.js:61  btn.disabled = !on          the attestation is unticked. Persistent. "not yet allowed."
     claim.js:129 btn.disabled = true         the submit is in flight. About a second. "working."
   The ghost treatment above is correct for the first, which is the state a visitor actually sits in and the
   one #fr-gate-why explains. It is mildly wrong for the second, where the old progress cursor was right.
   ⭐ Separating them needs a marker CSS can see — a class added at claim.js:129 and removed on
   completion — which is a JS change beyond a restyle, so it is recorded here rather than taken. */

/* ⟨COPY⟩ price + effort line, under the arrival Claim button (2026-09-05; once, not twice, since 2026-10-19).
   Quiet by design: it answers an objection, it is not an offer. Making it loud would turn "free" into the
   pitch, and the pitch is the board. */
.cp-free-line { margin: 6px 0 0; font-size: 14px; opacity: .75; }

/* ⭐ THE "WHAT THIS IS" STRIP — BODY SIZE, NOT DISPLAY SIZE (ruled 2026-09-05).
   It orients a cold VC in two lines and then gets out of the way; the profile below it is the argument.
   ⛔ Minimal vertical space above the profile is the ruling, so the margins are tight and the second line
   sits directly under the first. Anything that reads as a hero here competes with the perch. */
.cp-what { margin: 0 0 10px; }
.cp-what p { margin: 0 0 2px; font-size: 15px; line-height: 1.45; opacity: .85; }
.cp-what p:last-child { margin-bottom: 0; }

/* Recent filings — public SEC Form D lines on a fund profile (2026-10-15). Rendered by fund-page.js; moved into the open
   profile by funds.js. Sits on the cream ground beside .perch-def. */
.fund-filings { margin: 1.25rem 0 0; }
.fund-filings-h { font-family: 'Playfair Display', Georgia, serif; font-size: 1.05rem; font-weight: 600; margin: 0 0 .5rem; color: #2d2520; }
.fund-filings-list { list-style: none; margin: 0; padding: 0; font-size: .9rem; color: #4a4038; }
.fund-filings-list li { padding: .45rem 0; border-top: 1px solid rgba(45, 37, 32, .12); overflow-wrap: anywhere; }
.fund-filings-list a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
