From 97861a7b3106148604e46e1f7c6ec59d567c70da Mon Sep 17 00:00:00 2001 From: Ravi Teja KNTS Date: Mon, 12 Oct 2026 12:08:11 +0530 Subject: [PATCH] Rebuild free trial and KDP royalty calculators for most users Rebuild both pilot tools from scratch with the simplified tool pipeline: a small Basic form that opens on an example, optional Advanced depth, visual results with a clear hierarchy, and required marks on fields. Add the shared tool workbench, more ECharts types, a vertical metric list, and a vertical range legend. Fix KDP print rounding and eBook delivery half cents in the shared KDP engine, which also corrects the minimum list price and price profit calculators. Rewrite the tool docs and skills: short skills that make the model think, one Opus session per tool for plan, interface, and writing, a separate integration session, a friendly direct writing voice, and a slow default pace. Co-Authored-By: Claude Opus 5.5 --- .../examples/actions-and-forms.tsx | 24 +- .../components-ui/examples/charts.tsx | 117 ++ .../components-ui/examples/page-layout.tsx | 22 + .../components-ui/examples/results.tsx | 52 +- .../examples/text-and-choices.tsx | 22 +- .../(components-ui)/components-ui/sections.ts | 14 +- .../content/article.mdx | 91 +- .../content/intro.mdx | 4 +- .../content/usage.mdx | 36 +- ...e-trial-conversion-calculator-from-url.tsx | 17 + ...ree-trial-conversion-calculator.module.css | 193 +--- .../free-trial-conversion-calculator.tsx | 1025 +++++++++-------- .../free-trial-conversion-calculator/page.tsx | 4 +- .../published-figures.ts | 75 +- .../content/article.mdx | 63 +- .../kdp-royalty-calculator/content/intro.mdx | 2 +- .../kdp-royalty-calculator/content/usage.mdx | 28 +- .../kdp-royalty-calculator-from-url.tsx | 12 + .../kdp-royalty-calculator.module.css | 59 + .../kdp-royalty-calculator.tsx | 687 +++++++++-- .../(tools)/kdp-royalty-calculator/page.tsx | 8 +- app/ui/charts/chart-client.tsx | 89 +- app/ui/charts/chart-option.ts | 387 ++++++- app/ui/charts/chart-static.tsx | 6 +- app/ui/charts/echarts-more.ts | 10 + app/ui/controls.module.css | 352 ++++++ app/ui/controls.tsx | 328 +++++- app/ui/display.tsx | 208 +++- app/ui/tools/tool-workbench.module.css | 43 + app/ui/tools/tool-workbench.tsx | 45 + .../research.md | 298 ++--- .../tool-spec.md | 773 +++++++------ content_docs/kdp-royalty-calculator/brief.md | 13 - .../competitive-report.md | 147 --- .../edge-cases-and-tests.md | 193 ---- .../implementation-report.md | 321 ------ .../interface-contract.md | 273 ----- .../kdp-royalty-calculator/research.md | 259 +++++ .../kdp-royalty-calculator/tool-spec.md | 406 +++++++ .../kdp-royalty-calculator/writing-and-seo.md | 184 --- dev-space/simple-tools-plan.md | 141 +++ .../engine.ts | 476 +++++--- lib/kdp/minimum-list-price.ts | 10 +- lib/kdp/price-profit.ts | 6 +- lib/kdp/royalty-calculator.ts | 310 +++++ lib/kdp/royalty.ts | 220 +--- .../tools/stack127-tool-audit/SKILL.md | 21 +- .../tools/stack127-tool-backend/SKILL.md | 2 +- .../SKILL.md | 19 +- .../tools/stack127-tool-frontend/SKILL.md | 22 +- .../stack127-tool-interface-design/SKILL.md | 18 +- .../stack127-tool-interface-plan/SKILL.md | 36 +- .../tools/stack127-tool-orchestrator/SKILL.md | 32 +- .../references/research-template.md | 25 + .../references/tool-spec-template.md | 38 +- .../tools/stack127-tool-product-plan/SKILL.md | 44 +- .../tools/stack127-tool-research/SKILL.md | 22 +- .../tools/stack127-tool-writing/SKILL.md | 133 +-- stack127_docs/design-system.md | 2 +- stack127_docs/shared-ui.md | 35 +- stack127_docs/tool-catalogs.md | 4 +- stack127_docs/tool-interface.md | 369 ++---- stack127_docs/tool-pipeline.md | 65 +- stack127_docs/writing-style.md | 19 +- .../free-trial-conversion-calculator.test.mjs | 466 +++++--- .../minimum-list-price.test.mjs | 2 +- .../kdp-royalty-calculator.test.mjs | 526 +++++---- 67 files changed, 5969 insertions(+), 3984 deletions(-) create mode 100644 app/(site)/(tools)/(saas)/free-trial-conversion-calculator/free-trial-conversion-calculator-from-url.tsx create mode 100644 app/(site)/(tools)/kdp-royalty-calculator/kdp-royalty-calculator-from-url.tsx create mode 100644 app/(site)/(tools)/kdp-royalty-calculator/kdp-royalty-calculator.module.css create mode 100644 app/ui/charts/echarts-more.ts create mode 100644 app/ui/tools/tool-workbench.module.css create mode 100644 app/ui/tools/tool-workbench.tsx delete mode 100644 content_docs/kdp-royalty-calculator/brief.md delete mode 100644 content_docs/kdp-royalty-calculator/competitive-report.md delete mode 100644 content_docs/kdp-royalty-calculator/edge-cases-and-tests.md delete mode 100644 content_docs/kdp-royalty-calculator/implementation-report.md delete mode 100644 content_docs/kdp-royalty-calculator/interface-contract.md create mode 100644 content_docs/kdp-royalty-calculator/research.md create mode 100644 content_docs/kdp-royalty-calculator/tool-spec.md delete mode 100644 content_docs/kdp-royalty-calculator/writing-and-seo.md create mode 100644 dev-space/simple-tools-plan.md create mode 100644 lib/kdp/royalty-calculator.ts diff --git a/app/(site)/(components-ui)/components-ui/examples/actions-and-forms.tsx b/app/(site)/(components-ui)/components-ui/examples/actions-and-forms.tsx index 2925f940..7fc3ec19 100644 --- a/app/(site)/(components-ui)/components-ui/examples/actions-and-forms.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/actions-and-forms.tsx @@ -2,7 +2,7 @@ import { useRef, useState, type FormEvent } from "react"; import { RotateCcw } from "lucide-react"; -import { UiButton, UiCopyAction, UiDisclosure, UiDownloadAction, UiQuantityField, UiRepeatableList, UiSelect, UiTextField, type UiOption } from "@/app/ui/controls"; +import { UiButton, UiCopyAction, UiDisclosure, UiDownloadAction, UiQuantityField, UiRepeatableList, UiSelect, UiShareAction, UiTextField, uiSetSummary, type UiOption } from "@/app/ui/controls"; import { UiActionRow, UiStatus } from "@/app/ui/display"; import { Example } from "../gallery"; import styles from "../components-ui.module.css"; @@ -23,6 +23,9 @@ type Drive = { id: string; capacity: number; unit: string }; export function ActionsAndFormsExamples() { const [hostname, setHostname] = useState(""); const [recordSize, setRecordSize] = useState("128K"); + const [windowDays, setWindowDays] = useState(""); + const [annualShare, setAnnualShare] = useState(""); + const advancedSet = [windowDays, annualShare].filter((value) => value.trim()).length; const [hostnameError, setHostnameError] = useState(null); const [status, setStatus] = useState("Enter a hostname, then check it."); const hostnameRef = useRef(null); @@ -112,8 +115,17 @@ export function ActionsAndFormsExamples() { /> - - option.id === recordSize)?.label} record size`}> + + +
+ + +
+
+
+ + + option.id === recordSize)?.label} record size`}>
+ + + + + + ); } diff --git a/app/(site)/(components-ui)/components-ui/examples/charts.tsx b/app/(site)/(components-ui)/components-ui/examples/charts.tsx index b11112b8..cccbdd75 100644 --- a/app/(site)/(components-ui)/components-ui/examples/charts.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/charts.tsx @@ -45,6 +45,20 @@ const visitors: UiChartSpec = { xFormat: "date", }; +// A paperback that costs $4.60 to print: 50% below $9.99, 60% from $9.99, so the line steps up there. +const royaltyCurve: UiChartSpec = (() => { + const royalty = (cents: number) => (Math.round(cents * (cents >= 999 ? 0.6 : 0.5)) - 460) / 100; + const prices = [920, 998, 999, ...Array.from({ length: 21 }, (_, index) => 1000 + index * 100)]; + return { + format: "currency", + kind: "line", + marks: [{ label: "Your price", x: 14.99, y: royalty(1499) }], + series: [{ id: "royalty", label: "Royalty per sale", points: prices.map((cents): UiChartPoint => [cents / 100, royalty(cents)]) }], + title: "Royalty by list price", + xFormat: "currency", + }; +})(); + const minute = 60_000; const rawLatency = (() => { const random = seeded(9); @@ -161,6 +175,79 @@ const signups: UiChartSpec = { xFormat: "date", }; +const trialFunnel: UiChartSpec = { + format: "number", + height: 280, + kind: "funnel", + stages: [ + { id: "visitors", label: "Visitors", value: 4800 }, + { id: "signups", label: "Signups", value: 640 }, + { id: "trials", label: "Trials", value: 200 }, + { id: "paid", label: "Paid", value: 22 }, + ], + title: "Visitors to paid customers", +}; + +const goal: UiChartSpec = { format: "currency", height: 240, kind: "gauge", label: "of $10,000 MRR goal", max: 10_000, title: "MRR goal", value: 6_400 }; + +const costs: UiChartSpec = { + format: "currency", + height: 280, + kind: "treemap", + parts: [ + { id: "hosting", label: "Hosting", value: 420 }, + { id: "database", label: "Database", value: 260 }, + { id: "email", label: "Email", value: 90 }, + { id: "analytics", label: "Analytics", value: 60 }, + { id: "monitoring", label: "Monitoring", value: 40 }, + { id: "backups", label: "Backups", value: 30 }, + { id: "domains", label: "Domains", value: 25 }, + ], + title: "Monthly costs", +}; + +const mrrFlow: UiChartSpec = { + format: "currency", + height: 300, + kind: "sankey", + links: [ + { source: "start", target: "kept", value: 8200 }, + { source: "start", target: "churned", value: 600 }, + { source: "start", target: "contracted", value: 200 }, + { source: "kept", target: "end", value: 8200 }, + { source: "new", target: "end", value: 1400 }, + { source: "expansion", target: "end", value: 500 }, + ], + nodes: [ + { id: "start", label: "Starting MRR" }, + { id: "new", label: "New" }, + { id: "expansion", label: "Expansion" }, + { id: "kept", label: "Kept" }, + { id: "churned", label: "Churned" }, + { id: "contracted", label: "Contraction" }, + { id: "end", label: "Ending MRR" }, + ], + title: "Where this month's MRR came from and went", +}; + +const plans: UiChartSpec = { + axes: [ + { label: "Price", max: 10 }, + { label: "Storage", max: 10 }, + { label: "Seats", max: 10 }, + { label: "Support", max: 10 }, + { label: "Integrations", max: 10 }, + ], + format: "number", + height: 320, + kind: "radar", + series: [ + { id: "starter", label: "Starter", values: [9, 4, 3, 5, 6] }, + { id: "team", label: "Team", values: [5, 8, 9, 8, 7] }, + ], + title: "Two plans compared", +}; + const kilobytes = (text: string) => `${(new TextEncoder().encode(text).length / 1000).toFixed(1)} KB`; /** What a chart adds to the page HTML: the server drawing, and the data handed to the browser. */ @@ -196,6 +283,11 @@ export function ChartsExamples() { + + + + + @@ -226,6 +318,31 @@ export function ChartsExamples() { + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/(site)/(components-ui)/components-ui/examples/page-layout.tsx b/app/(site)/(components-ui)/components-ui/examples/page-layout.tsx index a6cbe494..9b97311b 100644 --- a/app/(site)/(components-ui)/components-ui/examples/page-layout.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/page-layout.tsx @@ -8,6 +8,9 @@ import { PageHeader } from "@/app/ui/site/page-header"; import { PageMeta } from "@/app/ui/site/page-meta"; import { PageTitleIcon } from "@/app/ui/site/page-title-icon"; import { ThemeToggle } from "@/app/ui/site/theme-toggle"; +import { UiButton, UiTextField } from "@/app/ui/controls"; +import { UiActionRow, UiPrimaryResult } from "@/app/ui/display"; +import { ToolFieldRow, ToolResult, ToolWorkbench } from "@/app/ui/tools/tool-workbench"; import { Example } from "../gallery"; import styles from "../components-ui.module.css"; @@ -60,6 +63,25 @@ export function PageLayoutExamples() {
+ + + + + + + + Calculate capacity + Reset + + + + + + + + + +
    {fullLayouts.map((layout) => ( diff --git a/app/(site)/(components-ui)/components-ui/examples/results.tsx b/app/(site)/(components-ui)/components-ui/examples/results.tsx index 6834ab10..b7eb60a6 100644 --- a/app/(site)/(components-ui)/components-ui/examples/results.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/results.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; import { UiButton, UiCopyAction, UiRawOutput } from "@/app/ui/controls"; -import { UiActionRow, UiAllocationBar, UiCodeOutput, UiKeyValueList, UiMetricGroup, UiPrimaryResult, UiProgressBar, UiResultLead, UiStatusWord, UiSteps, UiTimeline } from "@/app/ui/display"; +import { UiActionRow, UiAllocationBar, UiCodeOutput, UiRangeMarker, UiKeyValueList, UiMetricGroup, UiPrimaryResult, UiProgressBar, UiResultLead, UiStatusWord, UiSteps, UiTimeline, UiUnitGrid } from "@/app/ui/display"; import { Example } from "../gallery"; import styles from "../components-ui.module.css"; @@ -21,12 +21,26 @@ export function ResultsExamples() { items={[ { id: "raw", label: "Raw", value: "32", unit: "TiB" }, { id: "protection", label: "Protection", value: "8", unit: "TiB" }, - { id: "reserve", label: "Reserve", value: "2.2", unit: "TiB" }, + { id: "reserve", label: "Reserve", value: "2.2", unit: "TiB", hint: "Free space kept back so the pool stays fast." }, ]} /> + +
    + +
    +
    +
    @@ -64,6 +78,40 @@ export function ResultsExamples() {
    + +
    + + +
    +
    + + +
    + + +
    +
    +
    diff --git a/app/(site)/(components-ui)/components-ui/examples/text-and-choices.tsx b/app/(site)/(components-ui)/components-ui/examples/text-and-choices.tsx index 0cc1f153..21a5c8e9 100644 --- a/app/(site)/(components-ui)/components-ui/examples/text-and-choices.tsx +++ b/app/(site)/(components-ui)/components-ui/examples/text-and-choices.tsx @@ -33,6 +33,7 @@ export function TextAndChoicesExamples() { const [hourCycle, setHourCycle] = useState<12 | 24>(12); const [layout, setLayout] = useState("raidz1"); const [format, setFormat] = useState("paperback"); + const [trialType, setTrialType] = useState("no-card"); const [marketplaces, setMarketplaces] = useState(["us", "uk"]); const [recordTypes, setRecordTypes] = useState(["a"]); const [keepFree, setKeepFree] = useState(false); @@ -43,12 +44,29 @@ export function TextAndChoicesExamples() {
    - +
    + +
    + + +
    +
    +
    @@ -77,7 +95,7 @@ export function TextAndChoicesExamples() {
    - +