From c195bcb5f95668d47bbf905d77b6d59600c9dc87 Mon Sep 17 00:00:00 2001 From: Nick Beaugeard Date: Fri, 7 Aug 2026 08:22:32 +1000 Subject: [PATCH 1/2] feat: add SimplePrompt dual-product release --- .github/workflows/store-release.yml | 29 +++- desktop/main.mjs | 41 ++++- package.json | 3 + public/simpleprompt-mark.svg | 5 + scripts/package-windows.mjs | 30 ++-- scripts/product-variants.d.mts | 21 +++ scripts/product-variants.mjs | 47 ++++++ scripts/smoke-arm64-sidecar.mjs | 7 +- scripts/store-manifest.d.mts | 7 +- scripts/store-manifest.mjs | 13 +- server/index.ts | 5 +- site/README.md | 19 +++ site/index.html | 19 +++ site/render-static.tsx | 32 ++++ site/simpleprompt-mark.svg | 5 + site/site.js | 23 +++ site/src/App.tsx | 242 ++++++++++++++++++++++++++++ site/src/main.tsx | 10 ++ site/src/styles.css | 159 ++++++++++++++++++ site/src/vite-env.d.ts | 1 + site/tsconfig.json | 20 +++ src/components/AIGenerator.tsx | 3 +- src/components/Brand.tsx | 5 +- src/hooks/useLocalScripts.ts | 5 +- src/hooks/useSpeechFollower.ts | 3 +- src/lib/appBrand.ts | 18 +++ src/main.tsx | 7 + src/styles.css | 44 +++-- store/Package.appxmanifest.template | 16 +- store/README.md | 40 +++-- store/simpleprompt/PRIVACY.md | 48 ++++++ store/simpleprompt/STORE_DETAILS.md | 17 ++ store/simpleprompt/listing/en-AU.md | 62 +++++++ tests/storeManifest.test.ts | 45 ++++++ 34 files changed, 977 insertions(+), 74 deletions(-) create mode 100644 public/simpleprompt-mark.svg create mode 100644 scripts/product-variants.d.mts create mode 100644 scripts/product-variants.mjs create mode 100644 site/README.md create mode 100644 site/index.html create mode 100644 site/render-static.tsx create mode 100644 site/simpleprompt-mark.svg create mode 100644 site/site.js create mode 100644 site/src/App.tsx create mode 100644 site/src/main.tsx create mode 100644 site/src/styles.css create mode 100644 site/src/vite-env.d.ts create mode 100644 site/tsconfig.json create mode 100644 src/lib/appBrand.ts create mode 100644 store/simpleprompt/PRIVACY.md create mode 100644 store/simpleprompt/STORE_DETAILS.md create mode 100644 store/simpleprompt/listing/en-AU.md 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..ffc9a24 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,9 @@ 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 allowedAppEntries = new Set([ "desktop", "dist", @@ -121,8 +125,8 @@ 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, @@ -136,10 +140,10 @@ for (const architecture of architectures) { 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 +157,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( @@ -178,7 +182,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 +362,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..e458772 --- /dev/null +++ b/scripts/product-variants.d.mts @@ -0,0 +1,21 @@ +export interface ProductVariant { + readonly key: "prompterpro" | "simpleprompt"; + readonly productName: "PrompterPro" | "SimplePrompt"; + 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..0ee4050 --- /dev/null +++ b/scripts/product-variants.mjs @@ -0,0 +1,47 @@ +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", + 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", + 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..c1ef5d3 --- /dev/null +++ b/site/index.html @@ -0,0 +1,19 @@ + + + + + + + + + + SimplePrompt — Your words. Your pace. + + +
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
SimplePrompt
ScriptsStudio
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
VOICE PACENatural & 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.

01

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.

02

Record a clean take

Capture crisp camera and microphone footage. Your prompt stays on screen for you, never burned into the final video.

03

Private by design

Scripts, recordings, and voice recognition stay on your computer. Your work remains yours—full stop.

BRAND STORY · TAKE 02
Product launch2: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 means
private.

Voice recognition happens on your computer. Your recordings, scripts, and transcripts never leave it. No cloud processing. No quiet data collection.

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 SimplePromptWindows 10 & 11 · $1.99 per month · No account needed
+ + + 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..1893b68 --- /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 ( +
+
+ + + SimplePrompt + + + + + + Get SimplePrompt + + +
+ +
+
+
+ + 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 +
+
+ +
+
+
+
+
+
+ + SimplePrompt +
+
ScriptsStudio
+ 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.” +
+
+ +
+
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 launch2: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 means
private.

+

Voice recognition happens on your computer. Your recordings, scripts, and transcripts never leave it. No cloud processing. No quiet data collection.

+
+ On-device speech recognition + Local script library + No account required +
+
+
+
+
+
Stays on
this device
+ Voice + Scripts + +
+
+ +
+
+
+ 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 10 & 11 · $1.99 per month · No account needed +
+ + +
+ + +
+ ); +} 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.

diff --git a/src/components/Brand.tsx b/src/components/Brand.tsx index 80b826c..059bee4 100644 --- a/src/components/Brand.tsx +++ b/src/components/Brand.tsx @@ -1,12 +1,13 @@ import { AudioLines } from "lucide-react"; +import { appBrand } from "../lib/appBrand"; export function Brand() { return ( -
+
- PrompterPro + {appBrand.name}
); } diff --git a/src/hooks/useLocalScripts.ts b/src/hooks/useLocalScripts.ts index e25a28c..bb982b0 100644 --- a/src/hooks/useLocalScripts.ts +++ b/src/hooks/useLocalScripts.ts @@ -1,13 +1,14 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { loadScripts, parseScripts, saveScripts } from "../lib/scriptStorage"; import type { PrompterScript, ScriptSource } from "../types"; +import { appBrand } from "../lib/appBrand"; const sampleScript: PrompterScript = { id: "welcome-to-prompter", - title: "Welcome to PrompterPro", + title: `Welcome to ${appBrand.name}`, body: "Great delivery starts with a clear idea and the confidence to say it naturally.\n\n" + - "PrompterPro keeps your next words close to the camera, follows your voice, and waits when you improvise. " + + `${appBrand.name} keeps your next words close to the camera, follows your voice, and waits when you improvise. ` + "The recording keeps running, so you can stay present instead of worrying about the controls.\n\n" + "Take a breath, look toward the lens, and make the message your own.", source: "manual", diff --git a/src/hooks/useSpeechFollower.ts b/src/hooks/useSpeechFollower.ts index e33b1ad..86a478e 100644 --- a/src/hooks/useSpeechFollower.ts +++ b/src/hooks/useSpeechFollower.ts @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { alignTranscript, wordsFromText } from "../lib/alignment"; +import { appBrand } from "../lib/appBrand"; import { stabilizeAdvance, type PendingAdvance, @@ -198,7 +199,7 @@ export function useSpeechFollower( if (!disposed && !failureReported) { failureReported = true; setErrorMessage( - "The local speech engine could not be reached. Keep the PrompterPro API running.", + `The local speech engine could not be reached. Keep the ${appBrand.name} API running.`, ); setStatus("error"); } diff --git a/src/lib/appBrand.ts b/src/lib/appBrand.ts new file mode 100644 index 0000000..61e4c7c --- /dev/null +++ b/src/lib/appBrand.ts @@ -0,0 +1,18 @@ +export type AppVariant = "prompterpro" | "simpleprompt"; + +const requestedVariant = import.meta.env.VITE_APP_VARIANT?.toLowerCase(); + +export const appVariant: AppVariant = + requestedVariant === "simpleprompt" ? "simpleprompt" : "prompterpro"; + +export const appBrand = Object.freeze( + appVariant === "simpleprompt" + ? { + name: "SimplePrompt", + icon: "/simpleprompt-mark.svg", + } + : { + name: "PrompterPro", + icon: "/prompter-mark.svg", + }, +); diff --git a/src/main.tsx b/src/main.tsx index d89e75f..d8a0ec5 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,8 +1,15 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import App from "./App"; +import { appBrand, appVariant } from "./lib/appBrand"; import "./styles.css"; +document.documentElement.dataset.appVariant = appVariant; +document.title = appBrand.name; +document + .querySelector('link[rel="icon"]') + ?.setAttribute("href", appBrand.icon); + if ("serviceWorker" in navigator && import.meta.env.PROD) { window.addEventListener("load", () => { void navigator.serviceWorker.register("/sw.js"); diff --git a/src/styles.css b/src/styles.css index 930cef5..3ec3d1e 100644 --- a/src/styles.css +++ b/src/styles.css @@ -15,12 +15,26 @@ --muted: #8c949b; --muted-2: #626a71; --accent: #e8ff67; + --accent-rgb: 232, 255, 103; --accent-ink: #111508; --amber: #ffbd66; --red: #ff6259; --green: #79e89c; } +:root[data-app-variant="simpleprompt"] { + --accent: #55dc82; + --accent-rgb: 85, 220, 130; + --accent-ink: #07150c; + --green: #55dc82; +} + +:root[data-app-variant="simpleprompt"] body { + background: + radial-gradient(circle at 82% -15%, rgba(85, 220, 130, 0.08), transparent 28%), + var(--bg); +} + * { box-sizing: border-box; } @@ -36,7 +50,7 @@ body, body { min-height: 100vh; background: - radial-gradient(circle at 82% -15%, rgba(232, 255, 103, 0.06), transparent 28%), + radial-gradient(circle at 82% -15%, rgba(var(--accent-rgb), 0.06), transparent 28%), var(--bg); overflow: hidden; } @@ -111,7 +125,7 @@ a:focus-visible { border-radius: 10px; color: var(--accent-ink); background: var(--accent); - box-shadow: 0 7px 24px rgba(232, 255, 103, 0.12); + box-shadow: 0 7px 24px rgba(var(--accent-rgb), 0.12); } .app-header nav { @@ -235,19 +249,19 @@ p { align-items: center; gap: 11px; padding: 12px; - border: 1px solid rgba(232, 255, 103, 0.16); + border: 1px solid rgba(var(--accent-rgb), 0.16); border-radius: 12px; text-align: left; color: var(--text); background: - linear-gradient(120deg, rgba(232, 255, 103, 0.08), transparent 62%), + linear-gradient(120deg, rgba(var(--accent-rgb), 0.08), transparent 62%), #14181a; transition: border-color 160ms ease, transform 160ms ease; } .ai-draft-button:hover { transform: translateY(-1px); - border-color: rgba(232, 255, 103, 0.32); + border-color: rgba(var(--accent-rgb), 0.32); } .ai-draft-icon { @@ -353,7 +367,7 @@ p { .source-dot[data-source="ai"] { background: var(--accent); - box-shadow: 0 0 0 3px rgba(232, 255, 103, 0.09); + box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.09); } .card-menu-button { @@ -620,7 +634,7 @@ p { border: 1px solid var(--line-strong); border-radius: 18px; background: - radial-gradient(circle at 100% 0, rgba(232, 255, 103, 0.08), transparent 30%), + radial-gradient(circle at 100% 0, rgba(var(--accent-rgb), 0.08), transparent 30%), #15191c; box-shadow: 0 26px 90px rgba(0, 0, 0, 0.56); } @@ -698,7 +712,7 @@ p { .generator-form input:focus, .generator-form textarea:focus, .generator-form select:focus { - border-color: rgba(232, 255, 103, 0.42); + border-color: rgba(var(--accent-rgb), 0.42); } .field-row { @@ -782,7 +796,7 @@ p { grid-template-rows: 58px minmax(0, 1fr) 82px; padding: 0 26px 18px; background: - radial-gradient(circle at 50% 18%, rgba(232, 255, 103, 0.045), transparent 34%), + radial-gradient(circle at 50% 18%, rgba(var(--accent-rgb), 0.045), transparent 34%), #090b0c; } @@ -910,7 +924,7 @@ p { } .devices-popover select:focus { - border-color: rgba(232, 255, 103, 0.42); + border-color: rgba(var(--accent-rgb), 0.42); } .devices-popover p { @@ -958,7 +972,7 @@ p { .tool-button.active { color: var(--accent); - border-color: rgba(232, 255, 103, 0.25); + border-color: rgba(var(--accent-rgb), 0.25); } .font-control button { @@ -1016,10 +1030,10 @@ p { display: grid; place-items: center; margin-bottom: 18px; - border: 1px solid rgba(232, 255, 103, 0.16); + border: 1px solid rgba(var(--accent-rgb), 0.16); border-radius: 17px; color: var(--accent); - background: rgba(232, 255, 103, 0.055); + background: rgba(var(--accent-rgb), 0.055); } .device-setup h2 { @@ -1105,7 +1119,7 @@ p { .prompt-word.current, .prompt-word.current-line { color: var(--accent); - text-shadow: 0 2px 18px rgba(232, 255, 103, 0.2); + text-shadow: 0 2px 18px rgba(var(--accent-rgb), 0.2); } .prompt-word.upcoming, @@ -1123,7 +1137,7 @@ p { transform: translateY(-50%); border-radius: 4px; background: var(--accent); - box-shadow: 0 0 16px rgba(232, 255, 103, 0.35); + box-shadow: 0 0 16px rgba(var(--accent-rgb), 0.35); } .prompt-fade { diff --git a/store/Package.appxmanifest.template b/store/Package.appxmanifest.template index 53b1889..c515001 100644 --- a/store/Package.appxmanifest.template +++ b/store/Package.appxmanifest.template @@ -10,7 +10,7 @@ Version="__VERSION__" ProcessorArchitecture="__PROCESSOR_ARCHITECTURE__" /> - PrompterPro + __DISPLAY_NAME__ __PUBLISHER_DISPLAY_NAME__ Assets\StoreLogo.png @@ -25,22 +25,22 @@ + BackgroundColor="__BACKGROUND_COLOR__" /> diff --git a/store/README.md b/store/README.md index 28361a2..57160dc 100644 --- a/store/README.md +++ b/store/README.md @@ -1,8 +1,8 @@ # Microsoft Store release guide -PrompterPro is packaged as native x64 and ARM64 Electron desktop applications and -then combined into an unsigned MSIX bundle. Microsoft Store signs the accepted -bundle during submission. +PrompterPro and SimplePrompt are packaged as native x64 and ARM64 Electron +desktop applications and then combined into separate unsigned MSIX bundles. +Microsoft Store signs each accepted bundle during submission. The ARM64 shell uses a bundled x64 Node sidecar through Windows 11's compatibility layer because the offline sherpa runtime and FFmpeg do not publish @@ -26,9 +26,10 @@ The Store bundle therefore targets Windows 11 for both architecture slices. Repository variables: - `MS_STORE_PRODUCT_ID` -- `MS_STORE_IDENTITY_NAME` -- `MS_STORE_PUBLISHER` -- `MS_STORE_PUBLISHER_DISPLAY_NAME` + +`MS_STORE_PRODUCT_ID` is the PrompterPro Store ID. SimplePrompt's Store ID and +both products' Partner Center identities are versioned in the release matrix and +[`simpleprompt/STORE_DETAILS.md`](simpleprompt/STORE_DETAILS.md). The packager defaults to the production `ReleasedPtyLtd.PrompterPro` identity, its Partner Center publisher certificate subject and `Released Pty Ltd`. @@ -76,6 +77,22 @@ submitted as-is. Sideloading requires a certificate whose subject matches the manifest publisher; use the identity environment variables only when a deliberate development identity is needed. +To build SimplePrompt locally, set the flavour and its Store identity before +running the same packaging command: + +```powershell +$env:PROMPTER_VARIANT = "simpleprompt" +$env:VITE_APP_VARIANT = "simpleprompt" +$env:MS_STORE_IDENTITY_NAME = "ReleasedPtyLtd.SimplePrompt" +$env:MS_STORE_PUBLISHER = "CN=E3BDC624-0B0A-4256-85B0-5AE714EA8897" +$env:MS_STORE_PUBLISHER_DISPLAY_NAME = "Released Pty Ltd" +npm.cmd run package:windows +``` + +This writes `SimplePrompt__x64_arm64.msixbundle`. Use x64 Node.js for +both product builds; the packager uses it to assemble the x64 application and +the compatibility sidecar included in the ARM64 application. + The server build bundles ordinary JavaScript dependencies. Packaging retains only the FFmpeg executable and sherpa-onnx loader/native files outside that bundle, avoiding thousands of development and transitive dependency artifacts @@ -93,11 +110,12 @@ npm.cmd run speech:smoke:arm64 ## Automated releases -The Store release workflow validates and uploads an MSIX bundle artifact for a -`vMAJOR.MINOR.PATCH` tag or a manual run. Publishing is attempted for a tag, or -when **Publish to Microsoft Store** is selected manually. Store identity -variables enable the publish steps; missing authentication secrets then fail -the workflow with an explicit configuration error. +The Store release workflow runs a two-product matrix and uploads separate +PrompterPro and SimplePrompt MSIX bundle artifacts for a `vMAJOR.MINOR.PATCH` +tag or manual run. Publishing is attempted for a tag, or when **Publish to +Microsoft Store** is selected manually. A Store ID enables publishing for each +matrix entry; missing authentication secrets then fail that entry with an +explicit configuration error. Before a production release: diff --git a/store/simpleprompt/PRIVACY.md b/store/simpleprompt/PRIVACY.md new file mode 100644 index 0000000..6b96654 --- /dev/null +++ b/store/simpleprompt/PRIVACY.md @@ -0,0 +1,48 @@ +# SimplePrompt privacy policy + +Effective date: 6 August 2026 + +SimplePrompt is designed to keep your recording workflow on your Windows device. + +## Data the app handles + +SimplePrompt can access your camera and microphone after you grant Windows +permission. It uses them to show a preview, record a take and follow your spoken +position in a script. + +Scripts are stored in local application storage. Camera and microphone media, +speech samples and recognised words are processed locally. A recording may be +converted to MP4 in a temporary local folder, which is removed after export. + +SimplePrompt does not include advertising, analytics or telemetry. It does not +collect account information, contacts, location, payment information, your +script library, recordings or transcripts. + +## Network access + +The Microsoft Store build does not require a SimplePrompt cloud service for its +core script, speech-following and recording features. Windows and Microsoft +Store may make their own network requests under Microsoft's terms. + +If optional AI drafting is configured, the topic, audience, tone, duration and +key points entered in that form are sent to the configured OpenAI service to +create a draft. Camera media, microphone audio, recordings, transcripts and the +local script library are not included in that request. + +## Your choices + +You can deny or revoke camera and microphone permission in Windows Settings. +You can delete scripts inside SimplePrompt and delete exported recordings using +Windows File Explorer. Uninstalling the app removes its local application +storage, subject to Windows behaviour and any backups you control. + +## Children + +SimplePrompt does not knowingly collect personal information from children. + +## Changes and contact + +Material changes will be published in this file with a new effective date. For +privacy questions or support, open an issue at: + +https://github.com/ReleasedGroup/Prompter/issues diff --git a/store/simpleprompt/STORE_DETAILS.md b/store/simpleprompt/STORE_DETAILS.md new file mode 100644 index 0000000..30eba55 --- /dev/null +++ b/store/simpleprompt/STORE_DETAILS.md @@ -0,0 +1,17 @@ +# SimplePrompt Microsoft Store details + +## Partner Center identity + +| Field | Value | +| --- | --- | +| Package/Identity/Name | `ReleasedPtyLtd.SimplePrompt` | +| Package/Identity/Publisher | `CN=E3BDC624-0B0A-4256-85B0-5AE714EA8897` | +| Package/Properties/PublisherDisplayName | `Released Pty Ltd` | +| Package Family Name (PFN) | `ReleasedPtyLtd.SimplePrompt_q0b077qanz1d8` | +| Package SID | `S-1-15-2-2676728462-2596204801-2700890954-455437082-720273692-286854053-1939976512` | +| Store ID | `9MT1X5BNTHQS` | +| Store deep link | Available after the product is live | +| Web Store URL | Available after the product is live | + +The PFN and Package SID are derived Partner Center values and are recorded here +for validation; they are not written directly into the MSIX manifest. diff --git a/store/simpleprompt/listing/en-AU.md b/store/simpleprompt/listing/en-AU.md new file mode 100644 index 0000000..faf2ade --- /dev/null +++ b/store/simpleprompt/listing/en-AU.md @@ -0,0 +1,62 @@ +# Microsoft Store listing — en-AU + +## Product name + +SimplePrompt + +## Short description + +Record naturally with a private teleprompter that follows your voice. + +## Description + +Stay on message without sounding scripted. + +SimplePrompt keeps your words close to the camera while you record. As you +speak, the prompt follows automatically. If you improvise, it waits; when you +return to the script, it picks up from the right place. + +Speech following and video export run on your Windows computer. Camera video, +microphone audio, recordings, transcripts and your script library are not +uploaded. + +Key features: + +- Write, organise, search and duplicate scripts. +- Preview your camera and microphone before recording. +- Follow scripts hands-free with offline voice recognition. +- Pause prompt movement automatically when you go off script. +- Adjust text size, mirror the prompt and move manually when needed. +- Review each take and save it as an MP4. +- Keep scripts and recordings under your control on this device. + +The included speech model currently recognises English. + +## What's new + +Initial Microsoft Store release with offline voice following, local script +management, camera and microphone recording, and MP4 export. + +## Search terms + +teleprompter, autocue, video recording, presenter, script, speech, creator + +## Category + +Productivity + +## Minimum system requirements + +- Windows 11 on an x64 or ARM64 processor +- Camera and microphone for recording +- Internet connection only for installation and optional online features + +## Suggested age rating + +Suitable for all ages. The app has no user-to-user communication, gambling, +violence or mature content. + +## Support and policy links + +- Support: https://github.com/ReleasedGroup/Prompter/issues +- Privacy policy: https://github.com/ReleasedGroup/Prompter/blob/main/store/simpleprompt/PRIVACY.md diff --git a/tests/storeManifest.test.ts b/tests/storeManifest.test.ts index 09d02e9..97a9b3f 100644 --- a/tests/storeManifest.test.ts +++ b/tests/storeManifest.test.ts @@ -6,6 +6,15 @@ import { renderStoreManifest, toWindowsVersion, } from "../scripts/store-manifest.mjs"; +import { PRODUCT_VARIANTS } from "../scripts/product-variants.mjs"; + +const prompterProManifestValues = { + displayName: PRODUCT_VARIANTS.prompterpro.productName, + executableName: PRODUCT_VARIANTS.prompterpro.productName, + applicationId: PRODUCT_VARIANTS.prompterpro.applicationId, + description: PRODUCT_VARIANTS.prompterpro.description, + backgroundColor: PRODUCT_VARIANTS.prompterpro.backgroundColor, +}; describe("Windows Store manifest", () => { it("uses the Partner Center identity and PrompterPro product metadata", () => { @@ -18,6 +27,7 @@ describe("Windows Store manifest", () => { ...DEFAULT_STORE_IDENTITY, minimumWindowsVersion: "10.0.22000.0", version: "1.2.3", + ...prompterProManifestValues, }); expect(manifest).toContain('Name="ReleasedPtyLtd.PrompterPro"'); @@ -33,6 +43,39 @@ describe("Windows Store manifest", () => { expect(manifest).toContain('ShortName="PrompterPro"'); }); + it("renders the SimplePrompt Store identity and product metadata", () => { + const template = readFileSync( + new URL("../store/Package.appxmanifest.template", import.meta.url), + "utf8", + ); + const simplePrompt = PRODUCT_VARIANTS.simpleprompt; + const manifest = renderStoreManifest(template, { + architecture: "arm64", + identityName: simplePrompt.store.identityName, + publisher: simplePrompt.store.publisher, + publisherDisplayName: simplePrompt.store.publisherDisplayName, + minimumWindowsVersion: "10.0.22000.0", + version: "1.2.3", + displayName: simplePrompt.productName, + executableName: simplePrompt.productName, + applicationId: simplePrompt.applicationId, + description: simplePrompt.description, + backgroundColor: simplePrompt.backgroundColor, + }); + + expect(manifest).toContain('Name="ReleasedPtyLtd.SimplePrompt"'); + expect(manifest).toContain('SimplePrompt'); + expect(manifest).toContain('Executable="SimplePrompt.exe"'); + expect(manifest).toContain('Id="SimplePrompt"'); + expect(simplePrompt.store.productId).toBe("9MT1X5BNTHQS"); + expect(simplePrompt.store.packageFamilyName).toBe( + "ReleasedPtyLtd.SimplePrompt_q0b077qanz1d8", + ); + expect(simplePrompt.store.packageSid).toBe( + "S-1-15-2-2676728462-2596204801-2700890954-455437082-720273692-286854053-1939976512", + ); + }); + it("converts semantic versions to four-part MSIX versions", () => { expect(toWindowsVersion("2.7.3")).toBe("2.7.3.0"); expect(toWindowsVersion("2.7.3-beta.1")).toBe("2.7.3.0"); @@ -60,6 +103,7 @@ describe("Windows Store manifest", () => { publisher: "CN=Released Group", publisherDisplayName: "Released Pty Ltd", version: "1.2.3", + ...prompterProManifestValues, }, ), ).toBe( @@ -76,6 +120,7 @@ describe("Windows Store manifest", () => { publisher: "CN=Released Group", publisherDisplayName: "Released Pty Ltd", version: "1.2.3", + ...prompterProManifestValues, }), ).toThrow(/x64 or arm64/); }); From 197fc6e2a0b3a82f27f8cac5ca413044bd80a75b Mon Sep 17 00:00:00 2001 From: Nick Beaugeard Date: Fri, 7 Aug 2026 09:08:43 +1000 Subject: [PATCH 2/2] fix: address SimplePrompt review feedback --- scripts/package-windows.mjs | 8 ++++++-- scripts/product-variants.d.mts | 1 + scripts/product-variants.mjs | 2 ++ site/index.html | 2 +- site/src/App.tsx | 4 ++-- store/README.md | 4 ++++ store/simpleprompt/assets/AppIcon.ico | Bin 0 -> 5316 bytes store/simpleprompt/assets/AppIcon.png | Bin 0 -> 11379 bytes store/simpleprompt/assets/SplashScreen.png | Bin 0 -> 4260 bytes store/simpleprompt/assets/Square150x150Logo.png | Bin 0 -> 3042 bytes store/simpleprompt/assets/Square310x310Logo.png | Bin 0 -> 6636 bytes store/simpleprompt/assets/Square44x44Logo.png | Bin 0 -> 921 bytes store/simpleprompt/assets/StoreLogo.png | Bin 0 -> 1018 bytes store/simpleprompt/assets/Wide310x150Logo.png | Bin 0 -> 2427 bytes tests/storeManifest.test.ts | 1 + 15 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 store/simpleprompt/assets/AppIcon.ico create mode 100644 store/simpleprompt/assets/AppIcon.png create mode 100644 store/simpleprompt/assets/SplashScreen.png create mode 100644 store/simpleprompt/assets/Square150x150Logo.png create mode 100644 store/simpleprompt/assets/Square310x310Logo.png create mode 100644 store/simpleprompt/assets/Square44x44Logo.png create mode 100644 store/simpleprompt/assets/StoreLogo.png create mode 100644 store/simpleprompt/assets/Wide310x150Logo.png diff --git a/scripts/package-windows.mjs b/scripts/package-windows.mjs index ffc9a24..1b62db1 100644 --- a/scripts/package-windows.mjs +++ b/scripts/package-windows.mjs @@ -38,6 +38,10 @@ const packageJson = JSON.parse( 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", @@ -133,7 +137,7 @@ for (const architecture of architectures) { 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, @@ -165,7 +169,7 @@ for (const architecture of architectures) { architecture.name, ); await cp( - path.join(storeDirectory, "assets"), + productAssetsDirectory, path.join(packageDirectory, "Assets"), { recursive: true }, ); diff --git a/scripts/product-variants.d.mts b/scripts/product-variants.d.mts index e458772..6eb95a5 100644 --- a/scripts/product-variants.d.mts +++ b/scripts/product-variants.d.mts @@ -1,6 +1,7 @@ export interface ProductVariant { readonly key: "prompterpro" | "simpleprompt"; readonly productName: "PrompterPro" | "SimplePrompt"; + readonly assetDirectory: string; readonly applicationId: string; readonly description: string; readonly backgroundColor: string; diff --git a/scripts/product-variants.mjs b/scripts/product-variants.mjs index 0ee4050..1761e02 100644 --- a/scripts/product-variants.mjs +++ b/scripts/product-variants.mjs @@ -7,6 +7,7 @@ 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.", @@ -20,6 +21,7 @@ export const PRODUCT_VARIANTS = Object.freeze({ simpleprompt: Object.freeze({ key: "simpleprompt", productName: "SimplePrompt", + assetDirectory: "simpleprompt/assets", applicationId: "SimplePrompt", description: "A private, voice-following teleprompter for clear video recording.", diff --git a/site/index.html b/site/index.html index c1ef5d3..ead26a1 100644 --- a/site/index.html +++ b/site/index.html @@ -13,7 +13,7 @@ SimplePrompt — Your words. Your pace. -
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
SimplePrompt
ScriptsStudio
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
VOICE PACENatural & 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.

01

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.

02

Record a clean take

Capture crisp camera and microphone footage. Your prompt stays on screen for you, never burned into the final video.

03

Private by design

Scripts, recordings, and voice recognition stay on your computer. Your work remains yours—full stop.

BRAND STORY · TAKE 02
Product launch2: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 means
private.

Voice recognition happens on your computer. Your recordings, scripts, and transcripts never leave it. No cloud processing. No quiet data collection.

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 SimplePromptWindows 10 & 11 · $1.99 per month · No account needed
+
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
SimplePrompt
ScriptsStudio
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
VOICE PACENatural & 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.

01

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.

02

Record a clean take

Capture crisp camera and microphone footage. Your prompt stays on screen for you, never burned into the final video.

03

Private by design

Scripts, recordings, and voice recognition stay on your computer. Your work remains yours—full stop.

BRAND STORY · TAKE 02
Product launch2: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 means
private.

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 SimplePromptWindows 11 · $1.99 per month · No account needed
diff --git a/site/src/App.tsx b/site/src/App.tsx index 1893b68..3e0f9b3 100644 --- a/site/src/App.tsx +++ b/site/src/App.tsx @@ -199,7 +199,7 @@ export default function App() { Your voice is nobody else's business

Private means
private.

-

Voice recognition happens on your computer. Your recordings, scripts, and transcripts never leave it. No cloud processing. No quiet data collection.

+

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 @@ -225,7 +225,7 @@ export default function App() { Get SimplePrompt - Windows 10 & 11 · $1.99 per month · No account needed + Windows 11 · $1.99 per month · No account needed
diff --git a/store/README.md b/store/README.md index 57160dc..54689b9 100644 --- a/store/README.md +++ b/store/README.md @@ -93,6 +93,10 @@ This writes `SimplePrompt__x64_arm64.msixbundle`. Use x64 Node.js for both product builds; the packager uses it to assemble the x64 application and the compatibility sidecar included in the ARM64 application. +Each product has its own Windows artwork. PrompterPro uses `store/assets/` and +SimplePrompt uses the green assets in `store/simpleprompt/assets/`; the +packager selects the matching icon, tile, splash and Store logo automatically. + The server build bundles ordinary JavaScript dependencies. Packaging retains only the FFmpeg executable and sherpa-onnx loader/native files outside that bundle, avoiding thousands of development and transitive dependency artifacts diff --git a/store/simpleprompt/assets/AppIcon.ico b/store/simpleprompt/assets/AppIcon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a3dc9c3c2792bda98e39c96d5c93115f03431cc6 GIT binary patch literal 5316 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 literal 0 HcmV?d00001 diff --git a/store/simpleprompt/assets/AppIcon.png b/store/simpleprompt/assets/AppIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..33dc7c7b3bba37a926e69c035294ed9849989bac GIT binary patch literal 11379 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 H(Bc08((aWo literal 0 HcmV?d00001 diff --git a/store/simpleprompt/assets/Square150x150Logo.png b/store/simpleprompt/assets/Square150x150Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f550cc5a92533138303b8912c6a882f68d2fb150 GIT binary patch literal 3042 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 literal 0 HcmV?d00001 diff --git a/store/simpleprompt/assets/Square310x310Logo.png b/store/simpleprompt/assets/Square310x310Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f93f19af492f313e8aa64bd877b9c272fd445d1e GIT binary patch literal 6636 zcmZu$XH-*7w+>YV5y1ejAVsMHN^jDO^j?)vlrDrKy%!bfAf3>Alk$cVI(elEB!n)Z zBTc~2dr5BizPs+a_pa~9nKd(K&)IY4dFI(?_KDWhQKckjAO`>dldD;W9BVix>U7wmF34^U^mZVSQc#z{sz6lf)OVw$M1&#Q_vw)$65 z1*1CRJp}Xt(!vw56*3dZV>R)b)3KEq*f=wj8JNAGn2)eqV6^C+rW)asyPBsOJZV)L zQwvqD3s->~iKAqMg~@G!omQLsx;kpRH`2PnhtlvVVvdx-k>tkFBbPj3WEvVgsGTwS zta8FOcjVbqb$boXLp4g#HL3E#pBqb2DLWa*v!O66opwYkQ`e>sacPDyLd{(3T3xlC?Kyw$swcL1EQc$;d&}Q94Yo_ys5#ED*jRfZgchXwGtTCl4HWT%?R_wDDBx;^&$$IYpbF$_w-BxMAPCaMm*G4g#3DI=7)c*N3;gW7AFZ z4K#xdOe{4k37n?NopXK<2l6DTg({{jdnC&9C0(}ppHf01{t79G7)J83PuLZ@ONf~~ zzJW*5d(^;aG21Z%o)MvG=+b+V0$XC`IIfH+tJ2`B!{>(J$URoacGa?pzuvwbXZ8y; ztMz*SB4fwp(pt0BfMTeNIUQ}TLYH~Ge(LnBz*9By3rySv%0Uy&G13vqkv$ki=!cZO zSoCk5U#4(^H5473r=|o`NUKy5oXaeX9I-^>sUbQQ^Bkh};&fex^ZiR3vSp@P@0}y_ z3!$lK~bwy?o!fa3ln3*Tz@!u`NR=JK;P-?(L_j3G$BCMQ&FMT%Y*}!ZsOWE zy}%?~hjB<{2rD+XL}rle)vVo=rt!e!rL(V~S`GplfP}(%lYUP&U!!f;ikRysQ046% zdmqtaqPK+WFbQD7=Yg1Fs-K-RzYZq3kPU7EJOG}z#u7lZIbVWn?cCre<)_fzjI&e- z$0I606+%k8A@5q4^RP|M?I{;U5XE(z0g>KH`A7DkqmZY_nCT#1hUR~sb6Wfm090JB zh-FXH@s#z)6vM00CL-xtAv0?W1Dd7)qce`zSk*%aqSKfkW-Ek#lim#R1p+FuN_l^c2GFad8BbSeggkun0B?^w)1U9vE%>z^cUv;T(eRRWD%MYsFP1 zWb1Q~EWAz#q)#!gjCXYu(XLL>c;o%b1UzW#k;L!TP{KM!SkX|=75bht1^u#+7W8Hj zS1<_>i3;26sMB)nqf2bgXGbmRAv3xF|BThnBGNLLwk+wLA7zlG$`diIURjm-%0!zqOo=!fk-g< z1%aN8?4^xBzV=A!DX_oEI@a#URL==8IpfRLIFPKA5g*9wr1O)8F^sdK*?FJ||Ax1! zlg}jpK>p1pYAP#;7X-<}7~~P|Yc)Uwl_E!I+O=acDS1Fl1weYyto-Ps(=J_fE{jkm zDTvm2$`{o)KD-2-k#=C=;sj`$x)KEcVv0o$T4*T)vfSk^SDIyb8RC5qhacB1q%Rre zg-x=p$VYg9mw}6o)(!F(J$M$D$xPDm<>IDmDz+Zi6O8um)tlX5@xcJy+MNf<5yHs_gcE*{1EMAC94$O$`pgb=z(~OgD;`3ry!33FS3BL&Sjlp$SI2>9x>F=HQ|( zT~m@8ta9hjP;{@#kk%=YT8)DD{#w(`sl!yeeP=yHkWjzog`H=j86PBI(N;6M__7TJ zsxB3;HmUfem~BKNZZrx5h14v_EpTuCC?@TLjr|&&WFEAqQ9XGq{=Di!+_HLgBuDA5 zl$q~KyQ93J-tfyS^3VDUPIE3$<+dd)rqB9Jm|=(QNjThSrSd~}n960n8E*5;(Orq^ zosmml*{8fUMP+@aiA6c9^3Y8o4a&M$E$!cNdlt1@-{`4TP8LHS93tYZzkRHwdNKJP zx35p~RH6HyY`?-Vllh?NHrcfg+yOsW9Wrao-*)ttA-wtgm3^|Qz+Uk~+- zo;7v%YbRG^=6Bcc(*;(yT+sNxGjWormuIh-=)zLJD&r(8?#8+PT~e_|*CYJAW6K~S zaW#{nzqRpq)K+SpB|FL{Sbr@4)zgX`bVhDb!i1kUae~ffpo8~Hu`VxC9^g&1YmWVZmCuZFT1#^#SrY+bC;@$>0Q{EF0|Vrt1o%dnHBp#-A8na7f`xZyc{Z0Js8*G&^U*~g zO`+CrN{qs*w&Yvt59CU!S~8mA48awTOnqiAQckvSR?JS$Xvs>*UzV#(oeVfQjnMM8 zxFlFBHN{gVIxgg66>m^~IOBM>_joIC&WTlLV;SDt;ZJ6J#xlRqc05Vot4~w%*jBZ$dtbi6GJ7*7(17shql}gur?m6x)Fy1ZFIbH13 zZ)GAPR)7{J`{2MS8XWno+a*SaO;--tx27B?H4Exw-acOi3ug4*6oWr)yMD%33t}vz zKz!+Ui$-Gl>aPX+!>yI^p?1?f*9^wuw6`T(V00lH!Q1AN+49_ClNy^tXnY-nhl$el_t=kc zKKewH<$&)$8b8nKLJHMPlW(mwOL!}Hk0f_V-Q)ff=(kkh76TN_0Pqa>Hx>RrFK$hG zrKkj4dq7kbn^=0$bMr)%`?f%aM;xhlxmCJjv^1-NFYX~*e#>a&3dHN-oers4nebm@ z*tUa@*TruF!W}uyTzLMV9NldaEjuxfyn`JJ(?i}jJvDr&++zZqvMhCJ*@iWzGVs*l z+0nF8H7C+>0(hqfu1N33#)(0*i**HGAWNEQ<$8Q z$3=-PoUY%RQO@!`TF@x4iE3U}=K5s$xm;GTc@gTILMz|UdUpytSbRcm@K zboy?3i1i)trAHSVSqxi(19HuyAQ-T&+f7EQd z8!iawn>K4hN&WkoHFo`x8QinEPorK&dvQz!uh88391ADE<2`Pb$AuE^uJAt1dyfv4 zvkE6e#o_OLFhpg2;|M){@@T)M>b{-lvl|Cg_ zgch328c;vK{PV`_7~Np>81e0Ixnk&(ylcTitqJN^XKXs_ zLJqm?7*?(|_nyU!sJ*|Gj1@Uc;RWVG>UW-!=o-LGIj9f9#SGg>OeEfc=Z#w2WxlUVLc2a0yJfZ&-chmT#`SDwtFl%Sg zE0)=+`P%P;Z??N8c;)q78$B}tin`GZsB!PCIp4?FAIq4v!IyP^7@P$xf5D0wrO zoM-od_wU#mT5G_|H788@4bJ>y*CQZ#1v8wvx6cY$C;gk2te%4}7A}ow_!Zz^bQT`^ULNkCQB>M(5$EdY=->NBLK@cQWy@E_dDM z{sTfDp5dILnQhs-Rv0c~fpTuxv`|Y3|C2C$$ZJZ+I`}*!NByzbLi#q zwd`$hb!I%RsuS0J00iA&I(>zo6bN~4bq*JeH(Mz0w{kygj5Cke50<%%vFVwc zt}Ujue;$%99*imP!2Rk-#g8ATtkcSDKHkjuR&WM2J^mAVBgre?fziOAafhBve%G|! z6+^4N^A%i=`b;?PFozoJ2HVHuKiobOs7Jbe4?Jv>@?KuD8=(MFaxIlLD<|K}^FA+_H6s z(UKS^O;uo2#AN!thv^JQ>W@p!^=*3ChA@wfPNzaBWt&Y~DFcm`DGg7i>AglUBEm&_ zGFGdmCM(TSUi-~bWJH-Qxp|TY6T}5}emZk9OP+Jlg!13^iDq|6N;7V&jwzN0LJ%$Z zTFX1iFtg>5DhnkgrO5koupej@8iLVlaLVx>%0^ffO5hlARVjw8oe8R8qXuX%IqU3L z@rx}{9sV5xaKe_qPQ1B1vE~Jmo({EI^b*P=1No=ET?BkjVbd=|9G>r{WG zP8OE6Cv)cvzDkjT&pfdc7i^uwHjuH6OAj?mnj!D0)d?IEEGl3d4N97eANqX!UX@El zlgl6dQS=_z!_9(Xk!fyRJOE_<+oLtyxkc>rY-HE(MfXBRfXF^$B{RxaXl}7-#uC)L zpkWKhT3-KwyFP_uecIY>pjxhVkb`XxKzzXXTUPw>G#P(9ZML3k=6ADS;&=(53>sgS zax-<>>#$A%#`}!ye=Nl$D=L!}e2;58oaqo>bP-6f0UH#KF0pOyvdRG{Tc~fwq|#=o zV=?<4Bk(uLx={cIHFV0=Y*I)M_3gx5eRQg40V3mXK7|h23V{+kxC80`=ZqeWKchpF z%Q=dblZ-n<6;~vMKnl|Ur?qKwpWo@l?UGpT zQiIBb`UZM-Z7w%}F^vEBR&QZpp_1y6oWJT-^F1mmDhW^11`ZOv8zpCKlPbtbwUahD zSo7t}OAnBQ&{WC9AhUehi@kFb+3eO=1IIZq8!=u6&HHjL^`-X+!pMKlelfQ{zo%Y2 za2OgmfTiDiM!Y7wo5N`T0AS=^nl88*y8T+y8(E<<1S(>rk#VKzallNJ|S*-JT$UQ zL)u^QoHzueU5DXUzIlElSZxGj+X9Fyo<2<_dI?W1H94?ODM|YN-6F5`h#liF3g|f6 zHN1nk*o?nViK#fw#~7~?IS4^A)&e)BDl=Ay_Qg)K_i9G$8$d>B;=QK&LJ;Zi88`T} zZ5&(J&-+!FLqu-ic@xppyD9Ab6|VrxzpEo93jzN~cCOvQbR;Q7Rs}p>{B}zJh>2l# z7ZTc*lK1v?xO%W2M%}Zt;hTk%=o@IK0(-g^ZnT8=uXk_9ErPBlf;dki$MY1QW4q)j z;h^``5|3iryIY8Au2S#o z`gn^;dHL4hcH3&QXrg;*mO4Z^rJ|n!8W)cyrCW84jt~yT;8#l}ulblC^Cn?RL92hHKJz5BJF`>A=^K1)C7BF?u9UPB z9l{OjG$qVxXO6!(FXeRb5Bw}tK!h>9cJS@yU{9_|BE7?Plj!p5r zjDyTV0>gNM4c?Aw;ns$1n%9mlf{~{e**Enu?oX4jQUA)Pn{yIsop;AO{1bFkN2VMP6kX4HCd{OE74KUko#aO=NS+Ke4tvg8p3WzHMf2h#)Tb1gzj0JU(~EJ55b0t3UmSq?D;?!p#kU{- E3ylXD%m4rY literal 0 HcmV?d00001 diff --git a/store/simpleprompt/assets/Square44x44Logo.png b/store/simpleprompt/assets/Square44x44Logo.png new file mode 100644 index 0000000000000000000000000000000000000000..3c606c6d2146471fbfafb471dadcfcc2b0925a54 GIT binary patch literal 921 zcmV;K17`e*P)}EFEkTxV6Y!yOUkw9rsOQ2GPV!@;Z4_5G|XR(JuPwBx^5&RcK zgdP-{ErJThi-AfZ)Otu$i?yb^B~_cvWPi+T=f~Sa*+XeUcCwqy1o|9@`SShDv(Ni| zXWk_?k!hMaPk5S;DG`nlk_VZ_PDrFCV3Cwt0$w!~W-ZGqZ*(i4zm!jPXV%XFI8V|< z1QHPoz@>WC{1PbIn*yg&{XlvqQ>k2D>E3SuhY>4DG4S*^ zYD#ym~a%y|L+j;g@I z6r!Q=pzZ}cLn79&06;S92AEc)yoWs^LZ);PzDkCDS|FA0g^@{YYh$Ln}Gn&oeA;;rvL-u9lV9bj{8->$6bn8QcD zn1a1hf%rjdkod&KimpcwnySKV?z@;CS;5%N& zV138KgCCc|0gFgAj2jE*3UD4)+Jacj3Mp)z}L?JaGq%#w!K;kJmYl;uE!|2H+)^2 zwCzU^nwOq=mGV5h;y!MCV{|2I047K#qbe|fGxh5FC!XW1Zt+j(Q)RQ+J~zNLA=3hm vK_pKyvqOMYkVQgn2~@#QQm`y*!-M__3G7hss0g(400000NkvXXu0mjf&Rwh? literal 0 HcmV?d00001 diff --git a/store/simpleprompt/assets/StoreLogo.png b/store/simpleprompt/assets/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..d01cb15bec1d875a24ec1e9a2cf040598589686b GIT binary patch literal 1018 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 bqTuZ36Hf;`LWpODLlppNVQWsgj3xdLyEL7| literal 0 HcmV?d00001 diff --git a/tests/storeManifest.test.ts b/tests/storeManifest.test.ts index 97a9b3f..0352348 100644 --- a/tests/storeManifest.test.ts +++ b/tests/storeManifest.test.ts @@ -68,6 +68,7 @@ describe("Windows Store manifest", () => { expect(manifest).toContain('Executable="SimplePrompt.exe"'); expect(manifest).toContain('Id="SimplePrompt"'); expect(simplePrompt.store.productId).toBe("9MT1X5BNTHQS"); + expect(simplePrompt.assetDirectory).toBe("simpleprompt/assets"); expect(simplePrompt.store.packageFamilyName).toBe( "ReleasedPtyLtd.SimplePrompt_q0b077qanz1d8", );