From e65e282084b4c47bac0cefda157d615c6f016315 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:34:34 +0530 Subject: [PATCH 01/19] feat: minimal-input Agent Desk (recovered from June worktree) Recovers 1,374 lines of uncommitted work from 2026-06-18 that reduces the public intake from an eight-part form to email plus one business snapshot, with optional fields progressively disclosed. Spec at specs/002-minimal-input-agent-desk/ documents the original rationale. --- MEMORY.md | 7 +- README.md | 4 +- ...-001-feat-pipeline-loop-agent-desk-plan.md | 127 +++ ...8-002-fix-minimal-input-agent-desk-plan.md | 91 ++ package.json | 4 +- public/index.html | 174 +-- public/llms.txt | 22 +- public/offer.md | 19 +- public/script.js | 226 +++- public/styles.css | 315 +++++- scripts/check-site.mjs | 119 ++- scripts/test-agent-ui.mjs | 192 ++++ scripts/test-agent-worker.mjs | 994 ++++++++++++++++++ specs/001-public-buyer-page/plan.md | 6 +- specs/001-public-buyer-page/spec.md | 15 +- specs/001-public-buyer-page/tasks.md | 2 + specs/002-minimal-input-agent-desk/plan.md | 27 + specs/002-minimal-input-agent-desk/spec.md | 57 + specs/002-minimal-input-agent-desk/tasks.md | 16 + src/worker.js | 666 +++++++++++- 20 files changed, 2878 insertions(+), 205 deletions(-) create mode 100644 docs/plans/2026-06-18-001-feat-pipeline-loop-agent-desk-plan.md create mode 100644 docs/plans/2026-06-18-002-fix-minimal-input-agent-desk-plan.md create mode 100644 scripts/test-agent-ui.mjs create mode 100644 scripts/test-agent-worker.mjs create mode 100644 specs/002-minimal-input-agent-desk/plan.md create mode 100644 specs/002-minimal-input-agent-desk/spec.md create mode 100644 specs/002-minimal-input-agent-desk/tasks.md diff --git a/MEMORY.md b/MEMORY.md index 8dbc4ed2..948134b4 100644 --- a/MEMORY.md +++ b/MEMORY.md @@ -5,13 +5,14 @@ - This repo is the deployable public website source for `tinystudio.io`. - The Growth Brain operating repo remains at `/Users/nish/Documents/TINY STUDIO` and is the source of deeper offer/delivery truth. - As of the Agent Desk pass, `tinystudio.io` is reopening as a self-serve AI workspace for high-ticket pipeline setup. -- Cloudflare Workers AI generates the Pipeline Brief from user-submitted context through `/api/agent-audit`. +- Cloudflare Workers AI generates the Pipeline Brief, Implementation Checklist, and Weekly Fix Report from user-submitted context through `/api/agent-audit`. ## Product Truth -- TinyStudio Agent Desk generates readiness diagnosis, funnel path, audience/pain map, first creative tests, lead qualification, follow-up/setter flow, CRM/tracking checklist, and decision plan. +- TinyStudio Agent Desk generates a Pipeline Brief, Implementation Checklist, and Weekly Fix Report covering readiness diagnosis, funnel path, audience/pain map, first creative tests, lead qualification, follow-up/setter flow, CRM/tracking checklist, weekly metrics, and decision plan. +- The default customer intake is minimal: email plus business snapshot. Offer, buyer, funnel, proof, follow-up, CRM, constraints, and weekly metrics are optional detail inputs, and agents should infer missing context before asking blocker questions. - Email capture remains live through `/api/signups`. -- The public app stores email and lightweight agent usage metadata in D1, including daily rate-limit counters and a daily IP-derived rate-limit key, but not the full submitted business brief. +- The public app stores email and lightweight agent usage metadata in D1, including daily rate-limit counters and a daily IP-derived rate-limit key, but not the submitted business snapshot, optional details, weekly metrics, or generated artifacts. - Public copy must not promise revenue, ROAS, SEO ranking, AI visibility, conversion lift, booked calls, sales lift, autonomous ad buying, or unapproved ad spend changes. ## Agent Desk Safety diff --git a/README.md b/README.md index bfb3a5f6..184fe6f9 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Self-serve TinyStudio Agent Desk for high-ticket pipeline setup. ## What This Repo Owns - The public `tinystudio.io` and `www.tinystudio.io` Agent Desk. -- Cloudflare Workers AI generation through `/api/agent-audit`. +- Cloudflare Workers AI generation through `/api/agent-audit` for Pipeline Brief, Implementation Checklist, and Weekly Fix Report outputs. - Email capture through `/api/signups`, stored in Cloudflare D1. - Lightweight agent usage metadata in Cloudflare D1, including daily rate-limit counters. - The intentional retirement responses for `app.tinystudio.io` and `api.tinystudio.io`. @@ -16,7 +16,7 @@ Self-serve TinyStudio Agent Desk for high-ticket pipeline setup. - Any live private TinyStudio app/API product. - Client folders, analytics exports, payment data, private sprint work, or ad-platform credentials. - Revenue claims, ROAS claims, booked-call claims, ranking claims, sales-lift promises, autonomous ad buying, ad spend changes, campaign publishing, or ad-platform write actions. -- Storage of submitted business briefs; the Agent Desk processes context for the generated output and stores only email plus lightweight usage metadata. +- Storage of submitted business snapshots, optional detail inputs, weekly metrics, or generated artifacts; the Agent Desk processes context for the generated output and stores only email plus lightweight usage metadata. ## Commands diff --git a/docs/plans/2026-06-18-001-feat-pipeline-loop-agent-desk-plan.md b/docs/plans/2026-06-18-001-feat-pipeline-loop-agent-desk-plan.md new file mode 100644 index 00000000..994711e2 --- /dev/null +++ b/docs/plans/2026-06-18-001-feat-pipeline-loop-agent-desk-plan.md @@ -0,0 +1,127 @@ +--- +title: feat: Pipeline Loop Agent Desk +type: feat +date: 2026-06-18 +execution: code +--- + +# feat: Pipeline Loop Agent Desk + +## Summary + +Expand the TinyStudio Agent Desk from a single Pipeline Brief generator into a three-artifact applied-AI workflow: Pipeline Brief, Implementation Checklist, and Weekly Fix Report. The app remains self-serve, server-side AI powered, claim-safe, and approval-gated for external account or spend actions. + +--- + +## Problem Frame + +The current Agent Desk proves TinyStudio can collect high-ticket pipeline context and generate a useful first brief. The next product layer should bridge AI output into operational work: what to build first, what to track weekly, and what to fix next without positioning TinyStudio as an autonomous ad agency. + +--- + +## Requirements + +**Agent workflow** + +- R1. The Agent Desk must generate three distinct artifacts from one intake: Pipeline Brief, Implementation Checklist, and Weekly Fix Report. +- R2. The Weekly Fix Report must use optional current-week metrics when supplied and must fall back to a tracker template when metrics are absent. +- R3. The Implementation Checklist must convert the diagnosis into approval-gated setup work across offer, funnel, creative, qualification, follow-up, CRM, tracking, and decision cadence. + +**Public UI** + +- R4. The public page must expose the three-artifact workflow as the first-class self-serve experience. +- R5. The intake must collect optional weekly lead-to-call metrics without making them required and without implying guaranteed performance targets. +- R6. The output panel must let users inspect and copy each artifact separately while preserving a useful all-in-one response for fallback. + +**Safety and data boundary** + +- R7. The Worker must keep AI generation server-side through Cloudflare Workers AI and must not store submitted business briefs or generated artifacts. +- R8. Public copy and generated output must avoid revenue, ROAS, booked-call, ranking, AI-visibility, conversion-lift, sales-lift, profit, or autonomous ad-buying guarantees. +- R9. Ad spend changes, campaign publishing, platform connections, CRM outcome syncing, claims, and compliance-sensitive work must remain approval-gated. + +**Verification** + +- R10. Static content checks must validate the new artifacts, section contract, safety language, and Cloudflare AI boundary. +- R11. A sample high-ticket scenario must exercise the end-to-end agent response and prove the outputs are useful without manual explanation. + +--- + +## Key Technical Decisions + +- **Single API response with structured sections:** The Worker should ask the model for exact top-level headings, split the markdown into named sections, and return both `sections` and a backward-compatible combined `brief`. This keeps the client simple while preserving compatibility with the existing one-output contract. +- **Optional metrics as prompt context only:** Weekly numbers belong in the request prompt and browser UI, not in D1. Existing D1 usage logging should remain lightweight and should not capture the business brief, the weekly metrics, or generated artifacts. +- **Client-side tabs over multiple API calls:** One agent run should produce all three artifacts. Tabs only change presentation, which keeps AI cost, rate limits, and consistency predictable. +- **Safety filter on combined generated text:** Existing forbidden-claim detection should run against the full generated response before any section is returned, so unsafe content cannot leak through a secondary tab. + +--- + +## Implementation Units + +### U1. Worker section contract + +- **Goal:** Generate and return Pipeline Brief, Implementation Checklist, and Weekly Fix Report as structured sections. +- **Requirements:** R1, R2, R3, R7, R8, R9 +- **Dependencies:** None +- **Files:** `src/worker.js`, `scripts/check-site.mjs` +- **Approach:** Extend the agent input with optional weekly metrics, update the prompt to require exact headings, add a section splitter, validate that all three sections are present, and keep the combined `brief` response for compatibility. +- **Patterns to follow:** Existing `/api/agent-audit` validation, retry, safety-filter, and no-brief-storage behavior in `src/worker.js`. +- **Test scenarios:** Submit a valid request with weekly metrics and expect all three response sections; submit a valid request without metrics and expect the Weekly Fix Report to become a tracker template; confirm unsafe generated claims are still rejected. +- **Verification:** Static checks cover the section splitter, prompt headings, metrics fields, and forbidden claims. + +### U2. Public workflow UI + +- **Goal:** Make the page read as a self-serve workflow desk rather than a single brief generator. +- **Requirements:** R4, R5, R6, R8, R9 +- **Dependencies:** U1 +- **Files:** `public/index.html`, `public/script.js`, `public/styles.css`, `scripts/check-site.mjs` +- **Approach:** Add an optional weekly metrics group to the intake, render three output tabs backed by the structured API response, keep copy buttons scoped to the active artifact, and preserve a clear empty/error state. +- **Patterns to follow:** Existing Agent Desk layout, form submission, progressive enhancement, and status handling in `public/index.html`, `public/script.js`, and `public/styles.css`. +- **Test scenarios:** Load the page at desktop and mobile sizes; generate from sample context; switch tabs; copy the active artifact; verify empty, loading, success, and error states do not overlap or require technical explanation. +- **Verification:** Browser inspection plus static checks confirm artifact labels, metric fields, output tabs, and safety copy. + +### U3. Public truth and repo docs + +- **Goal:** Keep machine-readable and maintainer-facing truth aligned with the new three-artifact workflow. +- **Requirements:** R4, R8, R9, R10 +- **Dependencies:** U1, U2 +- **Files:** `public/llms.txt`, `public/offer.md`, `README.md`, `MEMORY.md`, `specs/001-public-buyer-page/plan.md`, `specs/001-public-buyer-page/spec.md`, `specs/001-public-buyer-page/tasks.md` +- **Approach:** Update public docs to say the Agent Desk generates a Pipeline Brief, Implementation Checklist, and Weekly Fix Report. Preserve the no-guarantee and approval-gated boundaries. +- **Patterns to follow:** Existing public truth files use short, direct claim-safe wording. +- **Test scenarios:** Static content checks should fail if the new sections or safety boundaries are missing. +- **Verification:** `npm test` validates content, safety claims, and AI boundary checks. + +### U4. End-to-end verification and review + +- **Goal:** Prove the implementation works locally before any ship path. +- **Requirements:** R10, R11 +- **Dependencies:** U1, U2, U3 +- **Files:** `scripts/check-site.mjs` +- **Approach:** Run the normal test command, run syntax checks where useful, browser-check desktop and mobile, and exercise `/api/agent-audit` with a sample high-ticket scenario. +- **Patterns to follow:** Existing project verification in `README.md`, `MEMORY.md`, and `specs/001-public-buyer-page/plan.md`. +- **Test scenarios:** Static test pass; sample AI response has all three artifacts; no forbidden claims; rendered page is nonblank and usable. +- **Verification:** Final report lists exact checks run and any unverified items. + +--- + +## Scope Boundaries + +- No payment collection, account provisioning, or paid subscription logic. +- No Meta, Google, CRM, WhatsApp, SMS, or email platform write automation. +- No campaign publishing, ad spend changes, CRM outcome syncing, or lead-message sending. +- No storage of submitted business briefs, weekly metrics, or generated artifacts. +- No public guarantees for revenue, ROAS, booked calls, profit, rankings, AI visibility, conversion lift, or sales lift. + +--- + +## Risks & Dependencies + +- **Model shape drift:** The model may ignore exact headings. Mitigation: parse sections defensively, retry through existing fallback models, and fail safely if required artifacts are missing. +- **Overclaiming by generation:** The model may produce unsafe claims. Mitigation: run the existing safety detector on the full response before returning it. +- **Metric interpretation risk:** Weekly metrics can be incomplete or noisy. Mitigation: ask the model to diagnose bottlenecks and next checks, not guarantee outcomes. +- **AI availability:** The feature depends on Cloudflare Workers AI binding. Mitigation: keep the existing safe unavailable state. + +--- + +## Documentation / Operational Notes + +No D1 migration is planned because the new inputs and outputs are not stored. Any deploy path must still run the review gate and existing `npm test` before publishing. diff --git a/docs/plans/2026-06-18-002-fix-minimal-input-agent-desk-plan.md b/docs/plans/2026-06-18-002-fix-minimal-input-agent-desk-plan.md new file mode 100644 index 00000000..89daa948 --- /dev/null +++ b/docs/plans/2026-06-18-002-fix-minimal-input-agent-desk-plan.md @@ -0,0 +1,91 @@ +--- +title: fix: Minimal-input Agent Desk +type: fix +date: 2026-06-18 +execution: code +--- + +# fix: Minimal-input Agent Desk + +## Summary + +Make the Pipeline Loop Agent Desk feel agent-native: customers provide a small business seed, and the agents infer the offer, buyer, funnel, hooks, follow-up, CRM, tracking, and weekly fix loop unless a real blocker remains. + +--- + +## Problem Frame + +The current page looks and behaves like an agency intake form. That makes paying customers do diagnosis work the agents should handle, and it weakens the promise that TinyStudio is self-serve with AI doing the heavy lifting. + +--- + +## Requirements + +- R1. The public form must require only email and a compact business snapshot. +- R2. Optional fields must stay available for better output, but they must not dominate the first screen. +- R3. The Worker must accept minimal input and prompt the model to infer missing pieces, mark assumptions, and ask only true blocker questions. +- R4. The generated artifacts must stay claim-safe and approval-gated for spend, publishing, platform connections, and compliance-sensitive actions. +- R5. Existing structured section, metric snapshot, storage, and safety tests must keep passing. + +--- + +## Key Technical Decisions + +- **Keep one endpoint:** `/api/agent-audit` remains the only agent run endpoint so rate limits, storage boundaries, and safety filtering stay centralized. +- **Use business snapshot as the seed:** The existing `business` field becomes the one required context field, avoiding a data migration or extra API contract. +- **Progressive disclosure:** Offer, audience, proof, follow-up, metrics, and funnel controls move behind an optional detail section rather than being removed. +- **Prompt over workflow code:** Missing-input behavior belongs in the system/user prompt so the agents use judgment instead of hard-coded questionnaire logic. + +--- + +## Implementation Units + +### U1. Minimal-input validation + +- **Goal:** Let customers run the agents with only email and business snapshot. +- **Requirements:** R1, R3, R5 +- **Dependencies:** None +- **Files:** `src/worker.js`, `public/script.js`, `scripts/test-agent-worker.mjs`, `scripts/test-agent-ui.mjs` +- **Approach:** Relax validation to require only `email` and `business`; update client-side messages and tests to prove offer/audience are optional. +- **Patterns to follow:** Existing `validateAgentInput`, `ERROR_MESSAGES`, and mocked Worker/UI tests. +- **Test scenarios:** Minimal body succeeds; missing business seed fails; UI submits without offer/audience. +- **Verification:** `npm test`. + +### U2. Agent-heavy prompt behavior + +- **Goal:** Make the agent infer missing offer, buyer, funnel, creative, follow-up, CRM, and metrics assumptions before asking questions. +- **Requirements:** R3, R4, R5 +- **Dependencies:** U1 +- **Files:** `src/worker.js`, `scripts/test-agent-worker.mjs` +- **Approach:** Add prompt guidance for assumption-making, blocker-only questions, and customer-delight output. Keep the existing no-guarantee and approval-gated constraints. +- **Patterns to follow:** Current `agentSystemPrompt`, `agentUserPrompt`, and unsafe-output scrubber. +- **Test scenarios:** Minimal prompt includes inference guidance and labels absent optional fields as missing context rather than invalid input. +- **Verification:** Worker tests and live API smoke. + +### U3. Progressive-disclosure intake UI + +- **Goal:** Replace the visible questionnaire with a compact agent workspace. +- **Requirements:** R1, R2 +- **Dependencies:** U1 +- **Files:** `public/index.html`, `public/styles.css`, `scripts/check-site.mjs` +- **Approach:** Keep email, business snapshot, and constraints visible; move market, funnel, offer, audience, proof, follow-up, and weekly numbers into a collapsible optional section. +- **Patterns to follow:** Existing single-page Agent Desk layout and output tabs. +- **Test scenarios:** Desktop and mobile render without oversized form controls; optional details remain accessible. +- **Verification:** Browser check at `http://127.0.0.1:8788/`. + +--- + +## Scope Boundaries + +- No ad account, CRM, WhatsApp, SMS, email, or payment platform write automation in this fix. +- No storage of business snapshots, optional details, metrics, or generated artifacts. +- No public claims about guaranteed outcomes. +- No new paid onboarding, account provisioning, or subscription logic. + +--- + +## Risks & Dependencies + +- **Weak seed risk:** Some users will submit vague snapshots. Mitigation: prompt the agent to make explicit assumptions and ask only true blocker questions. +- **Over-inference risk:** The model may invent proof or outcomes. Mitigation: preserve the existing unsafe-output filter and no-invented-proof prompt rules. +- **Hidden detail risk:** Power users may miss optional fields. Mitigation: keep the optional drawer visible, short, and clearly labeled. diff --git a/package.json b/package.json index d4b0267d..2d8e3a46 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "scripts": { "dev": "wrangler dev --remote --ip 127.0.0.1 --port 8788 --persist-to /tmp/tinystudio-agent-worker", "check": "node scripts/check-site.mjs", - "test": "npm run check", + "test": "npm run check && npm run test:worker && npm run test:ui", + "test:worker": "node --test scripts/test-agent-worker.mjs", + "test:ui": "node --test scripts/test-agent-ui.mjs", "leads:list": "wrangler d1 execute tinystudio_email_signups --remote --command \"SELECT email, source, page_path, created_at, updated_at FROM email_signups ORDER BY updated_at DESC LIMIT 100;\"", "leads:export": "wrangler d1 execute tinystudio_email_signups --remote --command \"SELECT email, source, page_path, created_at, updated_at FROM email_signups ORDER BY updated_at DESC;\" --json", "migrate:remote": "wrangler d1 migrations apply tinystudio_email_signups --remote", diff --git a/public/index.html b/public/index.html index 15c8c99e..fc3ba8d8 100644 --- a/public/index.html +++ b/public/index.html @@ -6,12 +6,12 @@ TinyStudio Agent Desk @@ -63,7 +63,7 @@

TinyStudio Agent Desk

Build the pipeline system before you buy more ads.

- The agents turn your offer, audience, proof, funnel, follow-up, CRM, and market context into a practical lead-to-call plan. They draft the system; you still approve spend, publishing, and claims. + The agents turn your offer, audience, proof, funnel, follow-up, CRM, weekly numbers, and market context into a Pipeline Brief, Implementation Checklist, and Weekly Fix Report. They draft the system; you still approve spend, publishing, and claims.

Offer Agent @@ -79,8 +79,8 @@

Build the pipeline system before you buy more ads.

-

Pipeline intake

-

No ad account access. No spend changes. Do not paste passwords, API keys, or private ad-platform credentials.

+

Agent workspace

+

Give the agent raw context. It infers the offer, funnel, follow-up, CRM, and weekly decision loop before asking for anything else. No ad account access. No spend changes. Do not paste passwords, API keys, or private ad-platform credentials.

- - - - -
- - - -
- - - - - - - - +
+ + Optional detail pack + Use it only if you already know the numbers or exact setup. + + +
+
+ + + +
+ + + + + + + + + + + +
+ Current weekly numbers optional +
+ + + + + + + +
+ +
+
+
+ +

@@ -148,15 +197,20 @@

Pipeline intake

-

Pipeline Brief

+

Pipeline Brief

+
+ + + +
- +
- Your generated system will appear here: readiness diagnosis, funnel path, audience/pain map, four creative tests, lead form, follow-up, CRM, tracking, and decision rules. + Your generated system will appear here: Pipeline Brief, Implementation Checklist, and Weekly Fix Report.
- +
@@ -170,7 +224,7 @@

Pipeline Brief

Stored - Email and lightweight usage metadata, including daily rate-limit counters. The business brief is processed for the output and is not saved by this app. + Email and lightweight usage metadata, including daily rate-limit counters. The business snapshot, optional details, weekly metrics, and generated artifacts are processed for the output and are not saved by this app.
diff --git a/public/llms.txt b/public/llms.txt index 2f7a2fe7..febe86e5 100644 --- a/public/llms.txt +++ b/public/llms.txt @@ -7,25 +7,21 @@ Contact: hello@tinystudio.io ## Current Product -TinyStudio Agent Desk helps high-ticket coaches, consultants, service businesses, agencies, course sellers, and experts build a lead-to-call pipeline brief. +TinyStudio Agent Desk helps high-ticket coaches, consultants, service businesses, agencies, course sellers, and experts build a lead-to-call pipeline loop. -The public workflow asks for business context, offer, target buyer, proof/assets, market preference, current funnel, follow-up/CRM context, and constraints. +The public workflow requires only email and a compact business snapshot. Offer, target buyer, proof/assets, market preference, current funnel, follow-up/CRM context, optional weekly lead-to-call metrics, bottleneck context, and constraints are optional detail inputs. -Cloudflare Workers AI generates the Pipeline Brief server-side. Client-side code does not call model providers, platform admin APIs, ad accounts, databases, or private credentials directly. +The agents are expected to infer missing context, label assumptions, and ask only true blocker questions when missing information would prevent a useful next step. + +Cloudflare Workers AI generates the Pipeline Brief, Implementation Checklist, and Weekly Fix Report server-side. Client-side code does not call model providers, platform admin APIs, ad accounts, databases, or private credentials directly. ## Agent Outputs The Agent Desk can generate: -- Readiness diagnosis -- Recommended funnel path -- Audience and pain map -- First four creative tests -- Lead qualification form -- Follow-up and setter flow -- CRM and tracking checklist -- 7-day or 15-day decision plan -- Approval gates and risks +- Pipeline Brief with readiness diagnosis, funnel path, audience and pain map, first four creative tests, lead qualification, follow-up, CRM, tracking, decision plan, and approval gates +- Implementation Checklist for setup work across offer, funnel, creative, qualification, follow-up, booking/reminders, CRM stages, tracking events, and review cadence +- Weekly Fix Report that uses supplied weekly metrics when available, otherwise provides contextual first-week guidance plus the metrics to collect ## Safety And Limits @@ -37,6 +33,6 @@ Claims, ad spend, campaign publishing, platform connections, tracking implementa ## Data Handling -The public app stores email signup and lightweight usage metadata in Cloudflare D1, including daily rate-limit counters and a daily IP-derived rate-limit key. The business brief is processed to generate the output and is not stored by this app. +The public app stores email signup and lightweight usage metadata in Cloudflare D1, including daily rate-limit counters and a daily IP-derived rate-limit key. The business snapshot, optional details, weekly metrics, and generated artifacts are processed to generate the output and are not stored by this app. There is no public endpoint for reading collected emails or usage metadata. diff --git a/public/offer.md b/public/offer.md index 90719cc5..866d7d9c 100644 --- a/public/offer.md +++ b/public/offer.md @@ -2,19 +2,17 @@ TinyStudio Agent Desk is a self-serve AI workspace for high-ticket pipeline setup. -It helps build the operating system around an offer before a business spends more on ads: offer diagnosis, funnel path, ad angles, qualification, follow-up, CRM stages, tracking checklist, and decision rules. +It helps build the operating system around an offer before a business spends more on ads: offer diagnosis, funnel path, ad angles, qualification, follow-up, CRM stages, tracking checklist, implementation tasks, and weekly decision rules. + +The customer can start with only an email and a rough business snapshot. The agents infer missing context, state assumptions, and ask only true blocker questions when more information is genuinely needed. ## What It Generates -- Readiness diagnosis -- Recommended funnel path -- Audience and pain map -- First four creative tests -- Lead qualification form -- Follow-up and setter flow -- CRM and tracking checklist -- 7-day or 15-day decision plan -- Approval gates and risks +- Pipeline Brief +- Implementation Checklist +- Weekly Fix Report + +Those artifacts can include readiness diagnosis, recommended funnel path, audience and pain map, first four creative tests, lead qualification form, follow-up and setter flow, CRM and tracking checklist, 7-day or 15-day decision plan, metric tracker, and approval gates. ## What It Does Not Do @@ -26,6 +24,7 @@ It helps build the operating system around an offer before a business spends mor - No ad spend changes - No ad account connection - No prospect message sending +- No storage of submitted business snapshots, optional details, weekly metrics, or generated artifacts - No replacement for a sales team ## Contact diff --git a/public/script.js b/public/script.js index b7a231ef..b71fce9e 100644 --- a/public/script.js +++ b/public/script.js @@ -2,10 +2,17 @@ const agentForm = document.querySelector("[data-agent-form]"); const agentStatus = document.querySelector("[data-agent-status]"); const agentOutput = document.querySelector("[data-agent-output]"); const outputEmpty = document.querySelector("[data-output-empty]"); +const outputTitle = document.querySelector("[data-output-title]"); const copyButton = document.querySelector("[data-copy-output]"); +const outputTabs = [...document.querySelectorAll("[data-output-tab]")]; const EMPTY_OUTPUT_TEXT = - "Your generated system will appear here: readiness diagnosis, funnel path, audience/pain map, four creative tests, lead form, follow-up, CRM, tracking, and decision rules."; + "Your generated system will appear here: Pipeline Brief, Implementation Checklist, and Weekly Fix Report."; +const SECTION_LABELS = { + pipelineBrief: "Pipeline Brief", + implementationChecklist: "Implementation Checklist", + weeklyFixReport: "Weekly Fix Report" +}; const ERROR_MESSAGES = { ai_unavailable: "The AI desk is not available right now. Try again shortly.", cross_site_blocked: "Open TinyStudio.io directly and run the agents from this page.", @@ -13,7 +20,7 @@ const ERROR_MESSAGES = { daily_ip_limit: "This connection has reached today's run limit. Try again tomorrow.", empty_agent_output: "The agents could not produce a useful brief. Try again in a moment.", invalid_email: "Add a valid email address.", - invalid_input: "Add email, business, offer, and target buyer first.", + invalid_input: "Add email and a business snapshot first.", method_not_allowed: "That request type is not supported.", request_too_large: "That intake is too long. Shorten it and run the agents again.", same_origin_required: "Open TinyStudio.io directly and run the agents from this page.", @@ -22,6 +29,12 @@ const ERROR_MESSAGES = { }; let copyResetTimer; +let activeSection = "pipelineBrief"; +let currentSections = { + pipelineBrief: "", + implementationChecklist: "", + weeklyFixReport: "" +}; function setStatus(message, state = "") { if (!agentStatus || !agentForm) return; @@ -39,12 +52,18 @@ function resetCopyButton() { clearTimeout(copyResetTimer); copyResetTimer = undefined; } - copyButton.textContent = "Copy brief"; + copyButton.textContent = "Copy section"; copyButton.disabled = true; } function showEmpty(message = EMPTY_OUTPUT_TEXT) { if (!agentOutput || !outputEmpty) return; + currentSections = { + pipelineBrief: "", + implementationChecklist: "", + weeklyFixReport: "" + }; + setActiveSection("pipelineBrief"); agentOutput.textContent = ""; agentOutput.hidden = true; outputEmpty.textContent = message; @@ -52,13 +71,153 @@ function showEmpty(message = EMPTY_OUTPUT_TEXT) { resetCopyButton(); } -function showBrief(brief) { +function normalizeSections(data) { + const sections = data?.sections || {}; + return { + pipelineBrief: sections.pipelineBrief || data?.brief || "", + implementationChecklist: sections.implementationChecklist || "", + weeklyFixReport: sections.weeklyFixReport || "" + }; +} + +function activeSectionText() { + return currentSections[activeSection] || ""; +} + +function escapeHtml(value) { + return String(value || "") + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function inlineMarkdown(value) { + return escapeHtml(value).replace(/\*\*([^*]+)\*\*/g, "$1"); +} + +function renderTable(rows) { + const parsedRows = rows + .map((row) => row.trim().replace(/^\||\|$/g, "").split("|").map((cell) => cell.trim())) + .filter((cells) => cells.some(Boolean)); + const dataRows = parsedRows.filter((cells) => !cells.every((cell) => /^:?-{3,}:?$/.test(cell))); + if (!dataRows.length) return ""; + + const [head, ...body] = dataRows; + return [ + "", + `${head.map((cell) => ``).join("")}`, + `${body.map((cells) => `${cells.map((cell) => ``).join("")}`).join("")}`, + "
${inlineMarkdown(cell)}
${inlineMarkdown(cell)}
" + ].join(""); +} + +function renderMarkdown(markdown, sectionKey) { + const title = SECTION_LABELS[sectionKey]; + const lines = String(markdown || "").replace(/\r\n/g, "\n").split("\n"); + const html = []; + let listItems = []; + let tableRows = []; + let paragraph = []; + + const flushList = () => { + if (!listItems.length) return; + html.push(``); + listItems = []; + }; + + const flushTable = () => { + if (!tableRows.length) return; + html.push(renderTable(tableRows)); + tableRows = []; + }; + + const flushParagraph = () => { + if (!paragraph.length) return; + html.push(`

${inlineMarkdown(paragraph.join(" "))}

`); + paragraph = []; + }; + + lines.forEach((line, index) => { + const trimmed = line.trim(); + const heading = trimmed.match(/^#{1,6}\s+(.+)$/); + const bullet = trimmed.match(/^[-*]\s+(.+)$/); + + if (!trimmed) { + flushList(); + flushTable(); + flushParagraph(); + return; + } + + if (trimmed.startsWith("|")) { + flushList(); + flushParagraph(); + tableRows.push(trimmed); + return; + } + + flushTable(); + + if (heading) { + flushList(); + flushParagraph(); + if (index === 0 && heading[1].trim() === title) return; + html.push(`

${inlineMarkdown(heading[1])}

`); + return; + } + + if (bullet) { + flushParagraph(); + listItems.push(bullet[1]); + return; + } + + flushList(); + paragraph.push(trimmed); + }); + + flushList(); + flushTable(); + flushParagraph(); + + return html.join("") || `

${EMPTY_OUTPUT_TEXT}

`; +} + +function setActiveSection(sectionKey) { + if (!SECTION_LABELS[sectionKey]) return; + activeSection = sectionKey; + + outputTabs.forEach((tab) => { + const selected = tab.dataset.outputTab === sectionKey; + tab.setAttribute("aria-selected", String(selected)); + tab.tabIndex = selected ? 0 : -1; + }); + + if (outputTitle) { + outputTitle.textContent = SECTION_LABELS[sectionKey]; + } + + if (!agentOutput || !outputEmpty || !copyButton) return; + + const text = activeSectionText(); + agentOutput.innerHTML = renderMarkdown(text, sectionKey); + agentOutput.hidden = !text; + agentOutput.setAttribute("aria-labelledby", `output-tab-${sectionKey}`); + outputEmpty.hidden = Boolean(text); + if (!text) { + outputEmpty.textContent = EMPTY_OUTPUT_TEXT; + } + copyButton.disabled = !text; + copyButton.textContent = "Copy section"; +} + +function showAgentOutput(data) { if (!agentOutput || !outputEmpty || !copyButton) return; - agentOutput.textContent = brief; - agentOutput.hidden = false; + currentSections = normalizeSections(data); outputEmpty.hidden = true; - copyButton.textContent = "Copy brief"; - copyButton.disabled = false; + setActiveSection("pipelineBrief"); } agentForm?.addEventListener("submit", async (event) => { @@ -68,15 +227,15 @@ agentForm?.addEventListener("submit", async (event) => { const formData = new FormData(agentForm); const payload = Object.fromEntries(formData.entries()); - if (!payload.email || !payload.business || !payload.offer || !payload.audience) { - showEmpty("Complete the required fields to generate a fresh pipeline brief."); - setStatus("Add email, business, offer, and target buyer first.", "error"); + if (!payload.email || !payload.business) { + showEmpty("Add the minimum context to generate a fresh pipeline loop."); + setStatus("Add email and a business snapshot first.", "error"); return; } submitButton.disabled = true; - showEmpty("Agents are building a fresh pipeline brief..."); - setStatus("Agents are building the pipeline brief..."); + showEmpty("Agents are building the brief, checklist, and weekly report..."); + setStatus("Agents are building the pipeline loop..."); try { const response = await fetch("/api/agent-audit", { @@ -91,28 +250,55 @@ agentForm?.addEventListener("submit", async (event) => { throw new Error(data.message || ERROR_MESSAGES[data.error] || "The agents could not finish."); } - showBrief(data.brief); - setStatus("Pipeline Brief generated. Review before using anything in campaigns.", "saved"); + showAgentOutput(data); + setStatus("Pipeline loop generated. Review before using anything in campaigns.", "saved"); } catch (error) { - showEmpty("No current brief is available after that failed run. Fix the issue and run the agents again."); + showEmpty("No current output is available after that failed run. Fix the issue and run the agents again."); setStatus(error.message || "That did not work. Try again in a moment.", "error"); } finally { submitButton.disabled = false; } }); +outputTabs.forEach((tab) => { + tab.addEventListener("click", () => setActiveSection(tab.dataset.outputTab)); + tab.addEventListener("keydown", (event) => { + const currentIndex = outputTabs.indexOf(tab); + const lastIndex = outputTabs.length - 1; + let nextIndex = currentIndex; + + if (event.key === "ArrowRight") { + nextIndex = currentIndex === lastIndex ? 0 : currentIndex + 1; + } else if (event.key === "ArrowLeft") { + nextIndex = currentIndex === 0 ? lastIndex : currentIndex - 1; + } else if (event.key === "Home") { + nextIndex = 0; + } else if (event.key === "End") { + nextIndex = lastIndex; + } else { + return; + } + + event.preventDefault(); + const nextTab = outputTabs[nextIndex]; + nextTab.focus(); + setActiveSection(nextTab.dataset.outputTab); + }); +}); + copyButton?.addEventListener("click", async () => { - if (!agentOutput?.textContent) return; + const text = activeSectionText(); + if (!text) return; try { - await navigator.clipboard.writeText(agentOutput.textContent); + await navigator.clipboard.writeText(text); copyButton.textContent = "Copied"; if (copyResetTimer) clearTimeout(copyResetTimer); copyResetTimer = setTimeout(() => { - copyButton.textContent = "Copy brief"; + copyButton.textContent = "Copy section"; copyResetTimer = undefined; }, 1400); } catch { - setStatus("Copy failed. Select the brief and copy manually.", "error"); + setStatus("Copy failed. Select the output and copy manually.", "error"); } }); diff --git a/public/styles.css b/public/styles.css index ec6e736a..cc6e433d 100644 --- a/public/styles.css +++ b/public/styles.css @@ -22,6 +22,7 @@ html { } body { + font-size: 15px; min-width: 320px; min-height: 100vh; margin: 0; @@ -50,10 +51,10 @@ textarea { .agent-shell { display: grid; - gap: 18px; + gap: 14px; width: min(1440px, 100%); margin: 0 auto; - padding: 18px; + padding: 16px; } .app-header, @@ -115,7 +116,7 @@ textarea { .agent-grid { align-items: stretch; - gap: 18px; + gap: 16px; } .agent-copy, @@ -129,9 +130,9 @@ textarea { } .agent-copy { - flex: 0 0 38%; - min-height: 680px; - padding: clamp(24px, 4vw, 56px); + flex: 0 0 34%; + min-height: auto; + padding: clamp(18px, 2.4vw, 34px); } .section-label { @@ -153,45 +154,51 @@ h1 { max-width: 620px; margin: 0; color: var(--ink); - font-size: clamp(48px, 6.4vw, 92px); + font-size: clamp(34px, 3.8vw, 52px); font-weight: 950; - line-height: 0.9; + line-height: 1; letter-spacing: 0; } h2 { margin: 0; color: var(--ink); - font-size: clamp(26px, 2.6vw, 40px); + font-size: clamp(24px, 2.1vw, 34px); font-weight: 950; - line-height: 1; + line-height: 1.05; } .agent-lede { max-width: 620px; - margin: 22px 0 0; + margin: 18px 0 0; color: var(--muted); - font-size: 18px; + font-size: 15px; line-height: 1.48; } .agent-stack { flex-wrap: wrap; gap: 8px; - margin-top: 28px; + margin-top: 22px; } .agent-form { flex: 1; display: grid; - gap: 14px; - padding: clamp(18px, 2.5vw, 30px); + align-content: start; + gap: 10px; + padding: clamp(14px, 1.8vw, 22px); } .form-head { display: grid; - gap: 8px; - margin-bottom: 4px; + gap: 6px; + margin-bottom: 2px; +} + +.form-head h2 { + font-size: clamp(21px, 1.8vw, 26px); + line-height: 1.1; } .form-head p, @@ -200,36 +207,46 @@ h2 { .safety-panel span { margin: 0; color: var(--muted); - font-size: 14px; - line-height: 1.45; + font-size: 13px; + line-height: 1.4; } label { display: grid; - gap: 7px; + gap: 5px; } label span { color: var(--ink); - font-size: 12px; + font-size: 11px; font-weight: 950; text-transform: uppercase; } input, select, -textarea { +textarea, +fieldset { width: 100%; + min-width: 0; +} + +input, +select, +textarea { border: 1px solid var(--line); - border-radius: 8px; - padding: 12px; + border-radius: 7px; + min-height: 40px; + padding: 9px 10px; color: var(--ink); background: #fffdf8; + font-size: 14px; + line-height: 1.35; outline: none; } textarea { - min-height: 86px; + min-height: 68px; resize: vertical; } @@ -241,23 +258,127 @@ textarea:focus { } .field-grid { - gap: 12px; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; } .field-grid label { - flex: 1; + min-width: 0; +} + +.metrics-fieldset { + display: grid; + gap: 10px; + margin: 0; + border: 1px solid var(--line); + border-radius: 7px; + padding: 12px; + background: rgba(238, 235, 226, 0.36); +} + +.metrics-fieldset legend { + padding: 0 6px; + color: var(--ink); + font-size: 11px; + font-weight: 950; + text-transform: uppercase; +} + +.metrics-fieldset legend span { + color: var(--muted); + font-weight: 850; + text-transform: none; +} + +.metric-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 10px; +} + +.metrics-fieldset textarea { + min-height: 58px; +} + +.optional-panel { + display: grid; + gap: 0; + border: 1px solid var(--line); + border-radius: 7px; + background: rgba(238, 235, 226, 0.28); +} + +.optional-panel summary { + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + min-height: 42px; + padding: 0 12px; + list-style-position: inside; +} + +.optional-panel summary:hover, +.optional-panel summary:focus-visible { + background: rgba(12, 139, 99, 0.07); + outline: none; +} + +.optional-panel summary span { + color: var(--ink); + font-size: 11px; + font-weight: 950; + text-transform: uppercase; +} + +.optional-panel summary small { + color: var(--muted); + font-size: 12px; + line-height: 1.3; +} + +.optional-panel[open] summary { + border-bottom: 1px solid var(--line); +} + +.optional-content { + display: grid; + gap: 10px; + padding: 12px; } .agent-form button, .output-actions button { cursor: pointer; - min-height: 46px; + min-height: 42px; border: 1px solid var(--ink); - border-radius: 8px; + border-radius: 7px; padding: 0 16px; color: var(--paper); background: var(--ink); + font-size: 14px; font-weight: 950; + transition: + background 160ms ease, + border-color 160ms ease, + color 160ms ease, + transform 160ms ease, + box-shadow 160ms ease; +} + +.agent-form button:hover, +.agent-form button:focus-visible { + background: #24231f; + box-shadow: 0 8px 24px rgba(22, 22, 20, 0.16); + outline: none; +} + +.agent-form button:active, +.output-actions button:active, +.output-tabs button:active { + transform: translateY(1px); } .agent-form button:disabled, @@ -283,13 +404,15 @@ textarea:focus { .output-panel { align-items: center; justify-content: space-between; - gap: 14px; + gap: 12px; flex-wrap: wrap; min-height: 260px; padding: clamp(18px, 2.5vw, 30px); } .output-panel > div:first-child { + display: grid; + gap: 12px; min-width: min(380px, 100%); } @@ -297,12 +420,63 @@ textarea:focus { gap: 10px; } +.output-tabs { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.output-tabs button { + cursor: pointer; + min-height: 34px; + border: 1px solid var(--line); + border-radius: 7px; + padding: 0 10px; + color: var(--muted); + background: #fffdf8; + font-size: 12px; + font-weight: 950; + transition: + background 160ms ease, + border-color 160ms ease, + color 160ms ease, + transform 160ms ease, + box-shadow 160ms ease; +} + +.output-tabs button[aria-selected="true"] { + border-color: var(--ink); + color: var(--paper); + background: var(--ink); +} + +.output-tabs button:hover, +.output-tabs button:focus-visible { + border-color: var(--green); + color: var(--ink); + box-shadow: 0 0 0 3px rgba(12, 139, 99, 0.1); + outline: none; +} + +.output-tabs button[aria-selected="true"]:hover, +.output-tabs button[aria-selected="true"]:focus-visible { + color: var(--paper); + background: #24231f; +} + .output-actions button { min-height: 40px; color: var(--ink); background: var(--paper); } +.output-actions button:hover, +.output-actions button:focus-visible { + border-color: var(--green); + box-shadow: 0 0 0 3px rgba(12, 139, 99, 0.1); + outline: none; +} + .output-empty, .agent-output { flex: 1 0 100%; @@ -324,7 +498,59 @@ textarea:focus { color: #24231f; font-size: 14px; line-height: 1.55; - white-space: pre-wrap; +} + +.agent-output:focus-visible { + border-color: var(--green); + box-shadow: 0 0 0 3px rgba(12, 139, 99, 0.14); + outline: none; +} + +.agent-output h3 { + margin: 22px 0 8px; + color: var(--ink); + font-size: 15px; + font-weight: 950; +} + +.agent-output h3:first-child { + margin-top: 0; +} + +.agent-output p { + margin: 0 0 12px; +} + +.agent-output ul { + display: grid; + gap: 8px; + margin: 0 0 16px; + padding-left: 20px; +} + +.agent-output li { + padding-left: 2px; +} + +.agent-output table { + width: 100%; + margin: 0 0 16px; + border-collapse: collapse; + font-size: 13px; +} + +.agent-output th, +.agent-output td { + border: 1px solid var(--line); + padding: 8px 10px; + text-align: left; + vertical-align: top; +} + +.agent-output th { + background: rgba(238, 235, 226, 0.72); + color: var(--ink); + font-weight: 950; } .safety-panel { @@ -374,6 +600,14 @@ textarea:focus { flex-basis: auto; min-height: auto; } + + .agent-form { + order: 1; + } + + .agent-copy { + order: 2; + } } @media (max-width: 680px) { @@ -382,13 +616,28 @@ textarea:focus { } .app-header, - .field-grid, + .metric-grid, .output-panel, .agent-footer { align-items: stretch; flex-direction: column; } + .metric-grid { + grid-template-columns: 1fr; + } + + .field-grid { + grid-template-columns: 1fr; + } + + .optional-panel summary { + align-items: flex-start; + flex-direction: column; + gap: 2px; + padding: 10px 12px; + } + .output-panel { flex-wrap: nowrap; } @@ -398,6 +647,6 @@ textarea:focus { } h1 { - font-size: clamp(42px, 13vw, 70px); + font-size: clamp(34px, 9vw, 46px); } } diff --git a/scripts/check-site.mjs b/scripts/check-site.mjs index 679c5a3a..741e3cd7 100644 --- a/scripts/check-site.mjs +++ b/scripts/check-site.mjs @@ -24,9 +24,15 @@ const requiredIndexCopy = [ "Self-serve", "Approval-gated", "data-agent-form", - "Generate Pipeline Brief", + "Generate Pipeline Loop", + "Business snapshot", + "Give the agent raw context", + "Optional detail pack", + "Pipeline Brief, Implementation Checklist, and Weekly Fix Report", + "Current weekly numbers", + "data-output-tab", "No ad account access. No spend changes.", - "The business brief is processed for the output and is not saved by this app.", + "The business snapshot, optional details, weekly metrics, and generated artifacts are processed for the output and are not saved by this app.", "hello@tinystudio.io" ]; @@ -43,24 +49,68 @@ const requiredAgentStack = [ const requiredScriptCopy = [ "/api/agent-audit", + "SECTION_LABELS", + "normalizeSections", + "renderMarkdown", + "escapeHtml", "ERROR_MESSAGES", "showEmpty", "same_origin_required", - "Agents are building the pipeline brief...", - "Pipeline Brief generated", - "Copy brief" + "Add email and a business snapshot first.", + "Agents are building the pipeline loop...", + "Pipeline loop generated", + "Copy section" ]; const requiredWorkerCopy = [ "AGENT_MODELS", + "AGENT_SECTION_HEADINGS", "@cf/mistralai/mistral-small-3.1-24b-instruct", "@cf/openai/gpt-oss-20b", "agentAuditResponse", + "agentInputWithInferredWeeklyMetrics", + "inferWeeklyMetricsFromBusiness", + "splitAgentSections", + "missingAgentSections", + "ensureWeeklyReportContract", + "ensureWeeklyMetricSnapshot", + "appendMetricsToCollect", + "stripUnsupportedMetricValues", + "stripUnsupportedMetricsFromArtifactSections", + "metricLineContainsSuppliedValue", + "metricComparableTokens", + "metricLabelHasValueInClause", + "metricLabelsWithValuesInLine", + "currentMetricPhraseLabels", + "normalizeMetricValueForCompare", + "CURRENCY_AMOUNT_PATTERN", + "WEEKLY_METRIC_LABELS", + "unknownTopLevelHeadings", + "hasUnsafeMatch", + "hasApprovalGate", + "scrubUnsupportedPrecision", + "hasProvidedOfferPrice", + "buildMetricSnapshot", + "buildWeeklyTrackerReport", + "Weekly metrics mode", + "weeklySpend", + "Do the heavy lifting", + "Only include blocker questions", + "Do not invent exact prices", + "Keep assumptions directional", + "Implementation Checklist", + "Weekly Fix Report", "env.AI.run", "MAX_REQUEST_BYTES", + "isAllowedOrigin", + "isLoopbackHostname", + "isLocalPreviewRequest", + "hostHeaderHostname", "validateAgentRequest", "STALE_PUBLIC_PATHS", "unsafeOutputReasons", + "ad account connection", + "crm outcome sync", "agent_usage_limits", "agent_runs", "agent-self-serve", @@ -72,7 +122,7 @@ const requiredWorkerCopy = [ const requiredPublicArtifacts = [ "self-serve AI Agent Desk", - "Cloudflare Workers AI generates the Pipeline Brief server-side", + "Cloudflare Workers AI generates the Pipeline Brief, Implementation Checklist, and Weekly Fix Report server-side", "Client-side code does not call model providers", "does not promise revenue, ROAS, profit, booked calls", "No campaign publishing", @@ -120,6 +170,55 @@ for (const text of requiredPublicArtifacts) { if (!haystack.includes(text)) failures.push(`Missing public artifact copy: ${text}`); } +function formFieldTags(html) { + return [...html.matchAll(/<(input|textarea|select)\b[^>]*>/gi)].map((match) => match[0]); +} + +function fieldName(tag) { + return tag.match(/\bname="([^"]+)"/i)?.[1] || ""; +} + +const formFields = formFieldTags(index); +const requiredFields = formFields + .filter((tag) => /\srequired(?:\s|>|=)/i.test(tag)) + .map(fieldName) + .filter(Boolean) + .sort(); +const expectedRequiredFields = ["business", "email"]; + +if (JSON.stringify(requiredFields) !== JSON.stringify(expectedRequiredFields)) { + failures.push(`Agent Desk must require only email and business fields. Found required fields: ${requiredFields.join(", ") || "none"}`); +} + +for (const optionalName of [ + "market", + "funnel", + "offer", + "audience", + "proof", + "followup", + "constraints", + "weeklySpend", + "rawLeads", + "qualifiedLeads", + "bookedCalls", + "showedCalls", + "closedDeals", + "cashCollected", + "bottleneck" +]) { + const field = formFields.find((tag) => fieldName(tag) === optionalName); + if (!field) { + failures.push(`Missing optional Agent Desk field: ${optionalName}`); + } else if (/\srequired(?:\s|>|=)/i.test(field)) { + failures.push(`Optional Agent Desk field must not be required: ${optionalName}`); + } +} + +if (!index.includes("role=\"tabpanel\"") || !index.includes("aria-labelledby=\"output-tab-pipelineBrief\"")) { + failures.push("Agent output must expose a proper tabpanel relationship."); +} + for (const claim of forbiddenClaims) { const haystack = `${index}\n${script}\n${llms}\n${offer}`.toLowerCase(); if (haystack.includes(claim.toLowerCase())) { @@ -155,6 +254,14 @@ if (!packageJson.includes("\"migrate:remote\"") || !packageJson.includes("d1 mig failures.push("Deploy scripts must include the remote D1 migration command."); } +if (!packageJson.includes("\"test:worker\"") || !packageJson.includes("scripts/test-agent-worker.mjs")) { + failures.push("Worker agent contract tests must be wired into package scripts."); +} + +if (!packageJson.includes("\"test:ui\"") || !packageJson.includes("scripts/test-agent-ui.mjs")) { + failures.push("Agent UI interaction tests must be wired into package scripts."); +} + if (!packageJson.includes("\"dev\": \"wrangler dev --remote")) { failures.push("Dev script must run the Worker preview, not a static-only server."); } diff --git a/scripts/test-agent-ui.mjs b/scripts/test-agent-ui.mjs new file mode 100644 index 00000000..ff72ec81 --- /dev/null +++ b/scripts/test-agent-ui.mjs @@ -0,0 +1,192 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +const SAMPLE_SECTIONS = { + pipelineBrief: "# Pipeline Brief\n\n## Assumptions\n- **Offer**: \n\nBrief body", + implementationChecklist: "# Implementation Checklist\n\n- Checklist body", + weeklyFixReport: "# Weekly Fix Report\n\n| Metric | Current week |\n| --- | --- |\n| Spend | INR 7,000 |\n\nWeekly body" +}; + +class FakeElement { + constructor({ textContent = "", dataset = {}, disabled = false, hidden = false, value = "" } = {}) { + this.textContent = textContent; + this.dataset = dataset; + this.disabled = disabled; + this.hidden = hidden; + this.value = value; + this.tabIndex = 0; + this.attributes = new Map(); + this.listeners = new Map(); + } + + addEventListener(type, listener) { + const listeners = this.listeners.get(type) || []; + listeners.push(listener); + this.listeners.set(type, listeners); + } + + async dispatch(type, event = {}) { + for (const listener of this.listeners.get(type) || []) { + await listener(event); + } + } + + setAttribute(name, value) { + this.attributes.set(name, String(value)); + } + + getAttribute(name) { + if (name.startsWith("data-")) { + const key = name + .slice(5) + .replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()); + return this.dataset[key]; + } + return this.attributes.get(name) ?? null; + } + + focus() { + globalThis.document.activeElement = this; + } +} + +class FakeForm extends FakeElement { + constructor(values, submitButton) { + super(); + this.values = values; + this.submitButton = submitButton; + } + + querySelector(selector) { + if (selector === "button[type='submit']") return this.submitButton; + return null; + } +} + +class FakeFormData { + constructor(form) { + this.form = form; + } + + entries() { + return Object.entries(this.form.values); + } +} + +function setupDom() { + const submitButton = new FakeElement(); + const elements = { + agentStatus: new FakeElement(), + agentOutput: new FakeElement({ hidden: true }), + outputEmpty: new FakeElement({ textContent: "empty" }), + outputTitle: new FakeElement({ textContent: "Pipeline Brief" }), + copyButton: new FakeElement({ disabled: true }), + submitButton + }; + + const formValues = { + email: "nish+ui-test@tinystudio.io", + business: "B2B growth consultant", + weeklySpend: "INR 7000" + }; + elements.agentForm = new FakeForm(formValues, submitButton); + + const tabs = [ + new FakeElement({ textContent: "Brief", dataset: { outputTab: "pipelineBrief" } }), + new FakeElement({ textContent: "Checklist", dataset: { outputTab: "implementationChecklist" } }), + new FakeElement({ textContent: "Weekly Report", dataset: { outputTab: "weeklyFixReport" } }) + ]; + + const selectorMap = new Map([ + ["[data-agent-form]", elements.agentForm], + ["[data-agent-status]", elements.agentStatus], + ["[data-agent-output]", elements.agentOutput], + ["[data-output-empty]", elements.outputEmpty], + ["[data-output-title]", elements.outputTitle], + ["[data-copy-output]", elements.copyButton] + ]); + + globalThis.document = { + activeElement: null, + querySelector(selector) { + return selectorMap.get(selector) || null; + }, + querySelectorAll(selector) { + if (selector === "[data-output-tab]") return tabs; + return []; + } + }; + globalThis.FormData = FakeFormData; + + let clipboardText = ""; + Object.defineProperty(globalThis, "navigator", { + configurable: true, + value: { + clipboard: { + async writeText(text) { + clipboardText = text; + } + } + } + }); + + globalThis.fetch = async (url, options) => { + assert.equal(url, "/api/agent-audit"); + const payload = JSON.parse(options.body); + assert.equal(payload.business, "B2B growth consultant"); + assert.equal(payload.offer, undefined); + assert.equal(payload.audience, undefined); + assert.equal(payload.weeklySpend, "INR 7000"); + return Response.json({ ok: true, sections: SAMPLE_SECTIONS }); + }; + + return { + ...elements, + tabs, + clipboardText: () => clipboardText + }; +} + +async function loadScript() { + const url = new URL("../public/script.js", import.meta.url); + url.searchParams.set("testRun", crypto.randomUUID()); + await import(url.href); +} + +test("agent UI renders generated sections, switches tabs, supports keyboard tabs, and copies the active section", async () => { + const dom = setupDom(); + await loadScript(); + + await dom.agentForm.dispatch("submit", { + preventDefault() {} + }); + + assert.equal(dom.agentStatus.textContent, "Pipeline loop generated. Review before using anything in campaigns."); + assert.equal(dom.outputTitle.textContent, "Pipeline Brief"); + assert.match(dom.agentOutput.innerHTML, /

Assumptions<\/h3>/); + assert.match(dom.agentOutput.innerHTML, /Offer<\/strong>/); + assert.match(dom.agentOutput.innerHTML, /<script>alert\(1\)<\/script>/); + assert.doesNotMatch(dom.agentOutput.innerHTML, / + + +
+
+ + + TinyStudio Agent Desk + +
+ Cloudflare AI + Self-serve + Approval-gated +
+
+ +
+
+ +

Build the pipeline system before you buy more ads.

+

+ The agents turn your offer, audience, proof, funnel, follow-up, CRM, weekly numbers, and market context into a Pipeline Brief, Implementation Checklist, and Weekly Fix Report. They draft the system; you still approve spend, publishing, and claims. +

+
+ Offer Agent + Funnel Agent + Creative Agent + Qualification Agent + Follow-Up Agent + CRM Agent + Tracking Agent + Decision Agent +
+
+ +
+
+

Agent workspace

+

Give the agent raw context. It infers the offer, funnel, follow-up, CRM, and weekly decision loop before asking for anything else. No ad account access. No spend changes. Do not paste passwords, API keys, or private ad-platform credentials.

+
+ + + + + +
+ + Optional detail pack + Use it only if you already know the numbers or exact setup. + + +
+
+ + + +
+ + + + + + + + + + + +
+ Current weekly numbers optional +
+ + + + + + + +
+ +
+
+
+ + +

+
+
+ +
+
+ +

Pipeline Brief

+
+ + + +
+
+
+ +
+
+ Your generated system will appear here: Pipeline Brief, Implementation Checklist, and Weekly Fix Report. +
+ +
+ +
+
+ Automated now + Diagnosis, recommendations, hooks, lead forms, follow-up scripts, CRM stages, tracking checklist, and decision plan. +
+
+ Approval-gated + Claims, ad spend, campaign publishing, platform connections, CRM outcome syncing, and anything compliance-sensitive. +
+
+ Stored + Email and lightweight usage metadata, including daily rate-limit counters. The business snapshot, optional details, weekly metrics, and generated artifacts are processed for the output and are not saved by this app. +
+
+ + +
+ + + + diff --git a/public/agents.html b/public/agents.html new file mode 100644 index 00000000..2a34a858 --- /dev/null +++ b/public/agents.html @@ -0,0 +1,153 @@ + + + + + +The Desk — TinyStudio + + + + +
+ + +
+ The desk +

Seven specialists, one human signature.

+

Each one does a single job and nothing else. They read your actual pages, your actual reviews, your actual numbers — never a generic prompt about your industry. Nothing reaches you until a person has read it and put their name to it.

+
Six audits a month. When the sixth is taken, the intake closes until the next.
+
+ +
+
i
+

Landing Page Fixer

Turns a vague page into one with a clear audience, offer, proof and next action.

+
ReadsYour page, your buyer, your offer details, your reviews, an analytics snapshot, competitor pages.
+
Human-approved
+ +
ii
+

Product Page Fixer

The same discipline applied to the pages that carry a price and a decision.

+
ReadsPage copy, objections, proof assets, pricing context, comparable pages.
+
Human-approved
+ +
iii
+

Site Architecture Fixer

Fixes the order and hierarchy a first-time visitor actually moves through.

+
ReadsSite structure, internal links, search intent, service hierarchy.
+
Human-approved
+ +
iv
+

Ad Angle Generator

Builds testable angles from real customer language — and marks any claim you cannot prove.

+
ReadsReviews, objections, winning and losing ads, competitor hooks, channel notes.
+
Human-approved
+ +
v
+

Competitor Watcher

Turns competitor research into moves you can make — explicitly without copying anyone.

+
ReadsThree to eight competitor URLs, your positioning, reviews and objections.
+
Human-approved
+ +
vi
+

Email & SMS Generator

Writes the follow-up sequence in your voice, from your own proof.

+
ReadsBrand voice, offer, objections, prior sends, customer replies.
+
Human-approved
+ +
vii
+

Weekly Performance Analyst

Turns the week's numbers and what shipped into a report and a next-action list.

+
ReadsWeekly analytics, changes shipped, channel notes, client replies, last week's learnings.
+
Human-approved
+
+ +
+
+

What the desk does

+
    +
  • Research, drafting, quality checks, packaging and routing
  • +
  • Reads your real pages, reviews, ads and numbers
  • +
  • Flags every claim that needs proof before it can be said
  • +
  • Runs on the current frontier model tier, chosen per job
  • +
+
+
+

What it is never allowed to do

+
    +
  • Send anything to anyone
  • +
  • Publish anything to your site
  • +
  • Spend a penny of ad budget
  • +
  • Approve, accept or renew its own work
  • +
+
+
+ +
+

Why this isn't something you can just prompt

+

The models are available to everyone — that is precisely the point. What isn't, is seven purpose-built specialists with fixed inputs and checklists, pointed at your actual business, with a human refusing to let an unproven claim leave the building.

+
+
Specialised, not general
Each agent has one job, a defined input list and a checklist it must satisfy. A general chat window has none of those.
+
Your data, not your industry
They read your reviews, your pages, your numbers. Not a plausible-sounding average of your sector.
+
A person signs it
Fit, claims, client-facing work, delivery and renewal are all human decisions. Automation never gets the last word.
+
+
+ +
+

Every week the leak stays open is a week of traffic you already paid for, walking out.

+

That is the only urgency we will ever sell you. Not a countdown, not a discount that expires — just the plain arithmetic that the visitors arriving today are leaving through the same gap they left through last month.

+
We make no claim about how much that costs you. We will show you where it happens; what it is worth is yours to judge.
+
+ +
+ TinyStudio + The audit · Pricing + No revenue, ranking or booking guarantees. Only the work. +
+
+ + + diff --git a/public/audit.html b/public/audit.html new file mode 100644 index 00000000..98409ae9 --- /dev/null +++ b/public/audit.html @@ -0,0 +1,121 @@ + + + + + +The leak audit — TinyStudio + + + + +
+ + +
+ The leak audit +

We read your page the way a stranger reads it.

+

Four passes, by hand, on the surfaces that decide whether someone becomes a customer or a closed tab. You get a short, plain document — named leaks in the order they cost you money, with the fix beside each one. No deck. No dashboard.

+
+ + + +
+

Your website and your email. Yours to keep whether or not we ever speak again.

+
Six a month. When the sixth is taken, the intake closes until the next.
+
+ +
+
+
13 of 19
+
+

premium clinics across three cities give no price signal on their homepage.

+

We read nineteen of them across London, New York and Miami. Thirteen served a homepage carrying no figure and no promise of one. The more useful finding is that it is not the same leak in every city: Miami mostly publishes prices and mostly has no answers section at all; New York does the reverse, and one clinic asks thirty form fields before it will accept an enquiry. We will hand you the whole study, city by city, before you decide anything.

+
Method: automated read of the served homepage HTML, August 2026 — 7 London, 6 New York, 6 Miami clinics offering longevity, IV or hormone services, followed by a hand audit of one. Inner pages are deliberately not counted; the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper — that is itself one of the leaks.
+
+
+
+ +
+

The four passes

+

Each one is run by a specialist that does only that job, then read and signed by a person before it reaches you.

+
+
i

The money page

+

The one page your revenue depends on, read as a first-time visitor reads it, and the precise points at which they lose interest.

+
ii

The paid creative

+

Your top spend, checked for genuinely distinct concepts rather than one idea wearing ten hooks.

+
iii

The unanswered

+

The questions people need settled before they commit, and whether your site settles a single one of them.

+
iv

The reach

+

Whether new work is reaching new people, or quietly re-serving the audience you already had.

+
+
+ +
+

What lands in your inbox

+

A document, not a call. If you would rather never speak to us, the audit still works.

+
+
Landing page — named leaksIn priority order
+
Enquiry flow — friction countField by field
+
Paid creative — distinct conceptsConcepts, not hooks
+
Pre-purchase questionsAsked vs answered
+
Each findingWith the fix beside it
+
+
+ +
+

Then it is entirely your call

+

Most of what we find is fixable by a competent developer in an afternoon — including one who is not us. If you would rather have it closed than merely found, month one is the 7-Day Sprint — the fix in your hands inside seven working days. $2,500 a month, three-month minimum, with a delivery guarantee behind it. See the terms →

+
+ + +
+

Why there are no logos on this site

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with the clinic down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+
+ +
+ TinyStudio + The desk · Pricing + No revenue, ranking or booking guarantees. Only the work. +
+
+ + + diff --git a/public/brief-requested.html b/public/brief-requested.html new file mode 100644 index 00000000..fa63ce6b --- /dev/null +++ b/public/brief-requested.html @@ -0,0 +1,84 @@ + + + + + +Request received — TinyStudio + + + + + + + + + + +
+ + +
+
+

That's it. Nothing else needed from you.

+

We have your email and we are already looking. No call to book, no form to finish, no onboarding sequence.

+ + + + Back to the site +
+ +
+ TinyStudio + Market research shared freely · client work never published +
+
+ + diff --git a/public/index.html b/public/index.html index fc3ba8d8..9f11d0f8 100644 --- a/public/index.html +++ b/public/index.html @@ -1,239 +1,357 @@ - - - - TinyStudio Agent Desk - - - - - - - - - - - - - - - - - - - - -
-
- - - TinyStudio Agent Desk - -
- Cloudflare AI - Self-serve - Approval-gated -
-
- -
-
- -

Build the pipeline system before you buy more ads.

-

- The agents turn your offer, audience, proof, funnel, follow-up, CRM, weekly numbers, and market context into a Pipeline Brief, Implementation Checklist, and Weekly Fix Report. They draft the system; you still approve spend, publishing, and claims. -

-
- Offer Agent - Funnel Agent - Creative Agent - Qualification Agent - Follow-Up Agent - CRM Agent - Tracking Agent - Decision Agent -
-
- -
-
-

Agent workspace

-

Give the agent raw context. It infers the offer, funnel, follow-up, CRM, and weekly decision loop before asking for anything else. No ad account access. No spend changes. Do not paste passwords, API keys, or private ad-platform credentials.

-
- - - - - -
- - Optional detail pack - Use it only if you already know the numbers or exact setup. - - -
-
- - - -
- - - - - - - - - - - -
- Current weekly numbers optional -
- - - - - - - -
- -
-
-
- - -

-
-
- -
-
- -

Pipeline Brief

-
- - - -
-
-
- -
-
- Your generated system will appear here: Pipeline Brief, Implementation Checklist, and Weekly Fix Report. -
- -
- -
-
- Automated now - Diagnosis, recommendations, hooks, lead forms, follow-up scripts, CRM stages, tracking checklist, and decision plan. -
-
- Approval-gated - Claims, ad spend, campaign publishing, platform connections, CRM outcome syncing, and anything compliance-sensitive. -
-
- Stored - Email and lightweight usage metadata, including daily rate-limit counters. The business snapshot, optional details, weekly metrics, and generated artifacts are processed for the output and are not saved by this app. -
-
- - -
- - - + + + +TinyStudio — The Leak Audit + + + + + +
+ + +
+
The leak audit
+

Most of them leave before they ever get in touch.

+

We read the one page your revenue depends on the way a stranger reads it, and show you the exact points at which they go.

+
+ + + +
+

Your website and your email. That is the whole form — no call needed to receive it.

+
+ + Six a month. When the sixth is taken, the intake closes until the next. +
+
+
Complimentary
And yours to keep
+
Six a month
We look at fit first
+
Five days
From request to findings
+
+
+ +
+
13 of 19
+
+

premium clinics across three cities give no price signal on their homepage.

+

We read nineteen of them across London, New York and Miami. Thirteen served a homepage carrying no figure and no promise of one. The more useful finding is that it is not the same leak in every city: Miami mostly publishes prices and mostly has no answers section at all; New York does the reverse, and one clinic asks thirty form fields before it will accept an enquiry. We will hand you the whole study, city by city, before you decide anything.

+

Method: automated read of the served homepage HTML, August 2026 — 7 London, 6 New York, 6 Miami clinics offering longevity, IV or hormone services, followed by a hand audit of one. Inner pages are deliberately not counted; the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper — that is itself one of the leaks.

+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1 No price, anywhere
+
+
2 One idea, ten hooks
+
3 Nothing answered
+
+
+
+

What a leak audit gives you

+

A short, plain document. Named leaks, in the order they cost you money, with the fix beside each one. No deck, no dashboard, no jargon.

+
+
Landing page — named leaksIn priority order
+
Enquiry flow — friction countField by field
+
Paid creative — distinct conceptsConcepts, not hooks
+
Pre-purchase questionsAsked vs answered
+
+
+
+ +
+
+

What we actually look at

+

Four passes, done by hand, on the surfaces that decide whether a stranger becomes a customer or a closed tab.

+
+
+
i

The money page

+

The one page your revenue depends on, read the way a first-time visitor reads it, and the precise points at which they lose interest.

+
ii

The paid creative

+

Your top spend, checked for genuinely distinct concepts rather than one idea wearing ten hooks.

+
iii

The unanswered

+

The questions people need settled before they commit, and whether your site settles a single one of them.

+
iv

The reach

+

Whether new work is reaching new people, or quietly re-serving the audience you already had.

+
+
+ +
+

How the work runs

+
+
Day 0

Everything on the table

+

Payment, access, the approval owner and the implementation owner, all recorded. The clock starts here and nowhere earlier.

+
Days 1–5

The read

+

The four passes, by hand. You get the named leaks in the order they cost you money.

+
Days 6–14

The fix

+

The highest-leverage page rewritten or rebuilt, plus a dev-ready handoff if your team ships it.

+
Months 2–3

The loop

+

Weekly checks, one revision, and tracking that tells you whether the fix held.

+
+
+ +
+

We work mostly with premium wellness clinics. The audit itself is not fussy — it works on any page carrying real money. If yours does, put your email in and we will take a look.

+ Everyone welcome to ask +
+ +
+
+

Or have it closed, not merely found.

+

The audit is free and yours to keep, whether or not we ever speak again. Should you want the work done, we run a monthly desk. Month one is the 7-Day Sprint — the leak found and closed inside seven working days. Months two and three build the loop that keeps it closed.

+
+
+
$2,500
+
Per month · Three-month minimum
+
Delivery guarantee. If the month-one deliverables are not in your hands within fourteen working days of Day 0, month one is refunded in full. Day 0 begins only once payment, access and both named owners are recorded. If we are waiting on you, the clock pauses.
+
+
+ +
+

Before you ask

+

What exactly do I get for free?

+

A written leak audit on one page of your choosing — named leaks in priority order, with the fix beside each. Yours to keep and to hand to any developer, including one who is not us.

+

Why only six a month?

+

Because they are done by hand and we are also delivering paid work. Six is what we can do properly. When the month is full, you go on the list for the next one.

+

Do you guarantee more customers?

+

No, and be wary of anyone who does. We guarantee the work is delivered on time or month one is refunded. What the market does with a better page is not ours to promise.

+

What if I only want the audit?

+

Then take it and go, with our blessing. It is not bait. Most of what we find is fixable by a competent developer in an afternoon.

+
+ + +
+

Why there are no logos on this site

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with the clinic down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+
+ +
+ TinyStudio + No revenue, ranking or booking guarantees. Only the work. +
+
+ + diff --git a/public/pricing.html b/public/pricing.html new file mode 100644 index 00000000..b8bd74dd --- /dev/null +++ b/public/pricing.html @@ -0,0 +1,137 @@ + + + + + +Pricing & terms — TinyStudio + + + + +
+ + +
+ Pricing & terms +

One price. One way to get your money back.

+

No tiers, no bundles, no discovery-call-to-discuss-pricing. The audit is free. If you want the work done, this is what it costs and this is what happens if we are late.

+
+ +
+
+

The 7-Day Sprint, then the Growth Desk

+
    +
  • Month one — the 7-Day Sprint. The leak audit, then the highest-leverage page rewritten or rebuilt, inside seven working days
  • +
  • A dev-ready handoff if your own team ships it
  • +
  • Search-trust basics: the questions people ask before they commit, answered on the page
  • +
  • Months two and three — weekly checks, one revision, and tracking that says whether the fix held
  • +
  • Seven specialist agents behind it, a person signing every client-facing output
  • +
+
+
+
$2,500
+
Per month · Three-month minimum
+
Delivery guarantee. If the month-one deliverables are not in your hands within fourteen working days of Day 0, month one is refunded in full. Day 0 begins only once payment, access and both named owners are recorded. If we are waiting on you, the clock pauses.
+
+
+ +
+

How the seven days are counted

+

The clock only starts when we genuinely have everything, and it stops whenever the ball is back in your court.

+
+
Day 0

Everything on the table

+

Payment, access, the approval owner and the implementation owner, all recorded. Nothing starts earlier.

+
Days 1–3

The read

+

Four passes by hand. Named leaks, in the order they cost you money.

+
Days 4–7

The fix

+

The page rewritten or rebuilt, plus the handoff. Seven working days from Day 0, in your hands.

+
Months 2–3

The loop

+

Weekly checks, one revision, and tracking that tells you whether the fix held.

+
+
+ +
+

Before you ask

+

Why is the audit free?

+

Because it is the only honest way to show you we can do the work. You keep it either way, and most of what we find is fixable by a competent developer in an afternoon — including one who is not us.

+

Do you guarantee more customers?

+

No, and be wary of anyone who does. We guarantee the work is delivered on time or month one is refunded. What the market does with a better page is not ours to promise.

+

What if I am unhappy but you delivered?

+

Then you tell us, we use the revision, and you leave at the end of the three months. We would rather write that plainly than pretend a satisfaction clause we could not honour.

+

Why only six audits a month?

+

They are done by hand and we are also delivering paid work. Six is what we can do properly. When the month is full you go on the list for the next one.

+

Who actually does the work?

+

Seven specialist agents do the research, drafting and checking. A person reviews fit, every claim, all client-facing work, delivery and renewal. Automation never sends, publishes, spends or approves. See the desk →

+
+ +
+

The audit costs you an email. The rest is a decision you can make later.

+

Six a month, done by hand. When this month's sixth is taken the intake closes until the next one opens.

+
No revenue, ranking, ROAS, conversion, booked-call or sales-volume guarantees. The delivery guarantee above is the only promise we make about outcomes, and it is a promise about our work, not your market.
+
+ + +
+

Why there are no logos on this site

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with the clinic down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+
+ +
+ TinyStudio + The audit · The desk + No revenue, ranking or booking guarantees. Only the work. +
+
+ + + diff --git a/public/shared.css b/public/shared.css new file mode 100644 index 00000000..e06914c2 --- /dev/null +++ b/public/shared.css @@ -0,0 +1,111 @@ +/* TinyStudio — shared design system */ +@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght@0,9..144,200;0,9..144,300;0,9..144,400;1,9..144,200;1,9..144,300&family=Karla:wght@300;400;500;600;700&display=swap'); + +:root{ + --cream:#F6F0E5; --cream-2:#F1E9DB; --cream-3:#EBE1CF; + --ink:#191410; --soft:#5E5346; --muted:#8A7C6B; + --brass:#A47E3C; --brass-lt:#C9A566; --sig:#B04A2E; + --line:rgba(25,20,16,.11); --hair:rgba(25,20,16,.07); + --ease:cubic-bezier(.16,1,.3,1); --slow:1.25s; --mid:.66s; +} +*{box-sizing:border-box;margin:0;padding:0} +html{scroll-behavior:smooth} +body{background:var(--cream);color:var(--ink);font-family:'Karla',sans-serif; + -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:hidden} +body::after{content:'';position:fixed;inset:0;z-index:-2;pointer-events:none; + background: + radial-gradient(76% 48% at 50% 14%, #FFFCF5 0%, rgba(255,252,245,0) 60%), + radial-gradient(44% 36% at 90% 3%, rgba(201,165,102,.14) 0%, transparent 70%), + linear-gradient(180deg,var(--cream) 0%,var(--cream) 38%,var(--cream-2) 100%)} +body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:98;opacity:.19; + 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='.92' numOctaves='4'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E")} + +.wrap{max-width:1200px;margin:0 auto;padding:0 60px} +.sc{font-size:10.5px;letter-spacing:.26em;text-transform:uppercase;font-weight:600} +.fr{font-family:'Fraunces',serif;font-weight:200;letter-spacing:-.028em} + +/* nav */ +nav{display:flex;justify-content:space-between;align-items:center;padding:30px 0 0} +.logo{font-family:'Fraunces',serif;font-size:22px;font-weight:400;letter-spacing:.015em;text-decoration:none;color:var(--ink)} +.logo span{color:var(--brass)} +.navlinks{display:flex;gap:34px;font-size:13.5px;color:var(--muted);font-weight:500;align-items:center} +.navlinks a{color:inherit;text-decoration:none;transition:color var(--mid) var(--ease)} +.navlinks a:hover,.navlinks a[aria-current]{color:var(--ink)} +.navcta{border:1px solid var(--line);border-radius:999px;padding:9px 17px;color:var(--ink)!important; + transition:border-color var(--mid) var(--ease),background var(--mid) var(--ease)} +.navcta:hover{border-color:var(--brass);background:rgba(255,253,248,.7)} + +/* page head */ +.phead{padding:80px 0 0;max-width:900px} +.phead .orn{color:var(--brass);margin-bottom:24px;display:block} +.phead h1{font-family:'Fraunces',serif;font-weight:200;font-size:70px;line-height:1.03;letter-spacing:-.032em} +.phead h1 em{font-style:italic;font-weight:300; + background:linear-gradient(92deg,var(--brass) 12%,#C9A566 58%,var(--brass) 92%); + -webkit-background-clip:text;background-clip:text;color:transparent} +.phead .sub{font-size:18.5px;line-height:1.72;color:var(--soft);margin-top:26px;max-width:56ch} + +/* urgency strip */ +.urg{margin-top:38px;display:inline-flex;align-items:center;gap:14px; + background:rgba(255,253,248,.8);border:1px solid var(--line);border-radius:999px;padding:11px 22px 11px 16px; + box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 14px 34px -24px rgba(25,20,16,.35)} +.urg i{width:7px;height:7px;border-radius:50%;background:var(--sig);display:block;flex-shrink:0; + box-shadow:0 0 0 4px rgba(176,74,46,.14)} +.urg span{font-size:13.5px;color:var(--soft)} +.urg b{color:var(--ink);font-weight:600} + +/* form */ +form.lead{margin:36px 0 0;max-width:500px;display:flex;align-items:center; + background:rgba(255,253,248,.74);border:1px solid var(--line);border-radius:999px;padding:7px 7px 7px 26px; + box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 18px 44px -26px rgba(25,20,16,.34); + transition:box-shadow var(--slow) var(--ease),border-color var(--slow) var(--ease)} +form.lead:hover,form.lead:focus-within{border-color:rgba(164,126,60,.42)} +form.lead input{flex:1;border:0;background:transparent;outline:none;font-family:'Karla',sans-serif; + font-size:16px;color:var(--ink);padding:12px 0} +form.lead input::placeholder{color:#AC9E8B} +form.lead button{border:0;cursor:pointer;border-radius:999px;padding:15px 27px;white-space:nowrap; + font-family:'Karla',sans-serif;font-size:10.5px;letter-spacing:.19em;text-transform:uppercase;font-weight:600; + color:#FBF7EF;background:linear-gradient(168deg,#241D16,#100C09); + box-shadow:0 6px 18px -8px rgba(25,20,16,.6); + transition:transform var(--mid) var(--ease)} +form.lead button:hover{transform:translateY(-1.5px)} +.micro{margin-top:15px;font-size:13.5px;color:var(--muted)} + +/* section */ +section{margin-top:112px;padding-top:66px;border-top:1px solid var(--line)} +section h2{font-family:'Fraunces',serif;font-weight:200;font-size:46px;letter-spacing:-.028em;line-height:1.08;max-width:15ch} +section .lede{margin-top:18px;font-size:16px;line-height:1.75;color:var(--muted);max-width:48ch} + +/* dark band */ +.band{margin-top:104px;background:linear-gradient(168deg,#1C1712,#100C09);color:#F2EADC; + border-radius:16px;padding:58px 62px;position:relative;overflow:hidden; + box-shadow:0 40px 90px -50px rgba(25,20,16,.6)} +.band::after{content:'';position:absolute;inset:0;pointer-events:none; + background:radial-gradient(60% 90% at 88% 8%,rgba(201,165,102,.20),transparent 68%)} +.band>*{position:relative;z-index:2} +.band h2{font-family:'Fraunces',serif;font-weight:200;font-size:38px;line-height:1.16;letter-spacing:-.022em;max-width:20ch} +.band p{margin-top:16px;font-size:15.5px;line-height:1.75;color:#B3A794;max-width:56ch} +.band .note{margin-top:18px;padding-top:15px;border-top:1px solid rgba(242,234,220,.14); + font-size:12px;line-height:1.6;color:#8C8171;max-width:60ch} + +footer{margin-top:108px;padding:30px 0 64px;border-top:1px solid var(--hair); + display:flex;justify-content:space-between;color:var(--muted);gap:30px;flex-wrap:wrap} +footer a{color:inherit;text-decoration:none} +footer a:hover{color:var(--ink)} + +@media (prefers-reduced-motion:no-preference){ + [data-r]{opacity:0;transform:translateY(22px); + transition:opacity var(--slow) var(--ease),transform var(--slow) var(--ease)} + [data-r].in{opacity:1;transform:none} +} + +/* two-field lead form — website + email, still one line on desktop */ +form.lead.two{max-width:660px;padding:7px 7px 7px 0;overflow:hidden} +form.lead.two input{flex:1 1 0;min-width:0;padding:12px 18px 12px 24px} +form.lead.two input:first-child{border-right:1px solid var(--line)} +form.lead.two button{flex:0 0 auto} +@media (max-width:760px){ + form.lead.two{flex-direction:column;align-items:stretch;border-radius:20px;padding:8px;max-width:100%} + form.lead.two input{padding:14px 18px} + form.lead.two input:first-child{border-right:0;border-bottom:1px solid var(--line)} + form.lead.two button{width:100%} +} diff --git a/public/specimen.html b/public/specimen.html new file mode 100644 index 00000000..3a2cf2af --- /dev/null +++ b/public/specimen.html @@ -0,0 +1,120 @@ + + + + + +Leak audit — specimen — TinyStudio + + + + +
+ + +
+
+ Leak audit · specimen + Client name withheld +
+ +
+

Four ways this clinic loses people who were already sold.

+

A real audit with the name removed. This clinic does better work than its homepage admits — which is the expensive kind of problem, because the fix costs almost nothing.

+
+ +
+
i

The fee list is excellent, and the homepage never mentions it exists

+
+

They publish a full, line-by-line fee list — fifteen prices, framed around their own stated values of "simplicity and transparency." It is genuinely better than most of the market. A visitor comparing three clinics on a first pass will never see it.

+
EvidenceHomepage: no figure and no promise of one anywhere in the served page. The word "Fees" appears once, as one item in a top navigation carrying fourteen-plus links. The fee page itself carries fifteen prices from £72 to £795.
+

Fix — one line in the hero: consultations from £195, with the fee list linked. The asset already exists; it is one sentence away from doing its job.

+
+
+ +
+
ii

The headline could belong to any clinic in the country

+
+

"We help you to thrive at every stage of life." Nobody is named. No condition, no mechanism, no reason this clinic rather than another. Meanwhile the navigation carries five genuinely specific service lines that would each earn a stranger's attention.

+
EvidenceH1: "We help you to thrive at every stage of life." Navigation: Functional Medicine, Hormones, Longevity Medicine, IV Drips, Aesthetics — plus named conditions including adrenal fatigue, autoimmune disease and chronic fatigue syndrome.
+

Fix — the specificity is already in the navigation. Promote it into the first screen and the page starts speaking to someone.

+
+
+ +
+
iii

Two front doors, asking for different levels of commitment

+
+

"Book discovery call" sits in the hero. "Start Your Longevity Medicine Journey" appears three times and deep-links to a specific paid consultation. One is a low-commitment conversation, the other is a purchase decision. A visitor cannot tell which is the intended path, so some take neither.

+
EvidenceHero CTA at 588px: "Book discovery call" → /book-an-appointment/. Repeated CTA: "Start Your Longevity Medicine Journey" → /longevity-medicine/longevity-medicine-foundation-consultation. Ten booking-intent links on the page in total.
+

Fix — pick one primary action for the homepage and demote the other to the service pages where its context exists.

+
+
+ +
+
iv

The proof is real, and it is on the wrong page

+
+

Fourteen years, thousands of patients, GMC-registered doctors, CQC regulated. That is exactly what a nervous first-time visitor needs before they will book a private medical appointment. It sits on the fees page, which they reach after deciding to look.

+
EvidenceTrust markers found on /fees, not on the homepage. No answers or FAQ section detected on either page — the questions people ask before booking are not settled anywhere on the site.
+

Fix — move the credentials above the fold, and add the eight questions people ask before booking. Both are content that already exists internally.

+
+
+ +
+

Two passes not run

+

The paid-creative pass and the reach pass need access to the advertising account, which we do not have and did not seek. They are part of the full audit when a clinic asks for one. We would rather leave them visibly blank than guess — every finding above comes from something we actually observed.

+
+ +
+

Confidentiality

+

This clinic is not a client and has not been named. Client work is never published — no logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us. That is why there is no wall of names on this site, and there never will be.

The nineteen-clinic study behind this audit is a different thing entirely: public homepages, no client relationship, and yours on request in full — London, New York and Miami. Research we will hand over; client work we will not.

+
+
+ +
+ TinyStudio + The audit · Pricing + No revenue, ranking or booking guarantees. Only the work. +
+
+ + + diff --git a/scripts/check-site.mjs b/scripts/check-site.mjs index 741e3cd7..5de55dbd 100644 --- a/scripts/check-site.mjs +++ b/scripts/check-site.mjs @@ -3,7 +3,9 @@ import { execFileSync } from "node:child_process"; const read = (path) => readFileSync(new URL(`../${path}`, import.meta.url), "utf8"); -const index = read("public/index.html"); +// The Agent Desk moved to /agent-desk when the leak-audit site took the root. +// These checks are about the Desk's markup, so they follow it. +const index = read("public/agent-desk.html"); const styles = read("public/styles.css"); const script = read("public/script.js"); const llms = read("public/llms.txt"); From 963d76bf1cd5cf88d073fa17301b26adda987b7f Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Wed, 5 Aug 2026 23:08:24 +0530 Subject: [PATCH 07/19] feat: wire the brief request end to end MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - htmlRedirect() sends success to /brief-requested so the no-JS path reaches the only page that fires the conversion tag; failures still return to the form - signups now capture the website alongside the email — a brief request without a URL is not actionable - normalizeWebsite() adds a scheme, lowercases the host, requires a dot in the hostname, caps at 300 chars and rejects non-http(s) schemes; returns null rather than throwing - migration 0004 adds the nullable website column - forms post to /api/signups, not /api/agent-audit, which requires a business snapshot the leak-audit form does not collect and would have rejected on every submission - placeholder is yourwebsite.com, not yourclinic.com — the conversion field is the worst place to tell a non-clinic visitor they are in the wrong shop check and test both pass. --- migrations/0004_signup_website.sql | 4 +++ public/audit.html | 6 ++--- public/index.html | 6 ++--- public/pricing.html | 2 +- src/worker.js | 40 ++++++++++++++++++++++++------ 5 files changed, 44 insertions(+), 14 deletions(-) create mode 100644 migrations/0004_signup_website.sql diff --git a/migrations/0004_signup_website.sql b/migrations/0004_signup_website.sql new file mode 100644 index 00000000..75829f4c --- /dev/null +++ b/migrations/0004_signup_website.sql @@ -0,0 +1,4 @@ +-- The leak-audit form collects the site to be audited alongside the email. +-- Without the URL a brief request is not actionable — there is nothing to read. +-- Nullable so existing rows and the Agent Desk signup path stay valid. +ALTER TABLE email_signups ADD COLUMN website TEXT; diff --git a/public/audit.html b/public/audit.html index 98409ae9..f488ee2a 100644 --- a/public/audit.html +++ b/public/audit.html @@ -42,8 +42,8 @@ The leak audit

We read your page the way a stranger reads it.

Four passes, by hand, on the surfaces that decide whether someone becomes a customer or a closed tab. You get a short, plain document — named leaks in the order they cost you money, with the fix beside each one. No deck. No dashboard.

-
- + +
@@ -97,7 +97,7 @@

Then it is entirely your call

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with the clinic down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/index.html b/public/index.html index 9440ea04..0dddb0b4 100644 --- a/public/index.html +++ b/public/index.html @@ -189,6 +189,20 @@ form.two input:first-of-type{border-right:0;border-bottom:1px solid var(--line)} form.two button{width:100%} } + /* who signs it */ + .signer{margin-top:112px;padding-top:66px;border-top:1px solid var(--line); + display:grid;grid-template-columns:auto 1fr;gap:44px;align-items:start} + .sig-portrait{width:120px;height:120px;border-radius:50%;flex-shrink:0; + background:linear-gradient(168deg,#FFFDF8,#E8DDC9);border:1px solid var(--line); + display:flex;align-items:center;justify-content:center; + box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 22px 46px -30px rgba(25,20,16,.4)} + .sig-portrait span{font-family:'Fraunces',serif;font-size:15px;letter-spacing:.22em;color:var(--brass)} + .sig-portrait img{width:120px;height:120px;border-radius:50%;object-fit:cover;display:block} + .sig-body h2{font-family:'Fraunces',serif;font-weight:200;font-size:40px;letter-spacing:-.028em; + line-height:1.1;margin-top:14px} + .sig-body p{margin-top:20px;font-size:16.5px;line-height:1.75;color:var(--soft);max-width:58ch} + .sig-note:empty{display:none} + @media (max-width:760px){.signer{grid-template-columns:1fr;gap:26px}} @@ -205,9 +219,9 @@

Most of them leave before they ever get in touch.

- +
-

Your website and your email. That is the whole form — no call needed to receive it.

+

Thirty seconds to ask. Findings inside five working days. No call at any point.

Six a month. When the sixth is taken, the intake closes until the next. @@ -297,6 +311,21 @@

How the work runs

Everyone welcome to ask + +
+ +
+ Who signs it +

There is one person behind the desk, and it is not a department.

+

My name is Nish. I read every audit before it leaves, I decide what we will and will not claim, and I am the one who owes you a refund if the work is late. The seven agents do the research and the drafting. The judgement, and the liability, are mine.

+

+
+
+

Or have it closed, not merely found.

diff --git a/public/specimen.html b/public/specimen.html index 3a2cf2af..e4a9295c 100644 --- a/public/specimen.html +++ b/public/specimen.html @@ -44,7 +44,7 @@
Leak audit · specimen - Client name withheld + Read and signed by Nish  ·  Client name withheld
From f66187ae1cc0fdd1b8de28bdb3564da1714d41bf Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Wed, 5 Aug 2026 23:44:00 +0530 Subject: [PATCH 09/19] fix: extend PUBLIC_ASSET_PATHS so the new pages resolve The Worker gates static assets behind an explicit allowlist and runs first for every path, so the six new pages and the shared stylesheet all returned 404 on the first deploy despite uploading correctly. Adds each page with and without its .html extension, because the signup redirect targets /brief-requested and Cloudflare maps extensionless paths to the .html twin. Verified live: every page returns 200, /api/signups redirects to /brief-requested, and a submission writes to production D1 with the website captured. Test rows removed afterwards. --- src/worker.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/worker.js b/src/worker.js index 978e53e5..679bb423 100644 --- a/src/worker.js +++ b/src/worker.js @@ -13,6 +13,22 @@ const SECURITY_HEADERS = { const PUBLIC_ASSET_PATHS = new Set([ "/", "/index.html", + // Leak-audit site. Extensionless twins are listed because the signup + // redirect targets /brief-requested and Cloudflare serves the .html for it. + "/audit.html", + "/audit", + "/agents.html", + "/agents", + "/pricing.html", + "/pricing", + "/specimen.html", + "/specimen", + "/brief-requested.html", + "/brief-requested", + "/shared.css", + // The Agent Desk, now the engine behind the free brief rather than the product. + "/agent-desk.html", + "/agent-desk", "/styles.css", "/script.js", "/favicon.svg", From 3a7cbc0c66e7bac21ff52002a2389fb561f998c6 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:04:29 +0530 Subject: [PATCH 10/19] fix: CSP-safe assets, and no public delivery promise shorter than the guarantee Rendering: the CSP is style-src self, which blocks inline +
@@ -138,16 +107,7 @@

Every week the leak stays open is a week of traffic you already paid for, wa No revenue, ranking or booking guarantees. Only the work.

- + + diff --git a/public/agents.js b/public/agents.js new file mode 100644 index 00000000..5ccab0bc --- /dev/null +++ b/public/agents.js @@ -0,0 +1,9 @@ +(function(){var S='.phead h1,.phead .sub,.urg,.ag,.gatebox,section h2,section .lede,.stack div,.band'; +function r(){var e=[].slice.call(document.querySelectorAll(S));e.forEach(function(x){x.setAttribute('data-r','')}); +var io=new IntersectionObserver(function(en){en.forEach(function(v){if(!v.isIntersecting)return; +var s=[].slice.call(v.target.parentNode.children).filter(function(n){return n.hasAttribute&&n.hasAttribute('data-r')}); +v.target.style.transitionDelay=Math.min(Math.max(0,s.indexOf(v.target)),6)*90+'ms'; +v.target.classList.add('in');io.unobserve(v.target)})},{threshold:.08,rootMargin:'0px 0px -5% 0px'}); +e.forEach(function(x){io.observe(x)}); +setTimeout(function(){document.querySelectorAll('[data-r]').forEach(function(x){x.classList.add('in')})},1600)} +document.readyState==='loading'?document.addEventListener('DOMContentLoaded',r):r()})(); diff --git a/public/audit.css b/public/audit.css new file mode 100644 index 00000000..5bde2b26 --- /dev/null +++ b/public/audit.css @@ -0,0 +1,21 @@ +.stat{font-family:'Fraunces',serif;font-weight:200;font-size:128px;line-height:.84; + letter-spacing:-.05em;color:#F2EADC;white-space:nowrap} + .stat s{text-decoration:none;color:var(--brass-lt)} + .bandgrid{display:grid;grid-template-columns:auto 1fr;gap:66px;align-items:center} + .checks{margin-top:54px;display:grid;grid-template-columns:repeat(4,1fr)} + .check{padding-right:32px;border-right:1px solid var(--hair)} + .check:last-child{border-right:0;padding-right:0} + .check:not(:first-child){padding-left:32px} + .num{font-family:'Fraunces',serif;font-style:italic;font-weight:200;font-size:36px;color:var(--brass-lt); + margin-bottom:14px;line-height:1} + .check h3{font-size:16.5px;font-weight:600;margin-bottom:9px;letter-spacing:-.008em} + .check p{font-size:14.5px;line-height:1.7;color:var(--muted)} + .rows{margin-top:34px;max-width:760px} + .row{display:flex;justify-content:space-between;align-items:baseline;gap:22px;padding:16px 0; + border-bottom:1px solid var(--hair);transition:padding-left var(--mid) var(--ease)} + .row:hover{padding-left:9px} + .row .t{font-size:15.5px;color:var(--soft)} + .row .v{font-family:'Fraunces',serif;font-size:15px;color:var(--brass);white-space:nowrap} + +/* extracted from inline style attributes — CSP style-src 'self' blocks those */ +.xa1{color:var(--brass)} diff --git a/public/audit.html b/public/audit.html index dc595512..9d4b3b07 100644 --- a/public/audit.html +++ b/public/audit.html @@ -5,26 +5,8 @@ The leak audit — TinyStudio - + +
@@ -55,8 +37,8 @@

We read your page the way a stranger reads it.

13 of 19
-

premium clinics across three cities give no price signal on their homepage.

-

We read nineteen of them across London, New York and Miami. Thirteen served a homepage carrying no figure and no promise of one. The more useful finding is that it is not the same leak in every city: Miami mostly publishes prices and mostly has no answers section at all; New York does the reverse, and one clinic asks thirty form fields before it will accept an enquiry. We will hand you the whole study, city by city, before you decide anything.

+

premium websites across three cities give no price signal on their homepage.

+

We read nineteen of them across London, New York and Miami. Thirteen served a homepage carrying no figure and no promise of one. The more useful finding is that it is not the same leak in every city: Miami mostly publishes prices and mostly has no answers section at all; New York does the reverse, and one of them asks thirty form fields before it will accept an enquiry. We will hand you the whole study, city by city, before you decide anything.

Method: automated read of the served homepage HTML, August 2026 — 7 London, 6 New York, 6 Miami clinics offering longevity, IV or hormone services, followed by a hand audit of one. Inner pages are deliberately not counted; the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper — that is itself one of the leaks.
@@ -91,13 +73,13 @@

What lands in your inbox

Then it is entirely your call

-

Most of what we find is fixable by a competent developer in an afternoon — including one who is not us. If you would rather have it closed than merely found, month one is the 7-Day Sprint — the fix in your hands inside seven working days. $2,500 a month, three-month minimum, with a delivery guarantee behind it. See the terms →

+

Most of what we find is fixable by a competent developer in an afternoon — including one who is not us. If you would rather have it closed than merely found, the desk runs at $2,500 a month on a three-month minimum, with a delivery guarantee behind it. See the terms →

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

@@ -106,16 +88,7 @@

Why there are no logos on this site

No revenue, ranking or booking guarantees. Only the work.
- + + diff --git a/public/audit.js b/public/audit.js new file mode 100644 index 00000000..380d17fe --- /dev/null +++ b/public/audit.js @@ -0,0 +1,9 @@ +(function(){var S='.phead h1,.phead .sub,form.lead,.micro,.urg,.band,section h2,section .lede,.check,.row'; +function r(){var e=[].slice.call(document.querySelectorAll(S));e.forEach(function(x){x.setAttribute('data-r','')}); +var io=new IntersectionObserver(function(en){en.forEach(function(v){if(!v.isIntersecting)return; +var s=[].slice.call(v.target.parentNode.children).filter(function(n){return n.hasAttribute&&n.hasAttribute('data-r')}); +v.target.style.transitionDelay=Math.min(Math.max(0,s.indexOf(v.target)),6)*90+'ms'; +v.target.classList.add('in');io.unobserve(v.target)})},{threshold:.08,rootMargin:'0px 0px -5% 0px'}); +e.forEach(function(x){io.observe(x)}); +setTimeout(function(){document.querySelectorAll('[data-r]').forEach(function(x){x.classList.add('in')})},1600)} +document.readyState==='loading'?document.addEventListener('DOMContentLoaded',r):r()})(); diff --git a/public/brief-requested.css b/public/brief-requested.css new file mode 100644 index 00000000..131f22af --- /dev/null +++ b/public/brief-requested.css @@ -0,0 +1,17 @@ +.done{max-width:660px;margin:0 auto;padding:118px 0 0;text-align:center} + .tick{width:56px;height:56px;margin:0 auto 30px;border-radius:50%;border:1px solid var(--brass); + display:flex;align-items:center;justify-content:center;color:var(--brass);font-size:22px; + background:rgba(255,253,248,.7);box-shadow:0 1px 0 rgba(255,255,255,.9) inset, + 0 16px 38px -26px rgba(25,20,16,.35)} + .done h1{font-family:'Fraunces',serif;font-weight:200;font-size:60px;line-height:1.04;letter-spacing:-.032em} + .done h1 em{font-style:italic;font-weight:300;color:var(--brass)} + .done .sub{font-size:18px;line-height:1.72;color:var(--soft);margin:26px auto 0;max-width:46ch} + .next{margin:52px auto 0;max-width:560px;text-align:left;border-top:1px solid var(--line);padding-top:30px} + .step{display:grid;grid-template-columns:34px 1fr;gap:18px;padding:16px 0;border-bottom:1px solid var(--hair)} + .step:last-child{border-bottom:0} + .step .n{font-family:'Fraunces',serif;font-style:italic;font-weight:200;font-size:24px;color:var(--brass-lt);line-height:1.2} + .step h3{font-size:16.5px;font-weight:600;letter-spacing:-.008em} + .step p{margin-top:6px;font-size:15px;line-height:1.68;color:var(--muted)} + .back{margin-top:44px;display:inline-block;font-size:13.5px;color:var(--muted);text-decoration:none; + border-bottom:1px solid var(--line);padding-bottom:3px} + .back:hover{color:var(--ink);border-color:var(--brass)} diff --git a/public/brief-requested.html b/public/brief-requested.html index fa63ce6b..cf90fc8f 100644 --- a/public/brief-requested.html +++ b/public/brief-requested.html @@ -14,35 +14,10 @@ is noindex so it can never be reached organically. ────────────────────────────────────────────────────────────────────────── --> - - + + +
@@ -80,5 +55,6 @@

That's it. Nothing else needed from you.

Market research shared freely · client work never published
+ diff --git a/public/brief-requested.js b/public/brief-requested.js new file mode 100644 index 00000000..f0e1bc73 --- /dev/null +++ b/public/brief-requested.js @@ -0,0 +1,7 @@ +window.dataLayer = window.dataLayer || []; + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', 'AW-XXXXXXXXX'); + gtag('event', 'conversion', { + 'send_to': 'AW-XXXXXXXXX/YYYYYYYYYYYYYYYYYYY' + }); diff --git a/public/index.css b/public/index.css new file mode 100644 index 00000000..ec66bc42 --- /dev/null +++ b/public/index.css @@ -0,0 +1,218 @@ +:root{ + --cream:#F6F0E5; --cream-2:#F1E9DB; --cream-3:#EBE1CF; + --ink:#191410; --soft:#5E5346; --muted:#8A7C6B; + --brass:#A47E3C; --brass-lt:#C9A566; --sig:#B04A2E; + --dark:#171310; + --line:rgba(25,20,16,.11); --hair:rgba(25,20,16,.07); + --ease:cubic-bezier(.16,1,.3,1); --slow:1.25s; --mid:.66s; + } + *{box-sizing:border-box;margin:0;padding:0} + html{scroll-behavior:smooth} + body{background:var(--cream);color:var(--ink);font-family:'Karla',sans-serif; + -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;overflow-x:hidden} + body::after{content:'';position:fixed;inset:0;z-index:-2;pointer-events:none; + background: + radial-gradient(78% 50% at 50% 16%, #FFFCF5 0%, rgba(255,252,245,0) 60%), + radial-gradient(46% 38% at 90% 4%, rgba(201,165,102,.14) 0%, transparent 70%), + linear-gradient(180deg,var(--cream) 0%,var(--cream) 40%,var(--cream-2) 100%)} + body::before{content:'';position:fixed;inset:0;pointer-events:none;z-index:98;opacity:.19; + 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='.92' numOctaves='4'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E")} + .wrap{max-width:1200px;margin:0 auto;padding:0 60px} + .sc{font-size:10.5px;letter-spacing:.26em;text-transform:uppercase;font-weight:600} + .fr{font-family:'Fraunces',serif} + + nav{display:flex;justify-content:space-between;align-items:center;padding:30px 0 0} + .logo{font-family:'Fraunces',serif;font-size:22px;font-weight:400;letter-spacing:.015em} + .logo span{color:var(--brass)} + .navlinks{display:flex;gap:36px;font-size:13.5px;color:var(--muted);font-weight:500} + .navlinks a{color:inherit;text-decoration:none;transition:color var(--mid) var(--ease)} + .navlinks a:hover{color:var(--ink)} + + /* hero */ + header{padding:92px 0 0;text-align:center;max-width:980px;margin:0 auto} + .orn{display:flex;align-items:center;justify-content:center;gap:15px;margin-bottom:28px;color:var(--brass)} + .orn i{display:block;width:44px;height:1px;background:linear-gradient(90deg,transparent,var(--brass-lt))} + .orn i:last-child{background:linear-gradient(90deg,var(--brass-lt),transparent)} + .orn b{width:4px;height:4px;transform:rotate(45deg);background:var(--brass);display:block} + h1{font-family:'Fraunces',serif;font-weight:200;font-size:84px;line-height:1.02;letter-spacing:-.032em} + h1 em{font-style:italic;font-weight:300; + background:linear-gradient(92deg,var(--brass) 12%,#C9A566 58%,var(--brass) 92%); + -webkit-background-clip:text;background-clip:text;color:transparent} + .sub{font-size:18.5px;line-height:1.72;color:var(--soft);margin:28px auto 0;max-width:530px} + form{margin:42px auto 0;max-width:500px;display:flex;align-items:center; + background:rgba(255,253,248,.74);border:1px solid var(--line);border-radius:999px;padding:7px 7px 7px 26px; + box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 18px 44px -26px rgba(25,20,16,.34); + transition:box-shadow var(--slow) var(--ease),border-color var(--slow) var(--ease)} + form:hover,form:focus-within{border-color:rgba(164,126,60,.42); + box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 26px 56px -26px rgba(25,20,16,.38)} + form input{flex:1;border:0;background:transparent;outline:none;font-family:'Karla',sans-serif; + font-size:16px;color:var(--ink);padding:12px 0} + form input::placeholder{color:#AC9E8B} + form button{border:0;cursor:pointer;border-radius:999px;padding:15px 20px;white-space:nowrap; + font-family:'Karla',sans-serif;font-size:10px;letter-spacing:.14em;text-transform:uppercase;font-weight:600; + color:#FBF7EF;background:linear-gradient(168deg,#241D16,#100C09); + box-shadow:0 6px 18px -8px rgba(25,20,16,.6); + transition:transform var(--mid) var(--ease),box-shadow var(--mid) var(--ease)} + form button:hover{transform:translateY(-1.5px)} + .micro{margin-top:16px;font-size:13.5px;color:var(--muted)} + .meta{margin:54px auto 0;max-width:900px;display:grid;grid-template-columns:repeat(3,1fr); + border-top:1px solid var(--hair);padding-top:22px} + .meta div{text-align:center;border-right:1px solid var(--hair)} + .meta div:last-child{border-right:0} + .meta .k{font-family:'Fraunces',serif;font-size:24px;font-weight:300;letter-spacing:-.02em} + .meta .v{margin-top:6px;color:var(--muted);font-size:10px;letter-spacing:.14em;text-transform:uppercase;font-weight:600} + + /* ── the finding: dark band, real number ───────── */ + .finding{margin-top:110px;background:linear-gradient(168deg,#1C1712,#100C09);color:#F2EADC; + border-radius:16px;padding:64px 66px;display:grid;grid-template-columns:auto 1fr;gap:70px;align-items:center; + box-shadow:0 40px 90px -50px rgba(25,20,16,.6);position:relative;overflow:hidden} + .finding::after{content:'';position:absolute;inset:0;pointer-events:none; + background:radial-gradient(60% 90% at 88% 10%,rgba(201,165,102,.20),transparent 68%)} + .finding .stat{font-family:'Fraunces',serif;font-weight:200;font-size:132px;line-height:.84; + letter-spacing:-.05em;color:#F2EADC;white-space:nowrap;position:relative;z-index:2} + .finding .stat s{text-decoration:none;color:var(--brass-lt)} + .finding .body{position:relative;z-index:2} + .finding h2{font-family:'Fraunces',serif;font-weight:200;font-size:34px;line-height:1.22;letter-spacing:-.02em;max-width:22ch} + .finding p{margin-top:18px;font-size:15.5px;line-height:1.75;color:#B3A794;max-width:52ch} + .finding .note{margin-top:20px;padding-top:16px;border-top:1px solid rgba(242,234,220,.14); + font-size:12px;line-height:1.6;color:#8C8171;max-width:56ch} + + /* ── annotated specimen ────────────────────────── */ + .spec{margin-top:112px;display:grid;grid-template-columns:1.06fr .94fr;gap:56px;align-items:start} + .browser{background:linear-gradient(176deg,#FFFDF8,#F7F1E6);border:1px solid var(--line);border-radius:10px; + overflow:hidden;position:relative;box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 40px 84px -46px rgba(25,20,16,.34); + transition:transform var(--slow) var(--ease),box-shadow var(--slow) var(--ease)} + .browser:hover{transform:translateY(-6px)} + .chrome{display:flex;gap:6px;padding:12px 15px;border-bottom:1px solid var(--hair);background:rgba(235,225,207,.5)} + .dot{width:8px;height:8px;border-radius:50%;background:#D6C9B2} + .mock{padding:30px 30px 42px;position:relative} + .bar{background:#E4DACA;border-radius:3px} + .flag{position:absolute;display:flex;align-items:center;gap:8px;font-family:'Karla',sans-serif; + font-size:9px;letter-spacing:.16em;text-transform:uppercase;font-weight:700;color:var(--sig);white-space:nowrap} + .pin{width:17px;height:17px;border-radius:50%;border:1px solid var(--sig);display:flex; + align-items:center;justify-content:center;font-size:9px;background:rgba(176,74,46,.09)} + .lk{position:absolute;height:1px;background:var(--sig);opacity:.42} + .specbody h2{font-family:'Fraunces',serif;font-weight:200;font-size:42px;letter-spacing:-.028em;line-height:1.1;max-width:14ch} + .specbody>p{margin-top:18px;font-size:15.5px;line-height:1.75;color:var(--muted);max-width:44ch} + .rows{margin-top:32px} + .row{display:flex;justify-content:space-between;align-items:baseline;gap:22px;padding:15px 0; + border-bottom:1px solid var(--hair);transition:padding-left var(--mid) var(--ease)} + .row:hover{padding-left:9px} + .row .t{font-size:15.5px;color:var(--soft)} + .row .v{font-family:'Fraunces',serif;font-size:15px;color:var(--brass);white-space:nowrap} + + /* ── checks ────────────────────────────────────── */ + .checks{margin-top:124px;padding-top:70px;border-top:1px solid var(--line)} + .lead{display:flex;justify-content:space-between;align-items:flex-end;gap:70px} + .checks h2{font-family:'Fraunces',serif;font-weight:200;font-size:47px;letter-spacing:-.028em;max-width:12ch;line-height:1.08} + .lead p{max-width:38ch;font-size:15.5px;line-height:1.75;color:var(--muted)} + .checkgrid{margin-top:56px;display:grid;grid-template-columns:repeat(4,1fr)} + .check{padding-right:34px;border-right:1px solid var(--hair)} + .check:last-child{border-right:0;padding-right:0} + .check:not(:first-child){padding-left:34px} + .num{font-family:'Fraunces',serif;font-style:italic;font-weight:200;font-size:36px;color:var(--brass-lt);margin-bottom:14px;line-height:1} + .check h3{font-size:16.5px;font-weight:600;margin-bottom:9px;letter-spacing:-.008em} + .check p{font-size:14.5px;line-height:1.7;color:var(--muted)} + + /* ── method timeline ───────────────────────────── */ + .method{margin-top:118px;padding-top:70px;border-top:1px solid var(--line)} + .method h2{font-family:'Fraunces',serif;font-weight:200;font-size:47px;letter-spacing:-.028em;line-height:1.08;max-width:14ch} + .track{margin-top:52px;display:grid;grid-template-columns:repeat(4,1fr);gap:0;position:relative} + .track::before{content:'';position:absolute;top:9px;left:0;right:0;height:1px;background:var(--line)} + .stop{padding-right:32px;position:relative} + .stop::before{content:'';position:absolute;top:5px;left:0;width:9px;height:9px;border-radius:50%; + background:var(--cream);border:1px solid var(--brass)} + .stop.filled::before{background:var(--brass)} + .stop .d{margin-top:32px;color:var(--brass);font-size:10.5px;letter-spacing:.22em;text-transform:uppercase;font-weight:700} + .stop h4{margin-top:11px;font-size:17px;font-weight:600;letter-spacing:-.008em} + .stop p{margin-top:8px;font-size:14.5px;line-height:1.68;color:var(--muted);max-width:26ch} + + /* ── who ───────────────────────────────────────── */ + .who{margin-top:110px;background:linear-gradient(172deg,rgba(255,253,248,.9),rgba(235,225,207,.55)); + border:1px solid var(--hair);border-radius:14px;padding:40px 46px;display:flex; + justify-content:space-between;align-items:center;gap:50px;box-shadow:0 1px 0 rgba(255,255,255,.85) inset} + .who p{font-size:16.5px;line-height:1.7;color:var(--soft);max-width:64ch} + .who p b{font-weight:600;color:var(--ink)} + .who .sc{color:var(--brass);white-space:nowrap} + + /* ── terms ─────────────────────────────────────── */ + .offer{margin-top:112px;border-top:1px solid var(--ink);padding-top:52px; + display:flex;justify-content:space-between;align-items:flex-start;gap:90px} + .offer h2{font-family:'Fraunces',serif;font-weight:200;font-size:44px;letter-spacing:-.028em;line-height:1.1;max-width:11ch} + .offer h2 em{font-style:italic;color:var(--brass)} + .offer p{font-size:16px;line-height:1.74;color:var(--muted);margin-top:20px;max-width:44ch} + .price{flex-shrink:0;text-align:right} + .price .big{font-family:'Fraunces',serif;font-weight:200;font-size:88px;letter-spacing:-.05em;line-height:.86} + .price .per{margin-top:14px;color:var(--muted)} + .guarantee{margin-top:28px;padding-top:20px;border-top:1px solid var(--line); + font-size:13.5px;line-height:1.66;max-width:300px;text-align:left;color:var(--soft)} + .guarantee b{color:var(--brass);font-weight:600} + + /* ── faq ───────────────────────────────────────── */ + .faq{margin-top:118px;padding-top:70px;border-top:1px solid var(--line)} + .faq h2{font-family:'Fraunces',serif;font-weight:200;font-size:44px;letter-spacing:-.028em;margin-bottom:40px} + .q{display:grid;grid-template-columns:.9fr 1.1fr;gap:56px;padding:26px 0;border-top:1px solid var(--hair)} + .q:last-child{border-bottom:1px solid var(--hair)} + .q h4{font-size:18px;font-weight:600;letter-spacing:-.01em;max-width:24ch} + .q p{font-size:15.5px;line-height:1.74;color:var(--muted)} + + footer{margin-top:110px;padding:30px 0 66px;border-top:1px solid var(--hair); + display:flex;justify-content:space-between;color:var(--muted)} + + @media (prefers-reduced-motion:no-preference){ + [data-r]{opacity:0;transform:translateY(24px); + transition:opacity var(--slow) var(--ease),transform var(--slow) var(--ease)} + [data-r].in{opacity:1;transform:none} + } + .logo{text-decoration:none;color:var(--ink)} + .navlinks a{color:inherit;text-decoration:none;transition:color .66s cubic-bezier(.16,1,.3,1)} + .navlinks a:hover{color:var(--ink)} + .navcta{border:1px solid var(--line);border-radius:999px;padding:9px 17px;color:var(--ink)!important} + .navcta:hover{border-color:var(--brass);background:rgba(255,253,248,.7)} + /* two-field lead form — website + email */ + form.two{max-width:720px;padding:7px 7px 7px 0;overflow:hidden} + form.two input{flex:1 1 0;min-width:0;padding:12px 18px 12px 24px} + form.two input:first-of-type{border-right:1px solid var(--line)} + form.two button{flex:0 0 auto} + @media (max-width:760px){ + form.two{flex-direction:column;align-items:stretch;border-radius:20px;padding:8px;max-width:100%} + form.two input{padding:14px 18px} + form.two input:first-of-type{border-right:0;border-bottom:1px solid var(--line)} + form.two button{width:100%} + } + /* who signs it */ + .signer{margin-top:112px;padding-top:66px;border-top:1px solid var(--line); + display:grid;grid-template-columns:auto 1fr;gap:44px;align-items:start} + .sig-portrait{width:120px;height:120px;border-radius:50%;flex-shrink:0; + background:linear-gradient(168deg,#FFFDF8,#E8DDC9);border:1px solid var(--line); + display:flex;align-items:center;justify-content:center; + box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 22px 46px -30px rgba(25,20,16,.4)} + .sig-portrait span{font-family:'Fraunces',serif;font-size:15px;letter-spacing:.22em;color:var(--brass)} + .sig-portrait img{width:120px;height:120px;border-radius:50%;object-fit:cover;display:block} + .sig-body h2{font-family:'Fraunces',serif;font-weight:200;font-size:40px;letter-spacing:-.028em; + line-height:1.1;margin-top:14px} + .sig-body p{margin-top:20px;font-size:16.5px;line-height:1.75;color:var(--soft);max-width:58ch} + .sig-note:empty{display:none} + @media (max-width:760px){.signer{grid-template-columns:1fr;gap:26px}} + +/* extracted from inline style attributes — CSP style-src 'self' blocks those */ +.xi1{margin:26px auto 0;display:inline-flex;align-items:center;gap:13px;background:rgba(255,253,248,.8);border:1px solid var(--line);border-radius:999px;padding:11px 22px 11px 16px;box-shadow:0 1px 0 rgba(255,255,255,.9) inset,0 14px 34px -24px rgba(25,20,16,.35)} +.xi2{width:7px;height:7px;border-radius:50%;background:#B04A2E;display:block;box-shadow:0 0 0 4px rgba(176,74,46,.14)} +.xi3{font-size:13.5px;color:var(--soft)} +.xi4{color:var(--ink);font-weight:600} +.xi5{height:14px;width:30%} +.xi6{height:34px;width:74%;margin-top:22px} +.xi7{height:34px;width:54%;margin-top:9px} +.xi8{height:9px;width:60%;margin-top:22px;opacity:.62} +.xi9{height:9px;width:48%;margin-top:8px;opacity:.62} +.xi10{height:32px;width:28%;margin-top:26px;background:#D9CCB6} +.xi11{height:9px;width:22%;margin-top:50px;opacity:.5} +.xi12{height:26px;width:84%;margin-top:14px} +.xi13{height:26px;width:84%;margin-top:8px} +.xi14{top:96px;left:30%;width:30%} +.xi15{top:88px;left:62%} +.xi16{top:188px;left:30%;width:30%} +.xi17{top:180px;left:62%} +.xi18{top:300px;left:62%} +.xi19{color:var(--brass)} +.xi20{max-width:18ch} diff --git a/public/index.html b/public/index.html index 0dddb0b4..5e33483f 100644 --- a/public/index.html +++ b/public/index.html @@ -6,204 +6,8 @@ TinyStudio — The Leak Audit - +
@@ -222,9 +26,9 @@

Most of them leave before they ever get in touch.

Thirty seconds to ask. Findings inside five working days. No call at any point.

-
- - Six a month. When the sixth is taken, the intake closes until the next. +
+ + Six a month. When the sixth is taken, the intake closes until the next.
Complimentary
And yours to keep
@@ -236,8 +40,8 @@

Most of them leave before they ever get in touch.

13 of 19
-

premium clinics across three cities give no price signal on their homepage.

-

We read nineteen of them across London, New York and Miami. Thirteen served a homepage carrying no figure and no promise of one. The more useful finding is that it is not the same leak in every city: Miami mostly publishes prices and mostly has no answers section at all; New York does the reverse, and one clinic asks thirty form fields before it will accept an enquiry. We will hand you the whole study, city by city, before you decide anything.

+

premium websites across three cities give no price signal on their homepage.

+

We read nineteen of them across London, New York and Miami. Thirteen served a homepage carrying no figure and no promise of one. The more useful finding is that it is not the same leak in every city: Miami mostly publishes prices and mostly has no answers section at all; New York does the reverse, and one of them asks thirty form fields before it will accept an enquiry. We will hand you the whole study, city by city, before you decide anything.

Method: automated read of the served homepage HTML, August 2026 — 7 London, 6 New York, 6 Miami clinics offering longevity, IV or hormone services, followed by a hand audit of one. Inner pages are deliberately not counted; the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper — that is itself one of the leaks.

@@ -246,21 +50,21 @@

premium clinics across three cities give no price signal on their homepage.<
-
-
-
-
-
-
-
-
-
-
-
-
1 No price, anywhere
-
-
2 One idea, ten hooks
-
3 Nothing answered
+
+
+
+
+
+
+
+
+
+
+
+
1 No price, anywhere
+
+
2 One idea, ten hooks
+
3 Nothing answered
@@ -319,8 +123,8 @@

How the work runs

NISH
- Who signs it -

There is one person behind the desk, and it is not a department.

+ Who signs it +

There is one person behind the desk, and it is not a department.

My name is Nish. I read every audit before it leaves, I decide what we will and will not claim, and I am the one who owes you a refund if the work is late. The seven agents do the research and the drafting. The judgement, and the liability, are mine.

@@ -329,7 +133,7 @@

There is one person behind the desk, and it is not a

Or have it closed, not merely found.

-

The audit is free and yours to keep, whether or not we ever speak again. Should you want the work done, we run a monthly desk. Month one is the 7-Day Sprint — the leak found and closed inside seven working days. Months two and three build the loop that keeps it closed.

+

The audit is free and yours to keep, whether or not we ever speak again. Should you want the work done, we run a monthly desk. Month one closes the leak. Months two and three build the loop that keeps it closed.

$2,500
@@ -353,7 +157,7 @@

Before you ask

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

@@ -361,26 +165,7 @@

Why there are no logos on this site

No revenue, ranking or booking guarantees. Only the work.
- + + diff --git a/public/index.js b/public/index.js new file mode 100644 index 00000000..856585c1 --- /dev/null +++ b/public/index.js @@ -0,0 +1,19 @@ +(function(){ + var SEL='h1,.orn,.sub,form,.micro,.meta,.finding,.browser,.specbody,.lead,.check,.method h2,.stop,.who,.offer h2,.offer p,.price,.faq h2,.q'; + function run(){ + var els=[].slice.call(document.querySelectorAll(SEL)); + els.forEach(function(el){el.setAttribute('data-r','')}); + var io=new IntersectionObserver(function(en){ + en.forEach(function(e){ + if(!e.isIntersecting)return; + var sibs=[].slice.call(e.target.parentNode.children) + .filter(function(n){return n.hasAttribute&&n.hasAttribute('data-r')}); + e.target.style.transitionDelay=Math.min(Math.max(0,sibs.indexOf(e.target)),6)*100+'ms'; + e.target.classList.add('in'); io.unobserve(e.target); + }); + },{threshold:.08,rootMargin:'0px 0px -5% 0px'}); + els.forEach(function(el){io.observe(el)}); + setTimeout(function(){document.querySelectorAll('[data-r]').forEach(function(el){el.classList.add('in')})},1600); + } + document.readyState==='loading'?document.addEventListener('DOMContentLoaded',run):run(); +})(); diff --git a/public/pricing.css b/public/pricing.css new file mode 100644 index 00000000..a963750d --- /dev/null +++ b/public/pricing.css @@ -0,0 +1,34 @@ +.plan{margin-top:60px;display:grid;grid-template-columns:1.25fr .95fr;gap:0;border:1px solid var(--line); + border-radius:16px;overflow:hidden;background:linear-gradient(172deg,rgba(255,253,248,.92),rgba(235,225,207,.5)); + box-shadow:0 1px 0 rgba(255,255,255,.85) inset,0 40px 84px -50px rgba(25,20,16,.3)} + .plan .l{padding:46px 50px;border-right:1px solid var(--line)} + .plan .r{padding:46px 50px;text-align:right} + .plan h2{font-family:'Fraunces',serif;font-weight:200;font-size:36px;letter-spacing:-.025em;line-height:1.12;max-width:15ch} + .plan .inc{margin-top:28px;list-style:none} + .plan .inc li{font-size:15.5px;line-height:1.5;color:var(--soft);padding:11px 0 11px 26px;position:relative; + border-bottom:1px solid var(--hair)} + .plan .inc li:last-child{border-bottom:0} + .plan .inc li::before{content:'';position:absolute;left:0;top:19px;width:10px;height:1px;background:var(--brass)} + .plan .big{font-family:'Fraunces',serif;font-weight:200;font-size:82px;letter-spacing:-.05em;line-height:.86} + .plan .per{margin-top:14px;color:var(--muted)} + .plan .warranty{margin-top:30px;padding-top:22px;border-top:1px solid var(--line);text-align:left; + font-size:13.5px;line-height:1.68;color:var(--soft)} + .plan .warranty b{color:var(--brass);font-weight:600} + + .track{margin-top:50px;display:grid;grid-template-columns:repeat(4,1fr);position:relative} + .track::before{content:'';position:absolute;top:9px;left:0;right:0;height:1px;background:var(--line)} + .stop{padding-right:30px;position:relative} + .stop::before{content:'';position:absolute;top:5px;left:0;width:9px;height:9px;border-radius:50%; + background:var(--cream);border:1px solid var(--brass)} + .stop.filled::before{background:var(--brass)} + .stop .d{margin-top:30px;color:var(--brass);font-size:10.5px;letter-spacing:.22em;text-transform:uppercase;font-weight:700} + .stop h4{margin-top:10px;font-size:17px;font-weight:600;letter-spacing:-.008em} + .stop p{margin-top:8px;font-size:14.5px;line-height:1.68;color:var(--muted);max-width:26ch} + + .q{display:grid;grid-template-columns:.85fr 1.15fr;gap:52px;padding:26px 0;border-top:1px solid var(--hair)} + .q:last-of-type{border-bottom:1px solid var(--hair)} + .q h4{font-size:18px;font-weight:600;letter-spacing:-.01em;max-width:26ch} + .q p{font-size:15.5px;line-height:1.74;color:var(--muted)} + +/* extracted from inline style attributes — CSP style-src 'self' blocks those */ +.xp1{color:var(--brass)} diff --git a/public/pricing.html b/public/pricing.html index d9a11235..22c42622 100644 --- a/public/pricing.html +++ b/public/pricing.html @@ -5,39 +5,8 @@ Pricing & terms — TinyStudio - +
@@ -59,9 +28,9 @@

One price. One way to get your money back.

-

The 7-Day Sprint, then the Growth Desk

+

The Growth Desk

    -
  • Month one — the 7-Day Sprint. The leak audit, then the highest-leverage page rewritten or rebuilt, inside seven working days
  • +
  • Month one. The leak audit, then the highest-leverage page rewritten or rebuilt
  • A dev-ready handoff if your own team ships it
  • Search-trust basics: the questions people ask before they commit, answered on the page
  • Months two and three — weekly checks, one revision, and tracking that says whether the fix held
  • @@ -76,15 +45,15 @@

    The 7-Day Sprint, then the Growth Desk

-

How the seven days are counted

+

How the clock is counted

The clock only starts when we genuinely have everything, and it stops whenever the ball is back in your court.

Day 0

Everything on the table

Payment, access, the approval owner and the implementation owner, all recorded. Nothing starts earlier.

-
Days 1–3

The read

+
Stage two

The read

Four passes by hand. Named leaks, in the order they cost you money.

-
Days 4–7

The fix

-

The page rewritten or rebuilt, plus the handoff. Seven working days from Day 0, in your hands.

+
Stage three

The fix

+

The page rewritten or rebuilt, plus the handoff, in your hands.

Months 2–3

The loop

Weekly checks, one revision, and tracking that tells you whether the fix held.

@@ -101,7 +70,7 @@

Before you ask

Why only six audits a month?

They are done by hand and we are also delivering paid work. Six is what we can do properly. When the month is full you go on the list for the next one.

Who actually does the work?

-

Seven specialist agents do the research, drafting and checking. A person reviews fit, every claim, all client-facing work, delivery and renewal. Automation never sends, publishes, spends or approves. See the desk →

+

Seven specialist agents do the research, drafting and checking. A person reviews fit, every claim, all client-facing work, delivery and renewal. Automation never sends, publishes, spends or approves. See the desk →

@@ -113,7 +82,7 @@

The audit costs you an email. The rest is a decision you can make later.

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

@@ -122,16 +91,7 @@

Why there are no logos on this site

No revenue, ranking or booking guarantees. Only the work.

- + + diff --git a/public/pricing.js b/public/pricing.js new file mode 100644 index 00000000..86dd42fe --- /dev/null +++ b/public/pricing.js @@ -0,0 +1,9 @@ +(function(){var S='.phead h1,.phead .sub,.plan,section h2,section .lede,.stop,.q,.band'; +function r(){var e=[].slice.call(document.querySelectorAll(S));e.forEach(function(x){x.setAttribute('data-r','')}); +var io=new IntersectionObserver(function(en){en.forEach(function(v){if(!v.isIntersecting)return; +var s=[].slice.call(v.target.parentNode.children).filter(function(n){return n.hasAttribute&&n.hasAttribute('data-r')}); +v.target.style.transitionDelay=Math.min(Math.max(0,s.indexOf(v.target)),6)*90+'ms'; +v.target.classList.add('in');io.unobserve(v.target)})},{threshold:.08,rootMargin:'0px 0px -5% 0px'}); +e.forEach(function(x){io.observe(x)}); +setTimeout(function(){document.querySelectorAll('[data-r]').forEach(function(x){x.classList.add('in')})},1600)} +document.readyState==='loading'?document.addEventListener('DOMContentLoaded',r):r()})(); diff --git a/public/shared.css b/public/shared.css index e06914c2..8f0d3cd8 100644 --- a/public/shared.css +++ b/public/shared.css @@ -62,8 +62,8 @@ form.lead:hover,form.lead:focus-within{border-color:rgba(164,126,60,.42)} form.lead input{flex:1;border:0;background:transparent;outline:none;font-family:'Karla',sans-serif; font-size:16px;color:var(--ink);padding:12px 0} form.lead input::placeholder{color:#AC9E8B} -form.lead button{border:0;cursor:pointer;border-radius:999px;padding:15px 27px;white-space:nowrap; - font-family:'Karla',sans-serif;font-size:10.5px;letter-spacing:.19em;text-transform:uppercase;font-weight:600; +form.lead button{border:0;cursor:pointer;border-radius:999px;padding:15px 20px;white-space:nowrap; + font-family:'Karla',sans-serif;font-size:10px;letter-spacing:.14em;text-transform:uppercase;font-weight:600; color:#FBF7EF;background:linear-gradient(168deg,#241D16,#100C09); box-shadow:0 6px 18px -8px rgba(25,20,16,.6); transition:transform var(--mid) var(--ease)} @@ -99,7 +99,7 @@ footer a:hover{color:var(--ink)} } /* two-field lead form — website + email, still one line on desktop */ -form.lead.two{max-width:660px;padding:7px 7px 7px 0;overflow:hidden} +form.lead.two{max-width:720px;padding:7px 7px 7px 0;overflow:hidden} form.lead.two input{flex:1 1 0;min-width:0;padding:12px 18px 12px 24px} form.lead.two input:first-child{border-right:1px solid var(--line)} form.lead.two button{flex:0 0 auto} diff --git a/public/specimen.css b/public/specimen.css new file mode 100644 index 00000000..5d7b6916 --- /dev/null +++ b/public/specimen.css @@ -0,0 +1,25 @@ +.doc{max-width:840px;margin-top:56px} + .dochead{display:flex;justify-content:space-between;align-items:center;padding-bottom:16px; + border-bottom:1px solid var(--ink);color:var(--muted)} + .stamp{color:var(--sig);border:1px solid var(--sig);padding:6px 12px;border-radius:2px} + .f{padding:34px 0;border-bottom:1px solid var(--hair)} + .f .top{display:flex;gap:18px;align-items:baseline} + .f .idx{font-family:'Fraunces',serif;font-style:italic;font-weight:200;font-size:30px;color:var(--brass-lt);line-height:1} + .f h3{font-size:22px;font-weight:600;letter-spacing:-.014em;line-height:1.3} + .f .body{margin-left:48px} + .f p{margin-top:12px;font-size:16px;line-height:1.74;color:var(--soft)} + .ev{margin-top:16px;background:rgba(255,253,248,.75);border:1px solid var(--hair);border-left:2px solid var(--brass); + border-radius:0 6px 6px 0;padding:16px 20px} + .ev b{display:block;font-size:10px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted); + font-weight:600;margin-bottom:8px} + .ev span{font-size:14.5px;line-height:1.7;color:var(--soft)} + .fix{margin-top:14px;font-size:15.5px;line-height:1.7;color:var(--ink)} + .fix b{color:var(--brass);font-weight:600} + .notrun{margin-top:40px;background:rgba(176,74,46,.05);border:1px solid rgba(176,74,46,.22); + border-radius:10px;padding:26px 30px} + .notrun h4{font-family:'Fraunces',serif;font-weight:300;font-size:21px;letter-spacing:-.018em;color:var(--sig)} + .notrun p{margin-top:11px;font-size:15px;line-height:1.72;color:var(--soft)} + +/* extracted from inline style attributes — CSP style-src 'self' blocks those */ +.xs1{padding-top:44px} +.xs2{font-size:56px} diff --git a/public/specimen.html b/public/specimen.html index e4a9295c..ad67584d 100644 --- a/public/specimen.html +++ b/public/specimen.html @@ -5,29 +5,8 @@ Leak audit — specimen — TinyStudio - + +
@@ -47,8 +26,8 @@ Read and signed by Nish  ·  Client name withheld
-
-

Four ways this clinic loses people who were already sold.

+
+

Four ways this clinic loses people who were already sold.

A real audit with the name removed. This clinic does better work than its homepage admits — which is the expensive kind of problem, because the fix costs almost nothing.

@@ -105,16 +84,7 @@

Confidentiality

No revenue, ranking or booking guarantees. Only the work.
- + + diff --git a/public/specimen.js b/public/specimen.js new file mode 100644 index 00000000..db712544 --- /dev/null +++ b/public/specimen.js @@ -0,0 +1,9 @@ +(function(){var S='.phead h1,.phead .sub,.f,.notrun,section h2,section .lede'; +function r(){var e=[].slice.call(document.querySelectorAll(S));e.forEach(function(x){x.setAttribute('data-r','')}); +var io=new IntersectionObserver(function(en){en.forEach(function(v){if(!v.isIntersecting)return; +var s=[].slice.call(v.target.parentNode.children).filter(function(n){return n.hasAttribute&&n.hasAttribute('data-r')}); +v.target.style.transitionDelay=Math.min(Math.max(0,s.indexOf(v.target)),6)*90+'ms'; +v.target.classList.add('in');io.unobserve(v.target)})},{threshold:.08,rootMargin:'0px 0px -5% 0px'}); +e.forEach(function(x){io.observe(x)}); +setTimeout(function(){document.querySelectorAll('[data-r]').forEach(function(x){x.classList.add('in')})},1600)} +document.readyState==='loading'?document.addEventListener('DOMContentLoaded',r):r()})(); diff --git a/src/worker.js b/src/worker.js index 679bb423..2be78680 100644 --- a/src/worker.js +++ b/src/worker.js @@ -7,7 +7,7 @@ const SECURITY_HEADERS = { "Referrer-Policy": "strict-origin-when-cross-origin", "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=()", "Content-Security-Policy": - "default-src 'self'; img-src 'self' data:; style-src 'self'; script-src 'self' https://static.cloudflareinsights.com; connect-src 'self' https://cloudflareinsights.com; base-uri 'self'; frame-ancestors 'none'; form-action 'self'" + "default-src 'self'; img-src 'self' data:; style-src 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' https://static.cloudflareinsights.com; connect-src 'self' https://cloudflareinsights.com; base-uri 'self'; frame-ancestors 'none'; form-action 'self'" }; const PUBLIC_ASSET_PATHS = new Set([ @@ -26,6 +26,18 @@ const PUBLIC_ASSET_PATHS = new Set([ "/brief-requested.html", "/brief-requested", "/shared.css", + "/index.css", + "/index.js", + "/audit.css", + "/audit.js", + "/agents.css", + "/agents.js", + "/pricing.css", + "/pricing.js", + "/specimen.css", + "/specimen.js", + "/brief-requested.css", + "/brief-requested.js", // The Agent Desk, now the engine behind the free brief rather than the product. "/agent-desk.html", "/agent-desk", From 05ed124638f2e3fb9cd3dcc7af2c897a6e61fc49 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:08:30 +0530 Subject: [PATCH 11/19] copy: CTAs speak as the business, not the person A founder says "we" about their own company. "Show me where I am leaking" reads as personal; "Show me where we are leaking" is how the buyer actually talks. --- public/audit.html | 4 ++-- public/index.html | 6 +++--- public/pricing.html | 2 +- public/specimen.html | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/public/audit.html b/public/audit.html index 9d4b3b07..298061ab 100644 --- a/public/audit.html +++ b/public/audit.html @@ -27,7 +27,7 @@

We read your page the way a stranger reads it.

- +

Thirty seconds to ask. Findings inside five working days. Yours to keep either way.

Six a month. When the sixth is taken, the intake closes until the next.
@@ -79,7 +79,7 @@

Then it is entirely your call

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/index.html b/public/index.html index 5e33483f..f1be0ac8 100644 --- a/public/index.html +++ b/public/index.html @@ -23,7 +23,7 @@

Most of them leave before they ever get in touch.

- +

Thirty seconds to ask. Findings inside five working days. No call at any point.

@@ -111,7 +111,7 @@

How the work runs

-

We work mostly with premium wellness clinics. The audit itself is not fussy — it works on any page carrying real money. If yours does, put your email in and we will take a look.

+

If your website carries real money, it qualifies. Clinics, studios, firms, shops — the audit does not care what you sell. It cares whether the page earns. Put your website in and we will take a look.

Everyone welcome to ask
@@ -157,7 +157,7 @@

Before you ask

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/pricing.html b/public/pricing.html index 22c42622..98d6d950 100644 --- a/public/pricing.html +++ b/public/pricing.html @@ -82,7 +82,7 @@

The audit costs you an email. The rest is a decision you can make later.

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-clinic study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/specimen.html b/public/specimen.html index ad67584d..5ca9c235 100644 --- a/public/specimen.html +++ b/public/specimen.html @@ -74,7 +74,7 @@

Two passes not run

Confidentiality

-

This clinic is not a client and has not been named. Client work is never published — no logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us. That is why there is no wall of names on this site, and there never will be.

The nineteen-clinic study behind this audit is a different thing entirely: public homepages, no client relationship, and yours on request in full — London, New York and Miami. Research we will hand over; client work we will not.

+

This clinic is not a client and has not been named. Client work is never published — no logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us. That is why there is no wall of names on this site, and there never will be.

The nineteen-site study behind this audit is a different thing entirely: public homepages, no client relationship, and yours on request in full — London, New York and Miami. Research we will hand over; client work we will not.

From d37d0cfc57ed7f5ac3533a4f55136e2bfdaadc65 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:09:08 +0530 Subject: [PATCH 12/19] copy: state the openness, do not enumerate it Listing "clinics, studios, firms, shops" reads as a directory and undercuts the restraint the rest of the page relies on. The same meaning, stated once. --- public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/index.html b/public/index.html index f1be0ac8..5e42ae66 100644 --- a/public/index.html +++ b/public/index.html @@ -111,7 +111,7 @@

How the work runs

-

If your website carries real money, it qualifies. Clinics, studios, firms, shops — the audit does not care what you sell. It cares whether the page earns. Put your website in and we will take a look.

+

We do not ask what you sell. We ask what the page is worth. If revenue moves through it, it is worth reading.

Everyone welcome to ask
From 94a5f06a18c5d78defff5b4d60df2023b8008b85 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:10:10 +0530 Subject: [PATCH 13/19] copy: the reader is a customer with intent, not a stranger A stranger leaving costs nothing. Someone who arrived ready to buy and left anyway is the expensive case, and the one the audit is actually about. --- public/audit.html | 6 +++--- public/index.html | 6 +++--- public/specimen.html | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/public/audit.html b/public/audit.html index 298061ab..23009b4f 100644 --- a/public/audit.html +++ b/public/audit.html @@ -22,8 +22,8 @@
The leak audit -

We read your page the way a stranger reads it.

-

Four passes, by hand, on the surfaces that decide whether someone becomes a customer or a closed tab. You get a short, plain document — named leaks in the order they cost you money, with the fix beside each one. No deck. No dashboard.

+

We read your page as a customer with intent reads it.

+

Four passes, by hand, on the surfaces that decide whether someone who arrived ready to buy stays that way. You get a short, plain document — named leaks in the order they cost you money, with the fix beside each one. No deck. No dashboard.

@@ -49,7 +49,7 @@

The four passes

Each one is run by a specialist that does only that job, then read and signed by a person before it reaches you.

i

The money page

-

The one page your revenue depends on, read as a first-time visitor reads it, and the precise points at which they lose interest.

+

The one page your revenue depends on, read as a customer with intent reads it, and the precise points at which they lose interest.

ii

The paid creative

Your top spend, checked for genuinely distinct concepts rather than one idea wearing ten hooks.

iii

The unanswered

diff --git a/public/index.html b/public/index.html index 5e42ae66..e5646da6 100644 --- a/public/index.html +++ b/public/index.html @@ -19,7 +19,7 @@
The leak audit

Most of them leave before they ever get in touch.

-

We read the one page your revenue depends on the way a stranger reads it, and show you the exact points at which they go.

+

We read the one page your revenue depends on the way a customer with intent reads it, and show you the exact points at which they go.

@@ -82,11 +82,11 @@

What a leak audit gives you

What we actually look at

-

Four passes, done by hand, on the surfaces that decide whether a stranger becomes a customer or a closed tab.

+

Four passes, done by hand, on the surfaces that decide whether someone who arrived ready to buy stays that way.

i

The money page

-

The one page your revenue depends on, read the way a first-time visitor reads it, and the precise points at which they lose interest.

+

The one page your revenue depends on, read the way a customer with intent reads it, and the precise points at which they lose interest.

ii

The paid creative

Your top spend, checked for genuinely distinct concepts rather than one idea wearing ten hooks.

iii

The unanswered

diff --git a/public/specimen.html b/public/specimen.html index 5ca9c235..c15cde0a 100644 --- a/public/specimen.html +++ b/public/specimen.html @@ -43,7 +43,7 @@

Four ways this clinic loses people who were already sold.
ii

The headline could belong to any clinic in the country

-

"We help you to thrive at every stage of life." Nobody is named. No condition, no mechanism, no reason this clinic rather than another. Meanwhile the navigation carries five genuinely specific service lines that would each earn a stranger's attention.

+

"We help you to thrive at every stage of life." Nobody is named. No condition, no mechanism, no reason this clinic rather than another. Meanwhile the navigation carries five genuinely specific service lines that would each hold the attention of someone already looking.

EvidenceH1: "We help you to thrive at every stage of life." Navigation: Functional Medicine, Hormones, Longevity Medicine, IV Drips, Aesthetics — plus named conditions including adrenal fatigue, autoimmune disease and chronic fatigue syndrome.

Fix — the specificity is already in the navigation. Promote it into the first screen and the page starts speaking to someone.

From c7d64174ff004e81a9c488a19c1a2fb19d5686c0 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:10:53 +0530 Subject: [PATCH 14/19] copy: it is a department, and that is the point "Not a department" reads as small. It IS a department of specialised agents; what is scarce is that one human signs its work and carries the liability. Leans into the agents rather than apologising for them. --- public/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/index.html b/public/index.html index e5646da6..405b7934 100644 --- a/public/index.html +++ b/public/index.html @@ -124,8 +124,8 @@

How the work runs

Who signs it -

There is one person behind the desk, and it is not a department.

-

My name is Nish. I read every audit before it leaves, I decide what we will and will not claim, and I am the one who owes you a refund if the work is late. The seven agents do the research and the drafting. The judgement, and the liability, are mine.

+

It is a department. Only one member of it is human.

+

My name is Nish. Seven specialist agents do the reading, the research and the drafting — each with a single job, a fixed set of inputs and a checklist it has to satisfy, all of it pointed at your business rather than your industry. I read every audit before it leaves, I decide what we will and will not claim, and I am the one who owes you a refund if the work is late. The judgement, and the liability, are mine.

From d22eaee5dabd64196be012c459d076a1296f89e5 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Thu, 6 Aug 2026 00:14:04 +0530 Subject: [PATCH 15/19] copy: industry context is an input, not the enemy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Your business rather than your industry" contradicted the roster — Competitor Watcher exists to read the market. The contrast that actually matters is your evidence and your real competitors versus a generic template for your sector. --- public/agents.html | 4 ++-- public/index.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/public/agents.html b/public/agents.html index dc6024b5..2299847b 100644 --- a/public/agents.html +++ b/public/agents.html @@ -23,7 +23,7 @@
The desk

Seven specialists, one human signature.

-

Each one does a single job and nothing else. They read your actual pages, your actual reviews, your actual numbers — never a generic prompt about your industry. Nothing reaches you until a person has read it and put their name to it.

+

Each one does a single job and nothing else. They read your actual pages, your actual reviews, your actual numbers, and the competitors around you — never a generic prompt about your sector. Nothing reaches you until a person has read it and put their name to it.

Six audits a month. When the sixth is taken, the intake closes until the next.
@@ -90,7 +90,7 @@

Why this isn't something you can just prompt

The models are available to everyone — that is precisely the point. What isn't, is seven purpose-built specialists with fixed inputs and checklists, pointed at your actual business, with a human refusing to let an unproven claim leave the building.

Specialised, not general
Each agent has one job, a defined input list and a checklist it must satisfy. A general chat window has none of those.
-
Your data, not your industry
They read your reviews, your pages, your numbers. Not a plausible-sounding average of your sector.
+
Your evidence, and your market
They read your reviews, your pages and your numbers, and the competitors you are actually losing to. What they do not do is recite the average of your sector.
A person signs it
Fit, claims, client-facing work, delivery and renewal are all human decisions. Automation never gets the last word.
diff --git a/public/index.html b/public/index.html index 405b7934..1eb1dfc7 100644 --- a/public/index.html +++ b/public/index.html @@ -125,7 +125,7 @@

How the work runs

Who signs it

It is a department. Only one member of it is human.

-

My name is Nish. Seven specialist agents do the reading, the research and the drafting — each with a single job, a fixed set of inputs and a checklist it has to satisfy, all of it pointed at your business rather than your industry. I read every audit before it leaves, I decide what we will and will not claim, and I am the one who owes you a refund if the work is late. The judgement, and the liability, are mine.

+

My name is Nish. Seven specialist agents do the reading, the research and the drafting — each with a single job, a fixed set of inputs and a checklist it has to satisfy — reading your own pages and numbers, and the competitors you actually have, rather than a template for your sector. I read every audit before it leaves, I decide what we will and will not claim, and I am the one who owes you a refund if the work is late. The judgement, and the liability, are mine.

From a1781f17457a447d66c31550ea484b5bc1e46fef Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Thu, 6 Aug 2026 09:47:21 +0530 Subject: [PATCH 16/19] =?UTF-8?q?feat:=20cross-industry=20study=20?= =?UTF-8?q?=E2=80=94=2048=20sites,=206=20industries,=204=20cities?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the 19-site single-vertical study. Adds supercar dealers, luxury property, cosmetic dentistry, spas and premium HVAC alongside wellness clinics, across London, New York, Miami and Dubai. 31 of 48 give no price signal on the homepage; 29 answer nothing a buyer would ask before committing. The cross-industry finding is the useful one: property is the only vertical that publishes prices, and the only one where every site left the questions unanswered. The 216-input outlier is deliberately not quoted — it is a property search form, not an enquiry form, and claiming otherwise would be false. Scanner and raw results committed so the numbers are reproducible and the study can be handed to prospects as promised. --- public/audit.html | 10 +- public/index.html | 10 +- public/pricing.html | 2 +- public/specimen.html | 2 +- .../study-results.json | 517 ++++++++++++++++++ .../003-wellness-clinic-launch/study-scan.py | 69 +++ 6 files changed, 598 insertions(+), 12 deletions(-) create mode 100644 specs/003-wellness-clinic-launch/study-results.json create mode 100644 specs/003-wellness-clinic-launch/study-scan.py diff --git a/public/audit.html b/public/audit.html index 23009b4f..ed554b98 100644 --- a/public/audit.html +++ b/public/audit.html @@ -35,11 +35,11 @@

We read your page as a customer with intent reads it.

-
13 of 19
+
31 of 48
-

premium websites across three cities give no price signal on their homepage.

-

We read nineteen of them across London, New York and Miami. Thirteen served a homepage carrying no figure and no promise of one. The more useful finding is that it is not the same leak in every city: Miami mostly publishes prices and mostly has no answers section at all; New York does the reverse, and one of them asks thirty form fields before it will accept an enquiry. We will hand you the whole study, city by city, before you decide anything.

-
Method: automated read of the served homepage HTML, August 2026 — 7 London, 6 New York, 6 Miami clinics offering longevity, IV or hormone services, followed by a hand audit of one. Inner pages are deliberately not counted; the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper — that is itself one of the leaks.
+

luxury businesses across six industries give no price signal on their homepage.

+

We read forty-eight of them — wellness clinics, spas, cosmetic dentists, luxury property, supercar dealers and premium climate control — across London, New York, Miami and Dubai. Thirty-one served a homepage with no figure and no promise of one. Twenty-nine answered nothing a buyer would ask before committing. The leak is not the same leak in every industry: property is the only one that publishes prices, because the listing is the product — and it is the only one where every single site left the questions unanswered. That is what the recipe is. The same four passes; what changes is which one your industry is failing.

+
Method: automated read of the served homepage HTML, August 2026. Fifty-three sites attempted across six industries and four cities; forty-eight readable, five blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.
@@ -79,7 +79,7 @@

Then it is entirely your call

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The forty-eight-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/index.html b/public/index.html index 1eb1dfc7..2094b21d 100644 --- a/public/index.html +++ b/public/index.html @@ -38,11 +38,11 @@

Most of them leave before they ever get in touch.

-
13 of 19
+
31 of 48
-

premium websites across three cities give no price signal on their homepage.

-

We read nineteen of them across London, New York and Miami. Thirteen served a homepage carrying no figure and no promise of one. The more useful finding is that it is not the same leak in every city: Miami mostly publishes prices and mostly has no answers section at all; New York does the reverse, and one of them asks thirty form fields before it will accept an enquiry. We will hand you the whole study, city by city, before you decide anything.

-

Method: automated read of the served homepage HTML, August 2026 — 7 London, 6 New York, 6 Miami clinics offering longevity, IV or hormone services, followed by a hand audit of one. Inner pages are deliberately not counted; the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper — that is itself one of the leaks.

+

luxury businesses across six industries give no price signal on their homepage.

+

We read forty-eight of them — wellness clinics, spas, cosmetic dentists, luxury property, supercar dealers and premium climate control — across London, New York, Miami and Dubai. Thirty-one served a homepage with no figure and no promise of one. Twenty-nine answered nothing a buyer would ask before committing. The leak is not the same leak in every industry: property is the only one that publishes prices, because the listing is the product — and it is the only one where every single site left the questions unanswered. That is what the recipe is. The same four passes; what changes is which one your industry is failing.

+

Method: automated read of the served homepage HTML, August 2026. Fifty-three sites attempted across six industries and four cities; forty-eight readable, five blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.

@@ -157,7 +157,7 @@

Before you ask

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The forty-eight-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/pricing.html b/public/pricing.html index 98d6d950..546fa0e0 100644 --- a/public/pricing.html +++ b/public/pricing.html @@ -82,7 +82,7 @@

The audit costs you an email. The rest is a decision you can make later.

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The nineteen-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, city by city. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The forty-eight-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/specimen.html b/public/specimen.html index c15cde0a..c5de86f4 100644 --- a/public/specimen.html +++ b/public/specimen.html @@ -74,7 +74,7 @@

Two passes not run

Confidentiality

-

This clinic is not a client and has not been named. Client work is never published — no logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us. That is why there is no wall of names on this site, and there never will be.

The nineteen-site study behind this audit is a different thing entirely: public homepages, no client relationship, and yours on request in full — London, New York and Miami. Research we will hand over; client work we will not.

+

This clinic is not a client and has not been named. Client work is never published — no logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us. That is why there is no wall of names on this site, and there never will be.

The forty-eight-site study behind this audit is a different thing entirely: public homepages, no client relationship, and yours on request in full — six industries, four cities. Research we will hand over; client work we will not.

diff --git a/specs/003-wellness-clinic-launch/study-results.json b/specs/003-wellness-clinic-launch/study-results.json new file mode 100644 index 00000000..a47f5823 --- /dev/null +++ b/specs/003-wellness-clinic-launch/study-results.json @@ -0,0 +1,517 @@ +[ + { + "vertical": "Wellness clinics", + "geo": "London", + "host": "www.harpalclinic.co.uk", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "vertical": "Wellness clinics", + "geo": "London", + "host": "the-hvn.com", + "ok": true, + "words": 2359, + "price": 0, + "faq": true, + "fields": 3 + }, + { + "vertical": "Wellness clinics", + "geo": "London", + "host": "optimisehealth.com", + "ok": true, + "words": 1424, + "price": 1, + "faq": true, + "fields": 6 + }, + { + "vertical": "Wellness clinics", + "geo": "London", + "host": "londoncryo.com", + "ok": true, + "words": 872, + "price": 1, + "faq": true, + "fields": 1 + }, + { + "vertical": "Wellness clinics", + "geo": "London", + "host": "nuutro.co.uk", + "ok": true, + "words": 1764, + "price": 0, + "faq": true, + "fields": 6 + }, + { + "vertical": "Wellness clinics", + "geo": "London", + "host": "thewellnesslondon.com", + "ok": true, + "words": 1368, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "vertical": "Wellness clinics", + "geo": "London", + "host": "hum2n.com", + "ok": true, + "words": 1252, + "price": 0, + "faq": false, + "fields": 1 + }, + { + "vertical": "Wellness clinics", + "geo": "New York", + "host": "collagenbar.nyc", + "ok": true, + "words": 2120, + "price": 2, + "faq": true, + "fields": 2 + }, + { + "vertical": "Wellness clinics", + "geo": "New York", + "host": "manhattanmedicalarts.com", + "ok": true, + "words": 1693, + "price": 0, + "faq": true, + "fields": 1 + }, + { + "vertical": "Wellness clinics", + "geo": "New York", + "host": "prefusionhealth.com", + "ok": true, + "words": 1704, + "price": 0, + "faq": true, + "fields": 0 + }, + { + "vertical": "Wellness clinics", + "geo": "New York", + "host": "apollohousenyc.com", + "ok": true, + "words": 2329, + "price": 0, + "faq": true, + "fields": 11 + }, + { + "vertical": "Wellness clinics", + "geo": "New York", + "host": "merakiintegrative.com", + "ok": true, + "words": 1150, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "vertical": "Wellness clinics", + "geo": "New York", + "host": "patientsmedical.com", + "ok": true, + "words": 7989, + "price": 0, + "faq": true, + "fields": 30 + }, + { + "vertical": "Wellness clinics", + "geo": "Miami", + "host": "vitaflowfl.com", + "ok": true, + "words": 1156, + "price": 11, + "faq": false, + "fields": 0 + }, + { + "vertical": "Wellness clinics", + "geo": "Miami", + "host": "hallongevity.com", + "ok": true, + "words": 1492, + "price": 4, + "faq": false, + "fields": 0 + }, + { + "vertical": "Wellness clinics", + "geo": "Miami", + "host": "alivemiami.com", + "ok": true, + "words": 4808, + "price": 9, + "faq": false, + "fields": 5 + }, + { + "vertical": "Wellness clinics", + "geo": "Miami", + "host": "neomedicineinstitute.com", + "ok": true, + "words": 1163, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "vertical": "Wellness clinics", + "geo": "Miami", + "host": "alphahormones.com", + "ok": true, + "words": 5463, + "price": 0, + "faq": true, + "fields": 10 + }, + { + "vertical": "Wellness clinics", + "geo": "Miami", + "host": "purehealthmiami.com", + "ok": true, + "words": 436, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "vertical": "Supercar dealers", + "geo": "Dubai", + "host": "f1rstmotors.com", + "ok": true, + "words": 205, + "price": 0, + "faq": false, + "fields": 1 + }, + { + "vertical": "Supercar dealers", + "geo": "Miami", + "host": "premierautomiami.com", + "ok": true, + "words": 1940, + "price": 12, + "faq": true, + "fields": 9 + }, + { + "vertical": "Supercar dealers", + "geo": "Miami", + "host": "limitedspec.com", + "ok": true, + "words": 901, + "price": 12, + "faq": false, + "fields": 6 + }, + { + "vertical": "Supercar dealers", + "geo": "Miami", + "host": "exoticshunter.com", + "ok": true, + "words": 564, + "price": 22, + "faq": false, + "fields": 0 + }, + { + "vertical": "Supercar dealers", + "geo": "Miami", + "host": "thecollection.com", + "ok": true, + "words": 1307, + "price": 0, + "faq": true, + "fields": 16 + }, + { + "vertical": "Supercar dealers", + "geo": "Miami", + "host": "pugachev.miami", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "vertical": "Supercar dealers", + "geo": "Dubai", + "host": "luxurysupercarsdubai.com", + "ok": true, + "words": 2603, + "price": 0, + "faq": true, + "fields": 8 + }, + { + "vertical": "Supercar dealers", + "geo": "Miami", + "host": "prestigeluxuryrentals.com", + "ok": true, + "words": 2051, + "price": 7, + "faq": true, + "fields": 2 + }, + { + "vertical": "Luxury real estate", + "geo": "London", + "host": "knightsbridgeprimeproperty.com", + "ok": true, + "words": 953, + "price": 17, + "faq": false, + "fields": 20 + }, + { + "vertical": "Luxury real estate", + "geo": "London", + "host": "jefferies.london", + "ok": true, + "words": 1195, + "price": 15, + "faq": false, + "fields": 0 + }, + { + "vertical": "Luxury real estate", + "geo": "London", + "host": "beauchamp.com", + "ok": true, + "words": 2807, + "price": 5, + "faq": false, + "fields": 216 + }, + { + "vertical": "Luxury real estate", + "geo": "London", + "host": "mayfairinternationalrealty.com", + "ok": true, + "words": 305, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "vertical": "Luxury real estate", + "geo": "London", + "host": "eatonpremier.com", + "ok": true, + "words": 1374, + "price": 7, + "faq": false, + "fields": 20 + }, + { + "vertical": "Cosmetic dentistry", + "geo": "London", + "host": "77harleystreet.co.uk", + "ok": true, + "words": 886, + "price": 0, + "faq": true, + "fields": 0 + }, + { + "vertical": "Cosmetic dentistry", + "geo": "London", + "host": "marylebonesmileclinic.co.uk", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "vertical": "Cosmetic dentistry", + "geo": "London", + "host": "harleystreetsmileclinic.co.uk", + "ok": true, + "words": 3339, + "price": 0, + "faq": true, + "fields": 29 + }, + { + "vertical": "Cosmetic dentistry", + "geo": "London", + "host": "prodentalclinic.london", + "ok": true, + "words": 870, + "price": 2, + "faq": false, + "fields": 1 + }, + { + "vertical": "Cosmetic dentistry", + "geo": "London", + "host": "harleystreetdentalandimplantclinic.co.uk", + "ok": true, + "words": 795, + "price": 0, + "faq": false, + "fields": 13 + }, + { + "vertical": "Cosmetic dentistry", + "geo": "London", + "host": "smilelondon.co.uk", + "ok": true, + "words": 1410, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "vertical": "Cosmetic dentistry", + "geo": "London", + "host": "harleystreetdentalartclinic.com", + "ok": true, + "words": 1184, + "price": 0, + "faq": false, + "fields": 5 + }, + { + "vertical": "Cosmetic dentistry", + "geo": "London", + "host": "harleystreetimplantcentre.co.uk", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "vertical": "Wellness centres & spas", + "geo": "London", + "host": "www.thirdspace.london", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "vertical": "Wellness centres & spas", + "geo": "London", + "host": "rebaserecovery.com", + "ok": true, + "words": 9, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "vertical": "Wellness centres & spas", + "geo": "London", + "host": "comohotels.com", + "ok": true, + "words": 1948, + "price": 0, + "faq": true, + "fields": 8 + }, + { + "vertical": "Wellness centres & spas", + "geo": "London", + "host": "corinthia.com", + "ok": true, + "words": 1185, + "price": 5, + "faq": false, + "fields": 2 + }, + { + "vertical": "Wellness centres & spas", + "geo": "London", + "host": "bulgarihotels.com", + "ok": true, + "words": 1642, + "price": 0, + "faq": false, + "fields": 21 + }, + { + "vertical": "Wellness centres & spas", + "geo": "Global", + "host": "luxewellnessclub.com", + "ok": true, + "words": 1422, + "price": 0, + "faq": false, + "fields": 25 + }, + { + "vertical": "Premium HVAC", + "geo": "Miami", + "host": "kleinairconditioning.com", + "ok": true, + "words": 1811, + "price": 0, + "faq": false, + "fields": 6 + }, + { + "vertical": "Premium HVAC", + "geo": "Miami", + "host": "ameritempac.com", + "ok": true, + "words": 1065, + "price": 0, + "faq": false, + "fields": 7 + }, + { + "vertical": "Premium HVAC", + "geo": "Miami", + "host": "luxury-cooling.com", + "ok": true, + "words": 598, + "price": 0, + "faq": false, + "fields": 7 + }, + { + "vertical": "Premium HVAC", + "geo": "Miami", + "host": "acpowercomfort.com", + "ok": true, + "words": 2970, + "price": 0, + "faq": true, + "fields": 0 + }, + { + "vertical": "Premium HVAC", + "geo": "Miami", + "host": "miamihvac.net", + "ok": true, + "words": 763, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "vertical": "Premium HVAC", + "geo": "Miami", + "host": "polluxair.com", + "ok": true, + "words": 3513, + "price": 0, + "faq": false, + "fields": 4 + }, + { + "vertical": "Premium HVAC", + "geo": "Miami", + "host": "centralcomfortairconditioning.com", + "ok": true, + "words": 1825, + "price": 1, + "faq": true, + "fields": 8 + } +] \ No newline at end of file diff --git a/specs/003-wellness-clinic-launch/study-scan.py b/specs/003-wellness-clinic-launch/study-scan.py new file mode 100644 index 00000000..97b16dfc --- /dev/null +++ b/specs/003-wellness-clinic-launch/study-scan.py @@ -0,0 +1,69 @@ +import re,json,ssl,urllib.request,concurrent.futures as cf +from html.parser import HTMLParser +ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE +UA={'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36'} + +SITES={ +"Wellness clinics":[("London","https://www.harpalclinic.co.uk/"),("London","https://the-hvn.com/"), + ("London","https://optimisehealth.com/"),("London","https://www.londoncryo.com/"),("London","https://nuutro.co.uk/"), + ("London","https://www.thewellnesslondon.com/"),("London","https://www.hum2n.com/"), + ("New York","https://collagenbar.nyc/pages/longevity"),("New York","https://manhattanmedicalarts.com/longevity-wellness"), + ("New York","https://www.prefusionhealth.com/"),("New York","https://apollohousenyc.com/"), + ("New York","https://www.merakiintegrative.com/longevity-protocol"),("New York","https://www.patientsmedical.com/"), + ("Miami","https://vitaflowfl.com/"),("Miami","https://hallongevity.com/"),("Miami","https://alivemiami.com/"), + ("Miami","https://neomedicineinstitute.com/"),("Miami","https://alphahormones.com/"),("Miami","https://purehealthmiami.com/")], +"Supercar dealers":[("Dubai","https://f1rstmotors.com/"),("Miami","https://www.premierautomiami.com/exotic-cars/"), + ("Miami","https://www.limitedspec.com/"),("Miami","https://www.exoticshunter.com/"),("Miami","https://www.thecollection.com/"), + ("Miami","https://pugachev.miami/"),("Dubai","https://luxurysupercarsdubai.com/"),("Miami","https://prestigeluxuryrentals.com/")], +"Luxury real estate":[("London","https://knightsbridgeprimeproperty.com/"),("London","https://jefferies.london/"), + ("London","https://www.beauchamp.com/"),("London","https://www.mayfairinternationalrealty.com/"), + ("London","https://eatonpremier.com/")], +"Cosmetic dentistry":[("London","https://www.77harleystreet.co.uk/"),("London","https://marylebonesmileclinic.co.uk/"), + ("London","https://harleystreetsmileclinic.co.uk/"),("London","https://prodentalclinic.london/"), + ("London","https://www.harleystreetdentalandimplantclinic.co.uk/"),("London","https://smilelondon.co.uk/"), + ("London","https://harleystreetdentalartclinic.com/"),("London","https://harleystreetimplantcentre.co.uk/")], +"Wellness centres & spas":[("London","https://www.thirdspace.london/"),("London","https://www.rebaserecovery.com/"), + ("London","https://www.comohotels.com/london/como-metropolitan-london/wellness"), + ("London","https://www.corinthia.com/en-gb/london/spa-at-corinthia-london/"), + ("London","https://www.bulgarihotels.com/en_US/london/spa-and-fitness/the-bulgari-spa"), + ("Global","https://www.luxewellnessclub.com/en/")], +"Premium HVAC":[("Miami","https://kleinairconditioning.com/"),("Miami","https://www.ameritempac.com/"), + ("Miami","https://luxury-cooling.com/"),("Miami","https://acpowercomfort.com/"),("Miami","https://www.miamihvac.net/"), + ("Miami","https://polluxair.com/miami/"),("Miami","https://centralcomfortairconditioning.com/")], +} + +class S(HTMLParser): + def __init__(s): + super().__init__(); s.txt=[]; s.skip=0; s.inputs=0 + def handle_starttag(s,t,a): + d=dict(a) + if t in ('script','style','noscript'): s.skip+=1 + if t in ('input','select','textarea') and d.get('type','text').lower() not in ('hidden','submit','button'): s.inputs+=1 + def handle_endtag(s,t): + if t in ('script','style','noscript') and s.skip: s.skip-=1 + def handle_data(s,d): + if not s.skip: s.txt.append(d) + +def scan(item): + vert,(geo,url)=item + try: + html=urllib.request.urlopen(urllib.request.Request(url,headers=UA),timeout=25,context=ctx).read().decode('utf-8','ignore') + p=S(); p.feed(html); txt=re.sub(r'\s+',' ',' '.join(p.txt)) + price=len(re.findall(r'[£$€]\s?\d',txt)) + return {"vertical":vert,"geo":geo,"host":url.split('//')[1].split('/')[0].replace('www.',''), + "ok":True,"words":len(txt.split()),"price":price, + "faq":bool(re.search(r'frequently asked|FAQ',txt,re.I)),"fields":p.inputs} + except Exception as e: + return {"vertical":vert,"geo":geo,"host":url.split('//')[1].split('/')[0],"ok":False,"err":str(e)[:40]} + +jobs=[(v,s) for v,lst in SITES.items() for s in lst] +with cf.ThreadPoolExecutor(max_workers=12) as ex: rows=list(ex.map(scan,jobs)) +ok=[r for r in rows if r["ok"]] +print(f"ATTEMPTED {len(rows)} · READABLE {len(ok)} · {len(SITES)} verticals · {len(set(r['geo'] for r in ok))} geographies\n") +print(f"{'VERTICAL':26}{'n':>4}{'no price':>10}{'no FAQ':>9}{'max fields':>12}") +for v in SITES: + g=[r for r in ok if r["vertical"]==v] + if not g: continue + print(f"{v:26}{len(g):>4}{sum(1 for r in g if r['price']==0):>10}{sum(1 for r in g if not r['faq']):>9}{max(r['fields'] for r in g):>12}") +print(f"\n{'TOTAL':26}{len(ok):>4}{sum(1 for r in ok if r['price']==0):>10}{sum(1 for r in ok if not r['faq']):>9}{max(r['fields'] for r in ok):>12}") +json.dump(rows,open("luxstudy.json","w"),indent=1) From 000eef6ce27a5f4fae68a3f177a9716ff6729cc2 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:00:24 +0530 Subject: [PATCH 17/19] =?UTF-8?q?feat:=20daily=20market=20scan=20=E2=80=94?= =?UTF-8?q?=2099=20sites,=2013=20industries,=208=20geographies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The study becomes a series instead of a snapshot. One scrape is a statistic; a daily record of who added pricing and who removed their answers is proprietary. - study/corpus.json — the site list, data not code, so widening it is an edit - study/scan.py — scans, writes a dated snapshot, prints only material changes (price appeared/vanished, answers section added/removed, form size moved by 3+) - study/render.py — rewrites the public figures from the newest snapshot, so the page cannot drift from the data it claims to be refreshed from - Hermes cron 0 7 daily: scan, render, deploy only if a figure moved, report only what changed. Silent when the market stood still. The public numbers are now data-study spans owned by render.py. They read 57 of 88 today; hand-typing them was already wrong within an hour of the last scan. study/INTAKE-STANDARD.md specifies the second pipeline — practitioner knowledge into the vault — and deliberately does not automate it. Pipeline A measures; pipeline B believes, and a wrong belief ends up in a signed client audit. --- public/audit.html | 10 +- public/index.html | 10 +- public/pricing.html | 2 +- public/specimen.html | 2 +- .../study-results.json | 517 ---------- .../003-wellness-clinic-launch/study-scan.py | 69 -- study/INTAKE-STANDARD.md | 83 ++ study/corpus.json | 497 +++++++++ study/render.py | 95 ++ study/scan.py | 157 +++ study/snapshots/2026-08-06.json | 972 ++++++++++++++++++ 11 files changed, 1816 insertions(+), 598 deletions(-) delete mode 100644 specs/003-wellness-clinic-launch/study-results.json delete mode 100644 specs/003-wellness-clinic-launch/study-scan.py create mode 100644 study/INTAKE-STANDARD.md create mode 100644 study/corpus.json create mode 100644 study/render.py create mode 100644 study/scan.py create mode 100644 study/snapshots/2026-08-06.json diff --git a/public/audit.html b/public/audit.html index ed554b98..c8e6018b 100644 --- a/public/audit.html +++ b/public/audit.html @@ -35,11 +35,11 @@

We read your page as a customer with intent reads it.

-
31 of 48
+
57 of 89
-

luxury businesses across six industries give no price signal on their homepage.

-

We read forty-eight of them — wellness clinics, spas, cosmetic dentists, luxury property, supercar dealers and premium climate control — across London, New York, Miami and Dubai. Thirty-one served a homepage with no figure and no promise of one. Twenty-nine answered nothing a buyer would ask before committing. The leak is not the same leak in every industry: property is the only one that publishes prices, because the listing is the product — and it is the only one where every single site left the questions unanswered. That is what the recipe is. The same four passes; what changes is which one your industry is failing.

-
Method: automated read of the served homepage HTML, August 2026. Fifty-three sites attempted across six industries and four cities; forty-eight readable, five blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.
+

luxury businesses across thirteen industries give no price signal on their homepage.

+

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, luxury property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. fifty-seven showed no price. fifty-three answered nothing a buyer would ask before committing. The leak is never the same leak twice: every private members' club we read hid both, while Savile Row — the oldest trade in the set — was the most transparent of all. That is the recipe. The same four passes; what changes is which one your industry is quietly failing. We re-read all of them every day, so this number is today's, not last quarter's.

+
Method: automated read of the served homepage HTML, refreshed daily. ninety-nine sites attempted across thirteen industries and eight geographies; eighty-nine readable, ten blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.
@@ -79,7 +79,7 @@

Then it is entirely your call

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The forty-eight-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The eighty-nine-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/index.html b/public/index.html index 2094b21d..d9e9dab9 100644 --- a/public/index.html +++ b/public/index.html @@ -38,11 +38,11 @@

Most of them leave before they ever get in touch.

-
31 of 48
+
57 of 89
-

luxury businesses across six industries give no price signal on their homepage.

-

We read forty-eight of them — wellness clinics, spas, cosmetic dentists, luxury property, supercar dealers and premium climate control — across London, New York, Miami and Dubai. Thirty-one served a homepage with no figure and no promise of one. Twenty-nine answered nothing a buyer would ask before committing. The leak is not the same leak in every industry: property is the only one that publishes prices, because the listing is the product — and it is the only one where every single site left the questions unanswered. That is what the recipe is. The same four passes; what changes is which one your industry is failing.

-

Method: automated read of the served homepage HTML, August 2026. Fifty-three sites attempted across six industries and four cities; forty-eight readable, five blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.

+

luxury businesses across thirteen industries give no price signal on their homepage.

+

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, luxury property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. fifty-seven showed no price. fifty-three answered nothing a buyer would ask before committing. The leak is never the same leak twice: every private members' club we read hid both, while Savile Row — the oldest trade in the set — was the most transparent of all. That is the recipe. The same four passes; what changes is which one your industry is quietly failing. We re-read all of them every day, so this number is today's, not last quarter's.

+

Method: automated read of the served homepage HTML, refreshed daily. ninety-nine sites attempted across thirteen industries and eight geographies; eighty-nine readable, ten blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.

@@ -157,7 +157,7 @@

Before you ask

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The forty-eight-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The eighty-nine-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/pricing.html b/public/pricing.html index 546fa0e0..8dc4b284 100644 --- a/public/pricing.html +++ b/public/pricing.html @@ -82,7 +82,7 @@

The audit costs you an email. The rest is a decision you can make later.

Why there are no logos on this site

-

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The forty-eight-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. Read the specimen →

+

We do not publish client work. No logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us — not on a site, not in a pitch deck, not in a conversation with your competitor down the road.

Market research is the opposite, and we share it freely. The eighty-nine-site study reads public homepages of businesses that are not our clients and have no relationship with us — you are welcome to all of it, industry by industry. What we never share is anything belonging to someone who paid us. Read the specimen →

diff --git a/public/specimen.html b/public/specimen.html index c5de86f4..4f2b3bba 100644 --- a/public/specimen.html +++ b/public/specimen.html @@ -74,7 +74,7 @@

Two passes not run

Confidentiality

-

This clinic is not a client and has not been named. Client work is never published — no logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us. That is why there is no wall of names on this site, and there never will be.

The forty-eight-site study behind this audit is a different thing entirely: public homepages, no client relationship, and yours on request in full — six industries, four cities. Research we will hand over; client work we will not.

+

This clinic is not a client and has not been named. Client work is never published — no logos, no case studies, no testimonials, no "as seen at." If you engage us, nobody learns it from us. That is why there is no wall of names on this site, and there never will be.

The eighty-nine-site study behind this audit is a different thing entirely: public homepages, no client relationship, and yours on request in full — thirteen industries, eight geographies. Research we will hand over; client work we will not.

diff --git a/specs/003-wellness-clinic-launch/study-results.json b/specs/003-wellness-clinic-launch/study-results.json deleted file mode 100644 index a47f5823..00000000 --- a/specs/003-wellness-clinic-launch/study-results.json +++ /dev/null @@ -1,517 +0,0 @@ -[ - { - "vertical": "Wellness clinics", - "geo": "London", - "host": "www.harpalclinic.co.uk", - "ok": false, - "err": "HTTP Error 403: Forbidden" - }, - { - "vertical": "Wellness clinics", - "geo": "London", - "host": "the-hvn.com", - "ok": true, - "words": 2359, - "price": 0, - "faq": true, - "fields": 3 - }, - { - "vertical": "Wellness clinics", - "geo": "London", - "host": "optimisehealth.com", - "ok": true, - "words": 1424, - "price": 1, - "faq": true, - "fields": 6 - }, - { - "vertical": "Wellness clinics", - "geo": "London", - "host": "londoncryo.com", - "ok": true, - "words": 872, - "price": 1, - "faq": true, - "fields": 1 - }, - { - "vertical": "Wellness clinics", - "geo": "London", - "host": "nuutro.co.uk", - "ok": true, - "words": 1764, - "price": 0, - "faq": true, - "fields": 6 - }, - { - "vertical": "Wellness clinics", - "geo": "London", - "host": "thewellnesslondon.com", - "ok": true, - "words": 1368, - "price": 0, - "faq": false, - "fields": 0 - }, - { - "vertical": "Wellness clinics", - "geo": "London", - "host": "hum2n.com", - "ok": true, - "words": 1252, - "price": 0, - "faq": false, - "fields": 1 - }, - { - "vertical": "Wellness clinics", - "geo": "New York", - "host": "collagenbar.nyc", - "ok": true, - "words": 2120, - "price": 2, - "faq": true, - "fields": 2 - }, - { - "vertical": "Wellness clinics", - "geo": "New York", - "host": "manhattanmedicalarts.com", - "ok": true, - "words": 1693, - "price": 0, - "faq": true, - "fields": 1 - }, - { - "vertical": "Wellness clinics", - "geo": "New York", - "host": "prefusionhealth.com", - "ok": true, - "words": 1704, - "price": 0, - "faq": true, - "fields": 0 - }, - { - "vertical": "Wellness clinics", - "geo": "New York", - "host": "apollohousenyc.com", - "ok": true, - "words": 2329, - "price": 0, - "faq": true, - "fields": 11 - }, - { - "vertical": "Wellness clinics", - "geo": "New York", - "host": "merakiintegrative.com", - "ok": true, - "words": 1150, - "price": 0, - "faq": false, - "fields": 0 - }, - { - "vertical": "Wellness clinics", - "geo": "New York", - "host": "patientsmedical.com", - "ok": true, - "words": 7989, - "price": 0, - "faq": true, - "fields": 30 - }, - { - "vertical": "Wellness clinics", - "geo": "Miami", - "host": "vitaflowfl.com", - "ok": true, - "words": 1156, - "price": 11, - "faq": false, - "fields": 0 - }, - { - "vertical": "Wellness clinics", - "geo": "Miami", - "host": "hallongevity.com", - "ok": true, - "words": 1492, - "price": 4, - "faq": false, - "fields": 0 - }, - { - "vertical": "Wellness clinics", - "geo": "Miami", - "host": "alivemiami.com", - "ok": true, - "words": 4808, - "price": 9, - "faq": false, - "fields": 5 - }, - { - "vertical": "Wellness clinics", - "geo": "Miami", - "host": "neomedicineinstitute.com", - "ok": true, - "words": 1163, - "price": 0, - "faq": false, - "fields": 0 - }, - { - "vertical": "Wellness clinics", - "geo": "Miami", - "host": "alphahormones.com", - "ok": true, - "words": 5463, - "price": 0, - "faq": true, - "fields": 10 - }, - { - "vertical": "Wellness clinics", - "geo": "Miami", - "host": "purehealthmiami.com", - "ok": true, - "words": 436, - "price": 0, - "faq": false, - "fields": 0 - }, - { - "vertical": "Supercar dealers", - "geo": "Dubai", - "host": "f1rstmotors.com", - "ok": true, - "words": 205, - "price": 0, - "faq": false, - "fields": 1 - }, - { - "vertical": "Supercar dealers", - "geo": "Miami", - "host": "premierautomiami.com", - "ok": true, - "words": 1940, - "price": 12, - "faq": true, - "fields": 9 - }, - { - "vertical": "Supercar dealers", - "geo": "Miami", - "host": "limitedspec.com", - "ok": true, - "words": 901, - "price": 12, - "faq": false, - "fields": 6 - }, - { - "vertical": "Supercar dealers", - "geo": "Miami", - "host": "exoticshunter.com", - "ok": true, - "words": 564, - "price": 22, - "faq": false, - "fields": 0 - }, - { - "vertical": "Supercar dealers", - "geo": "Miami", - "host": "thecollection.com", - "ok": true, - "words": 1307, - "price": 0, - "faq": true, - "fields": 16 - }, - { - "vertical": "Supercar dealers", - "geo": "Miami", - "host": "pugachev.miami", - "ok": false, - "err": "HTTP Error 403: Forbidden" - }, - { - "vertical": "Supercar dealers", - "geo": "Dubai", - "host": "luxurysupercarsdubai.com", - "ok": true, - "words": 2603, - "price": 0, - "faq": true, - "fields": 8 - }, - { - "vertical": "Supercar dealers", - "geo": "Miami", - "host": "prestigeluxuryrentals.com", - "ok": true, - "words": 2051, - "price": 7, - "faq": true, - "fields": 2 - }, - { - "vertical": "Luxury real estate", - "geo": "London", - "host": "knightsbridgeprimeproperty.com", - "ok": true, - "words": 953, - "price": 17, - "faq": false, - "fields": 20 - }, - { - "vertical": "Luxury real estate", - "geo": "London", - "host": "jefferies.london", - "ok": true, - "words": 1195, - "price": 15, - "faq": false, - "fields": 0 - }, - { - "vertical": "Luxury real estate", - "geo": "London", - "host": "beauchamp.com", - "ok": true, - "words": 2807, - "price": 5, - "faq": false, - "fields": 216 - }, - { - "vertical": "Luxury real estate", - "geo": "London", - "host": "mayfairinternationalrealty.com", - "ok": true, - "words": 305, - "price": 0, - "faq": false, - "fields": 0 - }, - { - "vertical": "Luxury real estate", - "geo": "London", - "host": "eatonpremier.com", - "ok": true, - "words": 1374, - "price": 7, - "faq": false, - "fields": 20 - }, - { - "vertical": "Cosmetic dentistry", - "geo": "London", - "host": "77harleystreet.co.uk", - "ok": true, - "words": 886, - "price": 0, - "faq": true, - "fields": 0 - }, - { - "vertical": "Cosmetic dentistry", - "geo": "London", - "host": "marylebonesmileclinic.co.uk", - "ok": false, - "err": "HTTP Error 403: Forbidden" - }, - { - "vertical": "Cosmetic dentistry", - "geo": "London", - "host": "harleystreetsmileclinic.co.uk", - "ok": true, - "words": 3339, - "price": 0, - "faq": true, - "fields": 29 - }, - { - "vertical": "Cosmetic dentistry", - "geo": "London", - "host": "prodentalclinic.london", - "ok": true, - "words": 870, - "price": 2, - "faq": false, - "fields": 1 - }, - { - "vertical": "Cosmetic dentistry", - "geo": "London", - "host": "harleystreetdentalandimplantclinic.co.uk", - "ok": true, - "words": 795, - "price": 0, - "faq": false, - "fields": 13 - }, - { - "vertical": "Cosmetic dentistry", - "geo": "London", - "host": "smilelondon.co.uk", - "ok": true, - "words": 1410, - "price": 0, - "faq": false, - "fields": 0 - }, - { - "vertical": "Cosmetic dentistry", - "geo": "London", - "host": "harleystreetdentalartclinic.com", - "ok": true, - "words": 1184, - "price": 0, - "faq": false, - "fields": 5 - }, - { - "vertical": "Cosmetic dentistry", - "geo": "London", - "host": "harleystreetimplantcentre.co.uk", - "ok": false, - "err": "HTTP Error 403: Forbidden" - }, - { - "vertical": "Wellness centres & spas", - "geo": "London", - "host": "www.thirdspace.london", - "ok": false, - "err": "HTTP Error 403: Forbidden" - }, - { - "vertical": "Wellness centres & spas", - "geo": "London", - "host": "rebaserecovery.com", - "ok": true, - "words": 9, - "price": 0, - "faq": false, - "fields": 0 - }, - { - "vertical": "Wellness centres & spas", - "geo": "London", - "host": "comohotels.com", - "ok": true, - "words": 1948, - "price": 0, - "faq": true, - "fields": 8 - }, - { - "vertical": "Wellness centres & spas", - "geo": "London", - "host": "corinthia.com", - "ok": true, - "words": 1185, - "price": 5, - "faq": false, - "fields": 2 - }, - { - "vertical": "Wellness centres & spas", - "geo": "London", - "host": "bulgarihotels.com", - "ok": true, - "words": 1642, - "price": 0, - "faq": false, - "fields": 21 - }, - { - "vertical": "Wellness centres & spas", - "geo": "Global", - "host": "luxewellnessclub.com", - "ok": true, - "words": 1422, - "price": 0, - "faq": false, - "fields": 25 - }, - { - "vertical": "Premium HVAC", - "geo": "Miami", - "host": "kleinairconditioning.com", - "ok": true, - "words": 1811, - "price": 0, - "faq": false, - "fields": 6 - }, - { - "vertical": "Premium HVAC", - "geo": "Miami", - "host": "ameritempac.com", - "ok": true, - "words": 1065, - "price": 0, - "faq": false, - "fields": 7 - }, - { - "vertical": "Premium HVAC", - "geo": "Miami", - "host": "luxury-cooling.com", - "ok": true, - "words": 598, - "price": 0, - "faq": false, - "fields": 7 - }, - { - "vertical": "Premium HVAC", - "geo": "Miami", - "host": "acpowercomfort.com", - "ok": true, - "words": 2970, - "price": 0, - "faq": true, - "fields": 0 - }, - { - "vertical": "Premium HVAC", - "geo": "Miami", - "host": "miamihvac.net", - "ok": true, - "words": 763, - "price": 0, - "faq": false, - "fields": 0 - }, - { - "vertical": "Premium HVAC", - "geo": "Miami", - "host": "polluxair.com", - "ok": true, - "words": 3513, - "price": 0, - "faq": false, - "fields": 4 - }, - { - "vertical": "Premium HVAC", - "geo": "Miami", - "host": "centralcomfortairconditioning.com", - "ok": true, - "words": 1825, - "price": 1, - "faq": true, - "fields": 8 - } -] \ No newline at end of file diff --git a/specs/003-wellness-clinic-launch/study-scan.py b/specs/003-wellness-clinic-launch/study-scan.py deleted file mode 100644 index 97b16dfc..00000000 --- a/specs/003-wellness-clinic-launch/study-scan.py +++ /dev/null @@ -1,69 +0,0 @@ -import re,json,ssl,urllib.request,concurrent.futures as cf -from html.parser import HTMLParser -ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE -UA={'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126 Safari/537.36'} - -SITES={ -"Wellness clinics":[("London","https://www.harpalclinic.co.uk/"),("London","https://the-hvn.com/"), - ("London","https://optimisehealth.com/"),("London","https://www.londoncryo.com/"),("London","https://nuutro.co.uk/"), - ("London","https://www.thewellnesslondon.com/"),("London","https://www.hum2n.com/"), - ("New York","https://collagenbar.nyc/pages/longevity"),("New York","https://manhattanmedicalarts.com/longevity-wellness"), - ("New York","https://www.prefusionhealth.com/"),("New York","https://apollohousenyc.com/"), - ("New York","https://www.merakiintegrative.com/longevity-protocol"),("New York","https://www.patientsmedical.com/"), - ("Miami","https://vitaflowfl.com/"),("Miami","https://hallongevity.com/"),("Miami","https://alivemiami.com/"), - ("Miami","https://neomedicineinstitute.com/"),("Miami","https://alphahormones.com/"),("Miami","https://purehealthmiami.com/")], -"Supercar dealers":[("Dubai","https://f1rstmotors.com/"),("Miami","https://www.premierautomiami.com/exotic-cars/"), - ("Miami","https://www.limitedspec.com/"),("Miami","https://www.exoticshunter.com/"),("Miami","https://www.thecollection.com/"), - ("Miami","https://pugachev.miami/"),("Dubai","https://luxurysupercarsdubai.com/"),("Miami","https://prestigeluxuryrentals.com/")], -"Luxury real estate":[("London","https://knightsbridgeprimeproperty.com/"),("London","https://jefferies.london/"), - ("London","https://www.beauchamp.com/"),("London","https://www.mayfairinternationalrealty.com/"), - ("London","https://eatonpremier.com/")], -"Cosmetic dentistry":[("London","https://www.77harleystreet.co.uk/"),("London","https://marylebonesmileclinic.co.uk/"), - ("London","https://harleystreetsmileclinic.co.uk/"),("London","https://prodentalclinic.london/"), - ("London","https://www.harleystreetdentalandimplantclinic.co.uk/"),("London","https://smilelondon.co.uk/"), - ("London","https://harleystreetdentalartclinic.com/"),("London","https://harleystreetimplantcentre.co.uk/")], -"Wellness centres & spas":[("London","https://www.thirdspace.london/"),("London","https://www.rebaserecovery.com/"), - ("London","https://www.comohotels.com/london/como-metropolitan-london/wellness"), - ("London","https://www.corinthia.com/en-gb/london/spa-at-corinthia-london/"), - ("London","https://www.bulgarihotels.com/en_US/london/spa-and-fitness/the-bulgari-spa"), - ("Global","https://www.luxewellnessclub.com/en/")], -"Premium HVAC":[("Miami","https://kleinairconditioning.com/"),("Miami","https://www.ameritempac.com/"), - ("Miami","https://luxury-cooling.com/"),("Miami","https://acpowercomfort.com/"),("Miami","https://www.miamihvac.net/"), - ("Miami","https://polluxair.com/miami/"),("Miami","https://centralcomfortairconditioning.com/")], -} - -class S(HTMLParser): - def __init__(s): - super().__init__(); s.txt=[]; s.skip=0; s.inputs=0 - def handle_starttag(s,t,a): - d=dict(a) - if t in ('script','style','noscript'): s.skip+=1 - if t in ('input','select','textarea') and d.get('type','text').lower() not in ('hidden','submit','button'): s.inputs+=1 - def handle_endtag(s,t): - if t in ('script','style','noscript') and s.skip: s.skip-=1 - def handle_data(s,d): - if not s.skip: s.txt.append(d) - -def scan(item): - vert,(geo,url)=item - try: - html=urllib.request.urlopen(urllib.request.Request(url,headers=UA),timeout=25,context=ctx).read().decode('utf-8','ignore') - p=S(); p.feed(html); txt=re.sub(r'\s+',' ',' '.join(p.txt)) - price=len(re.findall(r'[£$€]\s?\d',txt)) - return {"vertical":vert,"geo":geo,"host":url.split('//')[1].split('/')[0].replace('www.',''), - "ok":True,"words":len(txt.split()),"price":price, - "faq":bool(re.search(r'frequently asked|FAQ',txt,re.I)),"fields":p.inputs} - except Exception as e: - return {"vertical":vert,"geo":geo,"host":url.split('//')[1].split('/')[0],"ok":False,"err":str(e)[:40]} - -jobs=[(v,s) for v,lst in SITES.items() for s in lst] -with cf.ThreadPoolExecutor(max_workers=12) as ex: rows=list(ex.map(scan,jobs)) -ok=[r for r in rows if r["ok"]] -print(f"ATTEMPTED {len(rows)} · READABLE {len(ok)} · {len(SITES)} verticals · {len(set(r['geo'] for r in ok))} geographies\n") -print(f"{'VERTICAL':26}{'n':>4}{'no price':>10}{'no FAQ':>9}{'max fields':>12}") -for v in SITES: - g=[r for r in ok if r["vertical"]==v] - if not g: continue - print(f"{v:26}{len(g):>4}{sum(1 for r in g if r['price']==0):>10}{sum(1 for r in g if not r['faq']):>9}{max(r['fields'] for r in g):>12}") -print(f"\n{'TOTAL':26}{len(ok):>4}{sum(1 for r in ok if r['price']==0):>10}{sum(1 for r in ok if not r['faq']):>9}{max(r['fields'] for r in ok):>12}") -json.dump(rows,open("luxstudy.json","w"),indent=1) diff --git a/study/INTAKE-STANDARD.md b/study/INTAKE-STANDARD.md new file mode 100644 index 00000000..915c7997 --- /dev/null +++ b/study/INTAKE-STANDARD.md @@ -0,0 +1,83 @@ +# Intake standard — what is allowed into the vault + +Two pipelines feed the moat. Only the first is automated today. + +| | Source | Status | +|---|---|---| +| **A. Market scan** | 99 sites, 13 industries, 8 geographies, read daily | **Live** — `study/scan.py`, cron `0 7 * * *` | +| **B. Practitioner knowledge** | What operators publish for free — teardowns, post-mortems, platform docs | **Specified, not automated.** The gate below is the reason. | + +Pipeline A is safe to automate because it measures rather than believes: it reads +a page and counts what is on it. Pipeline B ingests *claims*, and a claim that +turns out to be wrong does not sit inertly in a file — it gets repeated to a +paying client in an audit. That is the failure that costs a client, so the gate +comes before the automation, not after. + +--- + +## The gate — all five, or it does not enter + +A candidate is a single claim about how something works, not an article. + +**1. Mechanism, not outcome.** It must explain *why*, in a way that could be + wrong. "Creative diversity is measured at the concept level, because hooks on + one concept reach the same audience" passes. "Post more UGC, it works" + does not — there is nothing there to test. + +**2. Named, checkable source.** A person, company or document, with a URL. + Anonymous, aggregated or "people are saying" does not enter. Screenshots + without a traceable origin do not enter. + +**3. Corroboration, or first-party status.** Either two independent sources that + are not quoting each other, or one first-party source that owns the system + (a platform's own documentation, an operator's own account). One confident + stranger is not evidence. + +**4. Falsifiable as written.** State the condition that would disprove it. A + claim nothing could contradict is a slogan. + +**5. It changes what we would do.** If knowing it alters no audit, no + recommendation and no decision, it is trivia. Trivia is the most common + rejection and the easiest to wave through. + +**Automatic rejection**, regardless of the five: anything selling a course, a +template or a service; engagement bait; numbers with no method; screenshots of +dashboards with no account context; anything restating a platform's marketing +copy as insight. + +--- + +## On entry + +Every accepted claim carries, or it is not accepted: + +- the claim in one sentence +- what would disprove it +- the source URL and date +- corroboration status: `first-party` · `two-source` · `single-source-pending` +- confidence: `verified` · `reported` · `contested` +- what it changes about the work + +`single-source-pending` may be stored but **may never be used in client-facing +work** until it corroborates or is dropped. That distinction is the whole point: +the vault is allowed to hold uncertainty, an audit is not. + +--- + +## Why B is not on a cron yet + +An automated harvester with a model-judged gate will pass things the gate would +reject on a careful reading — it will find a claim plausible, well-argued and +wrong. Pipeline A can run unattended because being wrong means a miscounted +form field. Pipeline B being wrong means a confident, incorrect recommendation +in a document with a human signature on it. + +The sequence: run B by hand at first, keep every rejection with its reason, and +once there are enough rejections to see the pattern, encode *that* as the +automated filter. Automating the gate before knowing what it rejects is how the +garbage gets in. + +Worked example of a claim that passed all five: the Meta creative-diversity +audit — mechanism stated, named operators, corroborated by Meta's own +"Creative Diversity" framework, falsifiable (asset count rises while reach stays +flat), and it changed the media plan. diff --git a/study/corpus.json b/study/corpus.json new file mode 100644 index 00000000..c453fc8f --- /dev/null +++ b/study/corpus.json @@ -0,0 +1,497 @@ +[ + { + "industry": "Wellness clinics", + "geo": "London", + "url": "https://www.harpalclinic.co.uk/" + }, + { + "industry": "Wellness clinics", + "geo": "London", + "url": "https://the-hvn.com/" + }, + { + "industry": "Wellness clinics", + "geo": "London", + "url": "https://optimisehealth.com/" + }, + { + "industry": "Wellness clinics", + "geo": "London", + "url": "https://www.londoncryo.com/" + }, + { + "industry": "Wellness clinics", + "geo": "London", + "url": "https://nuutro.co.uk/" + }, + { + "industry": "Wellness clinics", + "geo": "London", + "url": "https://www.thewellnesslondon.com/" + }, + { + "industry": "Wellness clinics", + "geo": "London", + "url": "https://www.hum2n.com/" + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "url": "https://collagenbar.nyc/pages/longevity" + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "url": "https://manhattanmedicalarts.com/longevity-wellness" + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "url": "https://www.prefusionhealth.com/" + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "url": "https://apollohousenyc.com/" + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "url": "https://www.merakiintegrative.com/longevity-protocol" + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "url": "https://www.patientsmedical.com/" + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "url": "https://vitaflowfl.com/" + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "url": "https://hallongevity.com/" + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "url": "https://alivemiami.com/" + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "url": "https://neomedicineinstitute.com/" + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "url": "https://alphahormones.com/" + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "url": "https://purehealthmiami.com/" + }, + { + "industry": "Supercar dealers", + "geo": "Dubai", + "url": "https://f1rstmotors.com/" + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "url": "https://www.premierautomiami.com/exotic-cars/" + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "url": "https://www.limitedspec.com/" + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "url": "https://www.exoticshunter.com/" + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "url": "https://www.thecollection.com/" + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "url": "https://pugachev.miami/" + }, + { + "industry": "Supercar dealers", + "geo": "Dubai", + "url": "https://luxurysupercarsdubai.com/" + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "url": "https://prestigeluxuryrentals.com/" + }, + { + "industry": "Luxury real estate", + "geo": "London", + "url": "https://knightsbridgeprimeproperty.com/" + }, + { + "industry": "Luxury real estate", + "geo": "London", + "url": "https://jefferies.london/" + }, + { + "industry": "Luxury real estate", + "geo": "London", + "url": "https://www.beauchamp.com/" + }, + { + "industry": "Luxury real estate", + "geo": "London", + "url": "https://www.mayfairinternationalrealty.com/" + }, + { + "industry": "Luxury real estate", + "geo": "London", + "url": "https://eatonpremier.com/" + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "url": "https://www.77harleystreet.co.uk/" + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "url": "https://marylebonesmileclinic.co.uk/" + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "url": "https://harleystreetsmileclinic.co.uk/" + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "url": "https://prodentalclinic.london/" + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "url": "https://www.harleystreetdentalandimplantclinic.co.uk/" + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "url": "https://smilelondon.co.uk/" + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "url": "https://harleystreetdentalartclinic.com/" + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "url": "https://harleystreetimplantcentre.co.uk/" + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "url": "https://www.thirdspace.london/" + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "url": "https://www.rebaserecovery.com/" + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "url": "https://www.comohotels.com/london/como-metropolitan-london/wellness" + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "url": "https://www.corinthia.com/en-gb/london/spa-at-corinthia-london/" + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "url": "https://www.bulgarihotels.com/en_US/london/spa-and-fitness/the-bulgari-spa" + }, + { + "industry": "Wellness centres & spas", + "geo": "Global", + "url": "https://www.luxewellnessclub.com/en/" + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "url": "https://kleinairconditioning.com/" + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "url": "https://www.ameritempac.com/" + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "url": "https://luxury-cooling.com/" + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "url": "https://acpowercomfort.com/" + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "url": "https://www.miamihvac.net/" + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "url": "https://polluxair.com/miami/" + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "url": "https://centralcomfortairconditioning.com/" + }, + { + "industry": "Yacht brokers", + "geo": "Monaco", + "url": "https://www.superyachtsmonaco.com/" + }, + { + "industry": "Yacht brokers", + "geo": "Monaco", + "url": "https://www.twwyachts.com/" + }, + { + "industry": "Yacht brokers", + "geo": "Monaco", + "url": "https://www.yacht-zoo.com/" + }, + { + "industry": "Yacht brokers", + "geo": "Fort Lauderdale", + "url": "https://www.moranyachts.com/" + }, + { + "industry": "Yacht brokers", + "geo": "Fort Lauderdale", + "url": "https://www.worthavenueyachts.com/" + }, + { + "industry": "Yacht brokers", + "geo": "Monaco", + "url": "https://iyc.com/" + }, + { + "industry": "Private aviation", + "geo": "Global", + "url": "https://www.vistajet.com/" + }, + { + "industry": "Private aviation", + "geo": "Dubai", + "url": "https://jetluxe.com/" + }, + { + "industry": "Private aviation", + "geo": "Global", + "url": "https://privatejet.com/" + }, + { + "industry": "Private aviation", + "geo": "Global", + "url": "https://www.jetfinder.com/" + }, + { + "industry": "Private aviation", + "geo": "Global", + "url": "https://skyaccess.com/" + }, + { + "industry": "Private aviation", + "geo": "Global", + "url": "https://charterjetone.com/" + }, + { + "industry": "Private aviation", + "geo": "Global", + "url": "https://www.paramountbusinessjets.com/" + }, + { + "industry": "Plastic surgery", + "geo": "Beverly Hills", + "url": "https://www.harrisfacialplastics.com/" + }, + { + "industry": "Plastic surgery", + "geo": "Beverly Hills", + "url": "https://www.drcalvert.com/" + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "url": "https://aesthetics.ae/" + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "url": "https://www.bhwaa.com/" + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "url": "https://elitecosmeticsurgery.ae/" + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "url": "https://skin111.com/plastic-surgery" + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "url": "https://dubailondonhospital.com/departments/plastic-surgery/" + }, + { + "industry": "Luxury watches", + "geo": "London", + "url": "https://www.bqwatches.com/" + }, + { + "industry": "Luxury watches", + "geo": "London", + "url": "https://chrono-group.com/" + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "url": "https://www.luxurysouq.com/" + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "url": "https://dubailuxurywatch.com/" + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "url": "https://www.watchtradeco.com/" + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "url": "https://www.bigmoewatches.com/" + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "url": "https://thewatchmeister.com/" + }, + { + "industry": "Private members clubs", + "geo": "London", + "url": "https://www.theartsclub.co.uk/" + }, + { + "industry": "Private members clubs", + "geo": "Global", + "url": "https://www.sohohouse.com/" + }, + { + "industry": "Private members clubs", + "geo": "London", + "url": "https://5hertfordstreet.com/" + }, + { + "industry": "Private members clubs", + "geo": "London", + "url": "https://www.annabels.co.uk/" + }, + { + "industry": "Private members clubs", + "geo": "New York", + "url": "https://www.casacipriani.com/" + }, + { + "industry": "Private members clubs", + "geo": "London", + "url": "https://www.homehouse.co.uk/" + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "url": "https://www.huntsmansavilerow.com/" + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "url": "https://englishcut.com/" + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "url": "https://www.gievesandhawkes.com/" + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "url": "https://nortonandsons.co.uk/" + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "url": "https://redmayne1860.com/" + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "url": "https://onsavilerow.com/" + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "url": "https://savilerowcustom.com/" + }, + { + "industry": "Interior design", + "geo": "London", + "url": "https://rosenarmani.com/" + }, + { + "industry": "Interior design", + "geo": "London", + "url": "https://www.elliotjames.com/" + }, + { + "industry": "Interior design", + "geo": "London", + "url": "https://www.interieurlondon.com/" + }, + { + "industry": "Interior design", + "geo": "Dubai", + "url": "https://algedra.ae/" + }, + { + "industry": "Interior design", + "geo": "London", + "url": "https://accouterdesign.com/" + }, + { + "industry": "Interior design", + "geo": "London", + "url": "https://hillhouseinteriors.com/" + } +] \ No newline at end of file diff --git a/study/render.py b/study/render.py new file mode 100644 index 00000000..2b5c6d4b --- /dev/null +++ b/study/render.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +"""Write the newest snapshot's numbers into the public pages. + +The study is advertised as refreshed daily, so the figures on the site cannot be +hand-typed — they would be a lie within 24 hours. This rewrites the marked spans +from the latest snapshot and nothing else. + + python3 study/render.py # rewrite pages from the newest snapshot + python3 study/render.py --check # exit 1 if the pages are out of date + +Only content inside data-study="..." spans is touched. +""" +import json +import os +import re +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(HERE) +SNAPS = os.path.join(HERE, "snapshots") +PAGES = ["index.html", "audit.html", "pricing.html", "specimen.html"] + +WORDS = {0: "zero", 1: "one", 2: "two", 3: "three", 4: "four", 5: "five", 6: "six", + 7: "seven", 8: "eight", 9: "nine", 10: "ten", 11: "eleven", 12: "twelve", + 13: "thirteen", 14: "fourteen", 15: "fifteen", 16: "sixteen", 17: "seventeen", + 18: "eighteen", 19: "nineteen", 20: "twenty"} +TENS = {2: "twenty", 3: "thirty", 4: "forty", 5: "fifty", 6: "sixty", 7: "seventy", + 8: "eighty", 9: "ninety"} + + +def spell(n): + if n in WORDS: + return WORDS[n] + if n < 100: + t, r = divmod(n, 10) + return TENS[t] + (f"-{WORDS[r]}" if r else "") + return str(n) + + +def newest(): + files = sorted(f for f in os.listdir(SNAPS) if f.endswith(".json")) + if not files: + raise SystemExit("no snapshots — run study/scan.py first") + with open(os.path.join(SNAPS, files[-1])) as fh: + return json.load(fh) + + +def values(s): + return { + "no_price": str(s["no_price"]), + "readable": str(s["readable"]), + "no_price_word": spell(s["no_price"]), + "readable_word": spell(s["readable"]), + "no_faq_word": spell(s["no_faq"]), + "attempted_word": spell(s["attempted"]), + "blocked_word": spell(s["attempted"] - s["readable"]), + "industries_word": spell(s["industries"]), + "geographies_word": spell(s["geographies"]), + } + + +def main(): + snap = newest() + vals = values(snap) + check = "--check" in sys.argv + stale = [] + + for page in PAGES: + path = os.path.join(ROOT, "public", page) + if not os.path.exists(path): + continue + src = open(path).read() + + def sub(m): + key = m.group(1) + return f'{vals.get(key, m.group(2))}' + + out = re.sub(r'(.*?)', sub, src, flags=re.S) + if out != src: + if check: + stale.append(page) + else: + open(path, "w").write(out) + print(f" {page}: updated") + + if check and stale: + print("stale: " + ", ".join(stale)) + return 1 + if check: + print(f"pages match snapshot {snap['date']}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/study/scan.py b/study/scan.py new file mode 100644 index 00000000..7f37644d --- /dev/null +++ b/study/scan.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Daily read of the luxury-market corpus. + +Writes a dated snapshot and prints only what CHANGED since the last one. +The snapshot is the asset: a single scrape is a statistic, a daily series is +a record of who fixed what and when — which nobody else has. + + python3 study/scan.py # scan, save snapshot, print the delta + python3 study/scan.py --summary # also print the full per-industry table + +Silent output means nothing moved. Designed for a --no-agent cron. +""" +import concurrent.futures as cf +import json +import os +import re +import ssl +import sys +import urllib.request +from datetime import date, datetime, timezone +from html.parser import HTMLParser + +HERE = os.path.dirname(os.path.abspath(__file__)) +SNAPS = os.path.join(HERE, "snapshots") +CORPUS = os.path.join(HERE, "corpus.json") + +CTX = ssl.create_default_context() +CTX.check_hostname = False +CTX.verify_mode = ssl.CERT_NONE +UA = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/126 Safari/537.36"} + + +class Extract(HTMLParser): + def __init__(self): + super().__init__() + self.txt, self.skip, self.inputs = [], 0, 0 + + def handle_starttag(self, tag, attrs): + if tag in ("script", "style", "noscript"): + self.skip += 1 + if tag in ("input", "select", "textarea"): + if dict(attrs).get("type", "text").lower() not in ("hidden", "submit", "button"): + self.inputs += 1 + + def handle_endtag(self, tag): + if tag in ("script", "style", "noscript") and self.skip: + self.skip -= 1 + + def handle_data(self, data): + if not self.skip: + self.txt.append(data) + + +def scan(site): + host = site["url"].split("//")[1].split("/")[0].replace("www.", "") + row = {"industry": site["industry"], "geo": site["geo"], "host": host} + try: + req = urllib.request.Request(site["url"], headers=UA) + html = urllib.request.urlopen(req, timeout=25, context=CTX).read().decode("utf-8", "ignore") + p = Extract() + p.feed(html) + txt = re.sub(r"\s+", " ", " ".join(p.txt)) + row.update(ok=True, words=len(txt.split()), + price=len(re.findall(r"[£$€]\s?\d", txt)), + faq=bool(re.search(r"frequently asked|FAQ", txt, re.I)), + fields=p.inputs) + except Exception as exc: + row.update(ok=False, err=str(exc)[:60]) + return row + + +def previous_snapshot(): + if not os.path.isdir(SNAPS): + return None + files = sorted(f for f in os.listdir(SNAPS) if f.endswith(".json")) + if not files: + return None + with open(os.path.join(SNAPS, files[-1])) as fh: + return json.load(fh) + + +def delta(old, new): + """Only material changes. Word-count drift is noise and is ignored.""" + if not old: + return [] + prev = {r["host"]: r for r in old["rows"]} + out = [] + for r in new: + p = prev.get(r["host"]) + if not p: + out.append(f"NEW {r['host']} ({r['industry']})") + continue + if p.get("ok") and not r.get("ok"): + out.append(f"BLOCKED {r['host']} — was readable, now {r.get('err', 'unreachable')}") + continue + if not p.get("ok") and r.get("ok"): + out.append(f"BACK {r['host']} — readable again") + if not (p.get("ok") and r.get("ok")): + continue + if (p["price"] == 0) != (r["price"] == 0): + out.append(f"PRICE {r['host']} — " + + ("now shows a price" if r["price"] else "removed its price")) + if p["faq"] != r["faq"]: + out.append(f"ANSWERS {r['host']} — " + + ("added an answers section" if r["faq"] else "removed its answers section")) + if abs(p["fields"] - r["fields"]) >= 3: + out.append(f"FORM {r['host']} — {p['fields']} to {r['fields']} input fields") + return out + + +def main(): + with open(CORPUS) as fh: + corpus = json.load(fh) + with cf.ThreadPoolExecutor(max_workers=12) as ex: + rows = list(ex.map(scan, corpus)) + ok = [r for r in rows if r["ok"]] + + prev = previous_snapshot() + changes = delta(prev, rows) + + os.makedirs(SNAPS, exist_ok=True) + snapshot = { + "date": date.today().isoformat(), + "scanned_at": datetime.now(timezone.utc).isoformat(timespec="seconds"), + "attempted": len(rows), "readable": len(ok), + "industries": len({r["industry"] for r in ok}), + "geographies": len({r["geo"] for r in ok}), + "no_price": sum(1 for r in ok if r["price"] == 0), + "no_faq": sum(1 for r in ok if not r["faq"]), + "rows": rows, + } + with open(os.path.join(SNAPS, f"{snapshot['date']}.json"), "w") as fh: + json.dump(snapshot, fh, indent=1) + + if "--summary" in sys.argv: + print(f"{snapshot['date']} · attempted {snapshot['attempted']} · readable {snapshot['readable']} · " + f"{snapshot['industries']} industries · {snapshot['geographies']} geographies") + print(f"{'INDUSTRY':26}{'n':>4}{'no price':>10}{'no FAQ':>9}") + for ind in dict.fromkeys(r["industry"] for r in ok): + g = [r for r in ok if r["industry"] == ind] + print(f"{ind:26}{len(g):>4}{sum(1 for r in g if r['price'] == 0):>10}" + f"{sum(1 for r in g if not r['faq']):>9}") + print(f"\n{'TOTAL':26}{len(ok):>4}{snapshot['no_price']:>10}{snapshot['no_faq']:>9}") + + if changes: + print(f"TinyStudio market scan — {len(changes)} change(s) since " + f"{prev['date'] if prev else 'first run'}\n") + for line in changes: + print(f" {line}") + print(f"\nCorpus: {snapshot['readable']}/{snapshot['attempted']} readable across " + f"{snapshot['industries']} industries.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/study/snapshots/2026-08-06.json b/study/snapshots/2026-08-06.json new file mode 100644 index 00000000..ad6aa563 --- /dev/null +++ b/study/snapshots/2026-08-06.json @@ -0,0 +1,972 @@ +{ + "date": "2026-08-06", + "scanned_at": "2026-08-06T04:29:15+00:00", + "attempted": 99, + "readable": 89, + "industries": 13, + "geographies": 8, + "no_price": 57, + "no_faq": 53, + "rows": [ + { + "industry": "Wellness clinics", + "geo": "London", + "host": "harpalclinic.co.uk", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "industry": "Wellness clinics", + "geo": "London", + "host": "the-hvn.com", + "ok": true, + "words": 2359, + "price": 0, + "faq": true, + "fields": 3 + }, + { + "industry": "Wellness clinics", + "geo": "London", + "host": "optimisehealth.com", + "ok": true, + "words": 1424, + "price": 1, + "faq": true, + "fields": 6 + }, + { + "industry": "Wellness clinics", + "geo": "London", + "host": "londoncryo.com", + "ok": true, + "words": 872, + "price": 1, + "faq": true, + "fields": 1 + }, + { + "industry": "Wellness clinics", + "geo": "London", + "host": "nuutro.co.uk", + "ok": true, + "words": 1764, + "price": 0, + "faq": true, + "fields": 6 + }, + { + "industry": "Wellness clinics", + "geo": "London", + "host": "thewellnesslondon.com", + "ok": true, + "words": 1368, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Wellness clinics", + "geo": "London", + "host": "hum2n.com", + "ok": true, + "words": 1252, + "price": 0, + "faq": false, + "fields": 1 + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "host": "collagenbar.nyc", + "ok": true, + "words": 2120, + "price": 2, + "faq": true, + "fields": 2 + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "host": "manhattanmedicalarts.com", + "ok": true, + "words": 1693, + "price": 0, + "faq": true, + "fields": 1 + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "host": "prefusionhealth.com", + "ok": true, + "words": 1704, + "price": 0, + "faq": true, + "fields": 0 + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "host": "apollohousenyc.com", + "ok": true, + "words": 2329, + "price": 0, + "faq": true, + "fields": 11 + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "host": "merakiintegrative.com", + "ok": true, + "words": 1150, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Wellness clinics", + "geo": "New York", + "host": "patientsmedical.com", + "ok": true, + "words": 7989, + "price": 0, + "faq": true, + "fields": 30 + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "host": "vitaflowfl.com", + "ok": true, + "words": 1156, + "price": 11, + "faq": false, + "fields": 0 + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "host": "hallongevity.com", + "ok": true, + "words": 1492, + "price": 4, + "faq": false, + "fields": 0 + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "host": "alivemiami.com", + "ok": true, + "words": 4808, + "price": 9, + "faq": false, + "fields": 5 + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "host": "neomedicineinstitute.com", + "ok": true, + "words": 1163, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "host": "alphahormones.com", + "ok": true, + "words": 5463, + "price": 0, + "faq": true, + "fields": 10 + }, + { + "industry": "Wellness clinics", + "geo": "Miami", + "host": "purehealthmiami.com", + "ok": true, + "words": 436, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Supercar dealers", + "geo": "Dubai", + "host": "f1rstmotors.com", + "ok": true, + "words": 205, + "price": 0, + "faq": false, + "fields": 1 + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "host": "premierautomiami.com", + "ok": true, + "words": 1940, + "price": 12, + "faq": true, + "fields": 9 + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "host": "limitedspec.com", + "ok": true, + "words": 901, + "price": 12, + "faq": false, + "fields": 6 + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "host": "exoticshunter.com", + "ok": true, + "words": 564, + "price": 22, + "faq": false, + "fields": 0 + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "host": "thecollection.com", + "ok": true, + "words": 1307, + "price": 0, + "faq": true, + "fields": 16 + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "host": "pugachev.miami", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "industry": "Supercar dealers", + "geo": "Dubai", + "host": "luxurysupercarsdubai.com", + "ok": true, + "words": 2603, + "price": 0, + "faq": true, + "fields": 8 + }, + { + "industry": "Supercar dealers", + "geo": "Miami", + "host": "prestigeluxuryrentals.com", + "ok": true, + "words": 2051, + "price": 7, + "faq": true, + "fields": 2 + }, + { + "industry": "Luxury real estate", + "geo": "London", + "host": "knightsbridgeprimeproperty.com", + "ok": true, + "words": 953, + "price": 17, + "faq": false, + "fields": 20 + }, + { + "industry": "Luxury real estate", + "geo": "London", + "host": "jefferies.london", + "ok": true, + "words": 1195, + "price": 15, + "faq": false, + "fields": 0 + }, + { + "industry": "Luxury real estate", + "geo": "London", + "host": "beauchamp.com", + "ok": true, + "words": 2807, + "price": 5, + "faq": false, + "fields": 216 + }, + { + "industry": "Luxury real estate", + "geo": "London", + "host": "mayfairinternationalrealty.com", + "ok": true, + "words": 305, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Luxury real estate", + "geo": "London", + "host": "eatonpremier.com", + "ok": true, + "words": 1374, + "price": 7, + "faq": false, + "fields": 20 + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "host": "77harleystreet.co.uk", + "ok": true, + "words": 886, + "price": 0, + "faq": true, + "fields": 0 + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "host": "marylebonesmileclinic.co.uk", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "host": "harleystreetsmileclinic.co.uk", + "ok": true, + "words": 3339, + "price": 0, + "faq": true, + "fields": 29 + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "host": "prodentalclinic.london", + "ok": true, + "words": 870, + "price": 2, + "faq": false, + "fields": 1 + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "host": "harleystreetdentalandimplantclinic.co.uk", + "ok": true, + "words": 795, + "price": 0, + "faq": false, + "fields": 13 + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "host": "smilelondon.co.uk", + "ok": true, + "words": 1410, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "host": "harleystreetdentalartclinic.com", + "ok": true, + "words": 1184, + "price": 0, + "faq": false, + "fields": 5 + }, + { + "industry": "Cosmetic dentistry", + "geo": "London", + "host": "harleystreetimplantcentre.co.uk", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "host": "thirdspace.london", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "host": "rebaserecovery.com", + "ok": true, + "words": 9, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "host": "comohotels.com", + "ok": true, + "words": 1948, + "price": 0, + "faq": true, + "fields": 8 + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "host": "corinthia.com", + "ok": true, + "words": 1185, + "price": 5, + "faq": false, + "fields": 2 + }, + { + "industry": "Wellness centres & spas", + "geo": "London", + "host": "bulgarihotels.com", + "ok": true, + "words": 1642, + "price": 0, + "faq": false, + "fields": 21 + }, + { + "industry": "Wellness centres & spas", + "geo": "Global", + "host": "luxewellnessclub.com", + "ok": true, + "words": 1422, + "price": 0, + "faq": false, + "fields": 25 + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "host": "kleinairconditioning.com", + "ok": true, + "words": 1811, + "price": 0, + "faq": false, + "fields": 6 + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "host": "ameritempac.com", + "ok": true, + "words": 1065, + "price": 0, + "faq": false, + "fields": 7 + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "host": "luxury-cooling.com", + "ok": true, + "words": 598, + "price": 0, + "faq": false, + "fields": 7 + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "host": "acpowercomfort.com", + "ok": true, + "words": 2970, + "price": 0, + "faq": true, + "fields": 0 + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "host": "miamihvac.net", + "ok": true, + "words": 763, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "host": "polluxair.com", + "ok": true, + "words": 3513, + "price": 0, + "faq": false, + "fields": 4 + }, + { + "industry": "Premium HVAC", + "geo": "Miami", + "host": "centralcomfortairconditioning.com", + "ok": true, + "words": 1825, + "price": 1, + "faq": true, + "fields": 8 + }, + { + "industry": "Yacht brokers", + "geo": "Monaco", + "host": "superyachtsmonaco.com", + "ok": true, + "words": 1343, + "price": 0, + "faq": true, + "fields": 17 + }, + { + "industry": "Yacht brokers", + "geo": "Monaco", + "host": "twwyachts.com", + "ok": true, + "words": 2029, + "price": 12, + "faq": true, + "fields": 11 + }, + { + "industry": "Yacht brokers", + "geo": "Monaco", + "host": "yacht-zoo.com", + "ok": true, + "words": 2120, + "price": 0, + "faq": true, + "fields": 1 + }, + { + "industry": "Yacht brokers", + "geo": "Fort Lauderdale", + "host": "moranyachts.com", + "ok": true, + "words": 1697, + "price": 8, + "faq": false, + "fields": 7 + }, + { + "industry": "Yacht brokers", + "geo": "Fort Lauderdale", + "host": "worthavenueyachts.com", + "ok": true, + "words": 6496, + "price": 35, + "faq": false, + "fields": 53 + }, + { + "industry": "Yacht brokers", + "geo": "Monaco", + "host": "iyc.com", + "ok": true, + "words": 922, + "price": 0, + "faq": true, + "fields": 2 + }, + { + "industry": "Private aviation", + "geo": "Global", + "host": "vistajet.com", + "ok": true, + "words": 1225, + "price": 1, + "faq": false, + "fields": 14 + }, + { + "industry": "Private aviation", + "geo": "Dubai", + "host": "jetluxe.com", + "ok": true, + "words": 916, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Private aviation", + "geo": "Global", + "host": "privatejet.com", + "ok": true, + "words": 1550, + "price": 0, + "faq": true, + "fields": 7 + }, + { + "industry": "Private aviation", + "geo": "Global", + "host": "jetfinder.com", + "ok": true, + "words": 2475, + "price": 0, + "faq": true, + "fields": 38 + }, + { + "industry": "Private aviation", + "geo": "Global", + "host": "skyaccess.com", + "ok": true, + "words": 1052, + "price": 11, + "faq": true, + "fields": 1 + }, + { + "industry": "Private aviation", + "geo": "Global", + "host": "charterjetone.com", + "ok": true, + "words": 2576, + "price": 1, + "faq": false, + "fields": 20 + }, + { + "industry": "Private aviation", + "geo": "Global", + "host": "paramountbusinessjets.com", + "ok": true, + "words": 2241, + "price": 12, + "faq": true, + "fields": 2 + }, + { + "industry": "Plastic surgery", + "geo": "Beverly Hills", + "host": "harrisfacialplastics.com", + "ok": true, + "words": 4030, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Plastic surgery", + "geo": "Beverly Hills", + "host": "drcalvert.com", + "ok": true, + "words": 782, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "host": "aesthetics.ae", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "host": "bhwaa.com", + "ok": true, + "words": 2080, + "price": 0, + "faq": true, + "fields": 1 + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "host": "elitecosmeticsurgery.ae", + "ok": true, + "words": 4598, + "price": 0, + "faq": false, + "fields": 6 + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "host": "skin111.com", + "ok": false, + "err": "HTTP Error 503: Service Unavailable" + }, + { + "industry": "Plastic surgery", + "geo": "Dubai", + "host": "dubailondonhospital.com", + "ok": true, + "words": 1287, + "price": 0, + "faq": false, + "fields": 2 + }, + { + "industry": "Luxury watches", + "geo": "London", + "host": "bqwatches.com", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "industry": "Luxury watches", + "geo": "London", + "host": "chrono-group.com", + "ok": true, + "words": 1806, + "price": 2, + "faq": true, + "fields": 3 + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "host": "luxurysouq.com", + "ok": true, + "words": 6421, + "price": 2, + "faq": true, + "fields": 14 + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "host": "dubailuxurywatch.com", + "ok": true, + "words": 3535, + "price": 0, + "faq": false, + "fields": 6 + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "host": "watchtradeco.com", + "ok": true, + "words": 512, + "price": 0, + "faq": false, + "fields": 3 + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "host": "bigmoewatches.com", + "ok": true, + "words": 1651, + "price": 24, + "faq": true, + "fields": 0 + }, + { + "industry": "Luxury watches", + "geo": "Dubai", + "host": "thewatchmeister.com", + "ok": true, + "words": 3403, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Private members clubs", + "geo": "London", + "host": "theartsclub.co.uk", + "ok": true, + "words": 1412, + "price": 0, + "faq": false, + "fields": 16 + }, + { + "industry": "Private members clubs", + "geo": "Global", + "host": "sohohouse.com", + "ok": true, + "words": 341, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Private members clubs", + "geo": "London", + "host": "5hertfordstreet.com", + "ok": true, + "words": 0, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Private members clubs", + "geo": "London", + "host": "annabels.co.uk", + "ok": true, + "words": 75, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Private members clubs", + "geo": "New York", + "host": "casacipriani.com", + "ok": true, + "words": 496, + "price": 0, + "faq": false, + "fields": 4 + }, + { + "industry": "Private members clubs", + "geo": "London", + "host": "homehouse.co.uk", + "ok": true, + "words": 249, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "host": "huntsmansavilerow.com", + "ok": true, + "words": 902, + "price": 10, + "faq": true, + "fields": 4 + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "host": "englishcut.com", + "ok": true, + "words": 302, + "price": 0, + "faq": false, + "fields": 1 + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "host": "gievesandhawkes.com", + "ok": true, + "words": 2052, + "price": 25, + "faq": true, + "fields": 19 + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "host": "nortonandsons.co.uk", + "ok": true, + "words": 1146, + "price": 9, + "faq": true, + "fields": 4 + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "host": "redmayne1860.com", + "ok": true, + "words": 552, + "price": 1, + "faq": true, + "fields": 3 + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "host": "onsavilerow.com", + "ok": true, + "words": 167, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Bespoke tailoring", + "geo": "London", + "host": "savilerowcustom.com", + "ok": true, + "words": 2501, + "price": 6, + "faq": true, + "fields": 4 + }, + { + "industry": "Interior design", + "geo": "London", + "host": "rosenarmani.com", + "ok": false, + "err": "HTTP Error 403: Forbidden" + }, + { + "industry": "Interior design", + "geo": "London", + "host": "elliotjames.com", + "ok": true, + "words": 141, + "price": 0, + "faq": false, + "fields": 4 + }, + { + "industry": "Interior design", + "geo": "London", + "host": "interieurlondon.com", + "ok": true, + "words": 370, + "price": 0, + "faq": false, + "fields": 0 + }, + { + "industry": "Interior design", + "geo": "Dubai", + "host": "algedra.ae", + "ok": false, + "err": " Date: Thu, 6 Aug 2026 10:17:57 +0530 Subject: [PATCH 18/19] =?UTF-8?q?feat:=20harvest=20the=20corpus=20from=20O?= =?UTF-8?q?penStreetMap=20=E2=80=94=20thousands,=20not=20dozens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hand-picked URLs cap out at 99. OSM tags millions of real businesses with a website, is openly licensed, and has a free query API — so the corpus can reach the scale the claim needs without scraping anyone or buying a list. 48 cities across Europe, the Americas, the Middle East and APAC; 64 trades from estate agents and jewellers down to stonemasons, piano tuners and upholsterers. Milan alone returns 1,077 usable homepages across 52 trades. First attempt issued one request per city per trade — 3,072 requests — and Overpass rate-limited immediately, correctly. Rewritten as one union query per city: 48 requests for the same data, with three endpoints and backoff. scan.py now rotates: above 500 sites it reads a deterministic 1/7 slice each day, so the whole corpus stays under measurement without hammering anyone. Also: the page never says "luxury" any more — announcing it is the opposite of having it — and sentence-initial figures are capitalised, which the data-study spans were getting wrong. --- public/audit.html | 6 +- public/index.html | 6 +- study/__pycache__/harvest.cpython-312.pyc | Bin 0 -> 13078 bytes study/harvest.py | 189 ++++++++++++++++++++++ study/render.py | 3 + study/scan.py | 9 ++ study/snapshots/2026-08-06.json | 4 +- 7 files changed, 209 insertions(+), 8 deletions(-) create mode 100644 study/__pycache__/harvest.cpython-312.pyc create mode 100644 study/harvest.py diff --git a/public/audit.html b/public/audit.html index c8e6018b..c68f409b 100644 --- a/public/audit.html +++ b/public/audit.html @@ -37,9 +37,9 @@

We read your page as a customer with intent reads it.

57 of 89
-

luxury businesses across thirteen industries give no price signal on their homepage.

-

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, luxury property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. fifty-seven showed no price. fifty-three answered nothing a buyer would ask before committing. The leak is never the same leak twice: every private members' club we read hid both, while Savile Row — the oldest trade in the set — was the most transparent of all. That is the recipe. The same four passes; what changes is which one your industry is quietly failing. We re-read all of them every day, so this number is today's, not last quarter's.

-
Method: automated read of the served homepage HTML, refreshed daily. ninety-nine sites attempted across thirteen industries and eight geographies; eighty-nine readable, ten blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.
+

across thirteen industries give no price signal on their homepage.

+

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. Fifty-seven showed no price. Fifty-three answered nothing a buyer would ask before committing. The leak is never the same leak twice: every private members' club we read hid both, while Savile Row — the oldest trade in the set — was the most transparent of all. That is the recipe. The same four passes; what changes is which one your industry is quietly failing. We re-read all of them every day, so this number is today's, not last quarter's.

+
Method: automated read of the served homepage HTML, refreshed daily. Ninety-nine sites attempted across thirteen industries and eight geographies; eighty-nine readable, ten blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.
diff --git a/public/index.html b/public/index.html index d9e9dab9..fb3b1af1 100644 --- a/public/index.html +++ b/public/index.html @@ -40,9 +40,9 @@

Most of them leave before they ever get in touch.

57 of 89
-

luxury businesses across thirteen industries give no price signal on their homepage.

-

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, luxury property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. fifty-seven showed no price. fifty-three answered nothing a buyer would ask before committing. The leak is never the same leak twice: every private members' club we read hid both, while Savile Row — the oldest trade in the set — was the most transparent of all. That is the recipe. The same four passes; what changes is which one your industry is quietly failing. We re-read all of them every day, so this number is today's, not last quarter's.

-

Method: automated read of the served homepage HTML, refreshed daily. ninety-nine sites attempted across thirteen industries and eight geographies; eighty-nine readable, ten blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.

+

across thirteen industries give no price signal on their homepage.

+

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. Fifty-seven showed no price. Fifty-three answered nothing a buyer would ask before committing. The leak is never the same leak twice: every private members' club we read hid both, while Savile Row — the oldest trade in the set — was the most transparent of all. That is the recipe. The same four passes; what changes is which one your industry is quietly failing. We re-read all of them every day, so this number is today's, not last quarter's.

+

Method: automated read of the served homepage HTML, refreshed daily. Ninety-nine sites attempted across thirteen industries and eight geographies; eighty-nine readable, ten blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.

diff --git a/study/__pycache__/harvest.cpython-312.pyc b/study/__pycache__/harvest.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7297268f487fb08b40815d43b7ddeca69c57d8a2 GIT binary patch literal 13078 zcmbt)dsrOTm1k8y=qGOk=v_h*0!e@nddpZK^g!}EIXnce-`k~7I@zx}@b z-CI?lXh>$iZ%g9Tx#ymH@44rkd+xcn`bW9Beh$xAq2Bxb3Xc0Pv|v947IOcGPL8|9 z5w3$HJaI(%4xYUo9S-((b~xGF)!|}qcZZw3b2>ci?d|YLj@}}>+ogB!iY45}ehX)r zUBC2}f^YC<$$od_{+8Bjms;7FluMj%ILuNh66@VMbCcw?4@+OaQ zwvZ~6s>xQsZDc!O4XFjJBRc@=$xgrqvI}rG*#o$j>;v3S4gek`j{!E4#{mzKCctKL z7_fyL0X#~M0UjrnJ0C2Ut$t1zahuB7fh9wrV+gclGYE+b+F9U-^brwATmEB^^me^^X#{aQx${GAQT~ zNeC%wJfR6as?slK8XmIE}qtekQf(~gf7T2)|MzK zDW=s5=i1JoN>yb^6GY+JL8)7lb?I3_7sG-Q6Z++7R90e|U+EE4NsJ2J2~CbknkH%6 z1z8gm3>!@fQ8|R(FjUpKBi*N~w+k3uh={by9>^qIO-O1|Xg+^J_|GFR3z|Yj@LS@C z#2D>56ai65XlSVkgEGZ+K?|v3T#kj&Us8!lf}#p>F-ZwMKt+tJ>6ok_#FJDNR+Yd$AgMyXLgXG9nxYyqNhA@6{zP3O zwL-Iw2HgoAIzJ!~Q5XGhzy6!H#YnVM@F=QQ>;H&)%Gp%FR3;n0+L_uC(e=1?psr5A zm~m0lYM{niBGutlC&zRURS7Uv^C)9&U!q^G)smVn^=tov{a^H3t7!eTp~$0*M$Ty^ zMwD2!EBsGYo!8&~_KBwO|F|%-?sNY*_xU-85fcE# z9HboFXOx&2Qozk*yL;50(b}dkoyYN?fnsv6rQC^_DG8f@5L04OKB#Q^g*h51jQjAG zCc}436=Qup301d651eRHJ+O6{O|1m#yV~eSLn`)RNsY=>B}|aVPBw*K!_@A4b*m-+ zF;GnPZw2=gvJ{J9l4^iuQK{ zTGqOmKe6VQTWp7@m<`hvzJ(???kL}~7a|EfgnNB3;v+n@o{gNmEXDc6I-ran{L zWOxs&3B;Q+3NGxps;aY z-VE&=NpzbIz=WT|)EH8=a@Mj0D5gu6&l)++h`up8Rnq|v+s4`3Wa^TGE}6Ig&0eVH zgrc*y&_bkHVWku}+KNGMWwQLffxj1XGML}UI z7`!-3m$TR}+Y^0jzva8d?X%Xh0)=VO+di8LoK`fUITn_p5|y7B{QoprVozR~HEmYd zY?;zdwc+&Fi9 z7Tua21?w~98Ws~nPJ_bS>8(>u;4*{YKFbrgg3EMLP?)^C85i^BqiAXB0880)O;DIZ z|Mmp!%TyU!vi{o;>zRXn4-}>{@i^sT8oG1;kmV4UE%%)Hr$g3yx=gvuShUA-^cOlU zuXqv^HplCol#2~C4|io=HXz#0016XkNTgh*(6-0^RhKO^D6E5bx)%FtIAq7UXYAbs z6jSy|b)kVt1RQJm!YrX!w+fwg8fWqYZ@7xdE&jhu^eQX~;XS!7*eTv4TzuT?<;b_&37w<~=~#c;PtNcjwJ zTSSb7Bce>VsNGnKuYC{{Zb5ztdXE0QcA;j#6&QBfHm%x#02dLs~&F%B4VtF zq{|^K6^Kf*mQbZeEiR_=+mHrQK_N2D-%|ywJ=CBQmFDv*)F5K_)>3))s3GhratOhY zy|q-XHJzw9m;}H3v=kPjsd9_jBgbfv6{B4u8IUzaO%*9Zz+b9&3%41EDbwTMUBMY-pOBy8( z&B|iT@SLLxVwW7#bQS%= z_W~ytaV=G73K7SAlrB9HgB*U=5P(kQn4;)|lBn8Kq2zwC4{YW_h)N<{O-<#VHW4AL zC`7aL-z|nM3-yTINWoKu$JiV8(4`?=#s+Q+kccBKqWKzp%74i&B<*3bHn0bx+VGrI zFn9QVfhjrNcOemuTt*P-m4&0;l&?>wYcghdPnn2jd0#igRu!eciy2yrz-egmY7((* zm=4&edMfW?B21T!S=1~A^{7}Ph6hWeFix^a zDpEz3))#4*SYV067QP2wO;^14n2BhqA|_W@R1pliaNNdW-w2$h$7LMtsTh`1nH;c- z*+3hV`>B*&aYc#Zqy>m+m_kGb#84_9%ex;wWF9P)=;A7Y-y~?>gmnP7G0{|^y@Wy` zB@shr2&Zbc@lbLxM#L0dgd=;;(l=duMOH&y99j$z!FF*-u%ZQmn9ZSz0}`SLisT6Q9$CZGF{X^Fb&vChj9^$QK*8}Iq-XVo>phVh}n<}!y zgtenU(C`Hn!h{ocb|ZY+i`x_ zp|c7Kj&N5xIBwXPcJ@-9M;_v^Z{#{rJM0>E59g$Fdfkt(p@=(b>y8^e)`8bUT)=Si zH|$M2(%vuPzB=s!%t?CyJ!y`3SE3zoA8`Lec;F#CXyLhE%1h;4)3c-yG~p zynBluDp)8fNc&{o9(}p*2=?74y{nh`H;dn}>@XboS0r5KOG7$9O5R*y-^=dihJ!kt zEPHxa+1S!Mz4&FKcbP8_q%_RgeT5wOa)5t!ndjQLja)sa@q-SYyUg+Umn<7{Ugie* z>hi?@#2DwWd8-ml6LbMvy%0hiLD0nw9TvSnZwHftu8KrrH=2Tg9H54k+4U*CpzW6s zZ-s{KJNE9hd%_hfE)vgK*L(t!DX9kW97}`<2>e*8phT0~{q+8F5F1d0UfcE{?PFK5 zGA@J{Z)vg6R5U{_IDo7bmyrUltZ)g8ixK2Xh>&4P$7LsOo;Aci)+4>5q*uzj_ALrj z%PI`+0pIw5KUnQBTrq`6hI3F%rW{XKq?{Fv6{)<6D^FKgm(3Mds#Ll@AMo2hqHE?O zTKI@sJ>{rAm|FQXLd*erv3licUG7H?uOF!2x8uses#Ku*AWHPET03aCs4OY}C5T_s zO!tmdbGsZ%w&6xtR%-f1wNKJ(%=_W`+8sg_q66Y>C~}Z(d}2r!24qn{#K+wyZBi>M z)zuF53gj?62+cSnYw~soSj`(AOAx~g)zHjJJ6oM^xZ0bKw;8@Ta!;A&w1!*5%`W!v zb59(-c)q!<&2UDMkK`m&T&i~)9?KaF&qdQK3^$r;*lxif#X?kTIo8_)61{RVTvQQ; zE2@aZaKawdxrVE|TNyGuBEockTsNFBm*Ip?3}+t>HGG%h8W5wJ!4DX&J~>7V{wl?= zxneRjwPC7w>g>#}S^m3wXR2rGXWQT1@Yheyo_P1dyLzVm(%%ohD`%d3Ds%Zt=9#X! z%g<(>5;I~bQ%2?r$PMSmPTJ?kPC7`|O(n|uxxl8$mdV{yfsA+8Nb}wNvI);?8()ij zwPvooZZ3bvNb6mH!S&j)+B<$>&Mz!@3tk?$mA;X_Q&uxqRx>p)eRZmDzU;uAvgWz6 z=J~Rgg~GL&_1&3*P}a=_Hr(eN0f`@J`6P!!FYCtE-SMrT^R1tBPPJux>*sy-S&nx% zOt(Wr*OOz(iISI|yOUormtQe?U`n0O-}x&is((d!pbmG#1C7S-clK^RQtA9brTa*o zCuD6!Aafkutmxvp|1E%9+|t&!+=gcdV{Bbb9M_4JIgITx%`dy-Zh8y9sUsJnjg+f6HpgPBJ(a54| zjD-!PQf;XT3I+f)y2+YLMjZxf1hBpmIBg}nlpggnpiruI0bqptdFiTKy*GMqM(=S> z_sTIp-1^e8jvmOrzHe;bc*{$T6GvV>dHdv~_Qi8Q32a*^E`RZK)`PZLFBdGhk50MY zFsus%X4qbs`xckxhIkEXBRnlX`q+jY2A{?!0hcrXk|8uVai+Nxx*6ttoKDALPw!%$ zVN4^cr6cE5#O7))R&D7@G7L#7qg7za*O3#+T+sp%a6>m zuw_U6BKIXH-Pe~gk4>EFr2Bi48|19r-E&rLhnUrll!tyz!&kQlUi-6}`unWLa8ZKc zX5&39iJ|mk94)3Yimah+t0A$I?>YL;~L#a1;KPpH%<&UW0zbZBn-A#`@vlmEhV&p$VQ z_14ghq4zuj?LRO7dSomz+A*$8)W5p#_P&X&nZV|ZYxA!^$>oBjG>(;gkYAidWGi`~ z6++f;u0LGKy+?T6p9cfEVkFS^IK6{7}xk zm>1igWWT8=AK|VeI7PUS$mc_?cPZa<5?!}roVBq z^UhwXn@1jdZGYZz?UV~=nPEqmlR2DKTsl4bu;+JW-bc7`{z|D8?sJAcVVqg?#q65+ zYzeU`U_B1|5Dyl!>a-{Avt=9h@?4B}acM6&-vVbK9q8R+OR)4N_#Mk)o1k88*DPh@ z!@2slCF-($6xmYQ@8P_3uIfnVVFNjo&ij9|$Nz_}hx60DAd$ZK;mFM)0wp_iNY;5U`laIBl@bH#vD}Q6ua=qT>OuKA8$g%Pv z`eL#G`l{vV578F;pfiP}Id-4W52x*VXeIBo$6b1-`83neax0pww&zP+Z?3UVnwa~N z2G=f)mCT`ONXRkl7^Hi`;`tGKLI=*zOKtp2>w&b-v<+q$)Ysb*rTun~gd@4D*nA#} zR)Wp9^oCvXACm2N+Xd%qbJP^#n>`ZUu?WuipZ3F6ADY-8zqo^~JDc(?{!m8!JVs7A z>gv=EAP@LDb~sD91_uXg*+!>!QU;eGY!FgD!8&#bDaQffBdY#(dgSZD1+U@mi6*qj zM%>>>G`x1EL{Bk_HKrh7V-&Vk^XhG|f8+&t!21*Q$A%C6+UD)=;Kas$ z`2-I8ShKn7ka$3$^ zJb$T;C4KZz7LVeTW4NQrAg*R}T28c|INGL;P=2I~`1Z`sFNW{vP)Nc@0D8=e;{;C+ zUOL^LbsQ8`q>ebMVtq2CjZlj;Y^6{Vn`yUDz&>7x-7aQv7Q9?edI&1CD07h1V~SU4B`A<=D;i zeD0=^V+&7S`A>moG8fxMn@6Rw*6U}-&Q9!_4+@iobHR;wg4J`u>M3LfwNqE;g7tTT z`{#oDXDVka=7UG?1W(NcPrV;JlLz=6}=I?8N1Jwx=WED zyGF@E@rqIJ-Lf?k7j6#S<8pj;qsOxjcW&L?)xu=S1RN)+1y!sv& z@a-KvjCQy7+}JZwIax7Zym|D<-Li_wUGrsCqpf#~$|hW|dT)CtSI(Djn=jfv+VVlk znlBeG6qGL%m)_ccWB)|^%|ny*bH$tQ6xYlZ*G%iN=N(gjrlOt?i;nQZF+5BN}I1}m}!3B`}kdNZYFQzd)`e8f#5&i zJH!RM_+Nig#FebYF&m05STW(6XrDMXSv_@pT73P~RO5`}FB@jGzt}h1GJA2Rai%U) zdh)#s?RPFbJ$K>hOep+*LF8^h$u0j4|HRJ8vZ?wnubx|7bEly8Ck3_tJL^W54>TGJ zzuTPGTH|_qf3S6@>)oBclPf&$9&FA(S>X9$fg1(G+to!Kv#SfyfM#&jYYb0U7g0i8 zU2Fv#4n;E@_-6DKRI1+qFoH;Xaj{a%l4h1j8+jJVzG`vB@tw!;5Lu0}PdH*X?leHb zXf$MX;j*Ol=*6SBX2I=<`Zd}$p$98U}daeGI<=Ohl!})&Gbhk3X#mp&F?!&wuO+@UHvi93T9U^RfT_54pgH zT=9q8il2IXFAP3E$TDg5*5Hl7ThHBiZgOb8Y{$H(e#Eul3tTT7E4#jCY|S{CI5Y3t zI+AnG;Rx;>@n-#N{EnZxf|#sXUsy-s>!7_L2m2LOvC9cE96-<`Lrg_YQqWz zoI8*SZq63cngTY?NwZMIxeGFdRoP-%Q^aj!aC2{lv=&9Lb)ZuN?KFSZQYgGeKyMqYdCj)reIrkEv;F@)nc3rW?>!YF3J?|$gZa~ z>$rx)e5U1Dvm|ityiESq>;_sTa5bzED^yrD8);1iSA&5rW?5kq6T2q6nbvILb~N*u z!&l5wB^#+WyMxXK;V>rEV{ zg_T&q}w{l6c3Z5jXo literal 0 HcmV?d00001 diff --git a/study/harvest.py b/study/harvest.py new file mode 100644 index 00000000..b358399a --- /dev/null +++ b/study/harvest.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +"""Grow the corpus from OpenStreetMap. + +Hand-picked URLs cap out in the dozens. OSM carries a `website` tag on millions +of real businesses, is openly licensed (ODbL), and has a free query API — so the +corpus can reach thousands without scraping anyone or paying for a list. + + python3 study/harvest.py --dry-run # show what would be added + python3 study/harvest.py # append new sites to corpus.json + +Existing entries are never modified and never duplicated. Attribution: data +© OpenStreetMap contributors. +""" +import json +import os +import re +import sys +import time +import urllib.parse +import urllib.request + +HERE = os.path.dirname(os.path.abspath(__file__)) +CORPUS = os.path.join(HERE, "corpus.json") +OVERPASS = ["https://overpass-api.de/api/interpreter", + "https://overpass.kumi.systems/api/interpreter", + "https://overpass.osm.ch/api/interpreter"] + +# bbox: south, west, north, east +CITIES = { + # Europe + "London": (51.42,-0.28,51.60,0.05), "Paris": (48.81,2.24,48.90,2.42), + "Milan": (45.42,9.10,45.52,9.24), "Zurich": (47.34,8.48,47.43,8.60), + "Geneva": (46.18,6.10,46.24,6.19), "Monaco": (43.72,7.40,43.76,7.44), + "Munich": (48.10,11.49,48.18,11.63), "Frankfurt": (50.09,8.60,50.16,8.72), + "Berlin": (52.47,13.32,52.55,13.46), "Vienna": (48.18,16.32,48.24,16.42), + "Madrid": (40.40,-3.72,40.46,-3.66), "Barcelona": (41.37,2.13,41.42,2.20), + "Rome": (41.88,12.45,41.93,12.52), "Lisbon": (38.70,-9.18,38.75,-9.12), + "Amsterdam": (52.34,4.84,52.39,4.93), "Brussels": (50.82,4.33,50.87,4.40), + "Copenhagen": (55.66,12.53,55.71,12.61), "Stockholm": (59.31,18.03,59.35,18.10), + "Oslo": (59.90,10.70,59.94,10.79), "Dublin": (53.32,-6.29,53.37,-6.22), + "Athens": (37.96,23.71,38.00,23.77), "Istanbul": (41.02,28.96,41.08,29.05), + # Americas + "New York": (40.66,-74.05,40.85,-73.87), "Miami": (25.70,-80.31,25.86,-80.13), + "Los Angeles": (33.98,-118.50,34.14,-118.24), "San Francisco": (37.75,-122.47,37.81,-122.39), + "Chicago": (41.86,-87.68,41.93,-87.60), "Boston": (42.33,-71.11,42.38,-71.04), + "Toronto": (43.63,-79.42,43.69,-79.34), "Aspen": (39.17,-106.85,39.21,-106.79), + "Palm Beach": (26.66,-80.06,26.75,-80.02), "Fort Lauderdale": (26.09,-80.17,26.16,-80.09), + "Mexico City": (19.39,-99.20,19.45,-99.14), "Sao Paulo": (-23.60,-46.70,-23.54,-46.62), + # Middle East, Asia, Pacific + "Dubai": (25.05,55.10,25.30,55.40), "Abu Dhabi": (24.44,54.33,24.51,54.42), + "Doha": (25.27,51.50,25.33,51.55), "Riyadh": (24.66,46.65,24.74,46.76), + "Singapore": (1.24,103.68,1.42,103.92), "Hong Kong": (22.24,114.12,22.34,114.24), + "Tokyo": (35.65,139.69,35.71,139.78), "Seoul": (37.50,126.97,37.55,127.05), + "Shanghai": (31.20,121.44,31.26,121.52), "Mumbai": (18.90,72.81,19.00,72.88), + "Sydney": (-33.92,151.15,-33.83,151.28), "Melbourne": (-37.83,144.94,-37.79,145.00), + "Auckland": (-36.87,174.73,-36.83,174.79), "Cape Town": (-33.95,18.39,-33.90,18.45), +} + +# OSM tag -> our industry label. Chosen for high-consideration, high-ticket trades. +TAGS = { + # original set + 'shop=jewelry': "Jewellery & watches", 'shop=watches': "Jewellery & watches", + 'office=estate_agent': "Property", 'shop=car;car_repair': "Automotive", + 'amenity=dentist': "Dentistry", 'shop=beauty': "Beauty & aesthetics", + 'leisure=spa': "Spas & wellness", 'shop=hairdresser': "Salons", + 'amenity=clinic': "Clinics", 'office=lawyer': "Legal", + 'office=financial_advisor': "Wealth management", 'shop=interior_decoration': "Interior design", + 'shop=boutique': "Boutique retail", 'shop=tailor': "Tailoring", + 'shop=art': "Art & galleries", 'office=architect': "Architecture", + 'shop=furniture': "Furniture", 'shop=optician': "Optical", + # niche trades + 'shop=antiques': "Antiques", 'craft=goldsmith': "Goldsmiths", + 'craft=jeweller': "Goldsmiths", 'shop=perfumery': "Perfumery", + 'shop=wine': "Fine wine", 'shop=chocolate': "Chocolatiers", + 'shop=musical_instrument': "Instruments", 'craft=piano_tuner': "Instruments", + 'shop=shoes': "Footwear", 'craft=shoemaker': "Footwear", + 'shop=leather': "Leather goods", 'shop=bag': "Leather goods", + 'shop=fabric': "Fabric & textiles", 'craft=upholsterer': "Upholstery", + 'craft=carpenter': "Joinery", 'craft=stonemason': "Stonemasonry", + 'shop=kitchen': "Kitchens", 'shop=bathroom_furnishing': "Bathrooms", + 'shop=carpet': "Rugs & carpets", 'shop=frame': "Framing", + 'shop=hifi': "Audio", 'shop=camera': "Photographic", + 'craft=photographer': "Photography", 'shop=florist': "Florists", + 'shop=garden_centre': "Landscaping", 'craft=gardener': "Landscaping", + 'shop=swimming_pool': "Pools", 'craft=hvac': "Climate control", + 'office=insurance': "Insurance", 'office=accountant': "Accountancy", + 'office=notary': "Notaries", 'office=tax_advisor': "Tax advisory", + 'office=travel_agent': "Travel", 'amenity=veterinary': "Veterinary", + 'leisure=fitness_centre': "Fitness", 'shop=cosmetics': "Cosmetics", + 'shop=herbalist': "Herbal & supplements", 'shop=hearing_aids': "Audiology", + 'shop=medical_supply': "Medical supply", 'shop=bicycle': "Cycling", + 'shop=outdoor': "Outdoor & field sports", 'shop=gift': "Gifting", + 'shop=deli': "Fine food", 'shop=cheese': "Fine food", + 'shop=security': "Security", 'shop=locksmith': "Security", +} + +BAD_HOST = re.compile( + r"(facebook|instagram|twitter|x\.com|linkedin|tiktok|youtube|wa\.me|whatsapp" + r"|google\.|goo\.gl|bit\.ly|linktr\.ee|yelp\.|tripadvisor|booksy|treatwell" + r"|fresha|squareup|wixsite|business\.site|weebly|blogspot|wordpress\.com)", re.I) + + +def query(bbox, attempt=0): + """One request per city covering every trade. + + Per-trade queries meant 3,072 requests and Overpass rate-limited immediately, + which is fair — it is a free shared service. A union selector gets the same + data in 48 requests. + """ + sel = "" + for tag in TAGS: + key, _, vals = tag.partition("=") + for v in vals.split(";"): + for kind in ("node", "way"): + sel += f'{kind}["{key}"="{v}"]["website"]({bbox[0]},{bbox[1]},{bbox[2]},{bbox[3]});' + q = f"[out:json][timeout:180];({sel});out tags;" + endpoint = OVERPASS[attempt % len(OVERPASS)] + req = urllib.request.Request( + endpoint, data=urllib.parse.urlencode({"data": q}).encode(), + headers={"User-Agent": "TinyStudio-market-study/1.0 (research; contact via tinystudio.io)"}) + with urllib.request.urlopen(req, timeout=300) as resp: + return json.load(resp).get("elements", []) + + +def industry_of(tags): + """Derive our label from whichever selector the element matched.""" + for tag, label in TAGS.items(): + key, _, vals = tag.partition("=") + if tags.get(key) in vals.split(";"): + return label + return None + + +def clean(url): + url = (url or "").strip() + if not url: + return None + if not url.startswith(("http://", "https://")): + url = "https://" + url + try: + p = urllib.parse.urlparse(url) + except ValueError: + return None + if p.scheme not in ("http", "https") or "." not in p.netloc: + return None + if BAD_HOST.search(p.netloc): # aggregators and socials are not the business's own site + return None + return f"{p.scheme}://{p.netloc}" # homepage only — the study is about homepages + + +def main(): + dry = "--dry-run" in sys.argv + corpus = json.load(open(CORPUS)) + seen = {c["url"].split("//")[1].split("/")[0].replace("www.", "").lower() for c in corpus} + added, per_city = [], {} + + for city, bbox in CITIES.items(): + elements = [] + for attempt in range(3): + try: + elements = query(bbox, attempt) + break + except Exception as exc: + print(f" ! {city} attempt {attempt + 1}: {str(exc)[:50]}", file=sys.stderr, flush=True) + time.sleep(20 * (attempt + 1)) + for el in elements: + tags = el.get("tags", {}) + industry = industry_of(tags) + url = clean(tags.get("website")) + if not (industry and url): + continue + host = url.split("//")[1].replace("www.", "").lower() + if host in seen: + continue + seen.add(host) + added.append({"industry": industry, "geo": city, "url": url}) + per_city[city] = per_city.get(city, 0) + 1 + print(f" {city}: +{per_city.get(city, 0)}", flush=True) + time.sleep(8) # one polite pause per city, not per trade + + print(f"\n{len(added)} new sites · corpus {len(corpus)} -> {len(corpus) + len(added)}") + if not dry: + json.dump(corpus + added, open(CORPUS, "w"), indent=1) + print("corpus.json written") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/study/render.py b/study/render.py index 2b5c6d4b..03621e1e 100644 --- a/study/render.py +++ b/study/render.py @@ -52,6 +52,9 @@ def values(s): "no_price_word": spell(s["no_price"]), "readable_word": spell(s["readable"]), "no_faq_word": spell(s["no_faq"]), + "no_price_word_cap": spell(s["no_price"]).capitalize(), + "no_faq_word_cap": spell(s["no_faq"]).capitalize(), + "attempted_word_cap": spell(s["attempted"]).capitalize(), "attempted_word": spell(s["attempted"]), "blocked_word": spell(s["attempted"] - s["readable"]), "industries_word": spell(s["industries"]), diff --git a/study/scan.py b/study/scan.py index 7f37644d..a597cc57 100644 --- a/study/scan.py +++ b/study/scan.py @@ -112,6 +112,15 @@ def delta(old, new): def main(): with open(CORPUS) as fh: corpus = json.load(fh) + + # At corpus scale a full daily sweep is neither kind to the sites nor useful — + # homepages do not change hourly. Read a deterministic slice each day so every + # site is re-read on a fixed cycle, and the whole corpus stays under measurement. + cycle = int(os.environ.get("SCAN_CYCLE_DAYS", "7")) + if cycle > 1 and len(corpus) > 500: + slot = date.today().toordinal() % cycle + corpus = [c for i, c in enumerate(sorted(corpus, key=lambda c: c["url"])) + if i % cycle == slot] with cf.ThreadPoolExecutor(max_workers=12) as ex: rows = list(ex.map(scan, corpus)) ok = [r for r in rows if r["ok"]] diff --git a/study/snapshots/2026-08-06.json b/study/snapshots/2026-08-06.json index ad6aa563..78753ad5 100644 --- a/study/snapshots/2026-08-06.json +++ b/study/snapshots/2026-08-06.json @@ -1,6 +1,6 @@ { "date": "2026-08-06", - "scanned_at": "2026-08-06T04:29:15+00:00", + "scanned_at": "2026-08-06T04:47:43+00:00", "attempted": 99, "readable": 89, "industries": 13, @@ -548,7 +548,7 @@ "geo": "Monaco", "host": "yacht-zoo.com", "ok": true, - "words": 2120, + "words": 2112, "price": 0, "faq": true, "fields": 1 From 3274e9409da5b020caa83020c52ec106255c38dc Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:19:55 +0530 Subject: [PATCH 19/19] copy: lead with unanswered questions, not withheld price MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Withholding a price is frequently deliberate — discretion is part of what these businesses sell — so a buyer reading "no price signal" thinks "we do that on purpose" and stops reading. The hook was measuring a choice and calling it a fault. Answering nothing is nobody strategy. That becomes the hero number, and the price finding stays as the contrast that shows we can tell the two apart: the gap between what a business chose and what merely happened to it. --- public/audit.html | 6 +++--- public/index.html | 6 +++--- study/render.py | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/public/audit.html b/public/audit.html index c68f409b..076126e4 100644 --- a/public/audit.html +++ b/public/audit.html @@ -35,10 +35,10 @@

We read your page as a customer with intent reads it.

-
57 of 89
+
53 of 89
-

across thirteen industries give no price signal on their homepage.

-

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. Fifty-seven showed no price. Fifty-three answered nothing a buyer would ask before committing. The leak is never the same leak twice: every private members' club we read hid both, while Savile Row — the oldest trade in the set — was the most transparent of all. That is the recipe. The same four passes; what changes is which one your industry is quietly failing. We re-read all of them every day, so this number is today's, not last quarter's.

+

answer none of the questions a buyer asks before committing.

+

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. Fifty-three settled nothing a buyer needs settled before they will commit — not what happens next, not who it is for, not what it costs to be wrong. Fifty-seven showed no price either, though we count that differently: withholding a figure is often a deliberate act, and discretion is part of what some of these businesses sell. Answering nothing at all is nobody's strategy. That is the difference we look for — the gap between what a business chose and what simply happened to it. We re-read the set continuously, so this number is today's.

Method: automated read of the served homepage HTML, refreshed daily. Ninety-nine sites attempted across thirteen industries and eight geographies; eighty-nine readable, ten blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.
diff --git a/public/index.html b/public/index.html index fb3b1af1..af93d229 100644 --- a/public/index.html +++ b/public/index.html @@ -38,10 +38,10 @@

Most of them leave before they ever get in touch.

-
57 of 89
+
53 of 89
-

across thirteen industries give no price signal on their homepage.

-

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. Fifty-seven showed no price. Fifty-three answered nothing a buyer would ask before committing. The leak is never the same leak twice: every private members' club we read hid both, while Savile Row — the oldest trade in the set — was the most transparent of all. That is the recipe. The same four passes; what changes is which one your industry is quietly failing. We re-read all of them every day, so this number is today's, not last quarter's.

+

answer none of the questions a buyer asks before committing.

+

We read eighty-nine of them — clinics, surgeons, dentists, spas, private members' clubs, property, supercar dealers, yacht brokers, private aviation, watch dealers, Savile Row tailors, interior designers and premium climate control — across London, New York, Miami, Dubai, Monaco, Beverly Hills and Fort Lauderdale. Fifty-three settled nothing a buyer needs settled before they will commit — not what happens next, not who it is for, not what it costs to be wrong. Fifty-seven showed no price either, though we count that differently: withholding a figure is often a deliberate act, and discretion is part of what some of these businesses sell. Answering nothing at all is nobody's strategy. That is the difference we look for — the gap between what a business chose and what simply happened to it. We re-read the set continuously, so this number is today's.

Method: automated read of the served homepage HTML, refreshed daily. Ninety-nine sites attempted across thirteen industries and eight geographies; eighty-nine readable, ten blocked by bot protection and excluded from every count. Inner pages are deliberately not counted — the finding is about the homepage, which is where the comparison happens. A homepage with no price may still have an excellent fee page one click deeper; that is itself one of the leaks.

diff --git a/study/render.py b/study/render.py index 03621e1e..9264c7be 100644 --- a/study/render.py +++ b/study/render.py @@ -48,6 +48,7 @@ def newest(): def values(s): return { "no_price": str(s["no_price"]), + "no_faq": str(s["no_faq"]), "readable": str(s["readable"]), "no_price_word": spell(s["no_price"]), "readable_word": spell(s["readable"]),