Harden public product reality surfaces

This commit is contained in:
axiomlogicnexus 2026-07-02 09:22:39 +00:00
parent 02399cf736
commit 2dbdf20e52
9 changed files with 791 additions and 440 deletions

View file

@ -398,7 +398,22 @@ When a new packet materially adds or changes a normalized feature:
## Summary
### Latest continuity note (`2026-06-30`)
### Latest continuity note (`2026-07-02`)
- the first-party `website/` public/auth shell now carries one shared
product-reality strip across public marketing routes and browser account
access routes:
- desktop Unreal remains the simulator authority
- the website remains the account, pricing, documentation, release, notices,
support, and protected handoff surface
- keyboard and mouse remain the viable current development and operator
review path
- live commerce/download/auth configuration plus physical headset/controller
observation remain explicit launch gates
- this is a copy and layout coherence hardening pass, not a topology widening
and not a promotion of the optional full-browser simulator branch
### Previous continuity note (`2026-06-30`)
- the first-party `website/` public manual lane widened again without changing
topology:

View file

@ -184,6 +184,25 @@ controller settings, layout presets, and rebinding truth for the exact shipped
XR input ids. But it is still not the same thing as packaged
headset/controller proof or generic marketed VR completion.
The current launch-UX truth on `2026-07-02` should be stated the same way on
public pages, manuals, and operator closeouts:
- keyboard and mouse development is viable now because the desktop runtime owns
shipped classic keyboard, pointer, orbit, zoom, and higher-dimensional
interaction surfaces
- bounded XR runtime, controller-settings, layout, and rebinding ownership are
real first-party seams, but live headset-connected and controller-input
observation remain the proof gates before marketing finished VR completion
- the raw first-run desktop experience is still dashboard/operator-surface
centered rather than a fully polished consumer start menu, so any rollout
copy must keep first-launch verification, pairing, and settings truth visible
- Paddle checkout URLs, public Windows release URLs, production auth/runtime
configuration, and corresponding-source/notices URLs remain deployment
configuration gates, not hardcoded product facts
- the website remains useful because it owns public docs, account, pricing,
notices, release posture, support, and protected download handoff; it should
not be described as simulator parity with the downloadable
Canonical audit note:
- `C:\HyperTwist\docs\ops\HYPERTWIST_UNREAL_INPUT_AND_XR_COMPLETENESS_AUDIT_2026-06-22.md`

View file

@ -413,7 +413,15 @@ Current consolidated milestone snapshot:
later same-family continuity follow-up widens that same decision guide
across the public open-source notices, privacy, terms, and
shipping/payment routes as well so the remaining legal/distribution pages do
not fall back to older implicit next-step wording
not fall back to older implicit next-step wording,
and the current `2026-07-02` public/auth shell polish pass now adds one
shared product-reality strip across the public marketing shell plus the
browser account routes so every public-facing route repeats the same
launch-safe truth: the downloadable desktop runtime is the simulator
authority, the website owns account/pricing/docs/release/notice handoff,
keyboard and mouse are the viable current development path, and live
checkout/download/auth plus physical headset/controller observation remain
explicit gates rather than hidden assumptions
- classic-cube `Phase 9A` replay recording is now closed through first-party
runtime capture, `.json` replay persistence, local playback reconstruction,
schema-light replay normalization across save/load/viewer import, and live

View file

@ -43,6 +43,22 @@ interface BillingStateStoreOptions {
now?: () => Date
}
interface BillingEventIdentityCandidates {
subscriptionIds: string[]
transactionIds: string[]
customerIds: string[]
}
interface BillingStateStoreRuntime {
defaultPlan: BillingPlan
defaultRole: BillingRole
productPlanMap: Record<string, BillingPlan>
pricePlanMap: Record<string, BillingPlan>
now: () => Date
loadState: () => BillingStateFile
saveState: (state: BillingStateFile) => void
}
export interface BillingEventApplyResult {
kind: 'applied' | 'duplicate' | 'ignored' | 'noop'
reason:
@ -311,6 +327,149 @@ function parsePaddleEvent(rawBody: string): PaddleEventEnvelope | null {
}
}
function readPrimaryEventId(envelope: PaddleEventEnvelope, prefix: 'subscription' | 'transaction'): string {
const primaryId = readStringAtPath(envelope.data, ['id'])
return primaryId && envelope.event_type.startsWith(`${prefix}.`) ? primaryId : ''
}
function buildEventIdentityCandidates(envelope: PaddleEventEnvelope): BillingEventIdentityCandidates {
return {
subscriptionIds: [
readPrimaryEventId(envelope, 'subscription'),
...readAllCandidateIds(envelope.data, 'subscriptionId'),
].filter((value): value is string => Boolean(value)),
transactionIds: [
readPrimaryEventId(envelope, 'transaction'),
...readAllCandidateIds(envelope.data, 'transactionId'),
].filter((value): value is string => Boolean(value)),
customerIds: readAllCandidateIds(envelope.data, 'customerId'),
}
}
function findExistingAccount(
state: BillingStateFile,
email: string,
ids: BillingEventIdentityCandidates,
): BillingEntitlementState | null {
if (email && state.accounts[email]) {
return state.accounts[email]
}
for (const account of Object.values(state.accounts)) {
if (
(account.subscriptionId && ids.subscriptionIds.includes(account.subscriptionId))
|| (account.transactionId && ids.transactionIds.includes(account.transactionId))
|| (account.customerId && ids.customerIds.includes(account.customerId))
) {
return account
}
}
return null
}
function markProcessedEvent(
state: BillingStateFile,
envelope: PaddleEventEnvelope,
saveState: (state: BillingStateFile) => void,
) {
state.processedEvents[envelope.event_id] = envelope.occurred_at
saveState(state)
}
function buildNextEntitlementState({
envelope,
existingAccount,
ids,
resolvedEmail,
runtime,
}: {
envelope: PaddleEventEnvelope
existingAccount: BillingEntitlementState | null
ids: BillingEventIdentityCandidates
resolvedEmail: string
runtime: Pick<BillingStateStoreRuntime, 'defaultPlan' | 'defaultRole' | 'productPlanMap' | 'pricePlanMap' | 'now'>
}): BillingEntitlementState {
const planFromItems = resolvePlanFromItemMaps(envelope.data, runtime.productPlanMap, runtime.pricePlanMap)
const nextPlan = planFromItems
?? readPlanFromCustomData(envelope.data, existingAccount?.plan ?? runtime.defaultPlan)
const nextRole = readRoleFromCustomData(envelope.data, existingAccount?.role ?? runtime.defaultRole)
const accessStatus = inferAccessStatus(envelope.event_type, envelope.data)
return {
email: resolvedEmail,
plan: nextPlan,
role: nextRole,
canDownload: deriveCanDownload(nextPlan, accessStatus),
accessStatus,
source: 'paddle',
customerId: ids.customerIds[0] || existingAccount?.customerId || null,
subscriptionId: ids.subscriptionIds[0] || existingAccount?.subscriptionId || null,
transactionId: ids.transactionIds[0] || existingAccount?.transactionId || null,
lastEventId: envelope.event_id,
lastEventType: envelope.event_type,
lastEventAt: envelope.occurred_at,
updatedAt: toIsoString(runtime.now()),
}
}
function applyParsedPaddleEvent(
envelope: PaddleEventEnvelope,
runtime: BillingStateStoreRuntime,
): BillingEventApplyResult {
const state = runtime.loadState()
if (state.processedEvents[envelope.event_id]) {
return {
kind: 'duplicate',
reason: 'duplicate_event',
eventId: envelope.event_id,
eventType: envelope.event_type,
}
}
const email = readEmailFromEventData(envelope.data)
const ids = buildEventIdentityCandidates(envelope)
const existingAccount = findExistingAccount(state, email, ids)
if (existingAccount && !isLaterEvent(existingAccount.lastEventAt, envelope.occurred_at)) {
markProcessedEvent(state, envelope, runtime.saveState)
return {
kind: 'ignored',
reason: 'stale_event',
eventId: envelope.event_id,
eventType: envelope.event_type,
email: existingAccount.email,
}
}
const resolvedEmail = email || existingAccount?.email || ''
if (!resolvedEmail) {
markProcessedEvent(state, envelope, runtime.saveState)
return {
kind: 'noop',
reason: 'unresolved_account',
eventId: envelope.event_id,
eventType: envelope.event_type,
}
}
state.accounts[resolvedEmail] = buildNextEntitlementState({
envelope,
existingAccount,
ids,
resolvedEmail,
runtime,
})
markProcessedEvent(state, envelope, runtime.saveState)
return {
kind: 'applied',
reason: 'processed',
eventId: envelope.event_id,
eventType: envelope.event_type,
email: resolvedEmail,
}
}
export function createBillingStateStore({
statePath,
defaultPlan,
@ -332,32 +491,6 @@ export function createBillingStateStore({
writeJsonFile(statePath, state)
}
function findExistingAccount(
state: BillingStateFile,
email: string,
ids: {
subscriptionIds: string[]
transactionIds: string[]
customerIds: string[]
},
): BillingEntitlementState | null {
if (email && state.accounts[email]) {
return state.accounts[email]
}
for (const account of Object.values(state.accounts)) {
if (
(account.subscriptionId && ids.subscriptionIds.includes(account.subscriptionId))
|| (account.transactionId && ids.transactionIds.includes(account.transactionId))
|| (account.customerId && ids.customerIds.includes(account.customerId))
) {
return account
}
}
return null
}
function getEntitlementByEmail(email: string): BillingEntitlementState | null {
const normalizedEmail = normalizeEmail(email)
if (!normalizedEmail) {
@ -379,89 +512,15 @@ export function createBillingStateStore({
}
}
const state = loadState()
if (state.processedEvents[envelope.event_id]) {
return {
kind: 'duplicate',
reason: 'duplicate_event',
eventId: envelope.event_id,
eventType: envelope.event_type,
}
}
const email = readEmailFromEventData(envelope.data)
const ids = {
subscriptionIds: [
readStringAtPath(envelope.data, ['id']) && envelope.event_type.startsWith('subscription.')
? String(readStringAtPath(envelope.data, ['id']))
: '',
...readAllCandidateIds(envelope.data, 'subscriptionId'),
].filter(Boolean),
transactionIds: [
readStringAtPath(envelope.data, ['id']) && envelope.event_type.startsWith('transaction.')
? String(readStringAtPath(envelope.data, ['id']))
: '',
...readAllCandidateIds(envelope.data, 'transactionId'),
].filter(Boolean),
customerIds: readAllCandidateIds(envelope.data, 'customerId'),
}
const existingAccount = findExistingAccount(state, email, ids)
if (existingAccount && !isLaterEvent(existingAccount.lastEventAt, envelope.occurred_at)) {
state.processedEvents[envelope.event_id] = envelope.occurred_at
saveState(state)
return {
kind: 'ignored',
reason: 'stale_event',
eventId: envelope.event_id,
eventType: envelope.event_type,
email: existingAccount.email,
}
}
const resolvedEmail = email || existingAccount?.email || ''
if (!resolvedEmail) {
state.processedEvents[envelope.event_id] = envelope.occurred_at
saveState(state)
return {
kind: 'noop',
reason: 'unresolved_account',
eventId: envelope.event_id,
eventType: envelope.event_type,
}
}
const planFromItems = resolvePlanFromItemMaps(envelope.data, productPlanMap, pricePlanMap)
const nextPlan = planFromItems
?? readPlanFromCustomData(envelope.data, existingAccount?.plan ?? defaultPlan)
const nextRole = readRoleFromCustomData(envelope.data, existingAccount?.role ?? defaultRole)
const accessStatus = inferAccessStatus(envelope.event_type, envelope.data)
const nextState: BillingEntitlementState = {
email: resolvedEmail,
plan: nextPlan,
role: nextRole,
canDownload: deriveCanDownload(nextPlan, accessStatus),
accessStatus,
source: 'paddle',
customerId: ids.customerIds[0] || existingAccount?.customerId || null,
subscriptionId: ids.subscriptionIds[0] || existingAccount?.subscriptionId || null,
transactionId: ids.transactionIds[0] || existingAccount?.transactionId || null,
lastEventId: envelope.event_id,
lastEventType: envelope.event_type,
lastEventAt: envelope.occurred_at,
updatedAt: toIsoString(now()),
}
state.accounts[resolvedEmail] = nextState
state.processedEvents[envelope.event_id] = envelope.occurred_at
saveState(state)
return {
kind: 'applied',
reason: 'processed',
eventId: envelope.event_id,
eventType: envelope.event_type,
email: resolvedEmail,
}
return applyParsedPaddleEvent(envelope, {
defaultPlan,
defaultRole,
productPlanMap,
pricePlanMap,
now,
loadState,
saveState,
})
}
function getProcessedEventCount() {

View file

@ -4,6 +4,59 @@ import { MarketingLaunchStatusBanner } from '../ui/PublicLaunchStatus'
import { brandConfig } from '../../site-config'
import { footerLinks, marketingNavLinks } from '../../public-route-registry'
export const publicProductRealityStrip = (
<section className="product-reality-strip" aria-label="Current HyperTwist product reality">
<article className="product-reality-strip__item">
<p className="eyebrow">Primary product</p>
<strong>Desktop simulator</strong>
<p>Native Unreal owns the real training runtime, package proof, and higher-dimensional execution.</p>
</article>
<article className="product-reality-strip__item">
<p className="eyebrow">Web purpose</p>
<strong>Access and rollout</strong>
<p>The browser owns docs, account, pricing, notices, release posture, and desktop pairing.</p>
</article>
<article className="product-reality-strip__item">
<p className="eyebrow">Ready today</p>
<strong>Keyboard and mouse</strong>
<p>Desktop control surfaces are usable for development and operator review without a headset.</p>
</article>
<article className="product-reality-strip__item">
<p className="eyebrow">Still gated</p>
<strong>Launch config and XR proof</strong>
<p>Public download, Paddle, live auth, and headset/controller observation remain explicit gates.</p>
</article>
</section>
)
export const compactProductRealityStrip = (
<section
className="product-reality-strip product-reality-strip--compact"
aria-label="Current HyperTwist product reality"
>
<article className="product-reality-strip__item">
<p className="eyebrow">Primary product</p>
<strong>Desktop simulator</strong>
<p>Native Unreal owns the real training runtime, package proof, and higher-dimensional execution.</p>
</article>
<article className="product-reality-strip__item">
<p className="eyebrow">Web purpose</p>
<strong>Access and rollout</strong>
<p>The browser owns docs, account, pricing, notices, release posture, and desktop pairing.</p>
</article>
<article className="product-reality-strip__item">
<p className="eyebrow">Ready today</p>
<strong>Keyboard and mouse</strong>
<p>Desktop control surfaces are usable for development and operator review without a headset.</p>
</article>
<article className="product-reality-strip__item">
<p className="eyebrow">Still gated</p>
<strong>Launch config and XR proof</strong>
<p>Public download, Paddle, live auth, and headset/controller observation remain explicit gates.</p>
</article>
</section>
)
export function MarketingShell({
title,
eyebrow,
@ -74,6 +127,7 @@ export function MarketingShell({
<p className="page-hero__lede">{lede}</p>
</section>
<MarketingLaunchStatusBanner />
{publicProductRealityStrip}
{children}
</main>

View file

@ -9,6 +9,7 @@ import {
isOrcidOAuthEnabled,
} from '../auth/supertokens-runtime'
import { SiteMetadata } from '../components/seo/SiteMetadata'
import { compactProductRealityStrip } from '../components/layout/MarketingShell'
import { OperationalStatusCallout } from '../components/ui/OperationalStatusCallout'
import { browserDesktopRealityCards, deliverySurfaceCards, desktopWorkflowTracks, operatorManualTracks } from '../site-data'
import { buildSupportPath, getDownloadPlatformLabel, normalizeDownloadPlatform } from '../site-routes'
@ -30,6 +31,7 @@ function AuthShell({
<p className="eyebrow">Browser account access</p>
<h1>{title}</h1>
<p className="auth-card__subtitle">{subtitle}</p>
{compactProductRealityStrip}
{children}
<div className="auth-card__footer">{footer}</div>
</div>

View file

@ -8,6 +8,7 @@ import { PublicLaunchStatus } from '../components/ui/PublicLaunchStatus'
import { ReleaseValidationSummary } from '../components/ui/ReleaseValidationSummary'
import { brandConfig, paddleReadyDescription } from '../site-config'
import { buildReleaseMetadataItems, resolveReleaseCommerceView } from '../release-manifest'
import type { ReleaseManifestPlatformView } from '../release-manifest'
import { buildLoginPath, buildProtectedDownloadPath, buildSupportPath } from '../site-routes'
import {
browserLimitCards,
@ -211,6 +212,101 @@ export function PricingPage() {
)
}
function ReleaseManifestStatusSections({
isLoading,
isError,
}: {
isLoading: boolean
isError: boolean
}) {
return (
<>
{isLoading ? (
<Section title="Release manifest status">
<article className="callout">
<p>Loading the current server-backed release manifest. Static preview metadata remains visible until the live manifest arrives.</p>
</article>
</Section>
) : null}
{isError ? (
<Section title="Release manifest status">
<OperationalStatusCallout
badge="Public release fallback"
title="The live release manifest could not be loaded from the auth server right now."
summary="This page is showing bounded fallback site metadata instead of current runtime release authority, and direct package delivery stays intentionally withheld until that authority returns."
sections={[
{
title: 'What still works',
items: [
'Supported targets, packaged proof, and public rollout guidance remain visible.',
'Platform selection can still be preserved into the protected sign-in and download handoff.',
],
},
{
title: 'What stays intentionally withheld',
items: [
'Raw desktop delivery URLs remain behind the protected release lane.',
'This public page does not claim live entitled release authority while the auth server is unavailable.',
],
},
{
title: 'Recommended recovery order',
items: [
'Use the protected dashboard once the auth server recovers if you need actual package delivery.',
'Open operator support if release-authority fallback persists during rollout or purchase work.',
],
},
]}
actions={[
{ label: 'Open protected downloads', to: buildProtectedDownloadPath('windows') },
{ label: 'Open support', to: buildSupportPath('operator-access') },
]}
/>
</Section>
) : null}
</>
)
}
function AvailableTargetsSection({ platforms }: { platforms: readonly ReleaseManifestPlatformView[] }) {
return (
<Section title="Available targets">
<div className="card-grid">
{platforms.map((platform) => {
const metadataItems = buildReleaseMetadataItems(platform)
return (
<article key={platform.platform_key} className="card">
<h3>{platform.platform}</h3>
<p className="status-pill">{platform.subtitle}</p>
<p>{platform.details}</p>
{metadataItems.length > 0 ? (
<ul className="list top-gap">
{metadataItems.map((item) => (
<li key={`${platform.platform_key}-${item.label}`}>
{item.label}: {item.value}
</li>
))}
</ul>
) : null}
<ReleaseValidationSummary platform={platform} />
{platform.configured ? (
<Link className="button button--primary button--full" to={buildProtectedDownloadPath(platform.platform_key)}>
Sign in for {platform.platform} access
</Link>
) : (
<div className="button button--ghost button--full is-disabled" aria-disabled="true">
Release URL not configured yet
</div>
)}
</article>
)
})}
</div>
</Section>
)
}
export function DownloadPage() {
const { releaseManifestQuery, releaseManifest, windowsValidationPlatform } = usePublicReleaseManifestView('public-download')
@ -226,84 +322,12 @@ export function DownloadPage() {
title="Download the desktop build and pair it with your browser account."
lede="The website provides account, release, and legal surfaces. The actual simulator ships through the desktop lane, with package validation and release discipline carried over from the HyperTwist roadmap."
>
{releaseManifestQuery.isLoading ? (
<Section title="Release manifest status">
<article className="callout">
<p>Loading the current server-backed release manifest. Static preview metadata remains visible until the live manifest arrives.</p>
</article>
</Section>
) : null}
<ReleaseManifestStatusSections
isLoading={releaseManifestQuery.isLoading}
isError={releaseManifestQuery.isError}
/>
{releaseManifestQuery.isError ? (
<Section title="Release manifest status">
<OperationalStatusCallout
badge="Public release fallback"
title="The live release manifest could not be loaded from the auth server right now."
summary="This page is showing bounded fallback site metadata instead of current runtime release authority, and direct package delivery stays intentionally withheld until that authority returns."
sections={[
{
title: 'What still works',
items: [
'Supported targets, packaged proof, and public rollout guidance remain visible.',
'Platform selection can still be preserved into the protected sign-in and download handoff.',
],
},
{
title: 'What stays intentionally withheld',
items: [
'Raw desktop delivery URLs remain behind the protected release lane.',
'This public page does not claim live entitled release authority while the auth server is unavailable.',
],
},
{
title: 'Recommended recovery order',
items: [
'Use the protected dashboard once the auth server recovers if you need actual package delivery.',
'Open operator support if release-authority fallback persists during rollout or purchase work.',
],
},
]}
actions={[
{ label: 'Open protected downloads', to: buildProtectedDownloadPath('windows') },
{ label: 'Open support', to: buildSupportPath('operator-access') },
]}
/>
</Section>
) : null}
<Section title="Available targets">
<div className="card-grid">
{releaseManifest.platforms.map((platform) => {
const metadataItems = buildReleaseMetadataItems(platform)
return (
<article key={platform.platform_key} className="card">
<h3>{platform.platform}</h3>
<p className="status-pill">{platform.subtitle}</p>
<p>{platform.details}</p>
{metadataItems.length > 0 ? (
<ul className="list top-gap">
{metadataItems.map((item) => (
<li key={`${platform.platform_key}-${item.label}`}>
{item.label}: {item.value}
</li>
))}
</ul>
) : null}
<ReleaseValidationSummary platform={platform} />
{platform.configured ? (
<Link className="button button--primary button--full" to={buildProtectedDownloadPath(platform.platform_key)}>
Sign in for {platform.platform} access
</Link>
) : (
<div className="button button--ghost button--full is-disabled" aria-disabled="true">
Release URL not configured yet
</div>
)}
</article>
)
})}
</div>
</Section>
<AvailableTargetsSection platforms={releaseManifest.platforms} />
<Section title="How the release lane works">
<div className="card-grid">

View file

@ -66,6 +66,325 @@ import {
usePublicReleaseManifestView,
} from './public-page-helpers'
function HomeHeroPanel() {
return (
<section className="hero-panel">
<div className="hero-grid">
<div className="hero-copy">
<p>
HyperTwist is a native training environment for classic cube practice, recognition
and correction closure, replay explanation, coaching, higher-dimensional puzzle
families, and serious packaged study sessions. The web lane is intentionally narrower:
it owns access, documentation, release posture, support, and pairing so the
downloadable can stay focused on the simulator itself.
</p>
<div className="button-row">
<Link className="button button--primary" to="/download">
Download desktop app
</Link>
<Link className="button button--ghost" to="/browser">
Why keep the web version?
</Link>
<Link className="button button--ghost" to="/help">
Open help center
</Link>
<Link className="button button--ghost" to="/app">
Open operator dashboard
</Link>
</div>
</div>
<div className="hero-visual-card">
<img
src="/branding/hypertwist-3d-symbol.png"
alt="HyperTwist symbol"
className="hero-visual-card__image"
/>
<p className="hero-visual-card__caption">
Browser shell for access, rollout, and support. Native Unreal runtime for the simulator.
</p>
<ul className="list top-gap">
<li>Use the web for pricing, docs, release notes, notices, and protected downloads.</li>
<li>Use the downloadable for real cube-state workflows, higher-dimensional maps, and diagnostics.</li>
<li>Read XR/controller widening honestly as a separate native completion packet, not as a finished browser or desktop claim today.</li>
</ul>
</div>
</div>
<div className="metric-grid">
{heroMetrics.map((metric) => (
<article key={metric.label} className="metric-card">
<strong>{metric.value}</strong>
<span>{metric.label}</span>
</article>
))}
</div>
</section>
)
}
function HomeShippingNowSection() {
return (
<Section
title="What ships now"
description="The public site only describes current product truth or explicitly marked retained/spec-only branches."
>
<div className="card-grid">
{shippingNowCards.map((item) => (
<article key={item} className="card card--compact">
<Sparkles size={18} />
<p>{item}</p>
</article>
))}
</div>
</Section>
)
}
function HomeCapabilityPillarsSection() {
return (
<Section
title="Capability pillars"
description="The page structure keeps HyperTwist's public shell disciplined around real runtime authority instead of mixing product narrative, rollout status, and simulator claims together."
>
<div className="card-grid">
{capabilityPillars.map((pillar) => (
<article key={pillar.title} className="card">
<h3>{pillar.title}</h3>
<p>{pillar.description}</p>
</article>
))}
</div>
</Section>
)
}
function HomeRoadmapHonestySection() {
return (
<Section
title="Roadmap-honest posture"
description="Important boundaries stay visible instead of being blurred into vague marketing claims."
>
<div className="split-grid">
{roadmapHonestyCards.map((item) => (
<article key={item} className="callout">
<p>{item}</p>
</article>
))}
</div>
</Section>
)
}
function HomeDeliverySurfacesSection() {
return (
<Section
title="Delivery surfaces"
description="Use the website for account, release, pricing, and notices. Use the desktop runtime for the core simulator."
>
<div className="feature-band">
<article className="feature-band__card">
<MonitorCog size={22} />
<h3>Browser account and operator shell</h3>
<p>Authenticated browser access for release posture, desktop pairing, notices, and operator state.</p>
<Link to="/app" className="inline-link">
Open dashboard <ArrowRight size={15} />
</Link>
</article>
<article className="feature-band__card">
<Download size={22} />
<h3>Desktop download and package lane</h3>
<p>Public download posture for the native Unreal build, with legal linkage already wired in.</p>
<Link to="/download" className="inline-link">
Open download center <ArrowRight size={15} />
</Link>
</article>
<article className="feature-band__card">
<Landmark size={22} />
<h3>Checkout and notices discipline</h3>
<p>Paddle-ready pricing plus public notices surfaces for any downloadable build containing MPL-covered material.</p>
<Link to="/open-source-notices" className="inline-link">
Review notices <ArrowRight size={15} />
</Link>
</article>
</div>
</Section>
)
}
function HomeFirstSessionSection() {
return (
<Section
title="How a real first session flows"
description="This is the public-facing operator path from curiosity into the actual simulator lane, without pretending the browser already replaced the desktop runtime."
>
<div className="card-grid">
{operatorManualTracks.slice(0, 3).map((track) => (
<article key={track.title} className="card">
<h3>{track.title}</h3>
<p>{track.description}</p>
<ul className="list top-gap">
{track.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</Section>
)
}
type ResourceCollection = {
readonly title: string
readonly items: readonly string[]
}
function filterResourceCollections(query: string): readonly ResourceCollection[] {
const normalized = query.trim().toLowerCase()
if (!normalized) return resourceCollections
return resourceCollections
.map((collection) => ({
...collection,
items: collection.items.filter((item) => (
item.toLowerCase().includes(normalized) || collection.title.toLowerCase().includes(normalized)
)),
}))
.filter((collection) => collection.items.length > 0)
}
function ResourcesFinderSection({
query,
filteredCollections,
onQueryChange,
}: {
query: string
filteredCollections: readonly ResourceCollection[]
onQueryChange: (value: string) => void
}) {
return (
<Section title="Resource finder" description="Search the public resource categories that matter to operators and buyers.">
<label className="input-label" htmlFor="resource-query">Filter resources</label>
<input
id="resource-query"
className="input"
value={query}
onChange={(event) => onQueryChange(event.target.value)}
placeholder="Search training, rollout, notices..."
/>
<div className="card-grid top-gap">
{filteredCollections.map((collection) => (
<article key={collection.title} className="card">
<h3>{collection.title}</h3>
<ul className="list">
{collection.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</article>
))}
</div>
</Section>
)
}
function ResourcesDirectRoutesSection() {
return (
<Section title="Direct routes">
<div className="feature-band">
<Link to="/browser" className="feature-band__card feature-band__card--link">
<ExternalLink size={22} />
<h3>Browser guide</h3>
<p>The public explanation of what the web version is for, what it is weaker at, and when to move into the downloadable.</p>
</Link>
<Link to="/features" className="feature-band__card feature-band__card--link">
<Boxes size={22} />
<h3>Feature atlas</h3>
<p>One public page for current capability, surface boundaries, and release posture.</p>
</Link>
<Link to="/help" className="feature-band__card feature-band__card--link">
<MonitorCog size={22} />
<h3>Help center</h3>
<p>Knowledge-base style guidance for onboarding, controls, release handoff, and rollout-safe troubleshooting.</p>
</Link>
<Link to="/getting-started" className="feature-band__card feature-band__card--link">
<MonitorCog size={22} />
<h3>Getting started</h3>
<p>The canonical first-session path from browser access into the packaged desktop runtime.</p>
</Link>
<Link to="/launch-status" className="feature-band__card feature-band__card--link">
<Landmark size={22} />
<h3>Launch status</h3>
<p>The canonical public checklist for preview-versus-launch posture, release authority, and rollout blockers.</p>
</Link>
<Link to="/docs" className="feature-band__card feature-band__card--link">
<BookOpenText size={22} />
<h3>Docs landing</h3>
<p>Product-facing documentation, boundaries, and rollout guidance.</p>
</Link>
<Link to="/support" className="feature-band__card feature-band__card--link">
<ExternalLink size={22} />
<h3>Support</h3>
<p>Contact, rollout questions, and account/download help.</p>
</Link>
<Link to="/contact" className="feature-band__card feature-band__card--link">
<Landmark size={22} />
<h3>Contact</h3>
<p>The direct operator contact route for access, runtime, rollout, pricing, and notice follow-through.</p>
</Link>
<Link to="/changelog" className="feature-band__card feature-band__card--link">
<Sparkles size={22} />
<h3>Release notes</h3>
<p>Recent public-facing packets and posture updates.</p>
</Link>
</div>
</Section>
)
}
function ResourcesOperatorPlaybooksSection() {
return (
<Section
title="Operator playbooks"
description="These are the public-safe working patterns that matter once a team moves from curiosity into real rollout."
>
<div className="card-grid">
{operatorPlaybooks.map((playbook) => (
<article key={playbook.title} className="card">
<h3>{playbook.title}</h3>
<ul className="list top-gap">
{playbook.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</Section>
)
}
function ResourcesDeploymentSnapshotSection() {
return (
<Section
title="Deployment readiness snapshot"
description="The public site can be detailed without becoming misleading when rollout guidance stays separated into identity, package, and legal lanes."
>
<div className="card-grid">
{deploymentReadinessTracks.map((track) => (
<article key={track.title} className="card">
<h3>{track.title}</h3>
<ul className="list top-gap">
{track.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</Section>
)
}
export function HomeLanding() {
const { releaseManifest, windowsValidationPlatform } = usePublicReleaseManifestView('public-home')
@ -81,56 +400,7 @@ export function HomeLanding() {
title="From first solves to 120-cell, HyperTwist keeps the real simulator in the downloadable."
lede="The website is a real product surface for account access, pricing, release posture, legal follow-through, and operator help. The downloadable is the much more powerful runtime for recognition, replay, coaching, higher-dimensional execution, and package-validated training."
>
<section className="hero-panel">
<div className="hero-grid">
<div className="hero-copy">
<p>
HyperTwist is a native training environment for classic cube practice, recognition
and correction closure, replay explanation, coaching, higher-dimensional puzzle
families, and serious packaged study sessions. The web lane is intentionally narrower:
it owns access, documentation, release posture, support, and pairing so the
downloadable can stay focused on the simulator itself.
</p>
<div className="button-row">
<Link className="button button--primary" to="/download">
Download desktop app
</Link>
<Link className="button button--ghost" to="/browser">
Why keep the web version?
</Link>
<Link className="button button--ghost" to="/help">
Open help center
</Link>
<Link className="button button--ghost" to="/app">
Open operator dashboard
</Link>
</div>
</div>
<div className="hero-visual-card">
<img
src="/branding/hypertwist-3d-symbol.png"
alt="HyperTwist symbol"
className="hero-visual-card__image"
/>
<p className="hero-visual-card__caption">
Browser shell for access, rollout, and support. Native Unreal runtime for the simulator.
</p>
<ul className="list top-gap">
<li>Use the web for pricing, docs, release notes, notices, and protected downloads.</li>
<li>Use the downloadable for real cube-state workflows, higher-dimensional maps, and diagnostics.</li>
<li>Read XR/controller widening honestly as a separate native completion packet, not as a finished browser or desktop claim today.</li>
</ul>
</div>
</div>
<div className="metric-grid">
{heroMetrics.map((metric) => (
<article key={metric.label} className="metric-card">
<strong>{metric.value}</strong>
<span>{metric.label}</span>
</article>
))}
</div>
</section>
<HomeHeroPanel />
<Section
title="Current public site status"
@ -151,19 +421,7 @@ export function HomeLanding() {
description="The homepage should answer the web-versus-desktop question directly where first-time operators actually encounter the product boundary, instead of forcing them to infer it from deeper manuals or support pages."
/>
<Section
title="What ships now"
description="The public site only describes current product truth or explicitly marked retained/spec-only branches."
>
<div className="card-grid">
{shippingNowCards.map((item) => (
<article key={item} className="card card--compact">
<Sparkles size={18} />
<p>{item}</p>
</article>
))}
</div>
</Section>
<HomeShippingNowSection />
<BulletCardSection
title="Puzzle families and study lanes"
@ -171,19 +429,7 @@ export function HomeLanding() {
cards={puzzleCatalogCards}
/>
<Section
title="Capability pillars"
description="The page structure keeps HyperTwist's public shell disciplined around real runtime authority instead of mixing product narrative, rollout status, and simulator claims together."
>
<div className="card-grid">
{capabilityPillars.map((pillar) => (
<article key={pillar.title} className="card">
<h3>{pillar.title}</h3>
<p>{pillar.description}</p>
</article>
))}
</div>
</Section>
<HomeCapabilityPillarsSection />
<BulletCardSection
title="Who HyperTwist is for"
@ -191,50 +437,9 @@ export function HomeLanding() {
cards={audienceFitCards}
/>
<Section
title="Roadmap-honest posture"
description="Important boundaries stay visible instead of being blurred into vague marketing claims."
>
<div className="split-grid">
{roadmapHonestyCards.map((item) => (
<article key={item} className="callout">
<p>{item}</p>
</article>
))}
</div>
</Section>
<HomeRoadmapHonestySection />
<Section
title="Delivery surfaces"
description="Use the website for account, release, pricing, and notices. Use the desktop runtime for the core simulator."
>
<div className="feature-band">
<article className="feature-band__card">
<MonitorCog size={22} />
<h3>Browser account and operator shell</h3>
<p>Authenticated browser access for release posture, desktop pairing, notices, and operator state.</p>
<Link to="/app" className="inline-link">
Open dashboard <ArrowRight size={15} />
</Link>
</article>
<article className="feature-band__card">
<Download size={22} />
<h3>Desktop download and package lane</h3>
<p>Public download posture for the native Unreal build, with legal linkage already wired in.</p>
<Link to="/download" className="inline-link">
Open download center <ArrowRight size={15} />
</Link>
</article>
<article className="feature-band__card">
<Landmark size={22} />
<h3>Checkout and notices discipline</h3>
<p>Paddle-ready pricing plus public notices surfaces for any downloadable build containing MPL-covered material.</p>
<Link to="/open-source-notices" className="inline-link">
Review notices <ArrowRight size={15} />
</Link>
</article>
</div>
</Section>
<HomeDeliverySurfacesSection />
<PublicManualRouteAtlasSection
title="Which public page should you open next?"
@ -268,24 +473,7 @@ export function HomeLanding() {
description="The homepage now also makes the current shared-auth sign-in lineup visible before operators commit to the protected dashboard, pricing, or desktop-release handoff."
/>
<Section
title="How a real first session flows"
description="This is the public-facing operator path from curiosity into the actual simulator lane, without pretending the browser already replaced the desktop runtime."
>
<div className="card-grid">
{operatorManualTracks.slice(0, 3).map((track) => (
<article key={track.title} className="card">
<h3>{track.title}</h3>
<p>{track.description}</p>
<ul className="list top-gap">
{track.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<HomeFirstSessionSection />
<StepCardSection
title="Desktop workflows already supported"
@ -465,16 +653,7 @@ export function ResourcesPage() {
const deferredQuery = useDeferredValue(query)
const { releaseManifest, windowsValidationPlatform } = usePublicReleaseManifestView('public-resources')
const filteredCollections = useMemo(() => {
const normalized = deferredQuery.trim().toLowerCase()
if (!normalized) return resourceCollections
return resourceCollections
.map((collection) => ({
...collection,
items: collection.items.filter((item) => item.toLowerCase().includes(normalized) || collection.title.toLowerCase().includes(normalized)),
}))
.filter((collection) => collection.items.length > 0)
}, [deferredQuery])
const filteredCollections = useMemo(() => filterResourceCollections(deferredQuery), [deferredQuery])
return (
<>
@ -488,78 +667,13 @@ export function ResourcesPage() {
title="Resources that explain the product without leaking operator-only internals."
lede="This surface is organized as a deliberate HyperTwist resource portal, constrained to product-safe rollout, simulator, and distribution guidance."
>
<Section title="Resource finder" description="Search the public resource categories that matter to operators and buyers.">
<label className="input-label" htmlFor="resource-query">Filter resources</label>
<input
id="resource-query"
className="input"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search training, rollout, notices..."
/>
<div className="card-grid top-gap">
{filteredCollections.map((collection) => (
<article key={collection.title} className="card">
<h3>{collection.title}</h3>
<ul className="list">
{collection.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<ResourcesFinderSection
query={query}
filteredCollections={filteredCollections}
onQueryChange={setQuery}
/>
<Section title="Direct routes">
<div className="feature-band">
<Link to="/browser" className="feature-band__card feature-band__card--link">
<ExternalLink size={22} />
<h3>Browser guide</h3>
<p>The public explanation of what the web version is for, what it is weaker at, and when to move into the downloadable.</p>
</Link>
<Link to="/features" className="feature-band__card feature-band__card--link">
<Boxes size={22} />
<h3>Feature atlas</h3>
<p>One public page for current capability, surface boundaries, and release posture.</p>
</Link>
<Link to="/help" className="feature-band__card feature-band__card--link">
<MonitorCog size={22} />
<h3>Help center</h3>
<p>Knowledge-base style guidance for onboarding, controls, release handoff, and rollout-safe troubleshooting.</p>
</Link>
<Link to="/getting-started" className="feature-band__card feature-band__card--link">
<MonitorCog size={22} />
<h3>Getting started</h3>
<p>The canonical first-session path from browser access into the packaged desktop runtime.</p>
</Link>
<Link to="/launch-status" className="feature-band__card feature-band__card--link">
<Landmark size={22} />
<h3>Launch status</h3>
<p>The canonical public checklist for preview-versus-launch posture, release authority, and rollout blockers.</p>
</Link>
<Link to="/docs" className="feature-band__card feature-band__card--link">
<BookOpenText size={22} />
<h3>Docs landing</h3>
<p>Product-facing documentation, boundaries, and rollout guidance.</p>
</Link>
<Link to="/support" className="feature-band__card feature-band__card--link">
<ExternalLink size={22} />
<h3>Support</h3>
<p>Contact, rollout questions, and account/download help.</p>
</Link>
<Link to="/contact" className="feature-band__card feature-band__card--link">
<Landmark size={22} />
<h3>Contact</h3>
<p>The direct operator contact route for access, runtime, rollout, pricing, and notice follow-through.</p>
</Link>
<Link to="/changelog" className="feature-band__card feature-band__card--link">
<Sparkles size={22} />
<h3>Release notes</h3>
<p>Recent public-facing packets and posture updates.</p>
</Link>
</div>
</Section>
<ResourcesDirectRoutesSection />
<PublicManualRouteAtlasSection
description="The resource portal is stronger when it also explains what each public page is for, so teams can move from reference reading into the exact route that owns the next question."
@ -584,23 +698,7 @@ export function ResourcesPage() {
description="Resources are stronger when they do not stop at explanation: this public guide says plainly whether the next move is protected downloads, pricing, browser/account follow-through, or notices/source review."
/>
<Section
title="Operator playbooks"
description="These are the public-safe working patterns that matter once a team moves from curiosity into real rollout."
>
<div className="card-grid">
{operatorPlaybooks.map((playbook) => (
<article key={playbook.title} className="card">
<h3>{playbook.title}</h3>
<ul className="list top-gap">
{playbook.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<ResourcesOperatorPlaybooksSection />
<StepCardSection
title="Practical software workflows"
@ -645,23 +743,7 @@ export function ResourcesPage() {
description="This public-safe guide focuses on how operators and trainees actually drive the current desktop runtime today."
/>
<Section
title="Deployment readiness snapshot"
description="The public site can be detailed without becoming misleading when rollout guidance stays separated into identity, package, and legal lanes."
>
<div className="card-grid">
{deploymentReadinessTracks.map((track) => (
<article key={track.title} className="card">
<h3>{track.title}</h3>
<ul className="list top-gap">
{track.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ul>
</article>
))}
</div>
</Section>
<ResourcesDeploymentSnapshotSection />
<PublicPackagedDesktopProofSection
platform={windowsValidationPlatform}

View file

@ -513,6 +513,85 @@ img {
margin-top: 0;
}
.product-reality-strip {
position: relative;
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 0.85rem;
margin: -0.35rem 0 1.4rem;
padding: 0.85rem;
border: 1px solid var(--ht-border);
border-radius: 1.45rem;
background:
radial-gradient(circle at 12% 0%, rgba(84, 203, 255, 0.12), transparent 34%),
radial-gradient(circle at 88% 12%, rgba(247, 178, 103, 0.11), transparent 30%),
rgba(8, 13, 27, 0.62);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.04),
0 18px 54px rgba(0, 0, 0, 0.14);
overflow: hidden;
}
.product-reality-strip::before {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background: linear-gradient(90deg, transparent, rgba(84, 203, 255, 0.09), transparent);
transform: translateX(-62%);
animation: ht-scanline 8s ease-in-out infinite;
}
.product-reality-strip__item {
position: relative;
min-width: 0;
padding: 0.9rem;
border: 1px solid rgba(255, 255, 255, 0.045);
border-radius: 1.05rem;
background: rgba(255, 255, 255, 0.025);
}
.product-reality-strip__item .eyebrow {
margin-bottom: 0.45rem;
}
.product-reality-strip__item strong {
display: block;
font-family: var(--font-display);
font-size: 1.05rem;
line-height: 1.1;
}
.product-reality-strip__item p:last-child {
margin: 0.55rem 0 0;
color: var(--ht-muted);
line-height: 1.55;
font-size: 0.92rem;
}
.product-reality-strip--compact {
grid-template-columns: repeat(2, minmax(0, 1fr));
margin: 0;
}
.product-reality-strip--compact .product-reality-strip__item {
padding: 0.75rem;
}
@keyframes ht-scanline {
0%, 45% {
transform: translateX(-62%);
opacity: 0;
}
55% {
opacity: 1;
}
100% {
transform: translateX(62%);
opacity: 0;
}
}
.list {
margin: 0;
padding-left: 1.15rem;
@ -923,6 +1002,10 @@ code {
.site-footer__grid {
grid-template-columns: 1fr;
}
.product-reality-strip {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 720px) {
@ -962,4 +1045,9 @@ code {
max-width: 100%;
white-space: normal;
}
.product-reality-strip,
.product-reality-strip--compact {
grid-template-columns: 1fr;
}
}