diff --git a/.github/workflows/store-release.yml b/.github/workflows/store-release.yml
index ba4dd67..b170865 100644
--- a/.github/workflows/store-release.yml
+++ b/.github/workflows/store-release.yml
@@ -21,13 +21,28 @@ permissions:
jobs:
package:
+ name: Package ${{ matrix.productName }}
runs-on: windows-latest
timeout-minutes: 45
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - variant: prompterpro
+ productName: PrompterPro
+ identityName: ReleasedPtyLtd.PrompterPro
+ productId: ""
+ - variant: simpleprompt
+ productName: SimplePrompt
+ identityName: ReleasedPtyLtd.SimplePrompt
+ productId: 9MT1X5BNTHQS
env:
- MS_STORE_PRODUCT_ID: ${{ vars.MS_STORE_PRODUCT_ID }}
- MS_STORE_IDENTITY_NAME: ${{ vars.MS_STORE_IDENTITY_NAME }}
- MS_STORE_PUBLISHER: ${{ vars.MS_STORE_PUBLISHER }}
- MS_STORE_PUBLISHER_DISPLAY_NAME: ${{ vars.MS_STORE_PUBLISHER_DISPLAY_NAME }}
+ PROMPTER_VARIANT: ${{ matrix.variant }}
+ VITE_APP_VARIANT: ${{ matrix.variant }}
+ MS_STORE_PRODUCT_ID: ${{ matrix.productId || vars.MS_STORE_PRODUCT_ID }}
+ MS_STORE_IDENTITY_NAME: ${{ matrix.identityName }}
+ MS_STORE_PUBLISHER: CN=E3BDC624-0B0A-4256-85B0-5AE714EA8897
+ MS_STORE_PUBLISHER_DISPLAY_NAME: Released Pty Ltd
steps:
- name: Check out repository
@@ -80,8 +95,8 @@ jobs:
- name: Upload MSIX bundle artifact
uses: actions/upload-artifact@v7
with:
- name: PrompterPro-${{ env.MS_STORE_VERSION }}-x64-arm64
- path: out/store/*.msixbundle
+ name: ${{ matrix.productName }}-${{ env.MS_STORE_VERSION }}-x64-arm64
+ path: out/store/${{ matrix.productName }}_*.msixbundle
if-no-files-found: error
compression-level: 0
@@ -129,6 +144,6 @@ jobs:
--sellerId $env:SELLER_ID `
--clientId $env:AZURE_AD_APPLICATION_CLIENT_ID `
--clientSecret $env:AZURE_AD_APPLICATION_SECRET
- $package = Get-ChildItem "out/store/*.msixbundle" |
+ $package = Get-ChildItem "out/store/${{ matrix.productName }}_*.msixbundle" |
Select-Object -First 1
msstore publish $package.FullName -id $env:MS_STORE_PRODUCT_ID
diff --git a/desktop/main.mjs b/desktop/main.mjs
index eecb05f..2a87c85 100644
--- a/desktop/main.mjs
+++ b/desktop/main.mjs
@@ -15,6 +15,22 @@ const MODEL_DIRECTORY_NAME =
"sherpa-onnx-streaming-zipformer-en-20M-2023-02-17";
const BACKGROUND_COLOR = "#0b0d0f";
const LEGACY_USER_DATA_DIRECTORY_NAME = "Prompter";
+const executableName = path.basename(
+ process.execPath,
+ path.extname(process.execPath),
+);
+const desktopBrand =
+ executableName.toLowerCase() === "simpleprompt"
+ ? {
+ name: "SimplePrompt",
+ appUserModelId: "ReleasedPtyLtd.SimplePrompt",
+ userDataDirectoryName: "SimplePrompt",
+ }
+ : {
+ name: "PrompterPro",
+ appUserModelId: "ReleasedPtyLtd.PrompterPro",
+ userDataDirectoryName: LEGACY_USER_DATA_DIRECTORY_NAME,
+ };
let mainWindow = null;
let localServer = null;
@@ -27,7 +43,7 @@ let readyToQuit = false;
// scripts, device preferences, and other local browser data survive upgrades.
app.setPath(
"userData",
- path.join(app.getPath("appData"), LEGACY_USER_DATA_DIRECTORY_NAME),
+ path.join(app.getPath("appData"), desktopBrand.userDataDirectoryName),
);
function isLoopbackUrl(value) {
@@ -68,13 +84,17 @@ function configurePermissions() {
function configureScriptStorage() {
ipcMain.handle("prompter:scripts:load", (event) => {
if (!isAppUrl(event.senderFrame?.url)) {
- throw new Error("Script storage is only available to PrompterPro.");
+ throw new Error(
+ `Script storage is only available to ${desktopBrand.name}.`,
+ );
}
return loadScriptStore(app.getPath("userData"));
});
ipcMain.handle("prompter:scripts:save", (event, scripts) => {
if (!isAppUrl(event.senderFrame?.url)) {
- throw new Error("Script storage is only available to PrompterPro.");
+ throw new Error(
+ `Script storage is only available to ${desktopBrand.name}.`,
+ );
}
const saveOperation = scriptSaveQueue
.catch(() => undefined)
@@ -87,6 +107,7 @@ function configureScriptStorage() {
async function startLocalServer() {
process.env.PORT = "0";
process.env.PROMPTER_PRODUCTION = "1";
+ process.env.PROMPTER_PRODUCT_NAME = desktopBrand.name;
process.env.SHERPA_ONNX_MODEL_DIR = app.isPackaged
? path.join(process.resourcesPath, MODEL_DIRECTORY_NAME)
: path.join(app.getAppPath(), ".models", MODEL_DIRECTORY_NAME);
@@ -104,7 +125,9 @@ async function startLocalServer() {
const address = localServer.address();
if (!address || typeof address === "string") {
- throw new Error("PrompterPro could not reserve a local server port.");
+ throw new Error(
+ `${desktopBrand.name} could not reserve a local server port.`,
+ );
}
appUrl = `http://127.0.0.1:${address.port}`;
}
@@ -169,10 +192,12 @@ async function startArm64Sidecar() {
}
async function createWindow() {
- if (!appUrl) throw new Error("The local PrompterPro server is not ready.");
+ if (!appUrl) {
+ throw new Error(`The local ${desktopBrand.name} server is not ready.`);
+ }
mainWindow = new BrowserWindow({
- title: "PrompterPro",
+ title: desktopBrand.name,
width: 1440,
height: 960,
minWidth: 1040,
@@ -215,7 +240,7 @@ if (!hasSingleInstanceLock) {
app.whenReady()
.then(async () => {
- app.setAppUserModelId("ReleasedPtyLtd.PrompterPro");
+ app.setAppUserModelId(desktopBrand.appUserModelId);
configurePermissions();
await startLocalServer();
configureScriptStorage();
@@ -224,7 +249,7 @@ if (!hasSingleInstanceLock) {
.catch((error) => {
const message =
error instanceof Error ? error.message : "Unknown startup error";
- dialog.showErrorBox("PrompterPro could not start", message);
+ dialog.showErrorBox(`${desktopBrand.name} could not start`, message);
app.quit();
});
diff --git a/package.json b/package.json
index 5b50af1..dc01054 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,9 @@
"main": "desktop/main.mjs",
"scripts": {
"dev": "concurrently -k -n API,WEB -c cyan,magenta \"tsx watch server/index.ts\" \"vite\"",
+ "site:render": "tsx site/render-static.tsx",
+ "site:dev": "npm run site:render && vite site",
+ "site:build": "npm run site:render && tsc -p site/tsconfig.json --pretty false && vite build site",
"build": "tsc -b && vite build",
"build:server": "node scripts/build-server.mjs",
"build:desktop": "npm run build && npm run build:server",
diff --git a/public/simpleprompt-mark.svg b/public/simpleprompt-mark.svg
new file mode 100644
index 0000000..4fe2ac0
--- /dev/null
+++ b/public/simpleprompt-mark.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/scripts/package-windows.mjs b/scripts/package-windows.mjs
index a9a5a6a..1b62db1 100644
--- a/scripts/package-windows.mjs
+++ b/scripts/package-windows.mjs
@@ -17,6 +17,7 @@ import {
DEFAULT_STORE_IDENTITY,
renderStoreManifest,
} from "./store-manifest.mjs";
+import { getProductVariant } from "./product-variants.mjs";
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
const rootDirectory = path.resolve(scriptDirectory, "..");
@@ -34,6 +35,13 @@ const outputDirectory = process.env.PROMPTER_PACKAGE_OUTPUT_DIR
const packageJson = JSON.parse(
await readFile(path.join(rootDirectory, "package.json"), "utf8"),
);
+const productVariant = getProductVariant(
+ process.env.PROMPTER_VARIANT || process.env.VITE_APP_VARIANT,
+);
+const productAssetsDirectory = path.join(
+ storeDirectory,
+ productVariant.assetDirectory,
+);
const allowedAppEntries = new Set([
"desktop",
"dist",
@@ -121,25 +129,25 @@ for (const architecture of architectures) {
console.log(`Packaging the ${architecture.name} Electron application...`);
const packagePaths = await packager({
dir: rootDirectory,
- name: packageJson.productName,
- executableName: packageJson.productName,
+ name: productVariant.productName,
+ executableName: productVariant.productName,
platform: "win32",
arch: architecture.name,
out: outputDirectory,
overwrite: true,
prune: true,
asar: architecture.asar,
- icon: path.join(storeDirectory, "assets", "AppIcon.ico"),
+ icon: path.join(productAssetsDirectory, "AppIcon.ico"),
extraResource: [modelDirectory],
afterPrune: [removeUnneededRuntimeModules],
appVersion: packageJson.version,
buildVersion: packageJson.version,
win32metadata: {
CompanyName: packageJson.author,
- FileDescription: packageJson.description,
- ProductName: packageJson.productName,
- InternalName: packageJson.productName,
- OriginalFilename: `${packageJson.productName}.exe`,
+ FileDescription: productVariant.description,
+ ProductName: productVariant.productName,
+ InternalName: productVariant.productName,
+ OriginalFilename: `${productVariant.productName}.exe`,
"requested-execution-level": "asInvoker",
},
ignore: shouldIgnorePackageFile,
@@ -153,7 +161,7 @@ for (const architecture of architectures) {
const packageDirectory = packagePaths[0];
await assertPeArchitecture(
- path.join(packageDirectory, `${packageJson.productName}.exe`),
+ path.join(packageDirectory, `${productVariant.productName}.exe`),
architecture.name,
);
await assertPackagedAppAllowList(
@@ -161,7 +169,7 @@ for (const architecture of architectures) {
architecture.name,
);
await cp(
- path.join(storeDirectory, "assets"),
+ productAssetsDirectory,
path.join(packageDirectory, "Assets"),
{ recursive: true },
);
@@ -178,7 +186,7 @@ const msixOutputDirectory = path.join(outputDirectory, "store");
await mkdir(msixOutputDirectory, { recursive: true });
const bundlePath = path.join(
msixOutputDirectory,
- `${packageJson.productName}_${toSafeVersion(version)}_x64_arm64.msixbundle`,
+ `${productVariant.productName}_${toSafeVersion(version)}_x64_arm64.msixbundle`,
);
const winAppCli = path.join(
rootDirectory,
@@ -358,15 +366,21 @@ async function addStoreManifest(packageDirectory, architecture) {
architecture: architecture.name,
identityName:
process.env.MS_STORE_IDENTITY_NAME ||
- DEFAULT_STORE_IDENTITY.identityName,
+ productVariant.store.identityName || DEFAULT_STORE_IDENTITY.identityName,
minimumWindowsVersion: architecture.minimumWindowsVersion,
publisher:
process.env.MS_STORE_PUBLISHER ||
- DEFAULT_STORE_IDENTITY.publisher,
+ productVariant.store.publisher || DEFAULT_STORE_IDENTITY.publisher,
publisherDisplayName:
process.env.MS_STORE_PUBLISHER_DISPLAY_NAME ||
+ productVariant.store.publisherDisplayName ||
DEFAULT_STORE_IDENTITY.publisherDisplayName,
version: process.env.MS_STORE_VERSION || packageJson.version,
+ displayName: productVariant.productName,
+ executableName: productVariant.productName,
+ applicationId: productVariant.applicationId,
+ description: productVariant.description,
+ backgroundColor: productVariant.backgroundColor,
});
await writeFile(
path.join(packageDirectory, "Package.appxmanifest"),
diff --git a/scripts/product-variants.d.mts b/scripts/product-variants.d.mts
new file mode 100644
index 0000000..6eb95a5
--- /dev/null
+++ b/scripts/product-variants.d.mts
@@ -0,0 +1,22 @@
+export interface ProductVariant {
+ readonly key: "prompterpro" | "simpleprompt";
+ readonly productName: "PrompterPro" | "SimplePrompt";
+ readonly assetDirectory: string;
+ readonly applicationId: string;
+ readonly description: string;
+ readonly backgroundColor: string;
+ readonly store: Readonly<{
+ identityName: string;
+ publisher: string;
+ publisherDisplayName: string;
+ productId: string;
+ packageFamilyName?: string;
+ packageSid?: string;
+ }>;
+}
+
+export const PRODUCT_VARIANTS: Readonly<
+ Record<"prompterpro" | "simpleprompt", ProductVariant>
+>;
+
+export function getProductVariant(value?: string): ProductVariant;
diff --git a/scripts/product-variants.mjs b/scripts/product-variants.mjs
new file mode 100644
index 0000000..1761e02
--- /dev/null
+++ b/scripts/product-variants.mjs
@@ -0,0 +1,49 @@
+const sharedPublisher = Object.freeze({
+ publisher: "CN=E3BDC624-0B0A-4256-85B0-5AE714EA8897",
+ publisherDisplayName: "Released Pty Ltd",
+});
+
+export const PRODUCT_VARIANTS = Object.freeze({
+ prompterpro: Object.freeze({
+ key: "prompterpro",
+ productName: "PrompterPro",
+ assetDirectory: "assets",
+ applicationId: "Prompter",
+ description:
+ "An offline, voice-following teleprompter for polished video recording.",
+ backgroundColor: "#0b0d0f",
+ store: Object.freeze({
+ ...sharedPublisher,
+ identityName: "ReleasedPtyLtd.PrompterPro",
+ productId: "",
+ }),
+ }),
+ simpleprompt: Object.freeze({
+ key: "simpleprompt",
+ productName: "SimplePrompt",
+ assetDirectory: "simpleprompt/assets",
+ applicationId: "SimplePrompt",
+ description:
+ "A private, voice-following teleprompter for clear video recording.",
+ backgroundColor: "#0b0d0f",
+ store: Object.freeze({
+ ...sharedPublisher,
+ identityName: "ReleasedPtyLtd.SimplePrompt",
+ productId: "9MT1X5BNTHQS",
+ packageFamilyName: "ReleasedPtyLtd.SimplePrompt_q0b077qanz1d8",
+ packageSid:
+ "S-1-15-2-2676728462-2596204801-2700890954-455437082-720273692-286854053-1939976512",
+ }),
+ }),
+});
+
+export function getProductVariant(value = "prompterpro") {
+ const key = value.trim().toLowerCase();
+ const variant = PRODUCT_VARIANTS[key];
+ if (!variant) {
+ throw new Error(
+ `Unknown product variant "${value}". Expected prompterpro or simpleprompt.`,
+ );
+ }
+ return variant;
+}
diff --git a/scripts/smoke-arm64-sidecar.mjs b/scripts/smoke-arm64-sidecar.mjs
index 3662b9f..9cc6c31 100644
--- a/scripts/smoke-arm64-sidecar.mjs
+++ b/scripts/smoke-arm64-sidecar.mjs
@@ -5,17 +5,21 @@ import { createRequire } from "node:module";
import path from "node:path";
import { fileURLToPath } from "node:url";
import WebSocket from "ws";
+import { getProductVariant } from "./product-variants.mjs";
const require = createRequire(import.meta.url);
const sherpa = require("sherpa-onnx-node");
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
const rootDirectory = path.resolve(scriptDirectory, "..");
+const productVariant = getProductVariant(
+ process.env.PROMPTER_VARIANT || process.env.VITE_APP_VARIANT,
+);
const packageOutputDirectory = process.env.PROMPTER_PACKAGE_OUTPUT_DIR
? path.resolve(rootDirectory, process.env.PROMPTER_PACKAGE_OUTPUT_DIR)
: path.join(rootDirectory, "out");
const arm64Directory = path.join(
packageOutputDirectory,
- "PrompterPro-win32-arm64",
+ `${productVariant.productName}-win32-arm64`,
);
const nodeExecutable = path.join(
arm64Directory,
@@ -44,6 +48,7 @@ const sidecar = spawn(nodeExecutable, [serverEntry], {
...process.env,
PORT: "0",
PROMPTER_PRODUCTION: "1",
+ PROMPTER_PRODUCT_NAME: productVariant.productName,
SHERPA_ONNX_MODEL_DIR: modelDirectory,
},
stdio: ["ignore", "pipe", "pipe", "ipc"],
diff --git a/scripts/store-manifest.d.mts b/scripts/store-manifest.d.mts
index 94d61c2..3436124 100644
--- a/scripts/store-manifest.d.mts
+++ b/scripts/store-manifest.d.mts
@@ -5,12 +5,17 @@ export interface StoreManifestValues {
publisher: string;
publisherDisplayName: string;
version: string;
+ displayName: string;
+ executableName: string;
+ applicationId: string;
+ description: string;
+ backgroundColor: string;
}
export const DEFAULT_STORE_IDENTITY: Readonly<{
identityName: "ReleasedPtyLtd.PrompterPro";
publisher: "CN=E3BDC624-0B0A-4256-85B0-5AE714EA8897";
- publisherDisplayName: "Released Pty Ltd";
+ publisherDisplayName: string;
}>;
export function toWindowsVersion(version: string): string;
diff --git a/scripts/store-manifest.mjs b/scripts/store-manifest.mjs
index 419f3fb..ee6317a 100644
--- a/scripts/store-manifest.mjs
+++ b/scripts/store-manifest.mjs
@@ -1,11 +1,9 @@
const WINDOWS_VERSION_PATTERN = /^\d+(?:\.\d+){0,3}$/;
const WINDOWS_ARCHITECTURES = new Set(["x64", "arm64"]);
-export const DEFAULT_STORE_IDENTITY = Object.freeze({
- identityName: "ReleasedPtyLtd.PrompterPro",
- publisher: "CN=E3BDC624-0B0A-4256-85B0-5AE714EA8897",
- publisherDisplayName: "Released Pty Ltd",
-});
+import { PRODUCT_VARIANTS } from "./product-variants.mjs";
+
+export const DEFAULT_STORE_IDENTITY = PRODUCT_VARIANTS.prompterpro.store;
export function toWindowsVersion(version) {
const numericVersion = version.split("-", 1)[0];
@@ -45,6 +43,11 @@ export function renderStoreManifest(template, values) {
"__VERSION__": toWindowsVersion(values.version),
"__PROCESSOR_ARCHITECTURE__": values.architecture,
"__MIN_WINDOWS_VERSION__": values.minimumWindowsVersion,
+ "__DISPLAY_NAME__": values.displayName,
+ "__EXECUTABLE_NAME__": values.executableName,
+ "__APPLICATION_ID__": values.applicationId,
+ "__DESCRIPTION__": values.description,
+ "__BACKGROUND_COLOR__": values.backgroundColor,
};
let manifest = template;
diff --git a/server/index.ts b/server/index.ts
index e517114..1700025 100644
--- a/server/index.ts
+++ b/server/index.ts
@@ -227,9 +227,10 @@ server.listen(port, "127.0.0.1", () => {
type: "prompter:server-ready",
port: activePort,
});
+ const productName = process.env.PROMPTER_PRODUCT_NAME || "PrompterPro";
console.log(
production
- ? `PrompterPro is running at http://127.0.0.1:${activePort}`
- : `PrompterPro API is running at http://127.0.0.1:${activePort}`,
+ ? `${productName} is running at http://127.0.0.1:${activePort}`
+ : `${productName} API is running at http://127.0.0.1:${activePort}`,
);
});
diff --git a/site/README.md b/site/README.md
new file mode 100644
index 0000000..a1951d7
--- /dev/null
+++ b/site/README.md
@@ -0,0 +1,19 @@
+# SimplePrompt website
+
+This folder contains the standalone SimplePrompt marketing website. Its entry
+point uses plain HTML, CSS, and JavaScript, so it works directly with VS Code
+Live Server. The React source remains available for typed component development,
+and the site builds separately from the desktop application.
+
+From the repository root:
+
+```powershell
+npm.cmd run site:dev
+npm.cmd run site:build
+```
+
+The production site is written to `site/dist/`.
+
+`site:dev` and `site:build` regenerate the Live Server-compatible `index.html`
+from `src/App.tsx` before starting or building, keeping the static entry point
+and typed source in sync.
diff --git a/site/index.html b/site/index.html
new file mode 100644
index 0000000..ead26a1
--- /dev/null
+++ b/site/index.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+ SimplePrompt — Your words. Your pace.
+
+
+
Built for natural deliveryYour words.Your pace. The private teleprompter that listens as you speak, follows naturally, and helps every take feel like you.
$1.99 per month Billed monthly
Scripts Studio
Local-first“The best ideas don’t need to sound rehearsed. They just need the space to land. Take a breath, look into the lens, and make it yours.”
Following your voice
VOICE PACE Natural & steady
Prompt hidden from recording Voice-following that keeps up100% local speech recognitionLess managing. More connecting. A teleprompter that gets out of your way. Everything you need to sound prepared—without looking or feeling scripted.
It follows your voice SimplePrompt tracks where you are in the script and moves at your pace. Pause, improvise, or take a breath—it waits for you.
Record a clean take Capture crisp camera and microphone footage. Your prompt stays on screen for you, never burned into the final video.
Private by design Scripts, recordings, and voice recognition stay on your computer. Your work remains yours—full stop.
BRAND STORY · TAKE 02
Great products start with a simple idea: make something people genuinely want to use.
Your new pre-record ritual From blank page to brilliant take. 1 Write or generate Paste your script or turn a few talking points into a polished first draft with AI.
2 Set your eye-line Place your words near the camera and choose the type size that feels natural.
3 Speak. It follows. SimplePrompt listens locally and keeps your next words right where you need them.
Your voice is nobody else's business Private meansprivate. Voice recognition and recording stay on your computer. Optional AI drafting sends only the details you enter in the draft form to OpenAI. Your local script library is never uploaded.
On-device speech recognition Local script library No account required
Your next take is the one Sound like yourself. Only better prepared. Get SimplePrompt for $1.99 per month and make camera confidence your new default.
Get SimplePromptWindows 11 · $1.99 per month · No account needed Make every word land.
© 2026 Released Pty Ltd
+
+
+
diff --git a/site/render-static.tsx b/site/render-static.tsx
new file mode 100644
index 0000000..8513c09
--- /dev/null
+++ b/site/render-static.tsx
@@ -0,0 +1,32 @@
+import { writeFile } from "node:fs/promises";
+import path from "node:path";
+import React from "react";
+import { renderToStaticMarkup } from "react-dom/server";
+
+(globalThis as typeof globalThis & { React: typeof React }).React = React;
+const { default: App } = await import("./src/App");
+const markup = renderToStaticMarkup( );
+const outputPath = path.join(import.meta.dirname, "index.html");
+
+const document = `
+
+
+
+
+
+
+
+
+ SimplePrompt — Your words. Your pace.
+
+
+ ${markup}
+
+
+
+`;
+
+await writeFile(outputPath, document, "utf8");
diff --git a/site/simpleprompt-mark.svg b/site/simpleprompt-mark.svg
new file mode 100644
index 0000000..4fe2ac0
--- /dev/null
+++ b/site/simpleprompt-mark.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/site/site.js b/site/site.js
new file mode 100644
index 0000000..87ab092
--- /dev/null
+++ b/site/site.js
@@ -0,0 +1,23 @@
+const menuButton = document.querySelector(".menu-button");
+const navigation = document.querySelector(".site-header nav");
+
+menuButton?.addEventListener("click", () => {
+ const open = navigation?.classList.toggle("nav-open") ?? false;
+ menuButton.setAttribute("aria-expanded", String(open));
+ menuButton.setAttribute("aria-label", open ? "Close menu" : "Open menu");
+});
+
+navigation?.querySelectorAll("a").forEach((link) => {
+ link.addEventListener("click", () => {
+ navigation.classList.remove("nav-open");
+ menuButton?.setAttribute("aria-expanded", "false");
+ menuButton?.setAttribute("aria-label", "Open menu");
+ });
+});
+
+const demoButton = document.querySelector(".studio-controls button");
+demoButton?.addEventListener("click", () => {
+ const isPaused = demoButton.getAttribute("aria-label") === "Play demo";
+ demoButton.setAttribute("aria-label", isPaused ? "Pause demo" : "Play demo");
+ demoButton.classList.toggle("is-paused", !isPaused);
+});
diff --git a/site/src/App.tsx b/site/src/App.tsx
new file mode 100644
index 0000000..3e0f9b3
--- /dev/null
+++ b/site/src/App.tsx
@@ -0,0 +1,242 @@
+import { useState } from "react";
+import {
+ ArrowRight,
+ AudioLines,
+ Check,
+ ChevronRight,
+ Clapperboard,
+ Download,
+ FileText,
+ Menu,
+ Mic2,
+ MonitorDown,
+ MousePointer2,
+ Pause,
+ Play,
+ ShieldCheck,
+ Sparkles,
+ Video,
+ WandSparkles,
+ X,
+ Zap,
+} from "lucide-react";
+
+const features = [
+ {
+ icon: Mic2,
+ number: "01",
+ title: "It follows your voice",
+ copy: "SimplePrompt tracks where you are in the script and moves at your pace. Pause, improvise, or take a breath—it waits for you.",
+ tone: "lime",
+ },
+ {
+ icon: Video,
+ number: "02",
+ title: "Record a clean take",
+ copy: "Capture crisp camera and microphone footage. Your prompt stays on screen for you, never burned into the final video.",
+ tone: "violet",
+ },
+ {
+ icon: ShieldCheck,
+ number: "03",
+ title: "Private by design",
+ copy: "Scripts, recordings, and voice recognition stay on your computer. Your work remains yours—full stop.",
+ tone: "blue",
+ },
+];
+
+export default function App() {
+ const [menuOpen, setMenuOpen] = useState(false);
+ const [demoPlaying, setDemoPlaying] = useState(true);
+
+ return (
+
+
+
+
+
+
+
+ Built for natural delivery
+
+
+
Your words.Your pace.
+
+ The private teleprompter that listens as you speak, follows naturally, and helps every take feel like you.
+
+
+
+ $1.99 per month
+ Billed monthly
+
+
+
+
+
+
+
+
+
+
Scripts Studio
+
Local-first
+
+
+
+ “The best ideas don’t need to sound rehearsed.
+ They just need the space to land.
+ Take a breath, look into the lens, and make it yours.”
+
+
+
+
Following your voice
+
+
00:18
+
setDemoPlaying((playing) => !playing)} aria-label={demoPlaying ? "Pause demo" : "Play demo"}>
+ {demoPlaying ? : }
+
+
+
+
+
+
+
+
VOICE PACE Natural & steady
+
+
+
+ Prompt hidden from recording
+
+
+
+
+
+ Voice-following that keeps up
+ 100% local speech recognition
+ Clean MP4 recordings
+ Ready in under a minute
+
+
+
+
+
Less managing. More connecting.
+
A teleprompter that gets out of your way.
+
Everything you need to sound prepared—without looking or feeling scripted.
+
+
+ {features.map(({ icon: Icon, number, title, copy, tone }) => (
+
+ {number}
+ {title}
+ {copy}
+
+
+ ))}
+
+
+
+
+
+
BRAND STORY · TAKE 02
+
+
Product launch 2:30 min
+
Great products start with a simple idea: make something people genuinely want to use.
+
+
+
You
+
+
+
Your new pre-record ritual
+
From blank page to brilliant take.
+
+
1 Write or generate Paste your script or turn a few talking points into a polished first draft with AI.
+
2 Set your eye-line Place your words near the camera and choose the type size that feels natural.
+
3 Speak. It follows. SimplePrompt listens locally and keeps your next words right where you need them.
+
+
+
+
+
+
+
+
Your voice is nobody else's business
+
Private meansprivate.
+
Voice recognition and recording stay on your computer. Optional AI drafting sends only the details you enter in the draft form to OpenAI. Your local script library is never uploaded.
+
+ On-device speech recognition
+ Local script library
+ No account required
+
+
+
+
+
+
Stays on this device
+
Voice
+
Scripts
+
Video
+
+
+
+
+
+
+
Your next take is the one
+
Sound like yourself. Only better prepared.
+
Get SimplePrompt for $1.99 per month and make camera confidence your new default.
+
+ Get SimplePrompt
+
+
Windows 11 · $1.99 per month · No account needed
+
+
+
+
+
+
+ SimplePrompt
+ Make every word land.
+
+ © 2026 Released Pty Ltd
+
+
+ );
+}
diff --git a/site/src/main.tsx b/site/src/main.tsx
new file mode 100644
index 0000000..693ac2e
--- /dev/null
+++ b/site/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+import "./styles.css";
+
+createRoot(document.getElementById("root")!).render(
+
+
+ ,
+);
diff --git a/site/src/styles.css b/site/src/styles.css
new file mode 100644
index 0000000..6c659a0
--- /dev/null
+++ b/site/src/styles.css
@@ -0,0 +1,159 @@
+@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Manrope:wght@400;500;600;700;800&display=swap');
+
+:root {
+ font-family: "DM Sans", system-ui, sans-serif;
+ color: #f6f7f2;
+ background: #0a0c0d;
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ --ink: #0a0c0d;
+ --paper: #f4f5ef;
+ --lime: #55dc82;
+ --muted: #9ca29c;
+ --line: rgba(255,255,255,.11);
+}
+
+* { box-sizing: border-box; }
+html { scroll-behavior: smooth; }
+body { margin: 0; min-width: 320px; min-height: 100vh; background: #0a0c0d; overflow-x: hidden; }
+button, a { font: inherit; }
+a { color: inherit; text-decoration: none; }
+button, a { -webkit-tap-highlight-color: transparent; }
+button:focus-visible, a:focus-visible { outline: 2px solid var(--lime); outline-offset: 4px; }
+
+.site-header {
+ position: absolute; inset: 0 0 auto; z-index: 20; height: 82px; max-width: 1240px; margin: auto;
+ padding: 0 28px; display: flex; align-items: center; justify-content: space-between;
+}
+.logo { display: inline-flex; align-items: center; gap: 10px; font: 700 17px "Manrope", sans-serif; letter-spacing: -.04em; }
+.logo > span:last-child > span { color: var(--lime); }
+.logo-mark { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 10px; color: #101309; background: var(--lime); box-shadow: 0 0 30px rgba(85,220,130,.12); }
+.site-header nav { position: absolute; left: 50%; transform: translateX(-50%); display: flex; gap: 34px; color: #a9aea9; font-size: 13px; }
+.site-header nav a { transition: color .2s; }
+.site-header nav a:hover { color: #fff; }
+.header-cta { display: flex; align-items: center; gap: 9px; padding: 10px 15px; border: 1px solid rgba(255,255,255,.18); border-radius: 9px; font-size: 12px; font-weight: 600; transition: .2s; }
+.header-cta:hover { background: #fff; color: #111; }
+.menu-button { display: none; border: 0; background: none; color: #fff; }
+
+.hero { position: relative; min-height: 820px; overflow: hidden; display: grid; grid-template-columns: .84fr 1.16fr; align-items: center; gap: 34px; padding: 142px max(28px, calc((100vw - 1184px)/2)) 92px; background: radial-gradient(circle at 74% 33%, #1d2521 0, #0d1110 29%, #0a0c0d 55%); }
+.hero::before { content: ""; position: absolute; inset: 0; opacity: .035; pointer-events: none; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 160 160' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.7'/%3E%3C/svg%3E"); }
+.hero-glow { position: absolute; width: 520px; height: 520px; right: 10%; top: 20%; border-radius: 50%; background: rgba(207,255,92,.05); filter: blur(70px); }
+.hero-copy { position: relative; z-index: 2; max-width: 570px; padding-top: 10px; }
+.announce { display: inline-flex; align-items: center; gap: 10px; border: 1px solid rgba(85,220,130,.18); border-radius: 999px; padding: 5px 8px 5px 5px; color: #c9cec8; font-size: 11px; }
+.announce span { display: flex; align-items: center; gap: 6px; border-radius: 999px; padding: 5px 10px; color: #171a0d; background: var(--lime); font-weight: 700; }
+.hero h1, .section h2 { font-family: "Manrope", sans-serif; }
+.hero h1 { margin: 26px 0 23px; font-size: clamp(58px, 6.15vw, 91px); line-height: .94; letter-spacing: -.075em; font-weight: 700; }
+.hero h1 em, .download h2 em { color: var(--lime); font-style: normal; font-weight: 500; }
+.hero-subtitle { max-width: 520px; margin: 0; color: #a7ada7; font-size: 18px; line-height: 1.65; }
+.hero-actions { display: flex; align-items: center; gap: 13px; margin-top: 32px; }
+.button { display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 48px; padding: 0 20px; border-radius: 10px; font-size: 13px; font-weight: 700; transition: transform .2s, box-shadow .2s, background .2s; }
+.button:hover { transform: translateY(-2px); }
+.button-primary { color: #111408; background: var(--lime); box-shadow: 0 12px 35px rgba(85,220,130,.12); }
+.button-secondary { border: 1px solid var(--line); color: #e6e9e4; background: rgba(255,255,255,.025); }
+.hero-note { display: flex; gap: 20px; margin-top: 18px; color: #737a75; font-size: 10px; }
+.hero-note span { display: flex; align-items: center; gap: 5px; }
+.hero-note svg { color: var(--lime); }
+
+.product-stage { position: relative; z-index: 2; min-width: 0; height: 590px; display: flex; align-items: center; justify-content: center; perspective: 1200px; }
+.stage-orbit { position: absolute; border: 1px solid rgba(85,220,130,.09); border-radius: 50%; }
+.orbit-one { width: 560px; height: 560px; }
+.orbit-two { width: 720px; height: 720px; }
+.app-window { position: relative; z-index: 2; width: min(680px, 100%); aspect-ratio: 1.33; overflow: hidden; border: 1px solid rgba(255,255,255,.15); border-radius: 16px; background: #111416; box-shadow: 0 55px 100px rgba(0,0,0,.58), 0 0 0 9px rgba(255,255,255,.018); transform: rotateY(-4deg) rotateX(2deg); }
+.window-bar { height: 44px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; padding: 0 13px; color: #7c837e; background: #101314; border-bottom: 1px solid rgba(255,255,255,.08); font-size: 8px; }
+.window-brand { display: flex; align-items: center; gap: 6px; color: #dadeda; font-size: 9px; font-weight: 700; }
+.mini-mark { width: 20px; height: 20px; display: grid; place-items: center; border-radius: 6px; color: #111; background: var(--lime); }
+.window-nav { display: flex; align-items: center; gap: 4px; padding: 3px; border: 1px solid rgba(255,255,255,.07); border-radius: 6px; }
+.window-nav span, .window-nav b { padding: 4px 10px; font-weight: 500; }
+.window-nav b { color: #e8ebe6; border-radius: 4px; background: rgba(255,255,255,.08); }
+.local-pill { justify-self: end; display: flex; align-items: center; gap: 4px; }
+.local-pill svg { color: #79e89c; }
+.studio-view { position: relative; height: calc(100% - 44px); overflow: hidden; background: radial-gradient(ellipse at 50% 28%, #4f5d50 0, #253027 34%, #121713 72%); }
+.studio-view::before { content: ""; position: absolute; inset: 0; background: linear-gradient(90deg, rgba(0,0,0,.4), transparent 25%, transparent 75%, rgba(0,0,0,.4)), linear-gradient(0deg, rgba(0,0,0,.55), transparent 35%); }
+.camera-copy { position: absolute; z-index: 4; top: 70px; left: 50%; width: 86%; transform: translateX(-50%); text-align: center; font: 500 clamp(14px, 1.45vw, 20px)/1.62 "Manrope", sans-serif; letter-spacing: -.025em; text-shadow: 0 2px 10px #000; }
+.camera-copy span, .camera-copy strong { display: block; }
+.camera-copy span { color: rgba(255,255,255,.42); }
+.camera-copy strong { margin: 2px -5px; padding: 2px 10px; color: #fff; background: rgba(85,220,130,.13); border-radius: 5px; font-weight: 600; }
+.camera-person { position: absolute; left: 50%; bottom: -80px; width: 250px; height: 320px; transform: translateX(-50%); filter: drop-shadow(0 12px 20px rgba(0,0,0,.3)); }
+.person-head { position: absolute; z-index: 2; width: 112px; height: 140px; left: 69px; top: 31px; border-radius: 47% 47% 42% 42%; background: linear-gradient(110deg, #bd876b, #e1a685 55%, #9a634e); }
+.person-hair { position: absolute; z-index: 3; width: 130px; height: 83px; left: 61px; top: 19px; border-radius: 58% 50% 36% 34%; background: #29201b; transform: rotate(-4deg); clip-path: polygon(0 0,100% 0,100% 76%,77% 56%,65% 80%,50% 50%,33% 72%,14% 55%,0 80%); }
+.person-head::before, .person-head::after { content: ""; position: absolute; top: 64px; width: 9px; height: 4px; border-radius: 50%; background: #36251f; }
+.person-head::before { left: 27px; }.person-head::after { right: 27px; }
+.person-neck { position: absolute; z-index: 1; left: 97px; top: 150px; width: 56px; height: 70px; background: #b8785d; }
+.person-body { position: absolute; left: 18px; bottom: 0; width: 215px; height: 165px; border-radius: 48% 48% 12px 12px; background: linear-gradient(120deg, #243d34, #162a23); }
+.focus-line { position: absolute; z-index: 3; left: 4%; right: 4%; top: 45%; height: 1px; background: linear-gradient(90deg, transparent, rgba(85,220,130,.6), transparent); }
+.following-badge { position: absolute; z-index: 5; bottom: 59px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 6px; padding: 6px 10px; border: 1px solid rgba(255,255,255,.12); border-radius: 999px; background: rgba(10,14,11,.7); backdrop-filter: blur(8px); color: #cbd2ca; font-size: 8px; }
+.following-badge span, .live-dot { width: 6px; height: 6px; border-radius: 50%; background: #79e89c; box-shadow: 0 0 0 3px rgba(121,232,156,.12); }
+.studio-controls { position: absolute; z-index: 5; left: 13px; right: 13px; bottom: 11px; height: 40px; display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; border: 1px solid rgba(255,255,255,.09); border-radius: 8px; background: rgba(15,18,16,.82); backdrop-filter: blur(12px); }
+.studio-controls button { width: 28px; height: 28px; display: grid; place-items: center; border: 0; border-radius: 50%; color: #10130b; background: var(--lime); }
+.control-left { display: flex; align-items: center; gap: 8px; padding-left: 13px; color: #d5d9d5; font-size: 8px; }
+.control-left i { width: 5px; height: 5px; border-radius: 50%; background: #ff645d; }
+.audio-meter { justify-self: end; display: flex; align-items: center; gap: 2px; padding-right: 14px; height: 18px; }
+.audio-meter i { display: block; width: 2px; height: 25%; background: #7be89c; animation: meter .8s ease-in-out infinite alternate; }
+.audio-meter i:nth-child(2), .audio-meter i:nth-child(5) { height: 65%; animation-delay: -.3s; }.audio-meter i:nth-child(3) { height: 90%; animation-delay: -.6s; }.audio-meter i:nth-child(4) { height: 45%; animation-delay: -.1s; }
+@keyframes meter { to { transform: scaleY(1.8); } }
+.floating-card { position: absolute; z-index: 5; display: flex; align-items: center; border: 1px solid rgba(255,255,255,.13); background: rgba(23,28,25,.88); backdrop-filter: blur(18px); box-shadow: 0 18px 45px rgba(0,0,0,.34); }
+.pace-card { top: 55px; right: -18px; gap: 10px; padding: 12px 14px; border-radius: 11px; }
+.pace-card > svg { color: var(--lime); }.pace-card div { display: grid; gap: 2px; }.pace-card small { color: #68706a; font-size: 6px; letter-spacing: .15em; }.pace-card strong { color: #dfe3df; font-size: 9px; }.pace-card .live-dot { margin-left: 7px; }
+.clean-card { left: 12px; bottom: 60px; gap: 8px; padding: 10px 13px; border-radius: 9px; color: #cbd0cb; font-size: 8px; }.clean-card svg { padding: 2px; border-radius: 50%; color: #152111; background: var(--lime); }
+
+.trust-strip { min-height: 78px; display: grid; grid-template-columns: repeat(4, 1fr); max-width: 1184px; margin: auto; border-bottom: 1px solid rgba(255,255,255,.08); }
+.trust-strip div { display: flex; align-items: center; justify-content: center; gap: 11px; color: #717872; font-size: 11px; }.trust-strip div + div { border-left: 1px solid rgba(255,255,255,.08); }.trust-strip svg { color: var(--lime); }.trust-strip strong { color: #c4c9c4; font-weight: 600; }
+.section { max-width: 1240px; margin: auto; padding: 132px 28px; }
+.section-heading { max-width: 660px; margin: 0 auto 60px; text-align: center; }
+.kicker { color: #9aad70; font-size: 11px; font-weight: 700; letter-spacing: .15em; text-transform: uppercase; }
+.section h2 { margin: 18px 0; font-size: clamp(40px, 4vw, 59px); line-height: 1.05; letter-spacing: -.06em; }
+.section-heading p { color: #858c86; font-size: 15px; line-height: 1.7; }
+.feature-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
+.feature-card { position: relative; min-height: 335px; overflow: hidden; padding: 29px; border: 1px solid rgba(255,255,255,.09); border-radius: 16px; background: #101314; }
+.feature-card::after { content: ""; position: absolute; right: -80px; bottom: -110px; width: 230px; height: 230px; border-radius: 50%; filter: blur(50px); opacity: .13; }
+.feature-card.lime::after { background: var(--lime); }.feature-card.violet::after { background: #a88cff; }.feature-card.blue::after { background: #5db8ff; }
+.feature-top { display: flex; justify-content: space-between; align-items: flex-start; color: #4d544e; font: 500 10px "Manrope"; letter-spacing: .12em; }
+.feature-icon { display: grid; place-items: center; width: 48px; height: 48px; border: 1px solid rgba(255,255,255,.09); border-radius: 12px; color: var(--lime); background: rgba(255,255,255,.025); }.violet .feature-icon { color: #aa9cff; }.blue .feature-icon { color: #78c4ff; }
+.feature-card h3 { margin: 75px 0 12px; font: 600 23px "Manrope"; letter-spacing: -.04em; }.feature-card p { max-width: 310px; color: #7f8680; font-size: 13px; line-height: 1.65; }.feature-line { position: absolute; right: 29px; bottom: 29px; left: 29px; height: 1px; background: linear-gradient(90deg, rgba(85,220,130,.65), transparent); }.violet .feature-line { background: linear-gradient(90deg, rgba(170,156,255,.65), transparent); }.blue .feature-line { background: linear-gradient(90deg, rgba(120,196,255,.65), transparent); }
+
+.workflow { display: grid; grid-template-columns: 1fr 1fr; align-items: center; gap: 110px; max-width: none; padding-left: max(28px, calc((100vw - 1184px)/2)); padding-right: max(28px, calc((100vw - 1184px)/2)); background: #f0f1eb; color: #151714; }
+.workflow-visual { position: relative; height: 530px; display: grid; place-items: center; }
+.workflow-visual::before { content: ""; position: absolute; width: 440px; height: 440px; border-radius: 50%; background: #e3e5db; }
+.script-card { position: absolute; width: min(470px, 92%); border-radius: 17px; background: #111513; box-shadow: 0 35px 70px rgba(33,38,30,.22); }
+.script-back { height: 310px; transform: rotate(-7deg) translate(-20px, -12px); color: #737a73; padding: 25px; font-size: 8px; letter-spacing: .15em; background: #dfe3d7; box-shadow: none; }
+.script-front { z-index: 2; min-height: 305px; padding: 25px; color: #eff1ed; transform: rotate(2deg); }
+.script-card-bar { display: flex; align-items: center; gap: 9px; padding-bottom: 22px; border-bottom: 1px solid rgba(255,255,255,.09); color: #b5bbb6; font-size: 10px; }.script-card-bar svg { color: var(--lime); }.script-card-bar i { margin-left: auto; color: #626a63; font-style: normal; }
+.script-card p { margin: 50px 10px 30px; font: 500 22px/1.55 "Manrope"; letter-spacing: -.035em; }.script-card mark { color: #0f120a; background: var(--lime); padding: 2px 3px; }
+.script-progress { height: 3px; margin: 0 10px; border-radius: 3px; background: #2b312c; }.script-progress i { display: block; width: 61%; height: 100%; border-radius: inherit; background: var(--lime); }
+.cursor-bubble { position: absolute; z-index: 4; right: -5px; top: 53%; display: flex; align-items: center; gap: 6px; padding: 8px 11px; border-radius: 7px 7px 7px 0; color: #111; background: var(--lime); font-size: 9px; font-weight: 700; box-shadow: 0 10px 20px rgba(0,0,0,.15); }
+.workflow-copy h2 { margin-bottom: 45px; }
+.steps { display: grid; gap: 25px; }.steps > div { display: grid; grid-template-columns: 35px 1fr; gap: 16px; }.steps > div > span { width: 34px; height: 34px; display: grid; place-items: center; border: 1px solid #cdd0c8; border-radius: 50%; color: #71804d; font-size: 11px; font-weight: 700; }.steps section { padding-bottom: 21px; border-bottom: 1px solid #d7d9d2; }.steps h3 { margin: 2px 0 7px; font: 700 15px "Manrope"; }.steps p { margin: 0; color: #737871; font-size: 12px; line-height: 1.6; }
+
+.privacy { display: grid; grid-template-columns: 1fr 1fr; align-items: center; gap: 80px; min-height: 740px; }
+.privacy-copy { max-width: 530px; }.privacy-icon { width: 50px; height: 50px; display: grid; place-items: center; margin-bottom: 35px; border-radius: 14px; color: #13170b; background: var(--lime); }.privacy h2 em { color: #6f7e48; font-style: normal; font-weight: 500; }.privacy-copy > p { color: #89908a; font-size: 15px; line-height: 1.75; }.privacy-points { display: grid; gap: 12px; margin-top: 27px; color: #b5bbb5; font-size: 12px; }.privacy-points span { display: flex; align-items: center; gap: 9px; }.privacy-points svg { color: var(--lime); }
+.privacy-graphic { position: relative; height: 500px; display: grid; place-items: center; }
+.privacy-ring { position: absolute; border: 1px solid rgba(85,220,130,.15); border-radius: 50%; }.ring-outer { width: 440px; height: 440px; }.ring-inner { width: 300px; height: 300px; border-style: dashed; animation: spin 30s linear infinite; }@keyframes spin { to { transform: rotate(360deg); } }
+.privacy-core { position: relative; z-index: 2; width: 176px; height: 176px; display: grid; place-content: center; gap: 13px; text-align: center; border: 1px solid rgba(85,220,130,.24); border-radius: 50%; color: var(--lime); background: radial-gradient(circle, #1b2417, #101410); box-shadow: 0 0 80px rgba(85,220,130,.07); }.privacy-core strong { color: #dce1db; font: 600 14px/1.35 "Manrope"; }
+.privacy-chip { position: absolute; z-index: 3; display: flex; align-items: center; gap: 7px; padding: 9px 12px; border: 1px solid rgba(255,255,255,.1); border-radius: 8px; color: #aeb5af; background: #141817; font-size: 10px; }.privacy-chip svg { color: var(--lime); }.chip-one { top: 60px; left: 25%; }.chip-two { right: 8%; top: 44%; }.chip-three { bottom: 65px; left: 22%; }
+
+.download { position: relative; max-width: 1184px; min-height: 490px; overflow: hidden; display: grid; place-items: center; margin-bottom: 80px; padding: 90px 28px; border-radius: 24px; color: #111409; background: var(--lime); text-align: center; }
+.download::before, .download::after { content: ""; position: absolute; width: 350px; height: 350px; border: 1px solid rgba(12,15,7,.12); border-radius: 50%; }.download::before { left: -170px; top: -140px; }.download::after { right: -160px; bottom: -190px; }
+.download-noise { position: absolute; inset: 0; opacity: .05; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 160 160' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }
+.download-copy { position: relative; z-index: 2; }.download .kicker { color: #64732a; }.download h2 { margin: 17px 0; font-size: clamp(42px, 5vw, 67px); }.download h2 em { color: #576622; }.download p { margin: 0 0 28px; color: #4a531f; font-size: 14px; }.button-dark { color: #f4f6f0; background: #11140e; box-shadow: 0 15px 30px rgba(29,34,13,.18); }.download small { display: block; margin-top: 16px; color: #64702f; font-size: 9px; }.download-spark { position: absolute; color: rgba(14,18,8,.18); }.spark-one { top: 80px; left: 12%; width: 34px; height: 34px; }.spark-two { right: 11%; bottom: 80px; width: 25px; height: 25px; transform: rotate(140deg); }
+
+footer { max-width: 1184px; min-height: 120px; display: grid; grid-template-columns: auto 1fr auto auto; align-items: center; gap: 40px; margin: auto; padding: 0 0 45px; color: #676d68; font-size: 10px; }.footer-logo { color: #f5f6f2; }.footer-links { display: flex; gap: 24px; }.footer-links a:hover { color: #fff; }.copyright { padding-left: 30px; border-left: 1px solid rgba(255,255,255,.09); }
+
+@media (max-width: 1000px) {
+ .hero { grid-template-columns: 1fr; padding-top: 145px; text-align: center; }.hero-copy { margin: auto; }.announce { margin: auto; }.hero-actions, .hero-note { justify-content: center; }.product-stage { width: min(730px, 100%); margin: auto; }.hero h1 { font-size: clamp(60px, 10vw, 88px); }.trust-strip { grid-template-columns: repeat(2, 1fr); }.trust-strip div { border-bottom: 1px solid rgba(255,255,255,.08); }.workflow { gap: 50px; }.privacy { gap: 25px; }
+ footer { padding: 0 28px 40px; }
+}
+
+@media (max-width: 760px) {
+ .site-header { height: 72px; padding: 0 20px; }.site-header nav { position: absolute; top: 65px; left: 20px; right: 20px; transform: none; display: none; flex-direction: column; gap: 0; padding: 10px; border: 1px solid var(--line); border-radius: 12px; background: rgba(17,20,19,.97); box-shadow: 0 15px 40px rgba(0,0,0,.4); }.site-header nav.nav-open { display: flex; }.site-header nav a { padding: 13px; }.header-cta { display: none; }.menu-button { display: grid; place-items: center; }
+ .hero { min-height: auto; padding: 125px 20px 80px; }.hero h1 { font-size: clamp(51px, 16vw, 75px); }.hero-subtitle { font-size: 16px; }.hero-actions { flex-direction: column; }.hero-actions .button { width: 100%; }.hero-note { flex-wrap: wrap; }.product-stage { height: 420px; margin-top: 35px; }.app-window { width: 110%; transform: none; }.pace-card { right: -5px; }.clean-card { left: -5px; }.orbit-one { width: 400px; height: 400px; }.orbit-two { display: none; }
+ .trust-strip { padding: 16px 20px; grid-template-columns: 1fr; }.trust-strip div { min-height: 44px; justify-content: flex-start; border: 0 !important; }
+ .section { padding: 90px 20px; }.feature-grid { grid-template-columns: 1fr; }.feature-card { min-height: 300px; }.feature-card h3 { margin-top: 55px; }
+ .workflow { grid-template-columns: 1fr; padding-left: 20px; padding-right: 20px; }.workflow-visual { height: 430px; order: 2; }.script-card p { font-size: 18px; }.workflow-copy { order: 1; }
+ .privacy { grid-template-columns: 1fr; }.privacy-graphic { height: 420px; }.ring-outer { width: 340px; height: 340px; }.ring-inner { width: 250px; height: 250px; }.chip-one { left: 10%; }.chip-three { left: 8%; }
+ .download { width: calc(100% - 40px); margin-bottom: 65px; border-radius: 18px; }.download h2 br { display: none; }
+ footer { grid-template-columns: 1fr 1fr; gap: 24px; }.footer-links { justify-self: end; }.copyright { padding: 0; border: 0; justify-self: end; }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ html { scroll-behavior: auto; } *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
+}
diff --git a/site/src/vite-env.d.ts b/site/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/site/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/site/tsconfig.json b/site/tsconfig.json
new file mode 100644
index 0000000..e935337
--- /dev/null
+++ b/site/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src"]
+}
diff --git a/src/components/AIGenerator.tsx b/src/components/AIGenerator.tsx
index 55efb18..a946a40 100644
--- a/src/components/AIGenerator.tsx
+++ b/src/components/AIGenerator.tsx
@@ -1,6 +1,7 @@
import { useState, type FormEvent } from "react";
import { ArrowRight, Sparkles, X } from "lucide-react";
import type { GenerateScriptInput } from "../types";
+import { appBrand } from "../lib/appBrand";
interface AIGeneratorProps {
onClose: () => void;
@@ -69,7 +70,7 @@ export function AIGenerator({ onClose, onGenerated }: AIGeneratorProps) {
Turn an idea into something worth saying.
- Give PrompterPro the substance. You stay in control of every word.
+ Give {appBrand.name} the substance. You stay in control of every word.