diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md index a17ba0d..c8e5708 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/FEATURE_REGISTRY.md @@ -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: diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md index f149889..2686cd4 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/PRD.md @@ -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` diff --git a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md index bbfc061..c593085 100644 --- a/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md +++ b/docs/v6_5_deep_manual_pack/HyperTwist/ROADMAP.md @@ -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 diff --git a/website/server/src/billing-state.ts b/website/server/src/billing-state.ts index 74667b7..e476638 100644 --- a/website/server/src/billing-state.ts +++ b/website/server/src/billing-state.ts @@ -43,6 +43,22 @@ interface BillingStateStoreOptions { now?: () => Date } +interface BillingEventIdentityCandidates { + subscriptionIds: string[] + transactionIds: string[] + customerIds: string[] +} + +interface BillingStateStoreRuntime { + defaultPlan: BillingPlan + defaultRole: BillingRole + productPlanMap: Record + pricePlanMap: Record + 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 +}): 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() { diff --git a/website/src/components/layout/MarketingShell.tsx b/website/src/components/layout/MarketingShell.tsx index 2a6cede..5037637 100644 --- a/website/src/components/layout/MarketingShell.tsx +++ b/website/src/components/layout/MarketingShell.tsx @@ -4,6 +4,59 @@ import { MarketingLaunchStatusBanner } from '../ui/PublicLaunchStatus' import { brandConfig } from '../../site-config' import { footerLinks, marketingNavLinks } from '../../public-route-registry' +export const publicProductRealityStrip = ( +
+
+

Primary product

+ Desktop simulator +

Native Unreal owns the real training runtime, package proof, and higher-dimensional execution.

+
+
+

Web purpose

+ Access and rollout +

The browser owns docs, account, pricing, notices, release posture, and desktop pairing.

+
+
+

Ready today

+ Keyboard and mouse +

Desktop control surfaces are usable for development and operator review without a headset.

+
+
+

Still gated

+ Launch config and XR proof +

Public download, Paddle, live auth, and headset/controller observation remain explicit gates.

+
+
+) + +export const compactProductRealityStrip = ( +
+
+

Primary product

+ Desktop simulator +

Native Unreal owns the real training runtime, package proof, and higher-dimensional execution.

+
+
+

Web purpose

+ Access and rollout +

The browser owns docs, account, pricing, notices, release posture, and desktop pairing.

+
+
+

Ready today

+ Keyboard and mouse +

Desktop control surfaces are usable for development and operator review without a headset.

+
+
+

Still gated

+ Launch config and XR proof +

Public download, Paddle, live auth, and headset/controller observation remain explicit gates.

+
+
+) + export function MarketingShell({ title, eyebrow, @@ -74,6 +127,7 @@ export function MarketingShell({

{lede}

+ {publicProductRealityStrip} {children} diff --git a/website/src/pages/auth-pages.tsx b/website/src/pages/auth-pages.tsx index 8d9d8e3..1cfb32a 100644 --- a/website/src/pages/auth-pages.tsx +++ b/website/src/pages/auth-pages.tsx @@ -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({

Browser account access

{title}

{subtitle}

+ {compactProductRealityStrip} {children}
{footer}
diff --git a/website/src/pages/public-pages-commerce.tsx b/website/src/pages/public-pages-commerce.tsx index cec22f2..dce17e8 100644 --- a/website/src/pages/public-pages-commerce.tsx +++ b/website/src/pages/public-pages-commerce.tsx @@ -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 ? ( +
+
+

Loading the current server-backed release manifest. Static preview metadata remains visible until the live manifest arrives.

+
+
+ ) : null} + + {isError ? ( +
+ +
+ ) : null} + + ) +} + +function AvailableTargetsSection({ platforms }: { platforms: readonly ReleaseManifestPlatformView[] }) { + return ( +
+
+ {platforms.map((platform) => { + const metadataItems = buildReleaseMetadataItems(platform) + return ( +
+

{platform.platform}

+

{platform.subtitle}

+

{platform.details}

+ {metadataItems.length > 0 ? ( +
    + {metadataItems.map((item) => ( +
  • + {item.label}: {item.value} +
  • + ))} +
+ ) : null} + + {platform.configured ? ( + + Sign in for {platform.platform} access + + ) : ( +
+ Release URL not configured yet +
+ )} +
+ ) + })} +
+
+ ) +} + 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 ? ( -
-
-

Loading the current server-backed release manifest. Static preview metadata remains visible until the live manifest arrives.

-
-
- ) : null} + - {releaseManifestQuery.isError ? ( -
- -
- ) : null} - -
-
- {releaseManifest.platforms.map((platform) => { - const metadataItems = buildReleaseMetadataItems(platform) - return ( -
-

{platform.platform}

-

{platform.subtitle}

-

{platform.details}

- {metadataItems.length > 0 ? ( -
    - {metadataItems.map((item) => ( -
  • - {item.label}: {item.value} -
  • - ))} -
- ) : null} - - {platform.configured ? ( - - Sign in for {platform.platform} access - - ) : ( -
- Release URL not configured yet -
- )} -
- ) - })} -
-
+
diff --git a/website/src/pages/public-pages-marketing.tsx b/website/src/pages/public-pages-marketing.tsx index 8c3da9f..5322f77 100644 --- a/website/src/pages/public-pages-marketing.tsx +++ b/website/src/pages/public-pages-marketing.tsx @@ -66,6 +66,325 @@ import { usePublicReleaseManifestView, } from './public-page-helpers' +function HomeHeroPanel() { + return ( +
+
+
+

+ 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. +

+
+ + Download desktop app + + + Why keep the web version? + + + Open help center + + + Open operator dashboard + +
+
+
+ HyperTwist symbol +

+ Browser shell for access, rollout, and support. Native Unreal runtime for the simulator. +

+
    +
  • Use the web for pricing, docs, release notes, notices, and protected downloads.
  • +
  • Use the downloadable for real cube-state workflows, higher-dimensional maps, and diagnostics.
  • +
  • Read XR/controller widening honestly as a separate native completion packet, not as a finished browser or desktop claim today.
  • +
+
+
+
+ {heroMetrics.map((metric) => ( +
+ {metric.value} + {metric.label} +
+ ))} +
+
+ ) +} + +function HomeShippingNowSection() { + return ( +
+
+ {shippingNowCards.map((item) => ( +
+ +

{item}

+
+ ))} +
+
+ ) +} + +function HomeCapabilityPillarsSection() { + return ( +
+
+ {capabilityPillars.map((pillar) => ( +
+

{pillar.title}

+

{pillar.description}

+
+ ))} +
+
+ ) +} + +function HomeRoadmapHonestySection() { + return ( +
+
+ {roadmapHonestyCards.map((item) => ( +
+

{item}

+
+ ))} +
+
+ ) +} + +function HomeDeliverySurfacesSection() { + return ( +
+
+
+ +

Browser account and operator shell

+

Authenticated browser access for release posture, desktop pairing, notices, and operator state.

+ + Open dashboard + +
+
+ +

Desktop download and package lane

+

Public download posture for the native Unreal build, with legal linkage already wired in.

+ + Open download center + +
+
+ +

Checkout and notices discipline

+

Paddle-ready pricing plus public notices surfaces for any downloadable build containing MPL-covered material.

+ + Review notices + +
+
+
+ ) +} + +function HomeFirstSessionSection() { + return ( +
+
+ {operatorManualTracks.slice(0, 3).map((track) => ( +
+

{track.title}

+

{track.description}

+
    + {track.steps.map((step) => ( +
  • {step}
  • + ))} +
+
+ ))} +
+
+ ) +} + +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 ( +
+ + onQueryChange(event.target.value)} + placeholder="Search training, rollout, notices..." + /> +
+ {filteredCollections.map((collection) => ( +
+

{collection.title}

+
    + {collection.items.map((item) => ( +
  • {item}
  • + ))} +
+
+ ))} +
+
+ ) +} + +function ResourcesDirectRoutesSection() { + return ( +
+
+ + +

Browser guide

+

The public explanation of what the web version is for, what it is weaker at, and when to move into the downloadable.

+ + + +

Feature atlas

+

One public page for current capability, surface boundaries, and release posture.

+ + + +

Help center

+

Knowledge-base style guidance for onboarding, controls, release handoff, and rollout-safe troubleshooting.

+ + + +

Getting started

+

The canonical first-session path from browser access into the packaged desktop runtime.

+ + + +

Launch status

+

The canonical public checklist for preview-versus-launch posture, release authority, and rollout blockers.

+ + + +

Docs landing

+

Product-facing documentation, boundaries, and rollout guidance.

+ + + +

Support

+

Contact, rollout questions, and account/download help.

+ + + +

Contact

+

The direct operator contact route for access, runtime, rollout, pricing, and notice follow-through.

+ + + +

Release notes

+

Recent public-facing packets and posture updates.

+ +
+
+ ) +} + +function ResourcesOperatorPlaybooksSection() { + return ( +
+
+ {operatorPlaybooks.map((playbook) => ( +
+

{playbook.title}

+
    + {playbook.steps.map((step) => ( +
  • {step}
  • + ))} +
+
+ ))} +
+
+ ) +} + +function ResourcesDeploymentSnapshotSection() { + return ( +
+
+ {deploymentReadinessTracks.map((track) => ( +
+

{track.title}

+
    + {track.steps.map((step) => ( +
  • {step}
  • + ))} +
+
+ ))} +
+
+ ) +} + 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." > -
-
-
-

- 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. -

-
- - Download desktop app - - - Why keep the web version? - - - Open help center - - - Open operator dashboard - -
-
-
- HyperTwist symbol -

- Browser shell for access, rollout, and support. Native Unreal runtime for the simulator. -

-
    -
  • Use the web for pricing, docs, release notes, notices, and protected downloads.
  • -
  • Use the downloadable for real cube-state workflows, higher-dimensional maps, and diagnostics.
  • -
  • Read XR/controller widening honestly as a separate native completion packet, not as a finished browser or desktop claim today.
  • -
-
-
-
- {heroMetrics.map((metric) => ( -
- {metric.value} - {metric.label} -
- ))} -
-
+
-
-
- {shippingNowCards.map((item) => ( -
- -

{item}

-
- ))} -
-
+ -
-
- {capabilityPillars.map((pillar) => ( -
-

{pillar.title}

-

{pillar.description}

-
- ))} -
-
+ -
-
- {roadmapHonestyCards.map((item) => ( -
-

{item}

-
- ))} -
-
+ -
-
-
- -

Browser account and operator shell

-

Authenticated browser access for release posture, desktop pairing, notices, and operator state.

- - Open dashboard - -
-
- -

Desktop download and package lane

-

Public download posture for the native Unreal build, with legal linkage already wired in.

- - Open download center - -
-
- -

Checkout and notices discipline

-

Paddle-ready pricing plus public notices surfaces for any downloadable build containing MPL-covered material.

- - Review notices - -
-
-
+ -
-
- {operatorManualTracks.slice(0, 3).map((track) => ( -
-

{track.title}

-

{track.description}

-
    - {track.steps.map((step) => ( -
  • {step}
  • - ))} -
-
- ))} -
-
+ { - 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." > -
- - setQuery(event.target.value)} - placeholder="Search training, rollout, notices..." - /> -
- {filteredCollections.map((collection) => ( -
-

{collection.title}

-
    - {collection.items.map((item) => ( -
  • {item}
  • - ))} -
-
- ))} -
-
+ -
-
- - -

Browser guide

-

The public explanation of what the web version is for, what it is weaker at, and when to move into the downloadable.

- - - -

Feature atlas

-

One public page for current capability, surface boundaries, and release posture.

- - - -

Help center

-

Knowledge-base style guidance for onboarding, controls, release handoff, and rollout-safe troubleshooting.

- - - -

Getting started

-

The canonical first-session path from browser access into the packaged desktop runtime.

- - - -

Launch status

-

The canonical public checklist for preview-versus-launch posture, release authority, and rollout blockers.

- - - -

Docs landing

-

Product-facing documentation, boundaries, and rollout guidance.

- - - -

Support

-

Contact, rollout questions, and account/download help.

- - - -

Contact

-

The direct operator contact route for access, runtime, rollout, pricing, and notice follow-through.

- - - -

Release notes

-

Recent public-facing packets and posture updates.

- -
-
+ -
-
- {operatorPlaybooks.map((playbook) => ( -
-

{playbook.title}

-
    - {playbook.steps.map((step) => ( -
  • {step}
  • - ))} -
-
- ))} -
-
+ -
-
- {deploymentReadinessTracks.map((track) => ( -
-

{track.title}

-
    - {track.steps.map((step) => ( -
  • {step}
  • - ))} -
-
- ))} -
-
+