/* global React, ReactDOM, useTweaks, TweaksPanel, TweakSection, TweakRadio, TweakToggle, TweakColor */ const { useState, useEffect, useRef, useMemo } = React; // ---------- TWEAK DEFAULTS ---------- const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{ "accent": "#2A3FA0", "hero": "editorial", "dark": false, "showKeenDigital": true }/*EDITMODE-END*/; // ---------- THEME APPLICATION ---------- function useTheme(tweaks) { useEffect(() => { const root = document.documentElement; root.style.setProperty('--brand', tweaks.accent); root.style.setProperty('--brand-soft', tweaks.accent + '22'); root.style.setProperty('--brand-line', tweaks.accent + '55'); root.dataset.theme = tweaks.dark ? 'dark' : 'light'; }, [tweaks.accent, tweaks.dark]); } // ---------- IN-VIEW HOOK ---------- function useInView(threshold = 0.15) { const ref = useRef(null); const [inView, setInView] = useState(false); useEffect(() => { if (!ref.current) return; const obs = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setInView(true); obs.disconnect(); } }, { threshold }); obs.observe(ref.current); return () => obs.disconnect(); }, []); return [ref, inView]; } // ---------- SUB-COMPONENTS ---------- function TopBar({ tweaks }) { return (
keenFRANCHISE
Sign in Book a demo
); } function Hero({ tweaks }) { if (tweaks.hero === 'split') return ; return ; } function HeroEditorial() { return (
The franchise operations platform Vol. 01 · Edition 2026 · Multi-tenant SaaS

Run the entire franchise relationship on one platform — from a franchisee's first order to corporate's network-wide compliance.

KeenFranchise unifies ordering, supply, training, audits, analytics, and the new-location launch — under your brand, behind your subdomain, in one operating system for the back of house.

See platform tour Tour the platform
BUILT FOR
  • Multi-unit franchisors
  • Emerging brands (5–50 units)
  • Enterprise networks (50+)
  • Multi-brand operators
); } function HeroSplit() { return (
The franchise operations platform

One platform.
Every location.
Your brand on top.

KeenFranchise unifies ordering, supply, training, audits, and analytics under your tenant-branded portal — replacing the patchwork of tools your franchisees juggle today.

); } function HeroDiagram() { const [ref, inView] = useInView(); return (
{/* Top: brand ring */}
portal.your-brand.com tenant-branded
{/* Center: orchestration core */}
Orchestration
KeenFranchise
multi-tenant · RLS-isolated
{/* Surrounding capability nodes */} {[ { label: 'Ordering', sub: 'Sysco · EDI · sauce', cls: 'n1' }, { label: 'Inventory', sub: 'per-location counts', cls: 'n2' }, { label: 'Audits', sub: 'scoring · CAs', cls: 'n3' }, { label: 'Training', sub: '5 quiz formats', cls: 'n4' }, { label: 'AI Search', sub: 'pgvector · cited', cls: 'n5' }, { label: 'Launcher', sub: 'dependency engine', cls: 'n6' }, { label: 'Analytics', sub: 'NPS · food cost', cls: 'n7' }, { label: 'Comms', sub: 'docs · tickets', cls: 'n8' }, ].map(n => (
{n.label}
{n.sub}
))} {/* Connector lines (svg) */} {[ 'M400,240 L120,80', 'M400,240 L400,80', 'M400,240 L680,80', 'M400,240 L60,240', 'M400,240 L740,240', 'M400,240 L120,400', 'M400,240 L400,400', 'M400,240 L680,400', ].map((d, i) => ( ))} {/* Floating ticker */}
+82 orders today 14 launches in flight 96.4% audit pass NPS 71 rolling 30d
); } function PillarsBand() { const pillars = [ { kw: 'One platform', body: 'Ordering, ops, training, comms, and analytics in one — not four vendors stitched together.' }, { kw: 'Tenant-branded', body: 'Your logo, your colors, your subdomain. Franchisees never see KeenFranchise.' }, { kw: 'AI-native', body: 'Natural-language answers from your SOPs and training, with citations and role-aware retrieval.' }, { kw: 'Mobile-first', body: 'The back-of-house lives on a phone. Every workflow is built tablet- and phone-ready.' }, ]; return (
The thesis

Built for how franchises actually run.

Most franchise platforms pick a lane — ordering, or ops, or training. KeenFranchise does all of it, because the work doesn't separate cleanly. An audit creates a corrective action. A failed quiz blocks a promotion. A low-stock alert triggers an order. We model the whole loop.

{pillars.map((p, i) => (
0{i + 1}

{p.kw}

{p.body}

))}
); } // ---------- THEATER: ORDERING ---------- function OrderingTheater() { const [ref, inView] = useInView(0.2); return (
Ordering & Supply

One cart. Every vendor. Including yours.

Sysco, EDI distributors, and your own proprietary fulfillment behave like a single supplier to the franchisee. Behind the scenes, a connector layer routes each line to the right system — API, EDI 850/855/856/810, or your corporate production queue.

{/* Left: franchisee cart card */}
Cart · Store #214 Westgate 3 vendors
  • SY-104822 Buffalo wings, IQF, 40 lb 2 $ 198.40
  • EDI-PFG-7711 Fryer oil, 35 lb jug 4 $ 128.60
  • SAUCE-ORG-12 Original wing sauce, 12-pack 3 $ 144.00
One checkout$ 471.00
{/* Center: connector layer */}
Vendor connector layer
API
EDI
Internal
{/* Right: three connector cards */}
API
Sysco GraphQL
apic-devportal · Apollo Federation · 8 subgraphs
EDI
Orderful gateway
850 · 855 · 856 · 810 · 997
Internal
Sauce production
batch planner · lot tracking · ShipStation
Franchisees don't learn three ordering systems. One catalog, one cart, one tracking page across all vendors. Add a new distributor in a config row, not a release. Manual, EDI, and API vendors look identical to the app. Sell proprietary product (sauces, packaging, retail goods) with the same cart — auto-invoice via Stripe. Unified delivery events from Sysco's API, EDI 856 ASNs, and AfterShip — one timeline per order.
); } function Benefit({ kw, children }) { return (
{kw}

{children}

); } // ---------- THEATER: AI SEARCH ---------- function AITheater() { const messages = [ { role: 'user', text: 'How do I handle a customer with a cold-order complaint at the counter?' }, { role: 'ai', text: 'Apologize, remake the order at no charge, and log the incident. The Customer Recovery SOP (rev. 4, signed Mar 2026) requires a manager to verify the remake and the POS comp code GR-04.', citations: [ { doc: 'Customer Recovery SOP', page: 'p.3' }, { doc: 'POS Comp Codes', page: '§2.1' }, ]}, ]; const [shown, setShown] = useState(0); const [ref, inView] = useInView(0.3); useEffect(() => { if (!inView) return; let i = 0; const t = setInterval(() => { i++; setShown(i); if (i >= messages.length) clearInterval(t); }, 900); return () => clearInterval(t); }, [inView]); return (
Intelligence Layer

Your SOPs, answered like a colleague.

KeenFranchise embeds every document, training transcript, and recipe into a per-tenant vector index. Staff ask in plain language; answers come back with citations, scoped to what their role is allowed to see. No more "go find the manual."

⌘K · Ask KeenFranchise
{messages.slice(0, shown + 1).map((m, i) => (
{m.text}
{m.citations && (
{m.citations.map(c => ( {c.doc} {c.page} ))}
)}
))} {shown < messages.length &&
}
1. Ingest SOPs · video transcripts · recipes
2. Embed per-tenant pgvector index
3. Filter role-scoped retrieval
4. Cite answer + linked sources
PDFs, text docs, and Whisper-transcribed video — searched in one index. Staff never receive an answer sourced from a doc they're not authorized to read. Every answer points to the exact doc and section. Trust, not hallucination. Questions with no good answer surface as a content backlog for franchisor ops.
); } // ---------- THEATER: LAUNCHER ---------- function LauncherTheater() { const phases = [ { name: 'Site Prep', status: 'done', tasks: 8, doneTasks: 8 }, { name: 'Permitting', status: 'done', tasks: 6, doneTasks: 6 }, { name: 'Construction', status: 'active', tasks: 12, doneTasks: 9 }, { name: 'Training', status: 'pending', tasks: 9, doneTasks: 0 }, { name: 'Pre-Opening', status: 'pending', tasks: 7, doneTasks: 0 }, { name: 'Grand Opening', status: 'pending', tasks: 4, doneTasks: 0 }, ]; return (
Location Launcher

From signed FDD to grand opening, in one shared board.

Templated launch playbooks with a real dependency engine: complete a permit task, and the construction tasks downstream automatically unlock with their due-date offsets applied. Corporate sees every active launch on one portfolio dashboard, color-coded on-track / at-risk / behind.

{phases.map((p, i) => (
{p.name}
{p.doneTasks}/{p.tasks}
))}
Portfolio · 14 launches in flight on-track at-risk behind
{[ ['Westgate Plaza', 'ok', 62], ['Riverbend Mall', 'ok', 88], ['Pine & 6th', 'risk', 34], ['Northpark', 'ok', 71], ['Harbor District', 'late', 18], ['Crossroads', 'ok', 54], ['Lakeshore', 'risk', 41], ['Old Mill', 'ok', 79], ].map(([name, st, pct]) => (
{name}
{pct}%
))}
Mark a task complete; downstream tasks unlock with offset due-dates and the right owner notified. Corporate and franchisee see the same board, with role-relevant tasks highlighted. Add a market-specific permit or weather contingency without modifying the master template. A single dashboard answers: which of our 14 launches needs attention this week?
); } // ---------- OPERATIONS GRID ---------- function OperationsGrid() { const groups = [ { eyebrow: 'Compliance', title: 'Audits, CAs, and certifications, joined up.', points: [ ['Audit templates & scoring', 'Schedule, score, and trend pass-rates network-wide.'], ['Corrective actions', 'Auto-spawned from failed audits or low NPS — assigned, tracked, closed.'], ['Staff certifications', 'Expiry tracking with nightly sweeps and re-cert nudges.'], ['Equipment & maintenance', 'Register, log, and schedule preventive maintenance per location.'], ], }, { eyebrow: 'Training', title: 'Microlearning your team finishes.', points: [ ['5 quiz formats', 'MCQ, true/false, reorder, fill-in-blank, matching — built in.'], ['Manager sign-off', 'Hands-on portions verified by a real manager before completion.'], ['Compliance auto-repeat', 'Food-safety re-cert every N days, automatic re-enrollment.'], ['Mobile-first player', 'Swipe-to-advance, auto-save per lesson, video resume.'], ], }, { eyebrow: 'Analytics', title: 'Numbers your operators actually use.', points: [ ['Location scorecard', 'Composite health score combining sales, audits, NPS, and waste.'], ['Menu costing', 'Theoretical food cost % per item, plus margin pressure alerts.'], ['NPS & feedback', 'Public submission form, trend charts, auto-CA on low ratings.'], ['Excel/CSV export', 'Every analytics surface exports — no "API only" lock-in.'], ], }, { eyebrow: 'Comms', title: 'One channel, properly logged.', points: [ ['Announcements', 'Email broadcast with read/unread per franchisee.'], ['Document library', 'SOPs and HR docs with acknowledgement tracking.'], ['Support tickets', 'Franchisee↔corporate, threaded, email-notified.'], ['Recipe versioning', 'Versioned recipes with required acknowledgement on changes.'], ], }, ]; return (
The rest of the platform

A real operating system, not a CMS with a logo.

Every loop you run today — audits, training, communications, performance — is a first-class module with its own data model, its own UI, and analytics built in. Not an integration. Not an afterthought.

{groups.map(g => (
{g.eyebrow}

{g.title}

    {g.points.map(([k, v]) => (
  • {k} {v}
  • ))}
))}
); } // ---------- WHITELABEL & SECURITY ---------- function WhyBand() { return (
White-label by design

Your brand is the front door.
KeenFranchise is invisible.

Tenants get their own subdomain (portal.your-brand.com), their own logo and color tokens, email notifications sent as them, and franchisee-facing UI that never mentions us. We're plumbing. You're the brand.

  • Per-tenant branding — logo, colors, and accent applied to every surface.
  • Custom subdomain — wildcard TLS via Caddy, provisioned in onboarding.
  • Branded email — DKIM-signed transactional mail from your domain.
  • Role-aware portals — corporate and franchisee see different navigation, same data spine.
{[ { name: 'Wing Republic', sub: 'portal.wingrepublic.com', accent: '#E8763A' }, { name: 'Saltline Co.', sub: 'app.saltline.co', accent: '#1F6F4A' }, { name: 'Brickyard Pizza', sub: 'ops.brickyardpizza.com', accent: '#B23A3A' }, ].map((b, i) => (
{b.name}
{b.sub}
OperationsCatalogSupplyCompliance
Active orders14
Open CAs3
Audit pass96%
))}
Multi-tenant, isolated by Postgres RLS

Every table carries a tenantId. Row-level security policies enforce isolation in the database, not the app — so a misplaced query can't leak across tenants. Closed-by-default; integration-tested with real Postgres in CI.

SELECT … FROM orders WHERE tenant_id = current_tenant()
); } // ---------- MOBILE STRIP ---------- function MobileStrip() { return (
Mobile-first

Designed for the back-of-house, where nobody has a laptop.

My tasks · 4 due today
  • Temp log — walk-in cooler
  • Sanitizer concentration
  • Receive PFG delivery
  • Retrain Maria on new sauce
⌘K Ask KeenFranchise
Lesson 3/8 · Wing-coat technique
Order the steps for sauce coating:
  1. Drain fryer basket
  2. Add sauce to bowl
  3. Toss 8–10 times
  4. Plate immediately
Order #4421 · Sauces
Submitted
Confirmed · ETA Apr 30
In production · Batch L-2026-08
Shipped
Delivered
); } // ---------- COMPARISON ---------- function Comparison() { const rows = [ ['Unified ordering (API + EDI + internal)', 'yes', 'partial', 'no'], ['Tenant-branded white-label portal', 'yes', 'no', 'partial'], ['AI search with RBAC-at-retrieval', 'yes', 'partial', 'no'], ['Dependency-aware Location Launcher', 'yes', 'yes', 'partial'], ['Microlearning + 5 quiz formats', 'yes', 'yes', 'no'], ['NPS, scorecard, food-cost analytics', 'yes', 'partial', 'partial'], ['Database-level tenant isolation', 'yes', 'unknown', 'unknown'], ['Resellable as your own platform', 'yes', 'no', 'no'], ]; const Cell = ({ v }) => { if (v === 'yes') return ; if (v === 'partial') return ; if (v === 'no') return ; return ?; }; return (
Where we land

Honest head-to-head.

KeenFranchise
Delightree
FranConnect
{rows.map(r => (
{r[0]}
))}

Comparison reflects publicly available product information as of Apr 2026 and our own evaluation. We'll show you the live demo.

); } // ---------- CTA + FOOTER ---------- function FinalCTA() { return (

See the platform tour.

A 25-minute walk-through with a KeenFranchise architect — your brand, your data shape, your real workflows. No SDR script.

e.preventDefault()}>
· No card required · White-glove onboarding · Live data within 14 days
); } function Footer({ tweaks }) { return ( ); } // ---------- TWEAKS ---------- function SiteTweaks({ tweaks, setTweak }) { return ( setTweak('hero', v)} options={[ { value: 'editorial', label: 'Editorial' }, { value: 'split', label: 'Split' }, ]} /> setTweak('dark', v)} /> setTweak('accent', v)} />
{[ ['#1F4D3A', 'Deep forest'], ['#0F3D6E', 'Deep ocean'], ['#2A3FA0', 'Indigo ink'], ['#6E2E3E', 'Bordeaux'], ['#7A5A2E', 'Bronze'], ['#1A1A1A', 'Charcoal'], ].map(([c, name]) => (
setTweak('showKeenDigital', v)} />
); } // ---------- APP ---------- function App() { const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS); useTheme(tweaks); return ( <>