From 53411a03838085c0d15ce524bc6333400ed91aac Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 03:33:09 +0000 Subject: [PATCH 1/2] Site: make the favicon the header mark, and stop painting it black on black MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two problems, one cause: the mark and the favicon were never the same drawing, and nothing tied either of them to the surface they sit on. The header glyph took its colour from `currentColor`, which inherits `--text` from `.brand`. That is near-white in dark mode and #10222c in light mode — and the square behind it is the hero gradient, which is dark in BOTH themes. So light mode rendered a near-black glyph on a near-black square at roughly 1.7:1. It now takes `--hero-text`, the token that exists for exactly this surface and is documented at 6.8:1 against the gradient's lightest stop. The favicon was a different mark altogether: three horizontal rules on a flat #0b5a86 square, drawn by hand and never revisited when the header became a layered-stack glyph. It is now generated from ICONS.stack itself, so the two cannot diverge again — same paths, same gradient, same rounded square, same inset hairline, same proportion of glyph to box. Two deliberate differences, both measured rather than eyeballed. Stroke is 2.4 against the header's 2, because at 16px in a browser tab the proportionally correct 3.375 renders under a pixel; 2.4 lands at 1.0px, which is where the old favicon sat. And the glyph is now explicitly 19px inside the 30px header square rather than filling it edge to edge — 0.633, the same ratio the favicon uses, and close to the 16-in-26 the mobile breakpoint already specified. The favicon carries a prefers-color-scheme block so its gradient follows the theme the way the header's does. A standalone SVG document cannot read tokens.css, so the six stop values are literal and commented as copies. Verified by rendering: header in both themes, and the favicon at 64, 32 and 16 beside the header mark at its real size. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014cLKVBJb2hPNi6QALTFPFk --- web/assets/site.css | 9 ++++++++- web/build.mjs | 21 ++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/web/assets/site.css b/web/assets/site.css index 66ee209..06e3ca1 100644 --- a/web/assets/site.css +++ b/web/assets/site.css @@ -99,13 +99,20 @@ img, svg, video { max-width: 100%; height: auto; } .nav-toggle:hover { background: var(--surface-alt); } .brand { display: flex; align-items: center; gap: 10px; color: var(--text); font-weight: 600; white-space: nowrap; min-width: 0; } .brand:hover { text-decoration: none; color: var(--text); } +/* The mark sits on the hero gradient, which is dark in BOTH themes — so the + glyph takes --hero-text rather than inheriting --text from .brand, which + would paint it near-black on near-black in light mode. Same reason the + favicon is drawn from these two values. */ .brand-mark { width: 30px; height: 30px; border-radius: var(--radius-sm); flex: none; background: var(--hero-bg); + color: var(--hero-text); display: grid; place-items: center; box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--hero-text) 18%, transparent); } -.brand-mark svg { display: block; } +/* sized rather than left to fill the box, so the glyph keeps the same breathing + room the favicon has — the two are meant to be the same mark */ +.brand-mark svg { display: block; width: 19px; height: 19px; } .brand-name { font-family: var(--font-display); font-size: 0.98rem; letter-spacing: -0.01em; overflow: hidden; text-overflow: ellipsis; } .brand-name .thin { color: var(--text-faint); font-weight: 500; } .topbar-spacer { flex: 1; } diff --git a/web/build.mjs b/web/build.mjs index 69ea30b..abd5850 100644 --- a/web/build.mjs +++ b/web/build.mjs @@ -876,8 +876,27 @@ for (const dir of stackSections) { cpSync(ASSETS, join(OUT, "assets"), { recursive: true, filter: (src) => !src.includes("diagrams") }); writeFileSync(join(OUT, "assets", "search-index.js"), `window.SEARCH_INDEX=${JSON.stringify(searchIndex)};`); +/* The favicon IS the header mark: same stack glyph as ICONS.stack, on the same + hero gradient, at the same proportion inside the same rounded square. The + values are literal because a standalone SVG document cannot read tokens.css — + they are copied from --hero-bg and --hero-text, light stops then dark, and + the media query is what the theme toggle does in the page. + Stroke is 2.4 rather than the header's 2 so the glyph survives being drawn at + 16px in a browser tab; everything else is scaled from the header exactly. */ +const FAVICON_GLYPH = ICONS.stack.replace(/^]*>|<\/svg>$/g, ""); writeFileSync(join(OUT, "assets", "favicon.svg"), - ``); + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + `` + + FAVICON_GLYPH + + ``); writeFileSync(join(OUT, ".nojekyll"), ""); writeFileSync(join(OUT, "robots.txt"), `User-agent: *\nAllow: /\nSitemap: ${SITE_BASE}sitemap.xml\n`); writeFileSync(join(OUT, "sitemap.xml"), From 5ccaa062cce15320660861f3fbfd02ffe0bb9775 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 25 Jul 2026 03:50:48 +0000 Subject: [PATCH 2/2] README and home page: fix what an audit of the claims actually found MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ran four independent audit passes over README.md — counts, coverage, links, and consistency against the rest of the repo — with every finding put to a separate refuter before being acted on. 24 raised, 12 survived. All twelve are fixed here. The one that matters most was self-inflicted. **The site had a second copy of the prose, and it had already drifted.** README.md is not a source for any page. The home page was typed out as HTML inside build.mjs, duplicating the hero, "The problem this solves", the three loops, the honest-cost blockquote and all seven rules — while the README and build.mjs's own header contract both claimed "There is no second copy of the prose anywhere". Two rules had already diverged: "Enrich, don't perfect." against "Enrich, do not perfect.", and two different endings to rule 7. Fixed by making the claim true rather than softening it. The home page now lifts those blocks out of README.md and renders them through the same renderer as every other page. What stays hand-written is the site's own furniture — hero buttons, step cards, the stat block, the "where to start" cards — none of which has a counterpart in the README. Rename a heading the home page depends on and the build fails by name; verified by renaming one and watching it exit 1. The rest, each confirmed against the filesystem before being touched: - "Everything in this repo is also published" was wrong — 22 markdown files never are, deliberately. The section now says what is published, what is not, and why: stack/ and portfolio/ hold files you fill in with your own business, and a fork that publishes its own site has build.mjs withholding the summary of any non-public section that has been filled in. - The workflow sentence said "every push to main". It has run on every branch and every pull request since e8cfed1; only main publishes. - The docs/ row enumerated 7 of 13 pages. automation.md and exchange.md appeared nowhere in the README at all, having been added in 53615bf by the same commit that updated the prompt count and missed this row. All 13 are now referenced, and a check confirms it. - "19 prompts that run on top of a filled-in stack" is contradicted by five of the prompts' own Requires lines. 00, 14 and 15 run against an empty stack by design; 16-18 read portfolio/ and never touch a founder's stack. - The tag vocabulary was stated twice as three values. There are four in the stack layer — [CONFLICT] is the missing one, and AGENTS.md rule 8 is built on it. Fixed in the loops diagram and in Loop 1. - stack/INDEX.md, the router AGENTS.md tells every AI to read first, was reachable from the README only at depth 3. It and CONTEXT.md are now linked from the row that describes the folder. - The framing above the folder table described exactly one reader. The second — the programme running a portfolio — was signalled only by a table row. - Fork instructions said delete web/ and .github/. A founder should delete portfolio/ too, which the same table had said two screens earlier. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014cLKVBJb2hPNi6QALTFPFk --- README.md | 26 +++++++++++-------- web/build.mjs | 72 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 67 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 7c0a830..860cd5f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Drop your pitch deck, market research, business plan, competitor notes and call > Every company, customer, metric and transcript used as an example in this repo is invented. Nothing here is drawn from any real client engagement. -[**New to working with AI**](docs/ai-basics.md) · [**What the words mean**](docs/what-things-are.md) · [**The method**](docs/method.md) · [**Build the knowledge base**](docs/knowledge-base.md) · [**Prompt library**](prompts/INDEX.md) · [**For coaches**](docs/for-coaches.md) · [**Quickstart**](QUICKSTART.md) +[**New to working with AI**](docs/ai-basics.md) · [**What the words mean**](docs/what-things-are.md) · [**The method**](docs/method.md) · [**Build the knowledge base**](docs/knowledge-base.md) · [**Prompt library**](prompts/INDEX.md) · [**For coaches**](docs/for-coaches.md) · [**For programmes**](docs/for-portfolios.md) · [**Quickstart**](QUICKSTART.md) [![Read it as a site](https://img.shields.io/badge/read%20it%20as%20a%20site-fritzhand.github.io%2Fstartup--stack-0b5a86?style=flat-square)](https://fritzhand.github.io/startup-stack/) ![No code required](https://img.shields.io/badge/code%20required-none-0E6B63?style=flat-square) @@ -28,6 +28,8 @@ Meanwhile the advice they need is not exotic. It is the same twenty things, aske `startup-stack` makes those questions answerable from one place. +There is a second reader, and the repo now serves them too: the **programme** running a portfolio of these companies — an incubator, accelerator, studio or university venture programme, whose coaching team meets the same founders in recurring sessions and whose institutional memory currently lives in scattered notes. [The page written for them](docs/for-portfolios.md) is the deeper end of the same method. A founder can ignore it entirely. + ## The three loops ``` @@ -39,7 +41,7 @@ Meanwhile the advice they need is not exotic. It is the same twenty things, aske business plan ──┼─▶ CONTEXT.md ← the one-pager ├─▶ list of 100 market research │ 01-company … 10-pulse │ unit economics competitor notes │ ↑ one .md per section, front-mattered │ investor one-pager - call transcripts │ ↑ tagged: confirmed / unverified / TBD │ weekly recap + call transcripts │ ↑ confirmed / unverified / TBD / clash │ weekly recap spreadsheets │ │ design refs │ │ └──────────────────────────────────────────┘ @@ -48,7 +50,7 @@ Meanwhile the advice they need is not exotic. It is the same twenty things, aske real happens) every week) ``` -**Loop 1 — Build.** You put raw material in `_inbox/`. One prompt reads it and writes the stack: ten numbered sections, one markdown file each, every fact tagged as confirmed, unverified or missing. Takes an afternoon. The output is deliberately incomplete — the gaps are the point, because a gap you can see is a task. +**Loop 1 — Build.** You put raw material in `_inbox/`. One prompt reads it and writes the stack: ten numbered sections, one markdown file each, every fact tagged confirmed, unverified, missing, or in conflict where two of your own documents disagree. Takes an afternoon. The output is deliberately incomplete — the gaps are the point, because a gap you can see is a task. **Loop 2 — Enrich.** Every real thing that happens — a customer call, a supplier quote, a rejected ad campaign, a new competitor — goes back into the stack. The base is never finished. Each addition makes the next request cheaper and better, because the AI stops guessing about your business. @@ -58,10 +60,10 @@ Meanwhile the advice they need is not exotic. It is the same twenty things, aske | Folder | What it is | | --- | --- | -| [`stack/`](stack/) | **The templates.** Ten numbered sections — company, customer, market, product, GTM, operations, money, capital, brand, pulse — one markdown file each, with front matter. This *becomes* your knowledge base; you fill it in, you do not copy it out. | -| [`prompts/`](prompts/INDEX.md) | **The prompt library.** 19 prompts that run on top of a filled-in stack: bootstrap, gap scan, competitive intelligence, unit economics, list of 100, pitch deck, fundraise readiness, weekly recap, and the adversarial review that tells you what an investor will attack. The last three are for programmes rather than founders. | +| [`stack/`](stack/) | **The templates.** Ten numbered sections — company, customer, market, product, GTM, operations, money, capital, brand, pulse — one markdown file each, with front matter. [`stack/INDEX.md`](stack/INDEX.md) is the router an AI reads first, and [`stack/CONTEXT.md`](stack/CONTEXT.md) is the one-pager it reads next. This *becomes* your knowledge base; you fill it in, you do not copy it out. | +| [`prompts/`](prompts/INDEX.md) | **The prompt library.** 19 prompts: bootstrap, gap scan, competitive intelligence, unit economics, the list of 100, the pitch deck, fundraise readiness, the weekly recap, meeting-to-actions, and the adversarial review that tells you what an investor will attack. Each opens with a `Requires:` line and stops if what it needs is missing. Most want a filled-in stack; [`00`](prompts/00-bootstrap-the-stack.md), [`14`](prompts/14-meeting-to-actions.md) and [`15`](prompts/15-scrape-a-site.md) run against an empty one, which is how a stack starts to fill. The last three belong to `portfolio/` and never touch a founder's stack. | | [`worksheets/`](worksheets/) | **The repeatable artifacts.** Fill-in files you produce over and over: startup-prep, customer interview, competitor profile, two-week experiment, SOP entry, trade/order sheet, advisor scope letter, one-pager, list of 100, weekly recap. | -| [`docs/`](docs/) | **The method.** Why front matter and an index make the base cheap to run; how to build it in your first hour; how a coach runs this with a cohort; the safety rules for giving an AI access to your files; plus [AI basics](docs/ai-basics.md) for anyone who has only ever used a chat window, [getting your material in](docs/getting-material-in.md) — mail, drive, chat exports and connectors, source by source — and [what the words mean](docs/what-things-are.md) — the terminal, an editor, Git, GitHub, connectors, model tiers — each answered in plain language with a diagram. | +| [`docs/`](docs/) | **The method, in thirteen pages.** New to any of this: [AI basics](docs/ai-basics.md) and [what the words mean](docs/what-things-are.md) — the terminal, an editor, Git, GitHub, connectors, model tiers — each in plain language with a diagram. The method: [why it works](docs/method.md), [building the knowledge base](docs/knowledge-base.md), [the front-matter schema](docs/front-matter.md), [getting your material in](docs/getting-material-in.md) source by source, and [website to inbox](docs/scraping.md). Running it: [the weekly recap](docs/weekly-recap.md), [for coaches](docs/for-coaches.md), [the safety rules](docs/safety.md) for giving an AI access to your files, and [on a schedule](docs/automation.md) — recipes for Microsoft 365, Google Workspace, Zoom, Slack, and none of them. Two are for programmes only: [for programmes](docs/for-portfolios.md) and [the exchange folder](docs/exchange.md). | | [`portfolio/`](portfolio/) | **Optional — for a programme, not a founder.** The templates an incubator, accelerator or studio uses to run this across many companies: a router, a 1-to-5 maturity rubric per function, the cross-company theme scan, and a company folder to copy. Delete it if you are a founder. See [docs/for-portfolios.md](docs/for-portfolios.md). | | [`_inbox/`](_inbox/) | Where raw material lands before it is processed. Git-ignored by default. | | [`tools/`](tools/) | **Optional.** One script that turns a website into citable markdown in `_inbox/`. You never need it — it saves an hour of copy-and-paste. See [docs/scraping.md](docs/scraping.md). | @@ -105,21 +107,23 @@ Nobody should tell you this is free. The AI drafts; you validate. A stack you ha 4. **Enrich, don't perfect.** Ship the skeleton. Add to it forever. 5. **One index, one summary line per file.** This is what keeps the base cheap: the AI reads a small router and opens one section, instead of re-reading everything you own on every question. 6. **Private master, shared derived.** Every file declares a sensitivity. What you share with a coach, an investor or a customer is carved from the master — never the master itself. -7. **Scope the AI to one folder.** Start narrow. Widen only as fast as your confidence. See [docs/safety.md](docs/safety.md). +7. **Scope the AI to one folder.** Start narrow. Widen only as fast as your confidence. See [the safety rules](docs/safety.md). ## Reading it as a site -Everything in this repo is also published at **[fritzhand.github.io/startup-stack](https://fritzhand.github.io/startup-stack/)** — the same markdown, with search, a filterable prompt library, and light and dark themes. +The method is published at **[fritzhand.github.io/startup-stack](https://fritzhand.github.io/startup-stack/)** — every page in `docs/`, every prompt, every worksheet, and this file, with search, a filterable prompt library, and light and dark themes. + +**Generated from these files, not written twice.** Each page is its markdown rendered; the home page is assembled out of this README's own sections. Edit the markdown and the page changes — and if a heading the home page depends on is renamed, the build fails rather than shipping an empty section. -The site is generated *from these files*, so it cannot drift from the repo: edit the markdown, and the page changes. There is no second copy of the prose anywhere. +What is deliberately *not* published: the section templates in `stack/` and the company templates in `portfolio/`. Those are files you fill in with your own business, so the site lists them with their status instead of their contents. That matters if you fork this and publish your own — [`web/build.mjs`](web/build.mjs) withholds the `summary:` of any section that is not `public` once it has been filled in, and prints what it withheld. ```bash node web/build.mjs # markdown + web/assets → _site/ (zero dependencies, Node 18+) ``` -`.github/workflows/pages.yml` runs that on every push to `main` and publishes the result. The build fails loudly on a broken internal link or a missing diagram rather than shipping a quietly wrong page. +[`.github/workflows/pages.yml`](.github/workflows/pages.yml) runs that on every push to every branch and on every pull request, so a broken page is caught before it is merged; only `main` publishes. The build fails loudly on a broken internal link, a missing diagram, a prompt not routed to a group or a page with no title, rather than shipping a quietly wrong page. -**When you fork this as your own company's stack, delete `web/` and `.github/`.** They belong to this project, not to yours. +**When you fork this as your own company's stack, delete `web/` and `.github/` — and `portfolio/` unless you are a programme.** They belong to this project, not to yours. ## Where this came from diff --git a/web/build.mjs b/web/build.mjs index abd5850..8e178d5 100644 --- a/web/build.mjs +++ b/web/build.mjs @@ -758,7 +758,40 @@ ${table} })); } -/* ---- 5. home --------------------------------------------------- */ +/* ---- 5. home ---------------------------------------------------- + The prose on this page is lifted out of README.md and rendered through + the same renderer as every other page. It used to be typed out here as + HTML, which quietly falsified the contract at the top of this file — + and the two copies had already drifted: "Enrich, don't perfect." against + "Enrich, do not perfect.", and two different endings to rule 7. + + What stays hand-written below is the site's own furniture — the hero + buttons, the step cards, the stat block, the "where to start" cards. + None of it has a counterpart in the README, so none of it can drift + from one. Anything that does have a counterpart is pulled, not copied, + and the build fails if the README heading it anchors to is renamed. */ +const README = read("README.md"); +const rctx = () => ({ src: "README.md", dir: ".", root: "", toc: [], ids: new Set(), kind: "Page" }); + +/* everything under `## ` up to the next `## ` */ +function readmeSection(heading) { + const lines = README.split("\n"); + const start = lines.findIndex((l) => l.trim() === `## ${heading}`); + if (start < 0) { fail(`README.md has no "## ${heading}" section, and the home page is built from it`); return ""; } + let end = start + 1; + while (end < lines.length && !/^##\s/.test(lines[end])) end++; + const block = lines.slice(start + 1, end).join("\n").trim(); + if (!block) { fail(`README.md section "${heading}" is empty`); return ""; } + return mdToHtml(block, rctx()); +} + +/* one paragraph, found by how it opens — for the bits that sit outside any heading */ +function readmeParagraph(prefix, { raw = false } = {}) { + const para = README.split(/\n{2,}/).map((p) => p.trim()).find((p) => p.startsWith(prefix)); + if (!para) { fail(`README.md has no paragraph starting "${prefix}", and the home page is built from it`); return ""; } + return raw ? para : mdToHtml(para, rctx()); +} + { const counts = { sections: stackSections.length, @@ -767,11 +800,22 @@ ${table} docs: DOC_PAGES.length, }; const card = (href, title, text) => `

${esc(title)}

${esc(text)}

`; + + /* the three loops are cards on the site and paragraphs in the README, so the + title is split off the front of each rather than written twice */ + const LOOP_CLASS = ["loop-build", "loop-enrich", "loop-pulse"]; + const loopCards = [1, 2, 3].map((n) => { + const para = readmeParagraph(`**Loop ${n} — `, { raw: true }); + const m = para.match(/^\*\*Loop (\d) — ([^.]+)\.\*\*\s*([\s\S]*)$/); + if (!m) { fail(`README.md: the "Loop ${n}" paragraph does not open "**Loop N — Name.**", so the card has no title`); return ""; } + return `

Loop ${m[1]} · ${esc(m[2])}

${mdToHtml(m[3], rctx())}
`; + }).join("\n"); + const body = `
-

The documents every startup should have — compiled into a knowledge base an AI can actually read.

-

Drop your pitch deck, market research, business plan, competitor notes and call transcripts into one folder. An AI turns them into a structured, front-mattered knowledge base. Then a library of prompts runs on top of it to produce the work: the deck, the list of 100, the unit economics, the weekly recap.

+

${esc(TAGLINE)}

+ ${readmeParagraph("Drop your pitch deck")}
Start with your first hour ${ICONS.arrow} New to working with AI @@ -786,7 +830,7 @@ ${table} @@ -800,15 +844,12 @@ ${table}

The problem this solves

-

Most early founders are carrying their company in their head and in twelve unrelated files. The pitch deck says one number, the projections say another, the market research lives in a PDF nobody has opened since March, and every AI conversation starts from zero — so every answer is generic, and the founder pays for the model to re-read everything, every time.

-

Meanwhile the advice they need is not exotic. It is the same twenty things, asked in the same order, by every coach who has ever sat across from an early-stage founder: who exactly is the customer, what does a unit cost you, who else is doing this, how many prospects are on your list, and what happens when the money runs out.

+${readmeSection("The problem this solves")}

The three loops

-

Loop 1 · Build

Raw material goes into _inbox/. One prompt reads it and writes the stack — ten sections, every fact tagged confirmed, unverified or missing. An afternoon. The output is deliberately incomplete, because a gap you can see is a task.

-

Loop 2 · Enrich

Every real thing that happens — a customer call, a supplier quote, a rejected campaign, a new competitor — goes back in. The base is never finished. Each addition makes the next request cheaper and better.

-

Loop 3 · Pulse

Once a week, one prompt reads the stack and what changed, and writes the recap: what moved, what did not, what you learned, and how many weeks of money are left. Thirty minutes.

+${loopCards}

Where to start

@@ -824,18 +865,9 @@ ${card("for-coaches.html", "You are running this with a cohort", "How the method

The honest cost

-

Skeleton in an hour. A working v1 in a few focused sessions. A stack you actually trust in about a month, because the long pole is you correcting what the AI got wrong — not the AI writing it. Then roughly thirty minutes a week to keep it alive.

-

Nobody should tell you this is free. The AI drafts; you validate. A stack you have not corrected is a confident-sounding guess about your own company, and that is worse than no stack at all.

+${readmeSection("The honest cost")}

The rules this runs on

-
    -
  1. The document is the data. If a metric needs special effort to gather, it is the wrong metric.
  2. -
  3. AI drafts, the founder validates. Nothing is true because the model wrote it.
  4. -
  5. Tag honestly. needs-verification and tbd are the quality system, not failures.
  6. -
  7. Enrich, do not perfect. Ship the skeleton. Add to it forever.
  8. -
  9. One index, one summary line per file. This is what keeps the base cheap to run.
  10. -
  11. Private master, shared derived. Every file declares a sensitivity; what you share is carved from the master.
  12. -
  13. Scope the AI to one folder. Start narrow, widen as fast as your confidence and no faster.
  14. -
+${readmeSection("The rules this system runs on")}

The full reasoning is in the method; the rules the AI itself must follow are in rules for the AI.

`; write("index.html", shell({