Files
Joey-nexu f1a0b60c6c chore: unify product name display to OpenDesign repo-wide (#6998)
* chore: unify product name display to OpenDesign repo-wide

Rename the user-facing product name from "Open Design" to "OpenDesign"
across documentation, web/desktop UI strings, i18n locales, prompts,
plugin/skill/design-system content, changelogs, and specs, matching the
landing-page rename already in flight.

Deliberately excluded so installed-app identity stays untouched:
- packages/release PRODUCT_NAME and everything derived from it
  (app bundle names, installer/registry identity, updater feeds)
- tools/pack, tools/release, apps/packaged, landing page (PR #6983)
- mocks/ recorded traces and LICENSE
- test fixtures and comments that encode real on-disk install paths
  (Open Design.app, Program Files\Open Design, AppData paths)

Validated with pnpm guard, pnpm typecheck, and targeted web/desktop/host
test runs over the renamed strings.

* fix(web): keep two-letter author monogram for CamelCase brand names

authorInitials split words on whitespace only, so the renamed single-token
author name "OpenDesign" produced a one-letter monogram. Treat lower-to-upper
CamelCase boundaries as word breaks so the avatar fallback stays "OD".

* fix(release): restore spaced Windows source artifact names in win.ps1

tools/pack still derives builder outputs from the unchanged PRODUCT_NAME
("Open Design"), so the asset script must look for the spaced filenames.
Matches the mac/mac-intel/linux asset scripts, which were already kept on
the original names.

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-08-18 07:09:45 +00:00

172 lines
9.0 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Write an Operating Review like a Disciplined COO - Corporate strategy</title>
<style>
:root {
--bg: #fafaf9; --fg: #1c1b1a; --muted: #6b6964; --accent: #c96442; --surface: #ffffff;
}
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body {
background: var(--bg);
color: var(--fg);
font: 18px/1.5 -apple-system, system-ui, sans-serif;
display: flex;
overflow-x: auto;
overflow-y: hidden;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
}
body::-webkit-scrollbar { display: none; }
.slide {
flex: 0 0 100vw;
height: 100vh;
scroll-snap-align: start;
padding: 80px 96px;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
}
.slide.title { background: var(--fg); color: var(--bg); }
.eyebrow { font-size: 12px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent); margin-bottom: 28px; }
.slide h1 { font-size: clamp(48px, 7vw, 96px); line-height: 1.05; letter-spacing: -0.025em; margin: 0 0 20px; max-width: 16ch; }
.slide h2 { font-size: clamp(32px, 4vw, 48px); letter-spacing: -0.015em; margin: 0 0 20px; max-width: 20ch; }
.slide .body { font-size: 22px; color: var(--muted); max-width: 56ch; }
.slide.title .body { color: rgba(250,250,249,0.7); }
.slide.big-stat .number { font-size: clamp(120px, 22vw, 280px); line-height: 0.9; letter-spacing: -0.04em; color: var(--accent); margin-bottom: 16px; font-weight: 600; }
.slide.big-stat .caption { font-size: 24px; color: var(--muted); max-width: 24ch; }
.quote-mark { font-family: Georgia, serif; font-size: 200px; line-height: 0.7; color: var(--accent); opacity: 0.18; margin-bottom: -40px; }
.quote-text { font-family: Georgia, serif; font-size: 36px; line-height: 1.3; max-width: 26ch; margin: 0 0 28px; }
.quote-author { font-size: 14px; color: var(--muted); }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 32px; margin-top: 40px; }
.grid-3 .pt { border-top: 2px solid var(--accent); padding-top: 16px; }
.grid-3 .pt .h { font-size: 18px; font-weight: 500; margin: 0 0 8px; }
.grid-3 .pt .p { color: var(--muted); margin: 0; font-size: 16px; }
.counter { position: fixed; bottom: 24px; right: 32px; font-family: ui-monospace, monospace; font-size: 12px; color: var(--muted); background: var(--surface); padding: 4px 10px; border-radius: 999px; border: 1px solid #e6e4e0; }
.hint { position: fixed; bottom: 24px; left: 32px; font-size: 11px; color: var(--muted); }
</style>
</head>
<body>
<section class="slide title" data-od-id="slide-1">
<div class="eyebrow" style="color:#c96442;">Corporate Strategy · Operating Review · Board Pre-Read</div>
<h1>Approve the plan: cut burn 35%, protect the open core.</h1>
<p class="body">OpenDesign's Q2 operating review — growth, burn, and the concrete path to sustainability, without gating the Apache-2.0 project that got us here.</p>
</section>
<section class="slide" data-od-id="slide-2">
<div class="eyebrow">Recommendation</div>
<h2>Extend runway to 22 months without touching the open core.</h2>
<p class="body">Growth is not the problem — 76K GitHub stars and 200+ community plugins prove demand. The gap is cost structure: we're subsidizing every user's model calls. The fix is to make BYOK the default, license self-hosted enterprise deployments, and leave the open-source desktop app untouched.</p>
</section>
<section class="slide big-stat" data-od-id="slide-3">
<div class="number">42%</div>
<div class="caption">Cloud inference spend cut, from $187K to $109K/month, since local-first agent routing shipped in Q1.</div>
</section>
<section class="slide" data-od-id="slide-4">
<div class="eyebrow">Options considered</div>
<h2>Three paths to sustainability.</h2>
<div class="grid-3">
<div class="pt"><h3 class="h">A — Hold course</h3><p class="p">Keep subsidizing hosted inference. Burn stays flat at $187K/mo; runway closes to 9 months by Q4.</p></div>
<div class="pt"><h3 class="h">B — BYOK default (recommended)</h3><p class="p">Users bring their own model keys by default. Cuts hosted inference cost ~40% with zero change to the open-source app.</p></div>
<div class="pt"><h3 class="h">C — Enterprise self-host license</h3><p class="p">Charge for managed self-hosted deployments and support, on top of B, without paywalling any existing feature.</p></div>
</div>
</section>
<section class="slide" data-od-id="slide-5">
<div class="quote-mark">"</div>
<p class="quote-text">If we gate any existing plugin or feature behind a paywall to hit these numbers, we break the open-source flywheel that built the 200+ plugin ecosystem — that risk isn't worth the savings.</p>
<p class="quote-author">— Risk flagged jointly by Engineering and Community leads</p>
</section>
<section class="slide title" data-od-id="slide-6">
<div class="eyebrow" style="color:#c96442;">Decision needed by July 23 board meeting</div>
<h1>Approve BYOK-default plus the enterprise self-host license.</h1>
<p class="body">Owners: Maria Chen (CFO) runs the burn-down plan · Alex Osei (Head of Platform) ships BYOK-default in Q3 · Community leads keep plugin governance fully open throughout.</p>
</section>
<div class="counter" id="counter">1 / 6</div>
<div class="hint">← / → to navigate</div>
<script>
const slides = document.querySelectorAll('.slide');
const counter = document.getElementById('counter');
let active = 0;
// Detect the real scroller — when body has `display: flex` + `overflow-x: auto`
// the scroller can be body OR documentElement depending on the host (in
// particular, the OD srcdoc iframe). Pick whichever actually overflows.
function scrollContainers() {
return [document.scrollingElement, document.documentElement, document.body]
.filter(Boolean)
.filter((el, idx, arr) => arr.indexOf(el) === idx);
}
function overflowX(el) {
if (!el) return 0;
return Math.max(0, (el.scrollWidth || 0) - (el.clientWidth || 0));
}
function scrollLeftOf(el) {
if (!el) return 0;
try { return Number(el.scrollLeft) || 0; } catch (_) { return 0; }
}
function activeScrollLeft() {
return scrollContainers().reduce((best, el) => {
const left = scrollLeftOf(el);
return Math.abs(left) > Math.abs(best) ? left : best;
}, 0);
}
function scroller() {
return scrollContainers().reduce((best, el) => {
const bestScore = overflowX(best) + Math.abs(scrollLeftOf(best)) * 2;
const score = overflowX(el) + Math.abs(scrollLeftOf(el)) * 2;
return score > bestScore ? el : best;
}, scrollContainers()[0] || document.documentElement);
}
function scrollToLeft(left, behavior) {
for (const el of scrollContainers()) {
try { el.scrollLeft = left; } catch (_) {}
try { el.scrollTo({ left, behavior: behavior || 'smooth' }); } catch (_) {}
}
}
function go(i) {
const next = Math.max(0, Math.min(slides.length - 1, i));
active = next;
counter.textContent = (next + 1) + ' / ' + slides.length;
scrollToLeft(next * window.innerWidth, 'smooth');
}
function syncFromScroll() {
const i = Math.round(activeScrollLeft() / window.innerWidth);
if (i !== active && i >= 0 && i < slides.length) {
active = i;
counter.textContent = (i + 1) + ' / ' + slides.length;
}
}
function onKey(e) {
if (e.defaultPrevented) return;
if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA')) return;
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'PageDown') { e.preventDefault(); go(active + 1); }
else if (e.key === 'ArrowLeft' || e.key === 'PageUp') { e.preventDefault(); go(active - 1); }
else if (e.key === 'Home') { e.preventDefault(); go(0); }
else if (e.key === 'End') { e.preventDefault(); go(slides.length - 1); }
}
// Listen on both window and document in capture phase so the handler
// fires regardless of which element holds focus inside the iframe.
window.addEventListener('keydown', onKey, true);
document.addEventListener('keydown', onKey, true);
// And listen for scroll on both surfaces — same reason.
document.addEventListener('scroll', syncFromScroll, { passive: true, capture: true });
window.addEventListener('scroll', syncFromScroll, { passive: true });
// Auto-focus body so arrow keys work without a click.
document.body.setAttribute('tabindex', '-1');
document.body.style.outline = 'none';
function focusDeck() { try { window.focus(); document.body.focus({ preventScroll: true }); } catch (_) {} }
document.addEventListener('mousedown', focusDeck);
window.addEventListener('load', focusDeck);
focusDeck();
</script>
</body>
</html>