From 72ce5132300a7448b27fb0c827b807c95f568fe1 Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Wed, 30 Sep 2026 20:57:06 -0400 Subject: [PATCH 01/13] Improve ProfitCtl search metadata and crawler guidance --- website/README.md | 8 +++++++- website/astro.config.mjs | 1 + website/package.json | 2 +- website/scripts/check-seo.mjs | 23 +++++++++++++++++++++++ website/src/layouts/ProductLayout.astro | 2 +- website/src/pages/index.astro | 2 +- website/src/pages/robots.txt.ts | 6 ++++++ website/src/pages/sitemap.xml.ts | 7 +++++++ 8 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 website/scripts/check-seo.mjs create mode 100644 website/src/pages/robots.txt.ts create mode 100644 website/src/pages/sitemap.xml.ts diff --git a/website/README.md b/website/README.md index 50e1678..a47475a 100644 --- a/website/README.md +++ b/website/README.md @@ -5,7 +5,9 @@ Standalone landing page app for ProfitCtl. ## Env - `PUBLIC_REPO_URL` for GitHub button links -- `PUBLIC_SITE_ORIGIN` for canonical metadata +- `PUBLIC_SITE_ORIGIN` for canonical metadata, the sitemap, and crawler guidance. + The proposed production origin defaults to `https://profitctl.intelip.co`. + Set it to the actual public origin before deploying elsewhere. ## Run @@ -19,3 +21,7 @@ npm run dev ```bash npm run build ``` + +The build produces static files in `dist/`, including `robots.txt` and +`sitemap.xml`. Local source and a successful build do not establish publication +or search indexing. Deployment and domain changes require maintainer approval. diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 7b42345..5e657e6 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -1,6 +1,7 @@ import { defineConfig } from "astro/config"; export default defineConfig({ + site: process.env.PUBLIC_SITE_ORIGIN || "https://profitctl.intelip.co", compressHTML: true, devToolbar: { enabled: false, diff --git a/website/package.json b/website/package.json index 1e44a57..6942f00 100644 --- a/website/package.json +++ b/website/package.json @@ -6,7 +6,7 @@ "packageManager": "bun@1.3.3", "scripts": { "dev": "astro dev", - "build": "astro build", + "build": "astro build && node scripts/check-seo.mjs", "preview": "astro preview", "typecheck": "astro check && tsc --noEmit" }, diff --git a/website/scripts/check-seo.mjs b/website/scripts/check-seo.mjs new file mode 100644 index 0000000..b718231 --- /dev/null +++ b/website/scripts/check-seo.mjs @@ -0,0 +1,23 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; + +const output = new URL("../dist/", import.meta.url); +const [html, robots, sitemap] = await Promise.all( + ["index.html", "robots.txt", "sitemap.xml"].map((file) => + readFile(new URL(file, output), "utf8"), + ), +); +const origin = process.env.PUBLIC_SITE_ORIGIN || "https://profitctl.intelip.co"; +const canonical = new URL("./", origin).href; +assert.ok(html.includes(`rel="canonical" href="${canonical}"`)); +assert.ok(html.includes(`property="og:url" content="${canonical}"`)); +assert.ok(html.includes('name="robots" content="index, follow"')); +assert.ok(robots.includes("User-agent: *\nAllow: /\n")); +assert.ok(robots.includes(`Sitemap: ${new URL("sitemap.xml", origin)}`)); +assert.ok(sitemap.includes(`${canonical}`)); +const schema = JSON.parse( + html.match(/]*type="application\/ld\+json"[^>]*>(.*?)<\/script>/s)[1], +); +assert.equal(schema["@type"], "SoftwareApplication"); +assert.equal(schema.url, canonical); +console.log(`Search metadata and crawler files verified for ${canonical}`); diff --git a/website/src/layouts/ProductLayout.astro b/website/src/layouts/ProductLayout.astro index 47a5dac..ccb8b79 100644 --- a/website/src/layouts/ProductLayout.astro +++ b/website/src/layouts/ProductLayout.astro @@ -7,7 +7,7 @@ export interface Props { } const { title, description } = Astro.props; -const canonicalOrigin = import.meta.env.PUBLIC_SITE_ORIGIN || Astro.url.origin; +const canonicalOrigin = Astro.site || Astro.url.origin; const canonicalURL = new URL(Astro.url.pathname, canonicalOrigin); const favicon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%23090B05'/%3E%3Crect x='12' y='12' width='40' height='40' rx='8' fill='none' stroke='%23B8FF18' stroke-width='4'/%3E%3Ctext x='32' y='41' text-anchor='middle' font-family='Arial,sans-serif' font-size='30' font-weight='700' fill='%23B8FF18'%3EP%3C/text%3E%3C/svg%3E"; diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index b48cd5e..4827d20 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -184,7 +184,7 @@ const footerLinks = [ ---
diff --git a/website/src/pages/robots.txt.ts b/website/src/pages/robots.txt.ts new file mode 100644 index 0000000..a34e403 --- /dev/null +++ b/website/src/pages/robots.txt.ts @@ -0,0 +1,6 @@ +import type { APIRoute } from "astro"; + +export const GET: APIRoute = ({ site }) => + new Response(`User-agent: *\nAllow: /\nSitemap: ${new URL("sitemap.xml", site)}\n`, { + headers: { "Content-Type": "text/plain; charset=utf-8" }, + }); diff --git a/website/src/pages/sitemap.xml.ts b/website/src/pages/sitemap.xml.ts new file mode 100644 index 0000000..b763d18 --- /dev/null +++ b/website/src/pages/sitemap.xml.ts @@ -0,0 +1,7 @@ +import type { APIRoute } from "astro"; + +export const GET: APIRoute = ({ site }) => + new Response( + `\n${new URL("./", site).href.replaceAll("&", "&")}\n`, + { headers: { "Content-Type": "application/xml; charset=utf-8" } }, + ); From 41a11b6a4ed009e8696db064b3cd7838084575bc Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Wed, 30 Sep 2026 21:37:17 -0400 Subject: [PATCH 02/13] Align ProfitCtl landing page across desktop and mobile --- website/src/pages/index.astro | 262 +++++++++++++++++++++++----------- 1 file changed, 175 insertions(+), 87 deletions(-) diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 4827d20..6c72643 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -250,8 +250,8 @@ const footerLinks = [
- $ profitctl compare examples/mix_profit.yml examples/hybrid_profit.yml --json - recommended: Workers + $ profitctl compare baseline.yml candidate.yml + Illustrative example
@@ -282,39 +282,39 @@ const footerLinks = [ - - - - $150 threshold + + + + $150 threshold - + - - - + + + - - - - - $87 + + + + + $87 - $200 - $160 - $120 - $80 - $0 + $200 + $160 + $120 + $80 + $0 May - Jun - Jul - Aug - Sep - Oct + Jun + Jul + Aug + Sep + Oct
@@ -534,7 +534,22 @@ const footerLinks = [ color: hsl(var(--pc-text)); } + .profitctl-page :is(h1, h2, h3, p, dl, dd) { + margin: 0; + } + + .profitctl-page :is(section, article, div) { + min-width: 0; + } + + .profitctl-page a:focus-visible { + outline: 3px solid hsl(var(--pc-profit-deep)); + outline-offset: 4px; + } + .hero-shell, + .site-header-inner, + .product-footer-inner, .evidence-band, .standards-section, .open-core-section, @@ -552,12 +567,12 @@ const footerLinks = [ background: hsl(var(--pc-bg-2) / 0.86); backdrop-filter: blur(18px); width: 100%; - padding-inline: 24px; + padding-inline: 0; } .site-header-inner, .product-footer-inner { - width: min(1280px, 100%); + width: min(1280px, calc(100% - 64px)); margin-inline: auto; } @@ -648,19 +663,19 @@ const footerLinks = [ .hero-shell { display: grid; - grid-template-columns: minmax(340px, 0.94fr) minmax(560px, 1.06fr); + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: 40px; align-items: start; - padding: 24px 0 28px; + padding: 56px 0 48px; min-height: auto; } .hero-copy h1 { max-width: 520px; - font-size: clamp(54px, 6.2vw, 80px); + font-size: clamp(48px, 4.6vw, 64px); font-weight: 520; - line-height: 0.94; - letter-spacing: 0; + line-height: 1.04; + letter-spacing: -0.035em; font-family: "Instrument Sans", sans-serif; } @@ -747,12 +762,15 @@ const footerLinks = [ .proof-points { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 14px; + grid-template-rows: auto auto auto; + gap: 5px 14px; margin-top: 20px; } .proof-points article { display: grid; + grid-row: span 3; + grid-template-rows: subgrid; gap: 5px; } @@ -791,7 +809,10 @@ const footerLinks = [ overflow: hidden; border-radius: 22px; max-width: 720px; - justify-self: end; + width: 100%; + min-width: 0; + justify-self: stretch; + scroll-margin-top: 120px; } .command-strip { @@ -803,11 +824,14 @@ const footerLinks = [ color: hsl(var(--pc-muted)); font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; font-size: 11px; + align-items: center; + flex-wrap: wrap; } .command-strip strong { color: hsl(var(--pc-text)); font-weight: 760; + white-space: nowrap; } .metrics-strip { @@ -822,6 +846,8 @@ const footerLinks = [ border-radius: 14px; background: hsl(var(--pc-panel-2)); padding: 11px 13px; + display: grid; + grid-template-rows: minmax(28px, auto) auto auto; } .metrics-strip span, @@ -859,8 +885,9 @@ const footerLinks = [ .chart-header { display: flex; justify-content: space-between; - gap: 18px; - align-items: end; + gap: 8px; + align-items: start; + flex-direction: column; margin-bottom: 12px; } @@ -926,6 +953,7 @@ const footerLinks = [ .compare-chart { width: 100%; + display: block; height: auto; border: 1px solid hsl(var(--pc-line)); border-radius: 18px; @@ -1007,9 +1035,12 @@ const footerLinks = [ } .threshold-label { - text-anchor: end; + text-anchor: start; } + .axis-label { text-anchor: end; } + .month-label { text-anchor: middle; } + .active-label { fill: hsl(var(--pc-profit-deep)); font-weight: 700; @@ -1130,6 +1161,7 @@ const footerLinks = [ .evidence-band { grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-rows: auto auto 34px auto auto auto; margin-top: 12px; scroll-margin-top: 96px; } @@ -1140,8 +1172,17 @@ const footerLinks = [ gap: 14px; border-radius: 20px; padding: 16px; + grid-row: span 6; + grid-template-rows: subgrid; } + .evidence-card .card-badge { grid-row: 1; } + .evidence-card h2 { grid-row: 2; } + .evidence-card .card-logos { grid-row: 3; } + .evidence-card .mini-chart { grid-row: 4; } + .evidence-card .card-metrics { grid-row: 5; } + .evidence-card > a { grid-row: 6; } + .card-badge { display: inline-flex; width: fit-content; @@ -1164,6 +1205,8 @@ const footerLinks = [ } .mini-chart { + --mini-label: 108px; + --mini-value: 42px; border: 1px solid hsl(var(--pc-line)); border-radius: 16px; background: hsl(var(--pc-panel-2)); @@ -1172,8 +1215,8 @@ const footerLinks = [ .mini-row { display: grid; - grid-template-columns: minmax(92px, 1fr) minmax(0, 1.5fr) auto; - gap: 12px; + grid-template-columns: var(--mini-label) minmax(0, 1fr) var(--mini-value); + gap: 10px; align-items: center; margin-bottom: 12px; } @@ -1221,11 +1264,15 @@ const footerLinks = [ .mini-row strong { font-size: 11px; font-weight: 700; + text-align: right; + white-space: nowrap; } .mini-axis { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); + display: flex; + justify-content: space-between; + margin-left: calc(var(--mini-label) + 10px); + margin-right: calc(var(--mini-value) + 10px); margin-top: 14px; color: hsl(var(--pc-subtle)); font-size: 10px; @@ -1288,8 +1335,8 @@ const footerLinks = [ .standards-section, .open-core-section { - margin-top: 18px; - padding: 28px 0 0; + margin-top: 0; + padding: 64px 0 0; scroll-margin-top: 96px; } @@ -1336,12 +1383,16 @@ const footerLinks = [ .core-map { grid-template-columns: repeat(4, minmax(0, 1fr)); + grid-template-rows: auto auto auto auto; + row-gap: 12px; align-items: stretch; margin-top: 24px; } .surface-card { display: grid; + grid-row: span 4; + grid-template-rows: subgrid; align-content: start; border-radius: 20px; padding: 18px; @@ -1363,21 +1414,21 @@ const footerLinks = [ } .surface-card h3 { - margin-top: 14px; + margin-top: 0; font-size: 21px; font-weight: 690; line-height: 1.04; } .surface-card p { - margin-top: 8px; + margin-top: 0; color: hsl(var(--pc-muted)); font-size: 13px; line-height: 1.5; } .surface-card ul { - margin: 14px 0 0; + margin: 0; padding: 0; list-style: none; } @@ -1462,7 +1513,7 @@ const footerLinks = [ width: 100%; margin-top: 20px; border-top: 1px solid hsl(var(--pc-line)); - padding: 20px 24px 40px; + padding: 24px 0 32px; } .product-footer-inner { @@ -1483,29 +1534,28 @@ const footerLinks = [ text-align: right; } - @media (max-width: 1180px) { - .hero-shell, - .evidence-band, + @media (max-width: 1100px) { .standards-grid, - .core-map, - .final-cta, - .product-footer-inner { - grid-template-columns: 1fr; + .core-map { + grid-template-columns: repeat(2, minmax(0, 1fr)); } - .product-footer nav, - .product-footer p { - justify-content: start; - text-align: left; + .standards-grid article:last-child { + grid-column: 1 / -1; } .metrics-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } + + .mini-chart { --mini-label: 90px; --mini-value: 36px; } + .mini-axis span:not(:first-child):not(:last-child) { display: none; } } @media (max-width: 980px) { .hero-shell, + .site-header-inner, + .product-footer-inner, .evidence-band, .standards-section, .open-core-section, @@ -1515,52 +1565,67 @@ const footerLinks = [ .site-header { height: auto; - padding-inline: 16px; + padding-inline: 0; } .site-header-inner { grid-template-columns: auto auto; - gap: 16px; - padding: 14px 0; + gap: 8px; + padding: 12px 0; } .site-nav { order: 3; grid-column: 1 / -1; justify-content: flex-start; - gap: 20px; + gap: clamp(12px, 3vw, 24px); overflow-x: auto; padding-bottom: 2px; } .hero-shell { + grid-template-columns: 1fr; gap: 28px; - padding-top: 28px; + padding: 32px 0; } - .proof-points, - .standards-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); + .hero-copy { + max-width: 620px; } - .provider-summary { - grid-template-columns: 1fr; + .decision-board { + max-width: none; } - .recommendation-bar { + .evidence-band { grid-template-columns: 1fr; + grid-template-rows: none; } + .evidence-card { + grid-row: auto; + grid-template-rows: auto auto auto auto auto auto; + padding: 24px; + } + + .mini-chart { --mini-label: 132px; --mini-value: 52px; } + .mini-axis span:not(:first-child):not(:last-child) { display: inline; } + .metrics-strip { grid-template-columns: repeat(4, minmax(0, 1fr)); } + .final-cta { grid-template-columns: 1fr; padding: 24px; } + .product-footer-inner { grid-template-columns: 1fr auto; } + .product-footer p { grid-column: 1 / -1; text-align: left; } + .product-footer nav { justify-content: end; } .cta-actions { justify-content: start; } } - @media (max-width: 720px) { + @media (max-width: 600px) { .hero-copy h1, .section-copy h2, .final-cta h2 { - font-size: clamp(38px, 12vw, 52px); + font-size: clamp(36px, 10vw, 48px); + line-height: 1.08; } .hero-copy p, @@ -1568,36 +1633,59 @@ const footerLinks = [ font-size: 15px; } - .metrics-strip, - .proof-points, - .card-metrics, - .standards-grid { + .standards-grid, + .core-map { grid-template-columns: 1fr; } + .core-map { grid-template-rows: none; } + .surface-card { grid-row: auto; grid-template-rows: auto auto auto auto; gap: 12px; } + + .metrics-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .metrics-strip span { letter-spacing: 0.04em; } + .provider-summary { grid-template-columns: 1fr; } + .summary-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; } + .summary-id { grid-row: 1 / span 2; } + .summary-card strong { margin-top: 0; text-align: right; } + .summary-card small { margin-top: 4px; text-align: right; } + .recommendation-bar { grid-template-columns: 1fr; } + .hero-actions { flex-wrap: wrap; gap: 12px; } + .proof-points { gap: 5px 12px; } + .proof-points strong { font-size: 12px; } + .proof-points small { font-size: 11px; } + .standards-section, .open-core-section { padding-top: 40px; } + .evidence-card { padding: 18px; } + .card-metrics div { padding: 12px 8px; } + .card-metrics dd { font-size: 14px; } + .command-strip span { overflow-wrap: anywhere; } + .compare-chart { min-height: 176px; } + .chart-header, .chart-legend { display: grid; justify-content: start; } - .product-footer { - padding-inline: 16px; + .chart-legend { + grid-template-columns: repeat(2, minmax(0, 1fr)); + width: 100%; + gap: 8px 12px; } - .mini-row { - grid-template-columns: 1fr; - gap: 8px; - } + .chart-legend span { font-size: 10px; white-space: normal; } + .threshold-label, .axis-label, .month-label, .active-label { font-size: 17px; } - .mini-axis { - grid-template-columns: repeat(4, minmax(0, 1fr)); + .product-footer { + padding-inline: 0; } - .entry-strip div, + .mini-chart { --mini-label: 100px; --mini-value: 44px; } + .mini-row { gap: 8px; } + .mini-axis { margin-left: calc(var(--mini-label) + 8px); margin-right: calc(var(--mini-value) + 8px); } + .mini-axis span:not(:first-child):not(:last-child) { display: none; } + .cta-actions { - display: grid; - grid-template-columns: 1fr; + flex-wrap: wrap; } } From 8c2019deae031b3791b73a49c3388935abeac725 Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Wed, 30 Sep 2026 22:44:07 -0400 Subject: [PATCH 03/13] Refine website content and text layout --- website/src/pages/index.astro | 978 ++++++---------------------------- 1 file changed, 177 insertions(+), 801 deletions(-) diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 6c72643..046dd9b 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -3,184 +3,84 @@ import ProductLayout from "../layouts/ProductLayout.astro"; const repoUrl = import.meta.env.PUBLIC_REPO_URL || "https://github.com/IntelIP/ProfitCtl"; const providers = [ - { - name: "Cloudflare Workers", - shortName: "Workers", - logo: "/logos/profitctl/cloudflare-workers.svg", - logoAlt: "Cloudflare Workers", - fixed: "$24", - variable: "$6.30", - monthly: "$87", - margin: "78%", - p95: "62%", - activeCost: "$9.40", - delta: "best", - state: "pass", - stateLabel: "Pass", - series: "workers", - }, - { - name: "Google Cloud Run", - shortName: "Cloud Run", - logo: "/logos/profitctl/google-cloud.svg", - logoAlt: "Google Cloud", - fixed: "$38", - variable: "$8.10", - monthly: "$128", - margin: "66%", - p95: "52%", - activeCost: "$12.30", - delta: "+47%", - state: "pass", - stateLabel: "Pass", - series: "cloud", - }, - { - name: "Vercel", - shortName: "Vercel", - logo: "/logos/profitctl/vercel.svg", - logoAlt: "Vercel", - fixed: "$52", - variable: "$11.40", - monthly: "$162", - margin: "58%", - p95: "41%", - activeCost: "$16.80", - delta: "+86%", - state: "watch", - stateLabel: "Watch", - series: "vercel", - }, + { name: "Workers", logo: "/logos/profitctl/cloudflare-workers.svg", monthly: "$94k", margin: "76.5%", state: "pass", stateLabel: "Within budget" }, + { name: "Cloud Run", logo: "/logos/profitctl/google-cloud.svg", monthly: "$123k", margin: "69.3%", state: "pass", stateLabel: "Within budget" }, + { name: "Vercel", logo: "/logos/profitctl/vercel.svg", monthly: "$168k", margin: "58.0%", state: "watch", stateLabel: "Over budget" }, ]; - const heroMetrics = [ - { label: "Monthly cost", value: "$87", detail: "best option" }, - { label: "Gross margin", value: "78%", detail: "meets >= 60%" }, - { label: "p95 margin", value: "62%", detail: "meets >= 40%" }, - { label: "Cost / active user", value: "$9.40", detail: "meets <= $18" }, + { label: "Monthly cost", value: "$94k", detail: "lowest sample cost" }, + { label: "Gross margin", value: "76.5%", detail: "target ≥ 60%" }, + { label: "Budget headroom", value: "$56k", detail: "below $150k / month" }, + { label: "Cost / active user", value: "$9.40", detail: "target ≤ $18" }, ]; - -const deliveryModes = ["CLI today", "Codex skill", "CI output"]; - const evidenceCards = [ { - badge: "Deploy choice", - title: "Workers vs Vercel for this Next.js app", - logos: [ - { src: "/logos/profitctl/cloudflare-workers.svg", alt: "Cloudflare Workers" }, - { src: "/logos/profitctl/vercel.svg", alt: "Vercel" }, - ], - bars: [ - { label: "Workers", value: 31, amount: "$87", tone: "workers" }, - { label: "Cloud Run", value: 46, amount: "$128", tone: "cloud" }, - { label: "Vercel", value: 58, amount: "$162", tone: "vercel" }, - ], - metrics: [ - { label: "Margin", value: "78%" }, - { label: "p95", value: "62%" }, - { label: "Cost / user", value: "$9.40" }, - ], - action: "See comparison", + badge: "AI features", + title: "Price a feature before launch.", + detail: "Model token, API, and research costs. See what each request leaves toward your margin.", + items: ["Model and API usage", "Cost per active user", "Evidence behind each assumption"], + action: "Read the simulation guide", + href: repoUrl + "/blob/main/docs/QUICK_START.md", }, { - badge: "Deep research risk", - title: "What is the cost risk of adding deep research?", - logos: [ - { src: "/logos/profitctl/openai.svg", alt: "OpenAI" }, - { src: "/logos/profitctl/google-cloud.svg", alt: "Google Cloud" }, - ], - bars: [ - { label: "Base", value: 18, amount: "$9.40", tone: "neutral" }, - { label: "+ light research", value: 36, amount: "$17.80", tone: "cloud" }, - { label: "+ deep research", value: 52, amount: "$26.60", tone: "risk" }, - ], - metrics: [ - { label: "Margin", value: "42%" }, - { label: "p95", value: "28%" }, - { label: "Cost / user", value: "$26.60" }, - ], - action: "Run this scenario", + badge: "Pricing", + title: "Choose a plan that pays for itself.", + detail: "Compare subscriptions and usage pricing against the same cost model.", + items: ["Revenue and payment fees", "Plan mix and recurring margin", "Side-by-side scenario output"], + action: "Read a pricing comparison", + href: repoUrl + "/blob/main/benchmark_scenarios/reports/open_core_tiered_vs_mix.md", }, { - badge: "Pricing model", - title: "Which pricing model keeps 60%+ margin?", - pricingRows: [ - { label: "Pro plan ($20/mo)", value: 78, tone: "workers" }, - { label: "Team plan ($40/mo)", value: 64, tone: "cloud" }, - { label: "Usage ($0.10 / msg)", value: 38, tone: "risk" }, - ], - metrics: [ - { label: "Best option", value: "Pro plan" }, - { label: "ARR potential", value: "$2.4M" }, - { label: "Confidence", value: "Medium" }, - ], - action: "Model your pricing", + badge: "Growth risk", + title: "Find where higher usage breaks margin.", + detail: "Stress your assumptions before growth turns a profitable feature into a cost problem.", + items: ["Growth and stress scenarios", "Tail-risk margin checks", "Explicit limits for acceptable costs"], + action: "Browse benchmark scenarios", + href: repoUrl + "/blob/main/benchmark_scenarios/README.md", }, ]; - const standards = [ - { label: "Provenance", detail: "Every cost line has source, capture date, and note." }, - { label: "Confidence", detail: "Low, medium, high based on actual evidence, not guesswork." }, - { label: "Covenants", detail: "Margins, p95, and user cost tracked against explicit targets." }, - { label: "Decision artifact", detail: "Shareable output with assumptions, tables, and charts." }, - { label: "No hand-wave claims", detail: "Judge blocks precise claims without trustworthy evidence." }, + { label: "Provenance", detail: "Every cost line has a source, capture date, and note." }, + { label: "Confidence", detail: "Low, medium, or high based on the available evidence." }, + { label: "Covenants", detail: "Check margin, tail risk, and user cost against explicit targets." }, + { label: "Decision artifact", detail: "Share assumptions, tables, and charts with your decision." }, + { label: "Evidence checks", detail: "The standards judge flags claims without trustworthy evidence." }, ]; - const productSurfaces = [ { status: "Open source now", title: "CLI", - detail: "Run compare, simulate, and validate locally.", - items: [ - "compare, simulate, validate", - "Scenario templates", - "Local modeling", - "JSON output", - "Standards judge", - ], + detail: "Compare choices and test assumptions on your machine.", + items: ["compare, simulate, validate", "Scenario templates", "JSON output", "Cost model standards"], }, { status: "Open source now", title: "Codex skill", - detail: "Pull repo context into cost-aware agent decisions.", - items: [ - "Repo-aware cost decisions", - "Architecture comparisons", - "Assumption-aware output", - "Temporary scenario edits", - "No new CLI command in v1", - ], + detail: "Bring cost evidence into agent decisions about your code.", + items: ["Architecture comparisons", "Assumption-aware output", "Temporary scenario edits", "Evidence from your repository"], }, { status: "Open source now", title: "CI / PR check", - detail: "Turn scenarios into gates before spend lands.", - items: [ - "JSON output in CI", - "Recurring margin checks", - "Covenant failures in PRs", - "Saved scenario files", - "Decision evidence in reviews", - ], + detail: "Use JSON output to check margins before changes land.", + items: ["Recurring margin checks", "Covenant failures in pull requests", "Saved scenario files", "Decision evidence in reviews"], }, { status: "Hosted later", title: "Team workflow", - detail: "Keep history, approvals, and calibration in one place.", - items: [ - "Team workspaces", - "Saved runs and history", - "Policies and approvals", - "Calibration connectors", - "RBAC and SSO", - ], + detail: "Keep team history, approvals, and calibration in one place.", + items: ["Workspaces and saved runs", "Policies and approvals", "Calibration connectors", "Roles and single sign-on"], }, ]; - const footerLinks = [ { label: "GitHub", href: repoUrl, external: true }, { label: "Get started", href: "#get-started" }, ]; +const quickStartCommand = [ + "git clone " + repoUrl + ".git", + "cd ProfitCtl", + "go run . compare examples/hybrid_steady_profit.yml examples/hybrid_profit.yml", +].join("\n"); ---

Compare spend before it ships.

-

- ProfitCtl helps AI-native teams compare infrastructure, model, API, and pricing - choices with unit economics that hold under real usage. -

- +

Compare infrastructure, AI, and pricing choices against the margins your business needs.

- -
- Use it as -
- {deliveryModes.map((mode) => {mode})} -
-
- -
-
-
-
-
-
-
+ Get started + Explore use cases
+

MIT licensed. Open source. Runs locally.

- $ profitctl compare baseline.yml candidate.yml + Infrastructure · monthly comparison Illustrative example
-
- { - heroMetrics.map((metric) => ( -
- {metric.label} - {metric.value} - {metric.detail} -
- )) - } + {heroMetrics.map((metric) => ( +
{metric.label}{metric.value}{metric.detail}
+ ))}
-
-
-
-

Projected monthly cost

-

Based on 10k active users

-
- -
- - - - - - $150 threshold - - - - - - - - - - - - $87 - - $200 - $160 - $120 - $80 - $0 - - May - Jun - Jul - Aug - Sep - Oct - -
- -
- { - providers.map((provider) => ( -
-
- {provider.logoAlt} -
-

{provider.shortName}

-

{provider.stateLabel}

-
-
- {provider.monthly} - {provider.margin} gross margin -
- )) - } -
- -
-
- Workers clears every covenant. -

Save $75 / month vs Vercel at 10k active users.

-
- View examples +

One scenario, three options

Compare cost and margin.

+ + + + {providers.map((provider) => ( + + + + + ))} + +
ProviderCost / monthGross margin
+ +
{provider.name}{provider.stateLabel}
+
{provider.monthly}{provider.margin}
+

Sample inputs: 10k active users, $40 revenue per user, $150k monthly budget. Costs are illustrative.

+
+ Workers has the lowest sample cost. +

$74k less per month than Vercel in this illustration.

+
-
- { - evidenceCards.map((card) => ( +
+
+

Decisions you can model.

+

Start with the question your business needs to answer.

+
+
+ {evidenceCards.map((card) => (
{card.badge} -

{card.title}

- - { - card.logos && ( -
- {card.logos.map((logo) => {logo.alt})} -
- ) - } - - { - card.bars && ( -
- {card.bars.map((bar) => ( -
- {bar.label} -
- -
- {bar.amount} -
- ))} - -
- ) - } - - { - card.pricingRows && ( -
- {card.pricingRows.map((row) => ( -
- {row.label} -
- -
- {row.value}% -
- ))} - -
- ) - } - -
- {card.metrics.map((metric) => ( -
-
{metric.label}
-
{metric.value}
-
- ))} -
- - {card.action} +

{card.title}

+

{card.detail}

+
    {card.items.map((item) =>
  • {item}
  • )}
+ {card.action}
- )) - } + ))} +
@@ -440,44 +192,31 @@ const footerLinks = [
-

ProfitCtl ships today as a CLI, a Codex skill, and a CI check.

-

- Same scenario engine, different delivery surface. Open source covers local compare, - repo-aware agent decisions, and PR gates now. Hosted workflow later adds saved history, - approvals, and calibration. -

+

Use it where you make decisions.

+

Start locally. Bring your scenarios into agent work and pull request checks. Hosted team workflows are planned.

-
- { - productSurfaces.map((surface) => ( -
- {surface.status} -

{surface.title}

-

{surface.detail}

-
    - {surface.items.map((item) =>
  • {item}
  • )} -
-
- )) - } + {productSurfaces.map((surface) => ( +
+ {surface.status} +

{surface.title}

+

{surface.detail}

+
    {surface.items.map((item) =>
  • {item}
  • )}
+
+ ))}
First step -

Run one compare from the CLI.

-

- Start with one local scenario and one real choice. Keep the output as your decision - artifact. Add the Codex skill or CI gate after the workflow proves useful. -

+

Run your first comparison.

+

Requires Git and Go. Run the saved examples in your terminal.

+
{quickStartCommand}
@@ -550,7 +289,7 @@ const footerLinks = [ .hero-shell, .site-header-inner, .product-footer-inner, - .evidence-band, + .evidence-section, .standards-section, .open-core-section, .final-cta { @@ -589,8 +328,7 @@ const footerLinks = [ .nav-button, .hero-actions, .product-footer, - .product-footer nav, - .card-logos { + .product-footer nav { display: flex; align-items: center; } @@ -697,46 +435,6 @@ const footerLinks = [ margin-top: 28px; } - .entry-strip { - display: grid; - gap: 12px; - margin-top: 24px; - border: 1px solid hsl(var(--pc-line)); - border-radius: 18px; - background: - linear-gradient(135deg, hsl(var(--pc-profit) / 0.08), transparent 52%), - hsl(var(--pc-panel) / 0.94); - padding: 14px 16px; - box-shadow: 0 18px 38px hsl(var(--pc-shadow) / 0.06); - } - - .entry-strip strong { - font-size: 11px; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; - color: hsl(var(--pc-subtle)); - } - - .entry-strip div { - display: flex; - flex-wrap: wrap; - gap: 10px; - } - - .entry-strip span { - display: inline-flex; - min-height: 30px; - align-items: center; - border: 1px solid hsl(var(--pc-line)); - border-radius: 999px; - background: hsl(var(--pc-panel-2)); - padding: 0 12px; - font-size: 12px; - font-weight: 650; - color: hsl(var(--pc-text)); - } - .primary-action, .secondary-action, .final-cta a { @@ -759,42 +457,6 @@ const footerLinks = [ color: hsl(var(--pc-text)); } - .proof-points { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - grid-template-rows: auto auto auto; - gap: 5px 14px; - margin-top: 20px; - } - - .proof-points article { - display: grid; - grid-row: span 3; - grid-template-rows: subgrid; - gap: 5px; - } - - .proof-points span { - width: 18px; - height: 18px; - border-radius: 999px; - border: 1px solid hsl(var(--pc-profit-deep) / 0.36); - background: - radial-gradient(circle at 50% 50%, hsl(var(--pc-profit)) 0 35%, transparent 37%), - hsl(var(--pc-panel)); - } - - .proof-points strong { - font-size: 13px; - font-weight: 680; - } - - .proof-points small { - color: hsl(var(--pc-muted)); - font-size: 12px; - line-height: 1.35; - } - .decision-board, .evidence-card, .standards-grid article, @@ -902,226 +564,9 @@ const footerLinks = [ font-weight: 720; } - .chart-legend { - display: flex; - flex-wrap: wrap; - justify-content: end; - gap: 10px 14px; - } - - .chart-legend span { - position: relative; - padding-left: 14px; - color: hsl(var(--pc-muted)); - font-size: 11px; - white-space: nowrap; - } - - .chart-legend span::before { - position: absolute; - top: 7px; - left: 0; - width: 8px; - height: 8px; - border-radius: 999px; - background: currentColor; - content: ""; - } - - .chart-legend .workers { - color: hsl(var(--pc-profit-deep)); - } - - .chart-legend .cloud { - color: hsl(var(--pc-blue)); - } - - .chart-legend .vercel { - color: hsl(var(--pc-copper)); - } - - .chart-legend .threshold { - color: hsl(var(--pc-subtle)); - } - - .chart-legend .threshold::before { - width: 10px; - height: 2px; - top: 10px; - border-radius: 0; - } - - .compare-chart { - width: 100%; - display: block; - height: auto; - border: 1px solid hsl(var(--pc-line)); - border-radius: 18px; - background: - linear-gradient(180deg, hsl(var(--pc-panel)) 0%, hsl(var(--pc-panel-2)) 100%); - overflow: visible; - } - - .axis-grid, - .axis-rule { - fill: none; - stroke: hsl(var(--pc-line)); - stroke-width: 1; - } - - .axis-grid { - stroke-dasharray: 4 6; - } - - .threshold-line, - .active-guide { - fill: none; - stroke: hsl(var(--pc-line-strong)); - stroke-width: 1.5; - stroke-dasharray: 6 6; - } - - .workers-area { - fill: hsl(var(--pc-profit) / 0.12); - } - - .workers-line, - .cloud-line, - .vercel-line { - fill: none; - stroke-linecap: round; - stroke-linejoin: round; - stroke-width: 3; - } - - .workers-line { - stroke: hsl(var(--pc-profit-deep)); - } - - .cloud-line { - stroke: hsl(var(--pc-blue)); - } - - .vercel-line { - stroke: hsl(var(--pc-copper)); - } - - .workers-point, - .cloud-point, - .vercel-point { - stroke: hsl(var(--pc-panel)); - stroke-width: 3; - } - - .workers-point { - fill: hsl(var(--pc-profit-deep)); - } - - .cloud-point { - fill: hsl(var(--pc-blue)); - } - - .vercel-point { - fill: hsl(var(--pc-copper)); - } - - .threshold-label, - .axis-label, - .month-label, - .active-label { - fill: hsl(var(--pc-muted)); - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 11px; - } - - .threshold-label { - text-anchor: start; - } - - .axis-label { text-anchor: end; } - .month-label { text-anchor: middle; } - - .active-label { - fill: hsl(var(--pc-profit-deep)); - font-weight: 700; - text-anchor: middle; - } - - .summary-id { - display: flex; - gap: 12px; - align-items: center; - } - - .summary-id img, - .card-logos img { - width: 34px; - height: 34px; - border-radius: 10px; - border: 1px solid hsl(var(--pc-line)); - background: hsl(var(--pc-panel)); - padding: 7px; - object-fit: contain; - } - - .provider-summary { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 10px; - padding: 14px 18px 0; - } - - .summary-card { - border: 1px solid hsl(var(--pc-line)); - border-radius: 16px; - background: hsl(var(--pc-panel-2)); - padding: 12px 13px; - } - - .summary-id h3 { - font-size: 14px; - font-weight: 690; - } - - .summary-id p { - margin-top: 2px; - color: hsl(var(--pc-subtle)); - font-size: 11px; - font-weight: 650; - letter-spacing: 0.08em; - text-transform: uppercase; - } - - .summary-card strong { - display: block; - margin-top: 12px; - font-size: 22px; - font-weight: 750; - line-height: 1; - } - - .summary-card small { - display: block; - margin-top: 6px; - color: hsl(var(--pc-muted)); - font-size: 11px; - } - - .summary-card.workers strong { - color: hsl(var(--pc-profit-deep)); - } - - .summary-card.cloud strong { - color: hsl(var(--pc-blue)); - } - - .summary-card.vercel strong { - color: hsl(var(--pc-copper)); - } - .recommendation-bar { display: grid; - grid-template-columns: 1fr auto; + grid-template-columns: 1fr; gap: 14px; align-items: center; margin: 14px 18px 18px; @@ -1162,7 +607,7 @@ const footerLinks = [ .evidence-band { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-rows: auto auto 34px auto auto auto; - margin-top: 12px; + margin-top: 24px; scroll-margin-top: 96px; } @@ -1172,15 +617,12 @@ const footerLinks = [ gap: 14px; border-radius: 20px; padding: 16px; - grid-row: span 6; + grid-row: span 5; grid-template-rows: subgrid; } .evidence-card .card-badge { grid-row: 1; } - .evidence-card h2 { grid-row: 2; } - .evidence-card .card-logos { grid-row: 3; } - .evidence-card .mini-chart { grid-row: 4; } - .evidence-card .card-metrics { grid-row: 5; } + .evidence-card h3 { grid-row: 2; } .evidence-card > a { grid-row: 6; } .card-badge { @@ -1194,113 +636,12 @@ const footerLinks = [ text-transform: none; } - .evidence-card h2 { + .evidence-card h3 { font-size: 17px; font-weight: 620; line-height: 1.24; } - .card-logos { - gap: 10px; - } - - .mini-chart { - --mini-label: 108px; - --mini-value: 42px; - border: 1px solid hsl(var(--pc-line)); - border-radius: 16px; - background: hsl(var(--pc-panel-2)); - padding: 12px; - } - - .mini-row { - display: grid; - grid-template-columns: var(--mini-label) minmax(0, 1fr) var(--mini-value); - gap: 10px; - align-items: center; - margin-bottom: 12px; - } - - .mini-row:last-of-type { - margin-bottom: 0; - } - - .mini-row span { - color: hsl(var(--pc-muted)); - font-size: 11px; - line-height: 1.35; - } - - .mini-track { - position: relative; - height: 8px; - overflow: hidden; - border-radius: 999px; - background: hsl(var(--pc-line) / 0.8); - } - - .mini-track i { - position: absolute; - inset: 0 auto 0 0; - width: var(--mini-width); - border-radius: 999px; - background: hsl(var(--pc-profit-deep)); - content: ""; - } - - .mini-row.cloud .mini-track i { - background: hsl(var(--pc-blue)); - } - - .mini-row.vercel .mini-track i, - .mini-row.risk .mini-track i { - background: hsl(var(--pc-copper)); - } - - .mini-row.neutral .mini-track i { - background: hsl(var(--pc-line-strong)); - } - - .mini-row strong { - font-size: 11px; - font-weight: 700; - text-align: right; - white-space: nowrap; - } - - .mini-axis { - display: flex; - justify-content: space-between; - margin-left: calc(var(--mini-label) + 10px); - margin-right: calc(var(--mini-value) + 10px); - margin-top: 14px; - color: hsl(var(--pc-subtle)); - font-size: 10px; - } - - .pricing-chart .mini-axis { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .card-metrics { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - margin: 0; - border: 1px solid hsl(var(--pc-line)); - border-radius: 16px; - overflow: hidden; - background: hsl(var(--pc-panel)); - } - - .card-metrics div { - padding: 12px 10px; - border-right: 1px solid hsl(var(--pc-line)); - } - - .card-metrics div:last-child { - border-right: 0; - } - dt { letter-spacing: 0; } @@ -1325,7 +666,7 @@ const footerLinks = [ .section-copy h2 { font-size: clamp(32px, 3.6vw, 46px); font-weight: 540; - line-height: 0.98; + line-height: 1.12; } .section-copy p { @@ -1417,7 +758,7 @@ const footerLinks = [ margin-top: 0; font-size: 21px; font-weight: 690; - line-height: 1.04; + line-height: 1.25; } .surface-card p { @@ -1547,16 +888,13 @@ const footerLinks = [ .metrics-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } - - .mini-chart { --mini-label: 90px; --mini-value: 36px; } - .mini-axis span:not(:first-child):not(:last-child) { display: none; } } @media (max-width: 980px) { .hero-shell, .site-header-inner, .product-footer-inner, - .evidence-band, + .evidence-section, .standards-section, .open-core-section, .final-cta { @@ -1607,9 +945,6 @@ const footerLinks = [ grid-template-rows: auto auto auto auto auto auto; padding: 24px; } - - .mini-chart { --mini-label: 132px; --mini-value: 52px; } - .mini-axis span:not(:first-child):not(:last-child) { display: inline; } .metrics-strip { grid-template-columns: repeat(4, minmax(0, 1fr)); } .final-cta { grid-template-columns: 1fr; padding: 24px; } .product-footer-inner { grid-template-columns: 1fr auto; } @@ -1643,49 +978,90 @@ const footerLinks = [ .metrics-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } .metrics-strip span { letter-spacing: 0.04em; } - .provider-summary { grid-template-columns: 1fr; } - .summary-card { display: grid; grid-template-columns: minmax(0, 1fr) auto; } - .summary-id { grid-row: 1 / span 2; } - .summary-card strong { margin-top: 0; text-align: right; } - .summary-card small { margin-top: 4px; text-align: right; } .recommendation-bar { grid-template-columns: 1fr; } .hero-actions { flex-wrap: wrap; gap: 12px; } - .proof-points { gap: 5px 12px; } - .proof-points strong { font-size: 12px; } - .proof-points small { font-size: 11px; } .standards-section, .open-core-section { padding-top: 40px; } .evidence-card { padding: 18px; } - .card-metrics div { padding: 12px 8px; } - .card-metrics dd { font-size: 14px; } .command-strip span { overflow-wrap: anywhere; } - .compare-chart { min-height: 176px; } - .chart-header, - .chart-legend { + .chart-header { display: grid; justify-content: start; } - .chart-legend { - grid-template-columns: repeat(2, minmax(0, 1fr)); - width: 100%; - gap: 8px 12px; - } - - .chart-legend span { font-size: 10px; white-space: normal; } - .threshold-label, .axis-label, .month-label, .active-label { font-size: 17px; } - .product-footer { padding-inline: 0; } - .mini-chart { --mini-label: 100px; --mini-value: 44px; } - .mini-row { gap: 8px; } - .mini-axis { margin-left: calc(var(--mini-label) + 8px); margin-right: calc(var(--mini-value) + 8px); } - .mini-axis span:not(:first-child):not(:last-child) { display: none; } - .cta-actions { flex-wrap: wrap; } } + + .metrics-strip article { align-content: start; } + .hero-copy h1, .section-copy h2, .evidence-card h3, .final-cta h2 { text-wrap: balance; } + + .hero-copy p, .section-copy p, .evidence-card p, .standards-grid p, .surface-card p { text-wrap: pretty; } + + .hero-copy .hero-note { margin-top: 18px; font-size: 13px; line-height: 1.5; } + + .evidence-section { padding-top: 24px; scroll-margin-top: 120px; } + + .evidence-card h3 { grid-row: 2; font-size: 21px; line-height: 1.3; font-weight: 620; } + + .evidence-card p { grid-row: 3; color: hsl(var(--pc-muted)); font-size: 14px; line-height: 1.6; } + + .evidence-card ul { grid-row: 4; margin: 0; padding-left: 18px; font-size: 13px; line-height: 1.6; } + + .evidence-card li + li { margin-top: 7px; } + + .evidence-card a { min-height: 44px; line-height: 1.4; } + + .comparison-table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: 14px; } + + .comparison-table th, .comparison-table td { padding: 16px 8px; border-bottom: 1px solid hsl(var(--pc-line)); vertical-align: middle; } + + .comparison-table th:first-child { width: 46%; padding-left: 0; text-align: left; } + + .comparison-table th:not(:first-child), .comparison-table td { text-align: right; } + + .comparison-table th:last-child, .comparison-table td:last-child { padding-right: 0; } + + .comparison-table thead th { color: hsl(var(--pc-muted)); font-size: 12px; line-height: 1.4; font-weight: 550; } + + .comparison-table tbody th { font-weight: 650; } + + .comparison-table td { font-variant-numeric: tabular-nums; white-space: nowrap; } + + .provider-id { display: flex; align-items: center; gap: 10px; } + + .provider-id img { flex-shrink: 0; } + + .provider-id small { display: block; margin-top: 5px; font-size: 11px; font-weight: 550; line-height: 1.4; } + + .provider-id .pass { color: hsl(var(--pc-profit-deep)); } + + .provider-id .watch { color: hsl(var(--pc-copper)); } + + .chart-shell .chart-note { margin-top: 16px; color: hsl(var(--pc-muted)); font-size: 12px; line-height: 1.6; } + + .standards-grid h3 { line-height: 1.3; } + + .final-cta { scroll-margin-top: 120px; } + + .final-cta h2 { margin-top: 16px; line-height: 1.15; } + + .start-command { margin: 20px 0 0; padding: 16px; border: 1px solid hsl(var(--pc-line)); border-radius: 12px; background: hsl(var(--pc-panel-2)); white-space: pre-wrap; overflow-wrap: anywhere; font-size: 12px; line-height: 1.7; } + + .cta-actions { align-content: center; } + + @media (max-width: 620px) { + .comparison-table { font-size: 12px; } + .comparison-table th, .comparison-table td { padding-inline: 4px; } + .comparison-table thead th { font-size: 11px; } + .provider-id { gap: 6px; } + .provider-id img { width: 20px; height: 20px; } + .provider-id small { font-size: 10px; } + .evidence-section { padding-top: 8px; } + } From f36ea7b61ff1b457d127c6ce41beccea2cd9e1a0 Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Thu, 1 Oct 2026 00:56:24 -0400 Subject: [PATCH 04/13] Rebuild website story around an interactive cost demo --- website/README.md | 16 +- website/astro.config.mjs | 1 + website/package.json | 2 +- website/public/images/profitctl-social.jpg | Bin 0 -> 42014 bytes website/public/images/profitctl-social.svg | 15 + website/scripts/check-demo.mjs | 26 + website/scripts/check-seo.mjs | 35 +- website/src/layouts/ProductLayout.astro | 106 +- website/src/pages/docs.astro | 33 + website/src/pages/index.astro | 1180 +++----------------- website/src/pages/sitemap.xml.ts | 2 +- website/src/scripts/demo.ts | 112 ++ 12 files changed, 452 insertions(+), 1076 deletions(-) create mode 100644 website/public/images/profitctl-social.jpg create mode 100644 website/public/images/profitctl-social.svg create mode 100644 website/scripts/check-demo.mjs create mode 100644 website/src/pages/docs.astro create mode 100644 website/src/scripts/demo.ts diff --git a/website/README.md b/website/README.md index a47475a..e637e97 100644 --- a/website/README.md +++ b/website/README.md @@ -1,6 +1,7 @@ # ProfitCtl Website -Standalone landing page app for ProfitCtl. +Static product website for ProfitCtl, with an interactive example and local +documentation at `/docs/`. ## Env @@ -12,16 +13,21 @@ Standalone landing page app for ProfitCtl. ## Run ```bash -npm install -npm run dev +bun install --frozen-lockfile +bun run dev ``` ## Build ```bash -npm run build +bun run build +bun run typecheck ``` The build produces static files in `dist/`, including `robots.txt` and -`sitemap.xml`. Local source and a successful build do not establish publication +`sitemap.xml`. It checks the demo calculations, page metadata, social image, +headings, structured data, and local links. The browser demo uses illustrative +costs; it does not run the CLI simulation or fetch live provider prices. + +Local source and a successful build do not establish publication or search indexing. Deployment and domain changes require maintainer approval. diff --git a/website/astro.config.mjs b/website/astro.config.mjs index 5e657e6..7c9d35c 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -2,6 +2,7 @@ import { defineConfig } from "astro/config"; export default defineConfig({ site: process.env.PUBLIC_SITE_ORIGIN || "https://profitctl.intelip.co", + trailingSlash: "always", compressHTML: true, devToolbar: { enabled: false, diff --git a/website/package.json b/website/package.json index 6942f00..c85ad78 100644 --- a/website/package.json +++ b/website/package.json @@ -6,7 +6,7 @@ "packageManager": "bun@1.3.3", "scripts": { "dev": "astro dev", - "build": "astro build && node scripts/check-seo.mjs", + "build": "astro build && bun scripts/check-demo.mjs && node scripts/check-seo.mjs", "preview": "astro preview", "typecheck": "astro check && tsc --noEmit" }, diff --git a/website/public/images/profitctl-social.jpg b/website/public/images/profitctl-social.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e53f5bba25ca0515c72754ba605332541ce2cf7 GIT binary patch literal 42014 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 D#9t5K literal 0 HcmV?d00001 diff --git a/website/public/images/profitctl-social.svg b/website/public/images/profitctl-social.svg new file mode 100644 index 0000000..fb31a19 --- /dev/null +++ b/website/public/images/profitctl-social.svg @@ -0,0 +1,15 @@ + + + + + + P + ProfitCtl + OPEN-SOURCE COST MODELING FOR AI PRODUCTS + Know your AI costs. + Protect your margin. + Model a change. Compare your options. Decide before you ship. + + + ProfitCtl · MIT licensed + diff --git a/website/scripts/check-demo.mjs b/website/scripts/check-demo.mjs new file mode 100644 index 0000000..8ee6e51 --- /dev/null +++ b/website/scripts/check-demo.mjs @@ -0,0 +1,26 @@ +import assert from "node:assert/strict"; +import { calculateExample } from "../src/scripts/demo.ts"; + +const feature = calculateExample({ users: 1000, requests: 60, price: 20 }); +assert.equal(feature.revenue, 20000); +assert.equal(feature.baseline.cost, 2500); +assert.equal(feature.candidate.cost, 6700); +assert.equal(feature.candidate.margin, 66.5); +assert.equal(feature.addedCost, 4200); +assert.equal(feature.minimumPrice, 16.75); +assert.equal(feature.passes, true); + +const pricing = calculateExample({ users: 1000, requests: 60, price: 12 }); +assert.ok(Math.abs(pricing.candidate.margin - 44.1666666667) < 1e-8); +assert.equal(pricing.passes, false); +const growth = calculateExample({ users: 10000, requests: 220, price: 20 }); +assert.equal(growth.candidate.cost, 191400); +assert.equal(growth.candidate.margin, 4.3); +assert.equal(growth.passes, false); +assert.equal(calculateExample({ users: 1000, requests: 0, price: 20 }).addedCost, 0); +for (const bad of [0, -1, NaN, Infinity]) { + assert.throws(() => calculateExample({ users: bad, requests: 60, price: 20 }), RangeError); + assert.throws(() => calculateExample({ users: 1000, requests: 60, price: bad }), RangeError); +} +assert.throws(() => calculateExample({ users: 1000, requests: -1, price: 20 }), RangeError); +console.log("Interactive example: costs, margin decisions, and invalid inputs verified."); diff --git a/website/scripts/check-seo.mjs b/website/scripts/check-seo.mjs index b718231..face2b3 100644 --- a/website/scripts/check-seo.mjs +++ b/website/scripts/check-seo.mjs @@ -1,5 +1,5 @@ import assert from "node:assert/strict"; -import { readFile } from "node:fs/promises"; +import { readFile, stat } from "node:fs/promises"; const output = new URL("../dist/", import.meta.url); const [html, robots, sitemap] = await Promise.all( @@ -20,4 +20,35 @@ const schema = JSON.parse( ); assert.equal(schema["@type"], "SoftwareApplication"); assert.equal(schema.url, canonical); -console.log(`Search metadata and crawler files verified for ${canonical}`); + +const routes = ["/", "/docs/"]; +const pages = new Map(); +for (const route of routes) { + const page = await readFile(new URL(route === "/" ? "index.html" : "docs/index.html", output), "utf8"); + const url = new URL(route, origin).href; + pages.set(route, page); + assert.ok(page.includes(`rel="canonical" href="${url}"`), `Missing canonical: ${route}`); + assert.ok(page.includes(`property="og:url" content="${url}"`), `Missing social URL: ${route}`); + assert.ok(page.includes(`property="og:image" content="${new URL("/images/profitctl-social.jpg", origin).href}"`)); + assert.ok(page.includes('name="twitter:card" content="summary_large_image"')); + assert.equal([...page.matchAll(/([^<]+)<\/title>/)?.[1].length > 20); + assert.ok(page.match(/name="description" content="([^"]+)"/)?.[1].length > 80); + assert.ok(sitemap.includes(`${url}`), `Missing sitemap entry: ${route}`); + const data = JSON.parse(page.match(/]*type="application\/ld\+json"[^>]*>(.*?)<\/script>/s)[1]); + assert.equal(data["@type"], route === "/" ? "SoftwareApplication" : "TechArticle"); + assert.equal(data.url, url); +} + +for (const [route, page] of pages) { + for (const match of page.matchAll(/]*href="([^"]+)"/g)) { + const link = new URL(match[1], new URL(route, origin)); + if (link.origin !== new URL(origin).origin) continue; + const target = pages.get(link.pathname); + assert.ok(target, `Unknown internal destination: ${match[1]}`); + if (link.hash) assert.ok(target.includes(`id="${decodeURIComponent(link.hash.slice(1))}"`), `Missing linked section: ${match[1]}`); + } +} +assert.ok(html.includes('id="demo"') && html.includes("Interactive example")); +assert.ok((await stat(new URL("images/profitctl-social.jpg", output))).size > 10000); +console.log("Both pages: metadata, social image, structured data, sitemap, and internal destinations verified."); diff --git a/website/src/layouts/ProductLayout.astro b/website/src/layouts/ProductLayout.astro index ccb8b79..9310eb4 100644 --- a/website/src/layouts/ProductLayout.astro +++ b/website/src/layouts/ProductLayout.astro @@ -4,11 +4,26 @@ import "@fontsource/instrument-sans"; export interface Props { title: string; description: string; + pageType?: "product" | "docs"; } -const { title, description } = Astro.props; +const { title, description, pageType = "product" } = Astro.props; +const repoUrl = import.meta.env.PUBLIC_REPO_URL || "https://github.com/IntelIP/ProfitCtl"; const canonicalOrigin = Astro.site || Astro.url.origin; const canonicalURL = new URL(Astro.url.pathname, canonicalOrigin); +const imageURL = new URL("/images/profitctl-social.jpg", canonicalOrigin); +const schema = pageType === "docs" ? { + "@context": "https://schema.org", "@type": "TechArticle", headline: title, + description, url: canonicalURL.href, image: imageURL.href, + author: { "@type": "Organization", name: "ProfitCtl contributors", url: repoUrl }, +} : { + "@context": "https://schema.org", "@type": "SoftwareApplication", name: "ProfitCtl", + applicationCategory: "DeveloperApplication", operatingSystem: "macOS, Linux, Windows", + description, url: canonicalURL.href, image: imageURL.href, + license: repoUrl + "/blob/main/LICENSE", downloadUrl: repoUrl + "/releases", + isAccessibleForFree: true, + offers: { "@type": "Offer", price: "0", priceCurrency: "USD" }, +}; const favicon = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%23090B05'/%3E%3Crect x='12' y='12' width='40' height='40' rx='8' fill='none' stroke='%23B8FF18' stroke-width='4'/%3E%3Ctext x='32' y='41' text-anchor='middle' font-family='Arial,sans-serif' font-size='30' font-weight='700' fill='%23B8FF18'%3EP%3C/text%3E%3C/svg%3E"; --- @@ -18,7 +33,7 @@ const favicon = - + {title} @@ -30,37 +45,55 @@ const favicon = - + + + + + + + + diff --git a/website/src/pages/sitemap.xml.ts b/website/src/pages/sitemap.xml.ts index b763d18..ee76c94 100644 --- a/website/src/pages/sitemap.xml.ts +++ b/website/src/pages/sitemap.xml.ts @@ -2,6 +2,6 @@ import type { APIRoute } from "astro"; export const GET: APIRoute = ({ site }) => new Response( - `\n${new URL("./", site).href.replaceAll("&", "&")}\n`, + `\n${["/", "/docs/"].map((path) => `${new URL(path, site).href.replaceAll("&", "&")}`).join("")}\n`, { headers: { "Content-Type": "application/xml; charset=utf-8" } }, ); diff --git a/website/src/scripts/demo.ts b/website/src/scripts/demo.ts new file mode 100644 index 0000000..fcb9d60 --- /dev/null +++ b/website/src/scripts/demo.ts @@ -0,0 +1,112 @@ +export interface ExampleInputs { + users: number; + requests: number; + price: number; +} + +export function calculateExample({ users, requests, price }: ExampleInputs) { + if (![users, requests, price].every(Number.isFinite) || users <= 0 || requests < 0 || price <= 0) { + throw new RangeError("Use positive users and price, and nonnegative requests."); + } + const revenue = users * price; + const option = (requestCost: number) => { + const cost = 400 + users * (1.5 + requests * requestCost); + return { cost, unitCost: cost / users, margin: (revenue - cost) / revenue * 100 }; + }; + const baseline = option(0.01); + const candidate = option(0.08); + return { + revenue, baseline, candidate, + addedCost: candidate.cost - baseline.cost, + minimumPrice: Math.ceil(candidate.unitCost / 0.4 * 100) / 100, + passes: candidate.margin >= 60, + }; +} + +export const money = (value: number, digits = 0) => value.toLocaleString("en-US", { + style: "currency", currency: "USD", minimumFractionDigits: digits, maximumFractionDigits: digits, +}); +export const percent = (value: number) => `${value.toFixed(1)}%`; + +export function initializeDemo() { + const element = document.querySelector("[data-demo]"); + if (!element) return; + const demo = element; + const users = demo.querySelector("#demo-users")!; + const requests = demo.querySelector("#demo-requests")!; + const price = demo.querySelector("#demo-price")!; + const presets: Record = { + feature: { users: 1000, requests: 60, price: 20 }, + pricing: { users: 1000, requests: 60, price: 12 }, + growth: { users: 10000, requests: 220, price: 20 }, + }; + const setText = (selector: string, value: string) => { + demo.querySelector(selector)!.textContent = value; + }; + const buttons = [...demo.querySelectorAll("[data-preset]")]; + + function update() { + try { + const inputs = { users: users.valueAsNumber, requests: requests.valueAsNumber, price: price.valueAsNumber }; + const result = calculateExample(inputs); + setText("#users-value", inputs.users.toLocaleString("en-US")); + setText("#requests-value", inputs.requests.toLocaleString("en-US")); + setText("#price-value", money(inputs.price)); + setText("[data-revenue]", money(result.revenue)); + for (const name of ["baseline", "candidate"] as const) { + const option = result[name]; + setText(`[data-${name}-cost]`, money(option.cost)); + setText(`[data-${name}-margin]`, percent(option.margin)); + setText(`[data-${name}-unit]`, money(option.unitCost, 2)); + const bar = demo.querySelector(`[data-${name}-bar]`)!; + bar.style.width = `${Math.min(100, option.cost / result.revenue * 100)}%`; + } + const verdict = demo.querySelector("[data-verdict]")!; + verdict.dataset.state = result.passes ? "pass" : "risk"; + setText("[data-verdict-title]", result.passes ? "The feature clears your margin target." : "The feature misses your margin target."); + setText("[data-verdict-detail]", result.passes + ? `Research adds ${money(result.addedCost)} per month. The modeled margin stays above 60%.` + : `At this usage, a price of ${money(result.minimumPrice, 2)} per user would meet the 60% target.`); + setText("[data-demo-error]", ""); + } catch { + setText("[data-demo-error]", "Choose valid users, usage, and price values to compare the scenarios."); + } + } + + buttons.forEach((button) => button.addEventListener("click", () => { + const preset = presets[button.dataset.preset!]; + users.value = String(preset.users); + requests.value = String(preset.requests); + price.value = String(preset.price); + buttons.forEach((item) => item.setAttribute("aria-pressed", String(item === button))); + update(); + })); + [users, requests, price].forEach((input) => input.addEventListener("input", () => { + buttons.forEach((button) => button.setAttribute("aria-pressed", "false")); + update(); + })); + + const copyButton = document.querySelector("[data-copy-command]"); + copyButton?.addEventListener("click", async () => { + const code = document.querySelector("[data-start-command]")!; + try { + await navigator.clipboard.writeText(code.textContent!.trim()); + copyButton.textContent = "Copied"; + } catch { + copyButton.textContent = "Select the command to copy"; + } + }); + + if (!matchMedia("(prefers-reduced-motion: reduce)").matches && "IntersectionObserver" in window) { + const observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + entry.target.classList.add("is-visible"); + observer.unobserve(entry.target); + } + }); + }, { threshold: 0.12 }); + document.querySelectorAll("[data-reveal]").forEach((element) => observer.observe(element)); + } + update(); +} From bcd9a8d243b8e4f43a4d0c64a529dd23f6b716b0 Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Thu, 1 Oct 2026 13:49:18 -0400 Subject: [PATCH 05/13] Unify ProfitCTL branding, development credit, and search icons --- website/README.md | 4 +++ website/public/apple-touch-icon.png | Bin 0 -> 3312 bytes website/public/favicon-96.png | Bin 0 -> 1244 bytes website/public/favicon.ico | Bin 0 -> 2718 bytes website/public/favicon.svg | 9 +++++ website/public/icons/profitctl-192.png | Bin 0 -> 3171 bytes website/public/images/profitctl-social.jpg | Bin 42014 -> 63196 bytes website/public/images/profitctl-social.svg | 18 +++++----- website/scripts/check-seo.mjs | 25 +++++++++++++- website/src/layouts/ProductLayout.astro | 38 ++++++++++++--------- website/src/pages/docs.astro | 2 +- website/src/pages/index.astro | 2 +- 12 files changed, 68 insertions(+), 30 deletions(-) create mode 100644 website/public/apple-touch-icon.png create mode 100644 website/public/favicon-96.png create mode 100644 website/public/favicon.ico create mode 100644 website/public/favicon.svg create mode 100644 website/public/icons/profitctl-192.png diff --git a/website/README.md b/website/README.md index e637e97..b65380a 100644 --- a/website/README.md +++ b/website/README.md @@ -29,5 +29,9 @@ The build produces static files in `dist/`, including `robots.txt` and headings, structured data, and local links. The browser demo uses illustrative costs; it does not run the CLI simulation or fetch live provider prices. +The page uses the ProfitCTL wordmark. Browser icons share its forest and lime +colors and are served at stable public paths. Development credit appears on +both pages and in their structured metadata: Intel IP and Hudson Aikman. + Local source and a successful build do not establish publication or search indexing. Deployment and domain changes require maintainer approval. diff --git a/website/public/apple-touch-icon.png b/website/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..deaf0a40581ac0fd7846b5fb136eac646bf3a0db GIT binary patch literal 3312 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 literal 0 HcmV?d00001 diff --git a/website/public/favicon-96.png b/website/public/favicon-96.png new file mode 100644 index 0000000000000000000000000000000000000000..12bc3c947d2863f2a454059d595a15b615ff362e GIT binary patch literal 1244 zcmV<21S9*2P)}fs_OH2!s>_ zr(mHFC2=?qi5$Y?ut#j;zY&DE0ZwvkiATiahy*Ao$t{YdBv?v<<%w!+37Z!_-@tpVm-CW71(b}lsRluw)Kj-y3f~pwb`$0N$ zX)^usw=-#2Xs?6(Hh1}0x_jTQ{NL^INo+6XAXR6sTb>61Yg1FDB$F#t^fZ7(Dn0s2IcO+or(kOC|QunC~Z=>F5ui^n*%YKtGFc2LE`kMN6ChOitB7DD zzyt_30@}6_vndEI1_Vn*#FMuVv7HB$EpH!U6QFE)`w*J|Wy{-#*aRpWQNaX26Tn75 z+omA0Dac|#upo>Ugl!enwq+>UGSv0~!8T%O8!_7nw=ESxmWr@kP_SeiS~5;MyBJFL zTuVoDNd(CI!lr%>WWN99OtjDK{lcc4uPl=T_g)Q}{_beB&+YljGGcg`=R@Ud2RU$m zoiSH`{x%)o(~9=Fy^R2TFpz62v(Hj4PG)DmFB>Aii6?EwnDLT5x2J0>?c!Eczqs|V zUI(z+7{QMdxj@=3??%1LyH{(++*0=jt{kqvP{0AE3ecgnN^l-NUthw+3izaIB}`UF z!K}7dP*vdZyp^$H#VV_v%Au35pHlY@jvJ2QWcRAs40p6UNznpscP(Wo&5C*AU_4(+ z!H?EPyOO_F){5bPm9xb>1z4gH{HQ1L*Wv-!Kh+JSqS*aTJDu9_@DSVT2>TEKZp&gO$+dl$yh9#O*l56R#R9uk}L;O>8OPZ6)a5;`6L0000kw4}D literal 0 HcmV?d00001 diff --git a/website/public/favicon.ico b/website/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a7e5ae6ab2acd33b235929e8bc7ef75d8b38e7d9 GIT binary patch literal 2718 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 + ProfitCTL + + + + + + + diff --git a/website/public/icons/profitctl-192.png b/website/public/icons/profitctl-192.png new file mode 100644 index 0000000000000000000000000000000000000000..0946bdf30f5528264ad1a81aa913092c29ed8dbd GIT binary patch literal 3171 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�WI*`Z-wGwN@Q=8q@!|K#;84uss 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%0A#Xj35AdSdH4UiDiOZK|X0|ZL0(gGyQ=zOYPGh4l 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 UJ^tWQ?`fT64}d_ui|_w)W}J=}*8-4Hb12 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 D#9t5K diff --git a/website/public/images/profitctl-social.svg b/website/public/images/profitctl-social.svg index fb31a19..ebc9feb 100644 --- a/website/public/images/profitctl-social.svg +++ b/website/public/images/profitctl-social.svg @@ -1,15 +1,13 @@ - + - - - P - ProfitCtl - OPEN-SOURCE COST MODELING FOR AI PRODUCTS + + ProfitCTL + OPEN-SOURCE COST MODELING FOR AI PRODUCTS Know your AI costs. - Protect your margin. - Model a change. Compare your options. Decide before you ship. + Protect your margin. + Model a change. Compare your options. Decide before you ship. - - ProfitCtl · MIT licensed + + ProfitCTL · MIT licensed diff --git a/website/scripts/check-seo.mjs b/website/scripts/check-seo.mjs index face2b3..4026972 100644 --- a/website/scripts/check-seo.mjs +++ b/website/scripts/check-seo.mjs @@ -38,6 +38,18 @@ for (const route of routes) { const data = JSON.parse(page.match(/]*type="application\/ld\+json"[^>]*>(.*?)<\/script>/s)[1]); assert.equal(data["@type"], route === "/" ? "SoftwareApplication" : "TechArticle"); assert.equal(data.url, url); + assert.equal(data.author.name, "Hudson Aikman"); + assert.equal(data.publisher.name, "Intel IP"); + assert.ok(page.includes('name="author" content="Intel IP and Hudson Aikman"')); + assert.ok(page.includes('Developed by Intel IP and Hudson Aikman.')); + assert.ok(page.includes('>GitHub ')); + assert.ok(page.includes('rel="icon" type="image/png" sizes="96x96" href="/favicon-96.png"'), `Missing crawlable favicon: ${route}`); + assert.ok(page.includes('rel="icon" type="image/svg+xml" sizes="any" href="/favicon.svg"')); + assert.ok(page.includes('rel="shortcut icon" href="/favicon.ico"')); + assert.ok(page.includes('rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"')); + assert.ok(page.includes('name="theme-color" content="#20251d"')); + assert.ok(!page.includes('class="brand-mark"'), `Old letter badge: ${route}`); + assert.ok(page.includes('aria-label="ProfitCTL home">ProfitCTL')); } for (const [route, page] of pages) { @@ -51,4 +63,15 @@ for (const [route, page] of pages) { } assert.ok(html.includes('id="demo"') && html.includes("Interactive example")); assert.ok((await stat(new URL("images/profitctl-social.jpg", output))).size > 10000); -console.log("Both pages: metadata, social image, structured data, sitemap, and internal destinations verified."); +for (const [file, size] of [["favicon-96.png", 96], ["icons/profitctl-192.png", 192], ["apple-touch-icon.png", 180]]) { + const icon = await readFile(new URL(file, output)); + assert.equal(icon.subarray(1, 4).toString(), "PNG", `Invalid icon: ${file}`); + assert.equal(icon.readUInt32BE(16), size, `Wrong icon width: ${file}`); + assert.equal(icon.readUInt32BE(20), size, `Wrong icon height: ${file}`); +} +const legacyIcon = await readFile(new URL("favicon.ico", output)); +assert.equal(legacyIcon.readUInt16LE(2), 1); +assert.equal(legacyIcon.readUInt16LE(4), 4); +assert.ok((await readFile(new URL("favicon.svg", output), "utf8")).includes('viewBox="0 0 96 96"')); +assert.ok(schema.image.includes(new URL("/icons/profitctl-192.png", origin).href)); +console.log("Both pages: metadata, brand icons, social image, structured data, sitemap, and internal destinations verified."); diff --git a/website/src/layouts/ProductLayout.astro b/website/src/layouts/ProductLayout.astro index 9310eb4..78300e8 100644 --- a/website/src/layouts/ProductLayout.astro +++ b/website/src/layouts/ProductLayout.astro @@ -12,50 +12,55 @@ const repoUrl = import.meta.env.PUBLIC_REPO_URL || "https://github.com/IntelIP/P const canonicalOrigin = Astro.site || Astro.url.origin; const canonicalURL = new URL(Astro.url.pathname, canonicalOrigin); const imageURL = new URL("/images/profitctl-social.jpg", canonicalOrigin); +const iconURL = new URL("/icons/profitctl-192.png", canonicalOrigin); +const developer = { "@type": "Person", name: "Hudson Aikman" }; +const publisher = { "@type": "Organization", name: "Intel IP", alternateName: "IntelIP", url: "https://github.com/IntelIP" }; const schema = pageType === "docs" ? { "@context": "https://schema.org", "@type": "TechArticle", headline: title, description, url: canonicalURL.href, image: imageURL.href, - author: { "@type": "Organization", name: "ProfitCtl contributors", url: repoUrl }, + author: developer, publisher, } : { - "@context": "https://schema.org", "@type": "SoftwareApplication", name: "ProfitCtl", + "@context": "https://schema.org", "@type": "SoftwareApplication", name: "ProfitCTL", applicationCategory: "DeveloperApplication", operatingSystem: "macOS, Linux, Windows", - description, url: canonicalURL.href, image: imageURL.href, + description, url: canonicalURL.href, image: [iconURL.href, imageURL.href], license: repoUrl + "/blob/main/LICENSE", downloadUrl: repoUrl + "/releases", + author: developer, publisher, sameAs: repoUrl, isAccessibleForFree: true, offers: { "@type": "Offer", price: "0", priceCurrency: "USD" }, }; -const favicon = - "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Crect width='64' height='64' rx='12' fill='%23090B05'/%3E%3Crect x='12' y='12' width='40' height='40' rx='8' fill='none' stroke='%23B8FF18' stroke-width='4'/%3E%3Ctext x='32' y='41' text-anchor='middle' font-family='Arial,sans-serif' font-size='30' font-weight='700' fill='%23B8FF18'%3EP%3C/text%3E%3C/svg%3E"; --- - + + + + {title} - + - + - + - - + + + diff --git a/website/src/pages/docs.astro b/website/src/pages/docs.astro index 3c1442f..d6fc25a 100644 --- a/website/src/pages/docs.astro +++ b/website/src/pages/docs.astro @@ -11,7 +11,7 @@ const quickStart = ["git clone " + repoUrl, "cd ProfitCtl", "go run . compare ex

3. Replace the assumptions.

A scenario is a YAML file containing the business inputs you want to test.

  • Costs: fixed and variable costs, including infrastructure, model calls, tools, and payment fees.
  • Pricing: subscriptions, usage pricing, plan mixes, or contract terms.
  • Usage: users, growth, and simulation inputs.
  • Targets: the margin and cost limits that define an acceptable outcome.
  • Evidence: sources, capture dates, confidence, and notes for your cost assumptions.
profitctl simulate -f your-scenario.yml
 profitctl compare baseline.yml candidate.yml
 profitctl validate -f your-scenario.yml

Use the installed CLI for these commands. The equivalent source invocation begins with go run ..

-

4. Know what the website demo shows.

The interactive demo calculates a basic gross-margin comparison with fixed sample costs. It runs in your browser. It does not fetch provider prices or run the full CLI simulation.

Your CLI scenarios can include payment fees, contract effects, cost provenance, and simulation settings. Treat an estimate as decision evidence only after you check its assumptions against your own usage and rates.

The local CLI, Codex skill, and CI output are available today. Hosted team workspaces and approvals are planned.

+

4. Know what the website demo shows.

The guided terminal runs the real ProfitCTL CLI in your browser. It validates a fictional scenario, simulates a baseline, shows a margin failure as usage grows, and compares a cheaper request route. Pause stops playback; Replay starts the story again.

The demo uses bundled, read-only scenario files and made-up rates. It does not call AI providers, fetch live prices, or give access to your local files. Download a scenario to inspect its inputs or run the same commands with the installed CLI.

The separate cost calculator is a simplified gross-margin illustration. Your CLI scenarios can include payment fees, contract effects, cost provenance, and simulation settings. Check assumptions against your own usage and rates before relying on an estimate.

The local CLI, Codex skill, and CI output are available today. Hosted team workspaces and approvals are planned.

diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index 51f51ee..7d9893e 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -101,7 +101,7 @@ const steps = [ .hero h1 { margin-top: 20px; font-size: clamp(42px, 4.9vw, 66px); line-height: 1.08; font-weight: 550; letter-spacing: -.065em; } .hero h1 span { color: var(--green); } .hero-description { max-width: 480px; margin-top: 24px; font-size: 17px; line-height: 1.65; color: var(--muted); text-wrap: pretty; } - .primary-action { display: inline-flex; align-items: center; gap: 32px; min-height: 50px; margin-top: 28px; padding: 0 22px; border-radius: 999px; background: var(--ink); color: white; font-size: 14px; text-decoration: none; transition: transform .2s, box-shadow .2s; } + .primary-action { display: inline-flex; align-items: center; gap: 32px; min-height: 50px; margin-top: 28px; padding: 0 22px; border-radius: 999px; background: var(--ink); color: var(--on-ink); font-size: 14px; text-decoration: none; transition: transform .2s, box-shadow .2s; } .primary-action:hover { transform: translateY(-3px); box-shadow: 0 10px 20px #20251d15; } .hero-note { margin-top: 16px; font-size: 12px; color: var(--muted); } .hero-copy { animation: arrive .7s ease-out both; } @@ -120,24 +120,24 @@ const steps = [ .section-heading { display: grid; grid-template-columns: 1.1fr 1fr; align-items: end; gap: 48px; margin-bottom: 30px; } h2 { margin-top: 12px; font-size: clamp(30px, 3.25vw, 43px); line-height: 1.13; letter-spacing: -.05em; font-weight: 540; text-wrap: balance; } .section-heading > p { max-width: 430px; font-size: 15px; line-height: 1.65; color: var(--muted); text-wrap: pretty; } - .demo-shell { overflow: hidden; border: 1px solid #cfd7c4; border-radius: 24px; background: var(--panel); box-shadow: 0 24px 50px #343c2710; } - .demo-toolbar { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px; padding: 16px 24px; border-bottom: 1px solid var(--line); font-size: 11px; color: var(--muted); background: #fafbf6; }.demo-toolbar > div { display: flex; align-items: center; gap: 8px; color: var(--ink); font-weight: 600; } + .demo-shell { overflow: hidden; border: 1px solid light-dark(#cfd7c4, #3a4432); border-radius: 24px; background: var(--panel); box-shadow: 0 24px 50px #343c2710; } + .demo-toolbar { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px; padding: 16px 24px; border-bottom: 1px solid var(--line); font-size: 11px; color: var(--muted); background: light-dark(#fafbf6, #252e20); }.demo-toolbar > div { display: flex; align-items: center; gap: 8px; color: var(--ink); font-weight: 600; } .demo-grid { display: grid; grid-template-columns: .8fr 1.2fr; }.demo-inputs { padding: 28px; border-right: 1px solid var(--line); }.demo-inputs h3 { font-size: 17px; font-weight: 600; } - .demo-presets { display: flex; padding: 4px; gap: 4px; margin-top: 18px; border: 1px solid var(--line); border-radius: 999px; background: #f7f8f2; }.demo-presets button { flex: 1; padding: 8px 10px; min-height: 38px; border: 0; border-radius: 999px; background: transparent; font-size: 11px; color: var(--muted); white-space: nowrap; transition: background .2s, color .2s; }.demo-presets button[aria-pressed=true] { background: var(--ink); color: white; } + .demo-presets { display: flex; padding: 4px; gap: 4px; margin-top: 18px; border: 1px solid var(--line); border-radius: 999px; background: light-dark(#f7f8f2, #252e20); }.demo-presets button { flex: 1; padding: 8px 10px; min-height: 38px; border: 0; border-radius: 999px; background: transparent; font-size: 11px; color: var(--muted); white-space: nowrap; transition: background .2s, color .2s; }.demo-presets button[aria-pressed=true] { background: var(--ink); color: var(--on-ink); } .input-row { margin-top: 28px; }.input-row > div { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; }.input-row label { font-size: 12px; line-height: 1.5; color: var(--muted); }.input-row output { font-size: 19px; font-weight: 580; font-variant-numeric: tabular-nums; }.input-row input { display: block; width: 100%; min-height: 28px; margin: 10px 0 2px; accent-color: var(--green); cursor: pointer; }.input-row p { font-size: 10px; color: var(--muted); line-height: 1.5; } .demo-error { color: var(--risk); font-size: 12px; line-height: 1.5; }.demo-error:not(:empty) { margin-top: 14px; } details summary { display: flex; align-items: center; justify-content: space-between; gap: 20px; cursor: pointer; list-style: none; }details summary::-webkit-details-marker { display: none; }details summary > span { font-size: 19px; font-weight: 400; transition: transform .2s; }details[open] summary > span { transform: rotate(45deg); } .assumptions { margin-top: 24px; border-top: 1px solid var(--line); padding-top: 10px; }.assumptions summary { min-height: 38px; font-size: 11px; font-weight: 550; }.assumptions p { margin-top: 10px; font-size: 11px; line-height: 1.6; color: var(--muted); } - .demo-results { padding: 28px; background: linear-gradient(150deg,#f8faf2,white 70%); }.revenue-strip { display: flex; justify-content: space-between; align-items: baseline; gap: 20px; padding-bottom: 20px; font-size: 12px; color: var(--muted); }.revenue-strip strong { color: var(--ink); font-size: 26px; font-weight: 550; font-variant-numeric: tabular-nums; } - .results-pair { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); grid-template-rows: auto auto auto; gap: 8px 12px; }.result-card { display: grid; grid-row: span 3; grid-template-rows: subgrid; gap: 8px; border: 1px solid var(--line); border-radius: 16px; padding: 18px; background: white; }.candidate { border-color: #bbcdac; background: #f1f6e9; }.option-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }.result-card h3 { font-size: 15px; line-height: 1.4; font-weight: 600; }.result-card dl { display: grid; gap: 16px; margin-top: 12px; }.result-card dt { font-size: 11px; color: var(--muted); }.result-card dd { margin-top: 5px; font-size: 24px; line-height: 1.2; font-weight: 550; font-variant-numeric: tabular-nums; }.result-card dl > div:first-child dd { font-size: 30px; letter-spacing: -.04em; } - .cost-bars { margin-top: 24px; }.cost-bars > p { margin-bottom: 14px; color: var(--muted); font-size: 11px; }.cost-bars > div { display: grid; grid-template-columns: 64px 1fr; align-items: center; gap: 14px; margin-top: 12px; font-size: 11px; }.bar-track { height: 10px; border-radius: 999px; overflow: hidden; background: #e6ebde; }.bar-track i { display: block; height: 100%; border-radius: 999px; background: #939e85; transition: width .55s cubic-bezier(.2,.8,.2,1); }.bar-track [data-candidate-bar] { background: var(--green); } - .verdict { display: flex; gap: 12px; margin-top: 26px; padding: 18px; border-radius: 14px; border: 1px solid #cfdec0; background: #e9f1df; }.verdict-icon { display: grid; place-items: center; flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%; background: var(--green); color: white; }.verdict h3 { font-size: 13px; font-weight: 600; line-height: 1.5; }.verdict p { margin-top: 6px; color: var(--muted); font-size: 11px; line-height: 1.6; }.verdict[data-state=risk] { border-color: #e5c5b9; background: #fcf0e9; }.verdict[data-state=risk] .verdict-icon { background: var(--risk); } + .demo-results { padding: 28px; background: linear-gradient(150deg,light-dark(#f8faf2, #252e20),var(--panel) 70%); }.revenue-strip { display: flex; justify-content: space-between; align-items: baseline; gap: 20px; padding-bottom: 20px; font-size: 12px; color: var(--muted); }.revenue-strip strong { color: var(--ink); font-size: 26px; font-weight: 550; font-variant-numeric: tabular-nums; } + .results-pair { display: grid; grid-template-columns: repeat(2,minmax(0,1fr)); grid-template-rows: auto auto auto; gap: 8px 12px; }.result-card { display: grid; grid-row: span 3; grid-template-rows: subgrid; gap: 8px; border: 1px solid var(--line); border-radius: 16px; padding: 18px; background: var(--panel); }.candidate { border-color: light-dark(#bbcdac, #526843); background: light-dark(#f1f6e9, #293923); }.option-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .03em; }.result-card h3 { font-size: 15px; line-height: 1.4; font-weight: 600; }.result-card dl { display: grid; gap: 16px; margin-top: 12px; }.result-card dt { font-size: 11px; color: var(--muted); }.result-card dd { margin-top: 5px; font-size: 24px; line-height: 1.2; font-weight: 550; font-variant-numeric: tabular-nums; }.result-card dl > div:first-child dd { font-size: 30px; letter-spacing: -.04em; } + .cost-bars { margin-top: 24px; }.cost-bars > p { margin-bottom: 14px; color: var(--muted); font-size: 11px; }.cost-bars > div { display: grid; grid-template-columns: 64px 1fr; align-items: center; gap: 14px; margin-top: 12px; font-size: 11px; }.bar-track { height: 10px; border-radius: 999px; overflow: hidden; background: light-dark(#e6ebde, #3e4933); }.bar-track i { display: block; height: 100%; border-radius: 999px; background: light-dark(#939e85, #829073); transition: width .55s cubic-bezier(.2,.8,.2,1); }.bar-track [data-candidate-bar] { background: var(--green); } + .verdict { display: flex; gap: 12px; margin-top: 26px; padding: 18px; border-radius: 14px; border: 1px solid light-dark(#cfdec0, #4c683c); background: light-dark(#e9f1df, #25391e); }.verdict-icon { display: grid; place-items: center; flex-shrink: 0; width: 26px; height: 26px; border-radius: 50%; background: var(--green); color: var(--on-ink); }.verdict h3 { font-size: 13px; font-weight: 600; line-height: 1.5; }.verdict p { margin-top: 6px; color: var(--muted); font-size: 11px; line-height: 1.6; }.verdict[data-state=risk] { border-color: light-dark(#e5c5b9, #775443); background: light-dark(#fcf0e9, #35251e); }.verdict[data-state=risk] .verdict-icon { background: var(--risk); } .demo-caption, .no-script { margin-top: 16px; font-size: 11px; line-height: 1.6; color: var(--muted); }.no-script { padding: 0 24px 20px; } .calculator-section { margin-top: 32px; }.calculator-disclosure { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding-block: 12px; }.calculator-disclosure > summary { min-height: 44px; font-size: 14px; color: var(--green); font-weight: 550; }.calculator-intro { margin-block: 22px; }.calculator-intro h2 { font-size: 25px; }.calculator-intro p { margin-top: 12px; font-size: 14px; line-height: 1.7; color: var(--muted); } - .how-section { padding-top: 100px; }.steps-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); grid-template-rows: auto auto auto auto; gap: 18px 28px; }.step-card { display: grid; grid-row: span 4; grid-template-rows: subgrid; gap: 18px; padding: 26px 0; border-top: 1px solid #b6c0aa; }.step-number { display: grid; place-items: center; width: 38px; height: 38px; border: 1px solid #c3d1b6; border-radius: 50%; color: var(--green); font-size: 12px; }.step-card h3 { font-size: 21px; font-weight: 560; line-height: 1.3; letter-spacing: -.025em; text-wrap: balance; }.step-card p { color: var(--muted); font-size: 14px; line-height: 1.7; text-wrap: pretty; }.step-mark { width: fit-content; border: 1px solid #d4dccb; border-radius: 8px; padding: 8px 12px; background: #ecefe5; color: var(--green); font-size: 10px; } - .delivery-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 20px; margin-top: 24px; padding: 22px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }.delivery-row p { font-size: 13px; color: var(--muted); }.delivery-row > div { display: flex; flex-wrap: wrap; gap: 10px; }.delivery-row span { font-size: 11px; border-radius: 999px; padding: 8px 12px; border: 1px solid var(--line); background: #fcfcf8; } - .evidence-section { display: grid; grid-template-columns: 1fr 1fr; gap: 70px; padding-top: 100px; align-items: start; }.evidence-copy > p:not(.eyebrow) { max-width: 430px; margin-top: 20px; font-size: 15px; line-height: 1.7; color: var(--muted); text-wrap: pretty; }.evidence-tags { display: flex; flex-wrap: wrap; gap: 10px 18px; margin-top: 22px; }.evidence-tags span { font-size: 10px; color: var(--green); }.questions { border-top: 1px solid #bbc5af; }.questions details { border-bottom: 1px solid #cfd6c5; padding-block: 14px; }.questions summary { min-height: 46px; font-size: 15px; font-weight: 550; line-height: 1.5; }.questions p { padding: 10px 32px 8px 0; font-size: 13px; color: var(--muted); line-height: 1.7; } - .start-section { display: grid; grid-template-columns: .75fr 1.25fr; gap: 48px; align-items: center; margin-top: 100px; padding: 38px; border-radius: 26px; background: #e7eddc; border: 1px solid #d4ddc8; }.start-copy > p:not(.eyebrow) { margin-top: 18px; font-size: 14px; color: var(--muted); line-height: 1.7; }.start-copy .prerequisite { font-size: 11px !important; }.terminal { overflow: hidden; min-width: 0; border: 1px solid #4d5842; border-radius: 16px; background: #252d21; color: #eef4e6; }.terminal-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: 10px 16px; border-bottom: 1px solid #4a553f; font-size: 10px; color: #c1cfb5; }.terminal-header button { border: 1px solid #67785a; border-radius: 7px; background: #36442c; color: #edf6e3; min-height: 38px; padding: 8px 12px; font-size: 10px; line-height: 1.4; }.terminal pre { margin: 0; padding: 24px 20px; font-size: 12px; line-height: 1.85; white-space: pre-wrap; overflow-wrap: anywhere; }.terminal-footer { display: flex; align-items: center; gap: 8px; padding: 16px; border-top: 1px solid #4a553f; font-size: 10px; color: #c1cfb5; }.terminal-footer .status-dot { background: var(--accent); } + .how-section { padding-top: 100px; }.steps-grid { display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); grid-template-rows: auto auto auto auto; gap: 18px 28px; }.step-card { display: grid; grid-row: span 4; grid-template-rows: subgrid; gap: 18px; padding: 26px 0; border-top: 1px solid light-dark(#b6c0aa, #526843); }.step-number { display: grid; place-items: center; width: 38px; height: 38px; border: 1px solid light-dark(#c3d1b6, #526843); border-radius: 50%; color: var(--green); font-size: 12px; }.step-card h3 { font-size: 21px; font-weight: 560; line-height: 1.3; letter-spacing: -.025em; text-wrap: balance; }.step-card p { color: var(--muted); font-size: 14px; line-height: 1.7; text-wrap: pretty; }.step-mark { width: fit-content; border: 1px solid light-dark(#d4dccb, #3a4432); border-radius: 8px; padding: 8px 12px; background: light-dark(#ecefe5, #252e20); color: var(--green); font-size: 10px; } + .delivery-row { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 20px; margin-top: 24px; padding: 22px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }.delivery-row p { font-size: 13px; color: var(--muted); }.delivery-row > div { display: flex; flex-wrap: wrap; gap: 10px; }.delivery-row span { font-size: 11px; border-radius: 999px; padding: 8px 12px; border: 1px solid var(--line); background: light-dark(#fcfcf8, #20271c); } + .evidence-section { display: grid; grid-template-columns: 1fr 1fr; gap: 70px; padding-top: 100px; align-items: start; }.evidence-copy > p:not(.eyebrow) { max-width: 430px; margin-top: 20px; font-size: 15px; line-height: 1.7; color: var(--muted); text-wrap: pretty; }.evidence-tags { display: flex; flex-wrap: wrap; gap: 10px 18px; margin-top: 22px; }.evidence-tags span { font-size: 10px; color: var(--green); }.questions { border-top: 1px solid light-dark(#bbc5af, #526843); }.questions details { border-bottom: 1px solid light-dark(#cfd6c5, #3a4432); padding-block: 14px; }.questions summary { min-height: 46px; font-size: 15px; font-weight: 550; line-height: 1.5; }.questions p { padding: 10px 32px 8px 0; font-size: 13px; color: var(--muted); line-height: 1.7; } + .start-section { display: grid; grid-template-columns: .75fr 1.25fr; gap: 48px; align-items: center; margin-top: 100px; padding: 38px; border-radius: 26px; background: light-dark(#e7eddc, #25321e); border: 1px solid light-dark(#d4ddc8, #3a4432); }.start-copy > p:not(.eyebrow) { margin-top: 18px; font-size: 14px; color: var(--muted); line-height: 1.7; }.start-copy .prerequisite { font-size: 11px !important; }.terminal { overflow: hidden; min-width: 0; border: 1px solid #4d5842; border-radius: 16px; background: #252d21; color: #eef4e6; }.terminal-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: 10px 16px; border-bottom: 1px solid #4a553f; font-size: 10px; color: #c1cfb5; }.terminal-header button { border: 1px solid #67785a; border-radius: 7px; background: #36442c; color: #edf6e3; min-height: 38px; padding: 8px 12px; font-size: 10px; line-height: 1.4; }.terminal pre { margin: 0; padding: 24px 20px; font-size: 12px; line-height: 1.85; white-space: pre-wrap; overflow-wrap: anywhere; }.terminal-footer { display: flex; align-items: center; gap: 8px; padding: 16px; border-top: 1px solid #4a553f; font-size: 10px; color: #c1cfb5; }.terminal-footer .status-dot { background: var(--accent); } @keyframes arrive { from { transform: translateY(14px); opacity: .4; } to { transform: translateY(0); opacity: 1; } } @keyframes flow { to { stroke-dashoffset: -76; } } .is-visible { animation: arrive .7s ease-out both; } diff --git a/website/src/scripts/terminal-demo.ts b/website/src/scripts/terminal-demo.ts index 864b145..1b8bd21 100644 --- a/website/src/scripts/terminal-demo.ts +++ b/website/src/scripts/terminal-demo.ts @@ -7,17 +7,16 @@ export function initializeTerminalDemo() { const output = root.querySelector("[data-terminal-output]")!; const status = root.querySelector("[data-terminal-status]")!; const detail = root.querySelector("[data-terminal-detail]")!; - const toggle = root.querySelector("[data-terminal-toggle]")!; const download = root.querySelector("[data-terminal-download]")!; const steps = [...root.querySelectorAll("[data-terminal-step]")]; const reducedMotion = matchMedia("(prefers-reduced-motion: reduce)"); const playback = new EventTarget(); let visible = false; - let paused = false; + let completed = false; let controller: AbortController | undefined; let worker: Worker | undefined; let failed = false; - const playing = () => !paused && visible && !document.hidden; + const playing = () => visible && !document.hidden; function changed(signal: AbortSignal, timeout?: number) { return new Promise((resolve, reject) => { @@ -40,8 +39,6 @@ export function initializeTerminalDemo() { } } function update() { - toggle.textContent = failed ? "Retry demo" : !controller ? "Run demo" : paused ? "Resume" : "Pause"; - toggle.setAttribute("aria-label", toggle.textContent + " terminal demo"); playback.dispatchEvent(new Event("change")); } function execute(index: number, signal: AbortSignal) { @@ -60,7 +57,7 @@ export function initializeTerminalDemo() { worker!.postMessage(index); }); } - async function run(first: number, signal: AbortSignal) { + async function run(first: number, signal: AbortSignal, singleStep: boolean) { let index = first; let cycles = 0; try { @@ -87,11 +84,11 @@ export function initializeTerminalDemo() { const marginFailed = step.args[0] === "simulate" && result.exitCode === 1; root!.dataset.state = marginFailed ? "risk" : "pass"; status.textContent = marginFailed ? "Margin target failed" : step.args[0] === "compare" ? "Comparison complete" : "Command complete"; + if (singleStep || reducedMotion.matches) { completed = true; controller = undefined; update(); return; } await wait(index === 3 ? 8000 : 5500, signal); index++; if (index === terminalStory.length) { root!.dataset.cycles = String(++cycles); - if (reducedMotion.matches) { status.textContent = "Demo complete"; controller = undefined; update(); return; } index = 0; } } @@ -105,23 +102,21 @@ export function initializeTerminalDemo() { update(); } } - function launch(index = 0) { + function launch(index: number, singleStep: boolean) { controller?.abort(); controller = new AbortController(); - paused = false; + completed = false; failed = false; visible = true; root!.dataset.cycles = "0"; update(); - void run(index, controller.signal); + void run(index, controller.signal, singleStep); } - toggle.addEventListener("click", () => { if (!controller) launch(); else { paused = !paused; update(); } }); - root.querySelector("[data-terminal-replay]")!.addEventListener("click", () => launch()); - steps.forEach((button, index) => button.addEventListener("click", () => launch(index))); + steps.forEach((button, index) => button.addEventListener("click", () => launch(index, true))); const observer = new IntersectionObserver(([entry]) => { visible = Boolean(entry?.isIntersecting); update(); - if (visible && !controller && !failed && !reducedMotion.matches) launch(); + if (visible && !controller && !failed && !completed) launch(reducedMotion.matches ? terminalStory.length - 1 : 0, reducedMotion.matches); }, { threshold: 0.15 }); observer.observe(root); document.addEventListener("visibilitychange", update); From 44956ee6bbb80c42ad9e5ee2de6b2d91c73147ce Mon Sep 17 00:00:00 2001 From: hudsonaikins-crown Date: Thu, 1 Oct 2026 22:52:37 -0400 Subject: [PATCH 10/13] style: shift dark theme to midnight green and black --- website/scripts/check-seo.mjs | 2 +- website/src/components/TerminalDemo.astro | 8 ++--- website/src/layouts/ProductLayout.astro | 20 ++++++------ website/src/pages/docs.astro | 6 ++-- website/src/pages/index.astro | 38 +++++++++++------------ 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/website/scripts/check-seo.mjs b/website/scripts/check-seo.mjs index 6b1a16f..a843360 100644 --- a/website/scripts/check-seo.mjs +++ b/website/scripts/check-seo.mjs @@ -49,7 +49,7 @@ for (const route of routes) { assert.ok(page.includes('rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"')); assert.ok(page.includes('name="color-scheme" content="light dark"')); assert.ok(page.includes('name="theme-color" content="#f5f5ee" media="(prefers-color-scheme: light)"')); - assert.ok(page.includes('name="theme-color" content="#151b13" media="(prefers-color-scheme: dark)"')); + assert.ok(page.includes('name="theme-color" content="#040f0d" media="(prefers-color-scheme: dark)"')); assert.ok(!page.includes('class="brand-mark"'), `Old letter badge: ${route}`); assert.ok(page.includes('aria-label="ProfitCTL home">ProfitCTL')); } diff --git a/website/src/components/TerminalDemo.astro b/website/src/components/TerminalDemo.astro index 2b32eab..e0678ba 100644 --- a/website/src/components/TerminalDemo.astro +++ b/website/src/components/TerminalDemo.astro @@ -26,13 +26,13 @@ Choose a story step to inspect its result. h2 { max-width: 480px; margin-top: 15px; font-size: clamp(30px,3.5vw,44px); line-height: 1.16; font-weight: 550; letter-spacing: -.05em; text-wrap: balance; } .cli-intro > p { max-width: 440px; font-size: 15px; line-height: 1.7; color: var(--muted); text-wrap: pretty; } .cli-grid { display: grid; grid-template-columns: minmax(0,1.7fr) minmax(240px,.8fr); gap: 26px; align-items: start; } - .cli-window { min-width: 0; overflow: hidden; border: 1px solid #4d5842; border-radius: 20px; background: #252d21; color: #eef4e6; box-shadow: 0 18px 40px #272e2010; } - .cli-toolbar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; padding: 12px 18px; border-bottom: 1px solid #4a553f; font-size: 12px; } + .cli-window { min-width: 0; overflow: hidden; border: 1px solid light-dark(#4d5842, #28513c); border-radius: 20px; background: light-dark(#252d21, #061b16); color: light-dark(#eef4e6, #e0efe5); box-shadow: 0 18px 40px #272e2010; } + .cli-toolbar { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 10px; padding: 12px 18px; border-bottom: 1px solid light-dark(#4a553f, #203f30); font-size: 12px; } .cli-toolbar > span { display: inline-flex; align-items: center; gap: 9px; font-weight: 600; } .cli-toolbar i { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); } pre { height: 390px; margin: 0; padding: 22px; overflow: auto; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11px; line-height: 1.8; tab-size: 4; } .cli-command { display: block; margin-bottom: 18px; color: var(--accent); } - .cli-footer { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: 14px 18px; border-top: 1px solid #4a553f; font-size: 10px; line-height: 1.5; color: #c1cfb5; } + .cli-footer { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 8px; padding: 14px 18px; border-top: 1px solid light-dark(#4a553f, #203f30); font-size: 10px; line-height: 1.5; color: light-dark(#c1cfb5, #a4c4b0); } .cli-state { color: var(--accent); } [data-state=risk] .cli-state, [data-state=error] .cli-state { color: #ffba97; } .cli-story { padding: 6px 0; } @@ -40,7 +40,7 @@ Choose a story step to inspect its result. ol { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--line); } li { border-bottom: 1px solid var(--line); } li button { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 64px; padding: 14px 10px; border: 0; background: transparent; color: var(--muted); text-align: left; font-size: 13px; line-height: 1.5; } - li button[aria-current=step] { background: light-dark(#e8efdf, #2a3723); color: var(--green); font-weight: 600; } + li button[aria-current=step] { background: light-dark(#e8efdf, #0e2b20); color: var(--green); font-weight: 600; } .step-index { font-size: 10px; font-variant-numeric: tabular-nums; } .step-arrow { margin-left: auto; } .story-detail { min-height: 95px; margin-top: 22px; font-size: 13px; line-height: 1.7; color: var(--muted); text-wrap: pretty; } diff --git a/website/src/layouts/ProductLayout.astro b/website/src/layouts/ProductLayout.astro index a5a1373..1eaa05d 100644 --- a/website/src/layouts/ProductLayout.astro +++ b/website/src/layouts/ProductLayout.astro @@ -61,7 +61,7 @@ const schema = pageType === "docs" ? { - +