Harden live auth and public release surfaces
This commit is contained in:
parent
a08e58fbcb
commit
5cba830813
37 changed files with 2167 additions and 1201 deletions
|
|
@ -1,7 +1,8 @@
|
|||
|
||||
|
||||
[/Script/EngineSettings.GameMapsSettings]
|
||||
GameDefaultMap=/Engine/Maps/Templates/OpenWorld
|
||||
GameDefaultMap=/Engine/Maps/Entry
|
||||
ServerDefaultMap=/Engine/Maps/Entry
|
||||
GlobalDefaultGameMode=/Script/UnrealHyperTwist.HyperTwistFirstRunLaunchGameMode
|
||||
|
||||
[/Script/Engine.RendererSettings]
|
||||
|
|
|
|||
88
scripts/Invoke-HyperTwistDesktopPackage.ps1
Normal file
88
scripts/Invoke-HyperTwistDesktopPackage.ps1
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
param(
|
||||
[string]$ProjectRoot = 'C:\HyperTwist',
|
||||
[string]$ArchiveDirectory = 'C:\HyperTwist\packaged\desktop',
|
||||
[ValidateSet('Development', 'Shipping')]
|
||||
[string]$Configuration = 'Development',
|
||||
[string]$CookMap = '/Game/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining',
|
||||
[string[]]$AdditionalCookMaps = @(
|
||||
'/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining',
|
||||
'/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining',
|
||||
'/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining'
|
||||
),
|
||||
[string[]]$SmokeMaps = @(
|
||||
'/Game/HyperTwistTraining/Maps/L_HyperTwist_ClassicTraining',
|
||||
'/Game/HyperTwistTraining/Maps/L_HyperTwist_FollowAlongTraining',
|
||||
'/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining',
|
||||
'/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining'
|
||||
),
|
||||
[string[]]$AdditionalCookerOptions = @(
|
||||
'-DisablePlugins=MovieRenderPipeline',
|
||||
'-SkipCookingEditorContent'
|
||||
),
|
||||
[string]$ValidationReportPath = '',
|
||||
[string]$LaunchSurfaceReportPath = '',
|
||||
[switch]$CleanArchive,
|
||||
[switch]$SkipBuild,
|
||||
[switch]$SkipLaunch
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$ClassicPackageScriptPath = Join-Path $PSScriptRoot 'Invoke-HyperTwistClassicCubePackage.ps1'
|
||||
$DesktopLaunchScriptPath = Join-Path $PSScriptRoot 'Launch-HyperTwistDesktopPackage.ps1'
|
||||
$ValidationDirectory = Join-Path $ArchiveDirectory 'validation'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ClassicPackageScriptPath))
|
||||
{
|
||||
throw "Classic-cube package helper was not found at '$ClassicPackageScriptPath'."
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $DesktopLaunchScriptPath))
|
||||
{
|
||||
throw "Desktop launch helper was not found at '$DesktopLaunchScriptPath'."
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ValidationReportPath))
|
||||
{
|
||||
$ValidationReportPath = Join-Path $ValidationDirectory 'desktop-package-validation-report.json'
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($LaunchSurfaceReportPath))
|
||||
{
|
||||
$LaunchSurfaceReportPath = Join-Path $ValidationDirectory 'desktop-launch-surface-report.json'
|
||||
}
|
||||
|
||||
$InvokeParameters = @{
|
||||
ProjectRoot = $ProjectRoot
|
||||
ArchiveDirectory = $ArchiveDirectory
|
||||
Configuration = $Configuration
|
||||
CookMap = $CookMap
|
||||
AdditionalCookMaps = $AdditionalCookMaps
|
||||
SmokeMaps = $SmokeMaps
|
||||
AdditionalCookerOptions = $AdditionalCookerOptions
|
||||
ValidationReportPath = $ValidationReportPath
|
||||
}
|
||||
|
||||
if ($CleanArchive)
|
||||
{
|
||||
$InvokeParameters.CleanArchive = $true
|
||||
}
|
||||
|
||||
if ($SkipBuild)
|
||||
{
|
||||
$InvokeParameters.SkipBuild = $true
|
||||
}
|
||||
|
||||
if ($SkipLaunch)
|
||||
{
|
||||
$InvokeParameters.SkipLaunch = $true
|
||||
}
|
||||
|
||||
& $ClassicPackageScriptPath @InvokeParameters
|
||||
|
||||
if (-not $SkipLaunch)
|
||||
{
|
||||
& $DesktopLaunchScriptPath `
|
||||
-PackageRoot $ArchiveDirectory `
|
||||
-ReportPath $LaunchSurfaceReportPath
|
||||
}
|
||||
130
scripts/Launch-HyperTwistDesktopPackage.ps1
Normal file
130
scripts/Launch-HyperTwistDesktopPackage.ps1
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
param(
|
||||
[string]$PackageRoot = 'C:\HyperTwist\packaged\desktop',
|
||||
[string]$MapUrl = '',
|
||||
[int]$SmokeSeconds = 12,
|
||||
[int]$ResX = 1600,
|
||||
[int]$ResY = 900,
|
||||
[string]$ReportPath = '',
|
||||
[switch]$KeepRunning
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Write-Utf8JsonFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[object]$Value
|
||||
)
|
||||
|
||||
$ParentPath = Split-Path -Parent $Path
|
||||
if (-not [string]::IsNullOrWhiteSpace($ParentPath))
|
||||
{
|
||||
New-Item -ItemType Directory -Force -Path $ParentPath | Out-Null
|
||||
}
|
||||
|
||||
$Json = $Value | ConvertTo-Json -Depth 8
|
||||
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText($Path, $Json, $Utf8NoBom)
|
||||
}
|
||||
|
||||
$CandidateExecutablePaths = @(
|
||||
(Join-Path $PackageRoot 'Windows\UnrealHyperTwist.exe'),
|
||||
(Join-Path $PackageRoot 'WindowsNoEditor\UnrealHyperTwist.exe'),
|
||||
(Join-Path $PackageRoot 'UnrealHyperTwist.exe')
|
||||
)
|
||||
|
||||
$ExecutablePath = $CandidateExecutablePaths | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
if ($null -eq $ExecutablePath)
|
||||
{
|
||||
throw "No packaged UnrealHyperTwist executable was found beneath '$PackageRoot'."
|
||||
}
|
||||
|
||||
$ArgumentList = @(
|
||||
"-ResX=$ResX",
|
||||
"-ResY=$ResY",
|
||||
'-windowed',
|
||||
'-log'
|
||||
)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($MapUrl))
|
||||
{
|
||||
$ArgumentList = @($MapUrl) + $ArgumentList
|
||||
}
|
||||
|
||||
Write-Host "Launching packaged HyperTwist desktop experience from '$ExecutablePath'..."
|
||||
$ResolvedPackageRoot = (Resolve-Path -LiteralPath $PackageRoot).Path
|
||||
$GeneratedAtUtc = [DateTime]::UtcNow.ToString('o')
|
||||
$Process = $null
|
||||
$Report = [ordered]@{
|
||||
reportVersion = 'ht-desktop-package-launch-surface/v1'
|
||||
generatedAtUtc = $GeneratedAtUtc
|
||||
packageRoot = $ResolvedPackageRoot
|
||||
executablePath = $ExecutablePath
|
||||
mapUrl = $MapUrl
|
||||
smokeSeconds = $SmokeSeconds
|
||||
resolution = [ordered]@{
|
||||
width = $ResX
|
||||
height = $ResY
|
||||
}
|
||||
keepRunning = [bool]$KeepRunning
|
||||
result = 'failed'
|
||||
processId = $null
|
||||
processStopped = $false
|
||||
exitCode = $null
|
||||
error = $null
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$Process = Start-Process -FilePath $ExecutablePath -ArgumentList $ArgumentList -PassThru
|
||||
Start-Sleep -Seconds $SmokeSeconds
|
||||
|
||||
$Process.Refresh()
|
||||
$Report.processId = $Process.Id
|
||||
if ($Process.HasExited)
|
||||
{
|
||||
$Report.exitCode = $Process.ExitCode
|
||||
throw "Packaged desktop executable exited early with code $($Process.ExitCode)."
|
||||
}
|
||||
|
||||
$Report.result = 'passed'
|
||||
}
|
||||
catch
|
||||
{
|
||||
$Report.error = $_.Exception.Message
|
||||
if ($null -ne $Process)
|
||||
{
|
||||
$Process.Refresh()
|
||||
if ($Process.HasExited)
|
||||
{
|
||||
$Report.exitCode = $Process.ExitCode
|
||||
}
|
||||
elseif (-not $KeepRunning)
|
||||
{
|
||||
Stop-Process -Id $Process.Id -Force
|
||||
$Report.processStopped = $true
|
||||
}
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($ReportPath))
|
||||
{
|
||||
Write-Utf8JsonFile -Path $ReportPath -Value $Report
|
||||
}
|
||||
|
||||
throw
|
||||
}
|
||||
|
||||
Write-Host "Packaged desktop smoke launch succeeded (PID $($Process.Id))."
|
||||
if (-not $KeepRunning)
|
||||
{
|
||||
Stop-Process -Id $Process.Id -Force
|
||||
$Report.processStopped = $true
|
||||
Write-Host 'Stopped packaged desktop smoke process after successful launch validation.'
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($ReportPath))
|
||||
{
|
||||
Write-Utf8JsonFile -Path $ReportPath -Value $Report
|
||||
}
|
||||
|
|
@ -5,14 +5,14 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="HyperTwist is a native cube and hypercube training environment for recognition, replay, coaching, higher-dimensional runtime ownership, and desktop-first operator workflows."
|
||||
content="HyperTwist is the desktop-first cube and hypercube simulator for guided practice, replay review, coaching, and serious 120‑cell and 5D study."
|
||||
/>
|
||||
<meta property="og:site_name" content="HyperTwist" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="HyperTwist" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="HyperTwist is a native cube and hypercube training environment for recognition, replay, coaching, higher-dimensional runtime ownership, and desktop-first operator workflows."
|
||||
content="HyperTwist is the desktop-first cube and hypercube simulator for guided practice, replay review, coaching, and serious 120‑cell and 5D study."
|
||||
/>
|
||||
<meta property="og:url" content="https://hypertwist.app/" />
|
||||
<meta property="og:image" content="https://hypertwist.app/branding/hypertwist-3d-symbol.png" />
|
||||
|
|
@ -20,7 +20,7 @@
|
|||
<meta name="twitter:title" content="HyperTwist" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="HyperTwist is a native cube and hypercube training environment for recognition, replay, coaching, higher-dimensional runtime ownership, and desktop-first operator workflows."
|
||||
content="HyperTwist is the desktop-first cube and hypercube simulator for guided practice, replay review, coaching, and serious 120‑cell and 5D study."
|
||||
/>
|
||||
<meta name="twitter:image" content="https://hypertwist.app/branding/hypertwist-3d-symbol.png" />
|
||||
<meta name="theme-color" content="#0b1020" />
|
||||
|
|
@ -134,7 +134,7 @@
|
|||
<div class="hypertwist-initial-shell__spinner" aria-hidden="true"></div>
|
||||
<p class="hypertwist-initial-shell__title">Loading HyperTwist...</p>
|
||||
<p class="hypertwist-initial-shell__body">
|
||||
Preparing the public product story, shared auth posture, and desktop-first release lane.
|
||||
Preparing your account access, downloads, release notes, and desktop training workspace.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -169,7 +169,7 @@
|
|||
backdrop.style.inset = '0';
|
||||
backdrop.style.zIndex = '0';
|
||||
backdrop.style.pointerEvents = 'none';
|
||||
backdrop.style.background = 'radial-gradient(circle at top, rgba(216,203,175,0.12), transparent 40%), linear-gradient(180deg, rgba(18,20,24,0.97), rgba(10,12,17,0.99))';
|
||||
backdrop.style.background = 'radial-gradient(circle at top, rgba(84,203,255,0.14), transparent 34%), radial-gradient(circle at 82% 14%, rgba(247,178,103,0.16), transparent 24%), linear-gradient(180deg, rgba(9,12,24,0.86), rgba(7,10,19,0.94))';
|
||||
document.body.appendChild(backdrop);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
"dev": "tsx watch src/index.ts",
|
||||
"start": "tsx src/index.ts",
|
||||
"type-check": "tsc --noEmit",
|
||||
"test": "vitest run"
|
||||
"test": "vitest run",
|
||||
"entitlement:set-manual": "tsx scripts/set-manual-entitlement.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.4.7",
|
||||
|
|
|
|||
106
website/server/scripts/set-manual-entitlement.ts
Normal file
106
website/server/scripts/set-manual-entitlement.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import path from 'node:path'
|
||||
import {
|
||||
createBillingStateStore,
|
||||
type BillingPlan,
|
||||
type BillingRole,
|
||||
} from '../src/billing-state'
|
||||
|
||||
type ParsedArgs = {
|
||||
email: string
|
||||
plan: BillingPlan
|
||||
role: BillingRole
|
||||
canDownload: boolean
|
||||
accessStatus: string
|
||||
}
|
||||
|
||||
function readArgValue(args: string[], flag: string) {
|
||||
const index = args.indexOf(flag)
|
||||
if (index === -1) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return String(args[index + 1] || '').trim()
|
||||
}
|
||||
|
||||
function readBooleanArg(rawValue: string, fallback: boolean) {
|
||||
const value = rawValue.trim().toLowerCase()
|
||||
if (!value) {
|
||||
return fallback
|
||||
}
|
||||
if (value === '1' || value === 'true' || value === 'yes' || value === 'on') {
|
||||
return true
|
||||
}
|
||||
if (value === '0' || value === 'false' || value === 'no' || value === 'off') {
|
||||
return false
|
||||
}
|
||||
throw new Error(`Invalid boolean value '${rawValue}'. Use true/false.`)
|
||||
}
|
||||
|
||||
function normalizePlan(value: string): BillingPlan {
|
||||
const plan = value.trim().toLowerCase()
|
||||
if (plan === 'free' || plan === 'operator' || plan === 'studio' || plan === 'enterprise') {
|
||||
return plan
|
||||
}
|
||||
if (plan === 'explorer') {
|
||||
return 'free'
|
||||
}
|
||||
throw new Error(`Unsupported plan '${value}'.`)
|
||||
}
|
||||
|
||||
function normalizeRole(value: string): BillingRole {
|
||||
const role = value.trim().toLowerCase()
|
||||
if (role === 'viewer' || role === 'operator' || role === 'reviewer' || role === 'admin') {
|
||||
return role
|
||||
}
|
||||
throw new Error(`Unsupported role '${value}'.`)
|
||||
}
|
||||
|
||||
function parseArgs(argv: string[]): ParsedArgs {
|
||||
const email = readArgValue(argv, '--email').toLowerCase()
|
||||
if (!email) {
|
||||
throw new Error('Provide --email user@example.com')
|
||||
}
|
||||
|
||||
const plan = normalizePlan(readArgValue(argv, '--plan') || 'enterprise')
|
||||
const role = normalizeRole(readArgValue(argv, '--role') || 'admin')
|
||||
const canDownload = readBooleanArg(readArgValue(argv, '--can-download'), plan !== 'free')
|
||||
const accessStatus = readArgValue(argv, '--access-status') || (canDownload ? 'active' : 'manual-review')
|
||||
|
||||
return {
|
||||
email,
|
||||
plan,
|
||||
role,
|
||||
canDownload,
|
||||
accessStatus,
|
||||
}
|
||||
}
|
||||
|
||||
function resolveBillingStatePath() {
|
||||
return process.env.BILLING_STATE_PATH
|
||||
|| path.join(process.cwd(), 'data', 'hypertwist-billing-state.json')
|
||||
}
|
||||
|
||||
function main() {
|
||||
const parsed = parseArgs(process.argv.slice(2))
|
||||
const statePath = resolveBillingStatePath()
|
||||
const store = createBillingStateStore({
|
||||
statePath,
|
||||
defaultPlan: 'free',
|
||||
defaultRole: 'operator',
|
||||
})
|
||||
|
||||
const entitlement = store.setManualEntitlement({
|
||||
email: parsed.email,
|
||||
plan: parsed.plan,
|
||||
role: parsed.role,
|
||||
canDownload: parsed.canDownload,
|
||||
accessStatus: parsed.accessStatus,
|
||||
})
|
||||
|
||||
console.log(JSON.stringify({
|
||||
statePath,
|
||||
entitlement,
|
||||
}, null, 2))
|
||||
}
|
||||
|
||||
main()
|
||||
70
website/server/src/__tests__/auth-session-payload.test.ts
Normal file
70
website/server/src/__tests__/auth-session-payload.test.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { buildEmailPasswordApiOverrides } from '../auth-session-payload'
|
||||
|
||||
describe('auth-session-payload', () => {
|
||||
it('stamps email, default plan, and default role onto successful sign-up and sign-in sessions', async () => {
|
||||
const mergeOnSignUp = vi.fn(async () => {})
|
||||
const mergeOnSignIn = vi.fn(async () => {})
|
||||
|
||||
const overrides = buildEmailPasswordApiOverrides({
|
||||
signUpPOST: vi.fn(async (_input: unknown) => ({
|
||||
status: 'OK',
|
||||
user: {
|
||||
emails: ['Operator@HyperTwist.App'],
|
||||
},
|
||||
session: {
|
||||
mergeIntoAccessTokenPayload: mergeOnSignUp,
|
||||
},
|
||||
})),
|
||||
signInPOST: vi.fn(async (_input: unknown) => ({
|
||||
status: 'OK',
|
||||
user: {
|
||||
emails: ['Operator@HyperTwist.App'],
|
||||
},
|
||||
session: {
|
||||
mergeIntoAccessTokenPayload: mergeOnSignIn,
|
||||
},
|
||||
})),
|
||||
}, {
|
||||
defaultPlan: 'free',
|
||||
defaultRole: 'operator',
|
||||
})
|
||||
|
||||
await overrides.signUpPOST?.({})
|
||||
await overrides.signInPOST?.({})
|
||||
|
||||
expect(mergeOnSignUp).toHaveBeenCalledWith({
|
||||
email: 'operator@hypertwist.app',
|
||||
plan: 'free',
|
||||
role: 'operator',
|
||||
})
|
||||
expect(mergeOnSignIn).toHaveBeenCalledWith({
|
||||
email: 'operator@hypertwist.app',
|
||||
plan: 'free',
|
||||
role: 'operator',
|
||||
})
|
||||
})
|
||||
|
||||
it('does not stamp payload fields when the auth response is not successful', async () => {
|
||||
const mergeIntoAccessTokenPayload = vi.fn(async () => {})
|
||||
|
||||
const overrides = buildEmailPasswordApiOverrides({
|
||||
signInPOST: vi.fn(async (_input: unknown) => ({
|
||||
status: 'WRONG_CREDENTIALS_ERROR',
|
||||
user: {
|
||||
emails: ['operator@hypertwist.app'],
|
||||
},
|
||||
session: {
|
||||
mergeIntoAccessTokenPayload,
|
||||
},
|
||||
})),
|
||||
}, {
|
||||
defaultPlan: 'free',
|
||||
defaultRole: 'operator',
|
||||
})
|
||||
|
||||
await overrides.signInPOST?.({})
|
||||
|
||||
expect(mergeIntoAccessTokenPayload).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
79
website/server/src/auth-session-payload.ts
Normal file
79
website/server/src/auth-session-payload.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
export interface SessionPayloadMergeCapable {
|
||||
mergeIntoAccessTokenPayload(payload: Record<string, unknown>): Promise<void>
|
||||
}
|
||||
|
||||
export interface SessionPayloadUserEnvelope {
|
||||
emails: string[]
|
||||
}
|
||||
|
||||
export interface SessionPayloadResponseEnvelope {
|
||||
status?: string
|
||||
session?: SessionPayloadMergeCapable
|
||||
user?: SessionPayloadUserEnvelope
|
||||
}
|
||||
|
||||
function readPrimaryEmail(response: SessionPayloadResponseEnvelope) {
|
||||
return String(response.user?.emails?.[0] || '').trim().toLowerCase()
|
||||
}
|
||||
|
||||
async function stampDefaultSessionPayload(
|
||||
response: SessionPayloadResponseEnvelope,
|
||||
{
|
||||
defaultPlan,
|
||||
defaultRole,
|
||||
}: {
|
||||
defaultPlan: string
|
||||
defaultRole: string
|
||||
},
|
||||
) {
|
||||
if (response.status !== 'OK' || !response.session) {
|
||||
return
|
||||
}
|
||||
|
||||
const email = readPrimaryEmail(response)
|
||||
if (!email) {
|
||||
return
|
||||
}
|
||||
|
||||
await response.session.mergeIntoAccessTokenPayload({
|
||||
email,
|
||||
plan: defaultPlan,
|
||||
role: defaultRole,
|
||||
})
|
||||
}
|
||||
|
||||
export function buildEmailPasswordApiOverrides<T extends Record<string, unknown>>(
|
||||
originalImplementation: T,
|
||||
{
|
||||
defaultPlan,
|
||||
defaultRole,
|
||||
}: {
|
||||
defaultPlan: string
|
||||
defaultRole: string
|
||||
},
|
||||
) {
|
||||
const typedImplementation = originalImplementation as T & {
|
||||
signUpPOST?: (input: unknown) => Promise<SessionPayloadResponseEnvelope>
|
||||
signInPOST?: (input: unknown) => Promise<SessionPayloadResponseEnvelope>
|
||||
}
|
||||
|
||||
return {
|
||||
...originalImplementation,
|
||||
signUpPOST: async (input: unknown) => {
|
||||
const response = await typedImplementation.signUpPOST!(input)
|
||||
await stampDefaultSessionPayload(response, {
|
||||
defaultPlan,
|
||||
defaultRole,
|
||||
})
|
||||
return response
|
||||
},
|
||||
signInPOST: async (input: unknown) => {
|
||||
const response = await typedImplementation.signInPOST!(input)
|
||||
await stampDefaultSessionPayload(response, {
|
||||
defaultPlan,
|
||||
defaultRole,
|
||||
})
|
||||
return response
|
||||
},
|
||||
} as T
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ import path from 'node:path'
|
|||
|
||||
export type BillingPlan = 'free' | 'operator' | 'studio' | 'enterprise'
|
||||
export type BillingRole = 'viewer' | 'operator' | 'reviewer' | 'admin'
|
||||
export type BillingEntitlementSource = 'paddle' | 'manual'
|
||||
|
||||
export interface BillingEntitlementState {
|
||||
email: string
|
||||
|
|
@ -10,7 +11,7 @@ export interface BillingEntitlementState {
|
|||
role: BillingRole
|
||||
canDownload: boolean
|
||||
accessStatus: string
|
||||
source: 'paddle'
|
||||
source: BillingEntitlementSource
|
||||
customerId: string | null
|
||||
subscriptionId: string | null
|
||||
transactionId: string | null
|
||||
|
|
@ -20,6 +21,14 @@ export interface BillingEntitlementState {
|
|||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface ManualBillingEntitlementInput {
|
||||
email: string
|
||||
plan: BillingPlan
|
||||
role: BillingRole
|
||||
canDownload?: boolean
|
||||
accessStatus?: string
|
||||
}
|
||||
|
||||
interface BillingStateFile {
|
||||
version: 'ht-billing-state/v1'
|
||||
updatedAt: string
|
||||
|
|
@ -413,6 +422,45 @@ function buildNextEntitlementState({
|
|||
}
|
||||
}
|
||||
|
||||
function buildManualEntitlementState({
|
||||
existingAccount,
|
||||
input,
|
||||
nowIso,
|
||||
}: {
|
||||
existingAccount: BillingEntitlementState | null
|
||||
input: ManualBillingEntitlementInput
|
||||
nowIso: string
|
||||
}): BillingEntitlementState {
|
||||
const normalizedEmail = normalizeEmail(input.email)
|
||||
const normalizedPlan = normalizePlan(input.plan, 'free')
|
||||
const normalizedRole = normalizeRole(input.role, 'viewer')
|
||||
const resolvedCanDownload =
|
||||
typeof input.canDownload === 'boolean'
|
||||
? input.canDownload
|
||||
: normalizedPlan !== 'free'
|
||||
const resolvedAccessStatus = String(
|
||||
input.accessStatus
|
||||
|| (resolvedCanDownload ? 'active' : normalizedPlan === 'free' ? 'free' : 'manual-review'),
|
||||
).trim().toLowerCase()
|
||||
const manualEventId = `manual:${normalizedEmail}:${Date.parse(nowIso)}`
|
||||
|
||||
return {
|
||||
email: normalizedEmail,
|
||||
plan: normalizedPlan,
|
||||
role: normalizedRole,
|
||||
canDownload: resolvedCanDownload,
|
||||
accessStatus: resolvedAccessStatus,
|
||||
source: 'manual',
|
||||
customerId: existingAccount?.customerId || null,
|
||||
subscriptionId: existingAccount?.subscriptionId || null,
|
||||
transactionId: existingAccount?.transactionId || null,
|
||||
lastEventId: manualEventId,
|
||||
lastEventType: 'manual.entitlement.set',
|
||||
lastEventAt: nowIso,
|
||||
updatedAt: nowIso,
|
||||
}
|
||||
}
|
||||
|
||||
function applyParsedPaddleEvent(
|
||||
envelope: PaddleEventEnvelope,
|
||||
runtime: BillingStateStoreRuntime,
|
||||
|
|
@ -527,10 +575,34 @@ export function createBillingStateStore({
|
|||
return Object.keys(loadState().processedEvents).length
|
||||
}
|
||||
|
||||
function setManualEntitlement(input: ManualBillingEntitlementInput): BillingEntitlementState {
|
||||
const normalizedEmail = normalizeEmail(input.email)
|
||||
if (!normalizedEmail) {
|
||||
throw new Error('Manual entitlement email is required.')
|
||||
}
|
||||
|
||||
const state = loadState()
|
||||
const nowIso = toIsoString(now())
|
||||
const existingAccount = state.accounts[normalizedEmail] || null
|
||||
const nextState = buildManualEntitlementState({
|
||||
existingAccount,
|
||||
input: {
|
||||
...input,
|
||||
email: normalizedEmail,
|
||||
},
|
||||
nowIso,
|
||||
})
|
||||
|
||||
state.accounts[normalizedEmail] = nextState
|
||||
saveState(state)
|
||||
return nextState
|
||||
}
|
||||
|
||||
return {
|
||||
applyVerifiedPaddleEvent,
|
||||
getEntitlementByEmail,
|
||||
getProcessedEventCount,
|
||||
getStatePath: () => statePath,
|
||||
setManualEntitlement,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import ThirdParty from 'supertokens-node/recipe/thirdparty'
|
|||
import GithubProvider from 'supertokens-node/lib/build/recipe/thirdparty/providers/github'
|
||||
import GoogleProvider from 'supertokens-node/lib/build/recipe/thirdparty/providers/google'
|
||||
import { resolvePublicLaunchStatusSummary } from '../../src/shared/public-launch'
|
||||
import { buildEmailPasswordApiOverrides } from './auth-session-payload'
|
||||
import { probeSuperTokensCoreHealth } from './auth-health'
|
||||
import { createBillingStateStore, type BillingPlan, type BillingRole } from './billing-state'
|
||||
import { verifyPaddleWebhookSignature } from './paddle-webhook'
|
||||
|
|
@ -225,19 +226,9 @@ const recipeList = [
|
|||
],
|
||||
},
|
||||
override: {
|
||||
apis: (originalImplementation) => ({
|
||||
...originalImplementation,
|
||||
signUpPOST: async (input) => {
|
||||
const response = await originalImplementation.signUpPOST!(input)
|
||||
if (response.status === 'OK') {
|
||||
await response.session.mergeIntoAccessTokenPayload({
|
||||
email: response.user.emails[0],
|
||||
plan: DEFAULT_PLAN,
|
||||
role: DEFAULT_ROLE,
|
||||
})
|
||||
}
|
||||
return response
|
||||
},
|
||||
apis: (originalImplementation) => buildEmailPasswordApiOverrides(originalImplementation, {
|
||||
defaultPlan: DEFAULT_PLAN,
|
||||
defaultRole: DEFAULT_ROLE,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ describe('App bootstrap', () => {
|
|||
|
||||
render(<App />)
|
||||
|
||||
expect(await screen.findByText('From first solves to 120-cell, HyperTwist keeps the real simulator in the downloadable.')).toBeTruthy()
|
||||
expect(await screen.findByText('One serious home for classic cubes, 120‑cell, 5D, and deep daily practice.')).toBeTruthy()
|
||||
await waitFor(() => {
|
||||
expect(window.location.pathname).toBe('/')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -208,7 +208,7 @@ describe('DashboardOverviewPage', () => {
|
|||
expect(screen.getByText('Protected operator quick routes')).toBeTruthy()
|
||||
expect(screen.getByText('Signed-in help lanes')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
|
||||
expect(screen.getByText('Current input, XR, and settings truth')).toBeTruthy()
|
||||
expect(screen.getByText('Controls and device support today')).toBeTruthy()
|
||||
expect(
|
||||
screen
|
||||
.getAllByRole('link', { name: 'Open protected launch status' })
|
||||
|
|
@ -222,7 +222,7 @@ describe('DashboardOverviewPage', () => {
|
|||
expect(screen.getByText(/Magic120Cell dedicated-family training map: passed/i)).toBeTruthy()
|
||||
expect(screen.getByText('First simulator session')).toBeTruthy()
|
||||
expect(screen.getByText('1. Resolve access and choose the right build')).toBeTruthy()
|
||||
expect(screen.getByText('4. Open the higher-dimensional lanes and read the control boundary honestly')).toBeTruthy()
|
||||
expect(screen.getByText('4. Open the higher-dimensional families and read the control boundary honestly')).toBeTruthy()
|
||||
expect(screen.getByText('Software workflows after access is resolved')).toBeTruthy()
|
||||
expect(screen.getByText('2. Run a recognition and correction workflow')).toBeTruthy()
|
||||
expect(screen.getByText('Native control and settings roster')).toBeTruthy()
|
||||
|
|
@ -230,7 +230,7 @@ describe('DashboardOverviewPage', () => {
|
|||
expect(screen.getByText('XR groundwork exists, but the full VR lane is not finished')).toBeTruthy()
|
||||
expect(screen.getByText('Desktop rollout follow-through')).toBeTruthy()
|
||||
expect(screen.getByText('Current product-surface map')).toBeTruthy()
|
||||
expect(screen.getByText('Embedded simulator browser shell')).toBeTruthy()
|
||||
expect(screen.getByText('Embedded simulator web tools')).toBeTruthy()
|
||||
expect(screen.getByText('Pair desktop access to the browser account')).toBeTruthy()
|
||||
|
||||
const generateDesktopLinkButtons = screen.getAllByRole('button', { name: /generate desktop-link token/i })
|
||||
|
|
|
|||
|
|
@ -156,10 +156,10 @@ describe('AppRouteTree', () => {
|
|||
renderRoute('/')
|
||||
|
||||
expect(screen.getByText('Loading HyperTwist...')).toBeTruthy()
|
||||
expect(screen.getByText('Public release shell')).toBeTruthy()
|
||||
expect(screen.getByText(/public product story, current launch status, and browser-versus-desktop authority map/i)).toBeTruthy()
|
||||
expect(screen.getByText('Public website')).toBeTruthy()
|
||||
expect(screen.getByText(/full HyperTwist story, the current access snapshot, and the easiest path into the desktop app/i)).toBeTruthy()
|
||||
expect(await screen.findByText(
|
||||
'From first solves to 120-cell, HyperTwist keeps the real simulator in the downloadable.',
|
||||
'One serious home for classic cubes, 120‑cell, 5D, and deep daily practice.',
|
||||
{},
|
||||
{ timeout: 3000 },
|
||||
)).toBeTruthy()
|
||||
|
|
@ -169,18 +169,17 @@ describe('AppRouteTree', () => {
|
|||
renderRoute('/browser')
|
||||
|
||||
expect(screen.getByText('Browser access guide')).toBeTruthy()
|
||||
expect(screen.getByText('Web version role')).toBeTruthy()
|
||||
expect(screen.getByText(/browser-versus-downloadable boundary, protected dashboard handoff, and the current web-lane purpose/i)).toBeTruthy()
|
||||
expect(await screen.findByText('The browser version exists to support the downloadable, not replace it.')).toBeTruthy()
|
||||
expect(screen.getByText('Browser companion')).toBeTruthy()
|
||||
expect(screen.getByText(/website, account access, and desktop app work together/i)).toBeTruthy()
|
||||
expect(await screen.findByText('The website gets you in. The desktop app becomes the training room.')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the help-center page through the real public route tree', async () => {
|
||||
renderRoute('/help')
|
||||
|
||||
expect(screen.getByText('Help center')).toBeTruthy()
|
||||
expect(screen.getByText('Operator knowledge base')).toBeTruthy()
|
||||
expect(screen.getByText(/onboarding, control truth, browser-to-desktop guidance, and rollout-safe troubleshooting/i)).toBeTruthy()
|
||||
expect(await screen.findByText('Production-grade help for the routes, runtime, and rollout you actually have.')).toBeTruthy()
|
||||
expect(screen.getAllByText('Help center').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText(/setup, controls, downloads, and practical troubleshooting/i)).toBeTruthy()
|
||||
expect(await screen.findByText('Practical help for setup, controls, downloads, and launch.')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the pricing page through the real public route tree', async () => {
|
||||
|
|
@ -191,8 +190,8 @@ describe('AppRouteTree', () => {
|
|||
|
||||
it('renders the public feature-atlas page through the real public route tree', async () => {
|
||||
renderRoute('/features')
|
||||
expect(await screen.findByText('Feature truth without the guesswork.')).toBeTruthy()
|
||||
expect(screen.getByText('How to read the product truth')).toBeTruthy()
|
||||
expect(await screen.findByText('A deep training toolkit, not just a timer.')).toBeTruthy()
|
||||
expect(screen.getByText('How to read this feature map')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the download page through the real public route tree', async () => {
|
||||
|
|
@ -203,25 +202,24 @@ describe('AppRouteTree', () => {
|
|||
|
||||
it('renders the getting-started page through the real public route tree', async () => {
|
||||
renderRoute('/getting-started')
|
||||
expect(screen.getByText('Getting started')).toBeTruthy()
|
||||
expect(screen.getByText('Operator onboarding')).toBeTruthy()
|
||||
expect(screen.getByText(/first-session quickstart, browser-to-desktop pairing flow, and the current desktop-first runtime boundary/i)).toBeTruthy()
|
||||
expect(await screen.findByText('Start in the browser. Train in the desktop runtime.')).toBeTruthy()
|
||||
expect(screen.getAllByText('Getting started').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText(/first-session path from account setup to the desktop app/i)).toBeTruthy()
|
||||
expect(await screen.findByText('The first serious HyperTwist session')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the launch-status page through the real public route tree', async () => {
|
||||
renderRoute('/launch-status')
|
||||
expect(screen.getByText('Launch status')).toBeTruthy()
|
||||
expect(screen.getByText('Launch authority')).toBeTruthy()
|
||||
expect(screen.getByText(/canonical public launch-readiness checklist, release status, and early-access authority map/i)).toBeTruthy()
|
||||
expect(screen.getByText('Access status')).toBeTruthy()
|
||||
expect(screen.getByText(/current availability, release progress, and access options/i)).toBeTruthy()
|
||||
expect(await screen.findByText('See how public pages, account access, and desktop delivery work together.')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('renders the login page through the real public route tree', async () => {
|
||||
renderRoute('/login?next=%2Fapp%2Fdownloads')
|
||||
|
||||
expect(screen.getByText('Browser account access')).toBeTruthy()
|
||||
expect(screen.getByText(/safe next-step routing, and protected release continuity/i)).toBeTruthy()
|
||||
expect(screen.getByText('Account access')).toBeTruthy()
|
||||
expect(screen.getByText(/sign-in, account access, and the path into your dashboard and downloads/i)).toBeTruthy()
|
||||
expect(await screen.findByText('Log in to HyperTwist')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: /create an account/i }).getAttribute('href')).toBe('/register?next=%2Fapp%2Fdownloads')
|
||||
})
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ describe('DownloadCenterPage', () => {
|
|||
expect(screen.getByText('First launch follow-through')).toBeTruthy()
|
||||
expect(screen.getByText('Verify the current training lanes')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
|
||||
expect(screen.getByText('What the desktop runtime is for')).toBeTruthy()
|
||||
expect(screen.getByText('Why the desktop app exists')).toBeTruthy()
|
||||
expect(screen.getByText('Download and rollout escalation')).toBeTruthy()
|
||||
expect(screen.getByText('Current product-surface map')).toBeTruthy()
|
||||
expect(await screen.findByText('Version: 1.0.0')).toBeTruthy()
|
||||
|
|
|
|||
|
|
@ -197,9 +197,9 @@ describe('protected app pages', () => {
|
|||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
expect(screen.getByText(/Magic120Cell dedicated-family training map: passed/i)).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
|
||||
expect(screen.getByText('Why the browser is intentionally narrower')).toBeTruthy()
|
||||
expect(screen.getByText('Why the website feels fast and focused')).toBeTruthy()
|
||||
expect(screen.getByText('Desktop workflows behind the browser handoff')).toBeTruthy()
|
||||
expect(screen.getByText('4. Open the higher-dimensional family lanes')).toBeTruthy()
|
||||
expect(screen.getByText('4. Open the higher-dimensional families')).toBeTruthy()
|
||||
expect(screen.getByText('Native control and XR boundary')).toBeTruthy()
|
||||
expect(screen.getByText('Shipped classic control profile')).toBeTruthy()
|
||||
expect(await screen.findByText(byExactTextContent('Auth runtime mode: public', 'LI'))).toBeTruthy()
|
||||
|
|
@ -395,12 +395,12 @@ describe('protected app pages', () => {
|
|||
expect(screen.getByText('What the downloaded software already supports')).toBeTruthy()
|
||||
expect(screen.getByText('3. Review replay, coaching, and analytics')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
|
||||
expect(screen.getByText('What the desktop runtime is for')).toBeTruthy()
|
||||
expect(screen.getByText('Why the desktop app exists')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Open account' }).getAttribute('href')).toBe('/app/account')
|
||||
expect(screen.getAllByRole('link', { name: 'Open browser access' }).every((link) => link.getAttribute('href') === '/app/browser-access')).toBe(true)
|
||||
expect(screen.getByText('Download and rollout escalation')).toBeTruthy()
|
||||
expect(screen.getByText('Current product-surface map')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser dashboard')).toBeTruthy()
|
||||
expect(screen.getByText('Signed-in dashboard')).toBeTruthy()
|
||||
expect(screen.getAllByRole('link', { name: 'Protected launch status' }).every((link) => link.getAttribute('href') === '/app/launch-status')).toBe(true)
|
||||
expect(screen.getByRole('link', { name: 'Protected notices' }).getAttribute('href')).toBe('/app/notices')
|
||||
expect(screen.getByRole('link', { name: 'Public notices' }).getAttribute('href')).toBe('/open-source-notices')
|
||||
|
|
@ -420,7 +420,7 @@ describe('protected app pages', () => {
|
|||
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
|
||||
expect(screen.getByText('Why the browser is intentionally narrower')).toBeTruthy()
|
||||
expect(screen.getByText('Why the website feels fast and focused')).toBeTruthy()
|
||||
expect(screen.getByText('Current simulator control ownership')).toBeTruthy()
|
||||
expect(screen.getByText('Current release action')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Download Windows package' }).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
|
||||
|
|
@ -449,7 +449,7 @@ describe('protected app pages', () => {
|
|||
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
|
||||
expect(screen.getByText('What the desktop runtime is for')).toBeTruthy()
|
||||
expect(screen.getByText('Why the desktop app exists')).toBeTruthy()
|
||||
expect(await screen.findAllByText(byExactTextContent('Configured release targets: 1/2', 'LI'))).toHaveLength(1)
|
||||
expect(screen.getByText(byExactTextContent('Public repository/notices URL: https://github.com/hypertwist/hypertwist', 'LI'))).toBeTruthy()
|
||||
expect(screen.getByText(byExactTextContent('Corresponding-source URL: https://hypertwist.app/open-source/source.zip', 'LI'))).toBeTruthy()
|
||||
|
|
|
|||
|
|
@ -206,12 +206,12 @@ describe('public auth and download pages', () => {
|
|||
|
||||
const registerLink = screen.getByRole('link', { name: /create an account/i })
|
||||
expect(registerLink.getAttribute('href')).toBe('/register?next=%2Fapp')
|
||||
expect(screen.getByText('Protected operator dashboard')).toBeTruthy()
|
||||
expect(screen.getAllByText(/After sign-in you will continue to the protected operator dashboard/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Signed-in dashboard').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText(/After sign-in you will continue to the signed-in dashboard/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Browser account access does not replace the simulator.')).toBeTruthy()
|
||||
expect(screen.getByText('Current input, XR, and settings truth')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser dashboard')).toBeTruthy()
|
||||
expect(screen.getByText('Native Unreal desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Controls and device support today')).toBeTruthy()
|
||||
expect(screen.getAllByText('Signed-in dashboard').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Desktop simulator').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('What the desktop runtime is for once you are signed in')).toBeTruthy()
|
||||
expect(screen.getByText('1. Run a classic-cube training session')).toBeTruthy()
|
||||
|
||||
|
|
@ -234,12 +234,12 @@ describe('public auth and download pages', () => {
|
|||
|
||||
const loginLink = screen.getByRole('link', { name: 'Log in' })
|
||||
expect(loginLink.getAttribute('href')).toBe('/login?next=%2Fapp%2Fdownloads%3Fplatform%3Dwindows')
|
||||
expect(screen.getByText('Protected Windows release lane')).toBeTruthy()
|
||||
expect(screen.getAllByText(/After sign-in you will continue to the protected Windows download center/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Signed-in Windows downloads')).toBeTruthy()
|
||||
expect(screen.getAllByText(/After sign-in you will continue to the signed-in Windows download center/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Browser account access does not replace the simulator.')).toBeTruthy()
|
||||
expect(screen.getByText('Why the browser is intentionally narrower')).toBeTruthy()
|
||||
expect(screen.getByText('Protected browser dashboard')).toBeTruthy()
|
||||
expect(screen.getByText('Native Unreal desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Why the website feels fast and focused')).toBeTruthy()
|
||||
expect(screen.getAllByText('Signed-in dashboard').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Desktop simulator').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('What the desktop runtime is for once you are signed in')).toBeTruthy()
|
||||
expect(screen.getByText('2. Run a recognition and correction workflow')).toBeTruthy()
|
||||
|
||||
|
|
@ -295,13 +295,13 @@ describe('public auth and download pages', () => {
|
|||
|
||||
expect(screen.getByText(/Account access can continue through the available browser-auth lane./i)).toBeTruthy()
|
||||
expect(screen.getByText('What still works')).toBeTruthy()
|
||||
expect(screen.getByText('What stays intentionally bounded')).toBeTruthy()
|
||||
expect(screen.getByText('Recommended recovery order')).toBeTruthy()
|
||||
expect(screen.getByText('What may still need follow-through')).toBeTruthy()
|
||||
expect(screen.getByText('Best next step')).toBeTruthy()
|
||||
expect(screen.getByText('Auth runtime setting pending: VITE_SUPERTOKENS_API_DOMAIN')).toBeTruthy()
|
||||
expect(screen.getByText('VITE_AUTH_API_BASE_URL not configured; same-origin auth fallback remains active.')).toBeTruthy()
|
||||
expect(screen.getByText('Browser account access does not replace the simulator.')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Open support' }).getAttribute('href')).toBe('/support?topic=operator-access')
|
||||
expect(screen.getByText('Protected operator dashboard')).toBeTruthy()
|
||||
expect(screen.getAllByText('Signed-in dashboard').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('What the desktop runtime is for once you are signed in')).toBeTruthy()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -274,8 +274,8 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('What the installed runtime already owns')).toBeTruthy()
|
||||
expect(screen.getByText('Higher-dimensional family guide')).toBeTruthy()
|
||||
expect(screen.getByText('Current input and runtime control truth')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist keeps both a website and a desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Why the browser is intentionally narrower')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist gives you both a website and a desktop app')).toBeTruthy()
|
||||
expect(screen.getByText('Why the website feels fast and focused')).toBeTruthy()
|
||||
expect(screen.getByRole('heading', { name: 'Browser account access methods' })).toBeTruthy()
|
||||
expect(screen.getByText('Auth methods ready')).toBeTruthy()
|
||||
expect(screen.getByText('Email and password')).toBeTruthy()
|
||||
|
|
@ -283,20 +283,20 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('ORCID sign-in')).toBeTruthy()
|
||||
expect(screen.queryByText('Google sign-in')).toBeNull()
|
||||
expect(screen.getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
const downloadDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next release move' })
|
||||
const downloadDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next best step' })
|
||||
const downloadDecisionGuideSection = downloadDecisionGuideHeading.closest('section')
|
||||
expect(downloadDecisionGuideSection).toBeTruthy()
|
||||
expect(within(downloadDecisionGuideSection as HTMLElement).getByText('Need the protected desktop-download lane?')).toBeTruthy()
|
||||
expect(within(downloadDecisionGuideSection as HTMLElement).getByText('Need notices, corresponding source, or release follow-through?')).toBeTruthy()
|
||||
expect(within(downloadDecisionGuideSection as HTMLElement).getByText('Need the desktop download?')).toBeTruthy()
|
||||
expect(within(downloadDecisionGuideSection as HTMLElement).getByText('Need legal details, release notes, or source links?')).toBeTruthy()
|
||||
expect(screen.getByText('First launch and desktop setup')).toBeTruthy()
|
||||
expect(screen.getByText('First-session verification checklist')).toBeTruthy()
|
||||
expect(screen.getByText('Verify the higher-dimensional lane you plan to use')).toBeTruthy()
|
||||
expect(screen.getByText('Offline operator manual')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Download offline operator manual' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getAllByText('Offline desktop guide').length).toBeGreaterThan(0)
|
||||
expect(screen.getByRole('link', { name: 'Download offline desktop guide' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getByText('First desktop session after install')).toBeTruthy()
|
||||
expect(screen.getByText('2. Pair the installed runtime without password reuse')).toBeTruthy()
|
||||
expect(screen.getByText('Digital delivery workflow')).toBeTruthy()
|
||||
expect(screen.getByText('Protected entitlement handoff')).toBeTruthy()
|
||||
expect(screen.getByText('Private download handoff')).toBeTruthy()
|
||||
expect(screen.getByText('Distribution doctrine')).toBeTruthy()
|
||||
expect(screen.getByText('Public pages are distribution surfaces')).toBeTruthy()
|
||||
expect(screen.getByText('Release references and source availability')).toBeTruthy()
|
||||
|
|
@ -311,8 +311,8 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('SHA-256: abc123')).toBeTruthy()
|
||||
expect(screen.getAllByText('Packaged validation passed').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText(/Magic120Cell dedicated-family training map: passed/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Generates desktop-link tokens for safe browser-to-desktop handoff')).toBeTruthy()
|
||||
expect(screen.getByText('Owns the device/runtime integrations the public website does not claim')).toBeTruthy()
|
||||
expect(screen.getByText('Generates desktop-link tokens for safe website-to-desktop pairing.')).toBeTruthy()
|
||||
expect(screen.getByText('Owns the device integrations the public website does not claim.')).toBeTruthy()
|
||||
expect(document.title).toBe('Download HyperTwist desktop | HyperTwist')
|
||||
expect(document.head.querySelector('meta[property="og:url"]')?.getAttribute('content')).toBe('https://hypertwist.app/download')
|
||||
})
|
||||
|
|
@ -361,7 +361,7 @@ describe('public marketing pages', () => {
|
|||
expect(await screen.findByText(/Release service recovery/i)).toBeTruthy()
|
||||
expect(screen.getByText(/direct package delivery stays intentionally withheld until that authority returns/i)).toBeTruthy()
|
||||
expect(screen.getByText('What stays intentionally withheld')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Open protected download center' }).getAttribute('href')).toBe('/app/downloads?platform=windows')
|
||||
expect(screen.getAllByRole('link', { name: 'Open protected download center' }).some((link) => link.getAttribute('href') === '/app/downloads?platform=windows')).toBe(true)
|
||||
expect(screen.getByRole('link', { name: 'Open support' }).getAttribute('href')).toBe('/support?topic=operator-access')
|
||||
})
|
||||
|
||||
|
|
@ -461,15 +461,15 @@ describe('public marketing pages', () => {
|
|||
|
||||
renderWithProviders(<HomeLanding />, ['/'])
|
||||
|
||||
expect(screen.getByText('Current public site status')).toBeTruthy()
|
||||
expect(screen.getByText('Current access snapshot')).toBeTruthy()
|
||||
expect(await screen.findByText('Public website and release access')).toBeTruthy()
|
||||
expect(screen.getAllByText('Account-gated early access').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Why HyperTwist keeps both a website and a desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist gives you both a website and a desktop app')).toBeTruthy()
|
||||
expect(screen.getByText('Common browser-versus-desktop questions')).toBeTruthy()
|
||||
expect(screen.getByText('If the desktop app is primary, why keep the web version?')).toBeTruthy()
|
||||
expect(screen.getByText(/It is intentionally inferior for the core simulator job/i)).toBeTruthy()
|
||||
expect(screen.getByText('What the website is for')).toBeTruthy()
|
||||
expect(screen.getByText('Current input, XR, and settings truth')).toBeTruthy()
|
||||
expect(screen.getByText('What is the website actually for?')).toBeTruthy()
|
||||
expect(screen.getByText('The desktop app stays primary because that is where practice starts to feel rich, responsive, and worth returning to every day.')).toBeTruthy()
|
||||
expect(screen.getByText('Why the website exists')).toBeTruthy()
|
||||
expect(screen.getByText('Controls and device support today')).toBeTruthy()
|
||||
expect(screen.getAllByText('Account-gated early access').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Subscription access: managed on the website/dashboard and handed to the downloadable through desktop-link pairing.')).toBeTruthy()
|
||||
expect(screen.getByRole('heading', { name: 'Browser account access methods' })).toBeTruthy()
|
||||
|
|
@ -480,23 +480,23 @@ describe('public marketing pages', () => {
|
|||
expect(screen.queryByText('Google sign-in')).toBeNull()
|
||||
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
const homeDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next release move' })
|
||||
const homeDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next best step' })
|
||||
const homeDecisionGuideSection = homeDecisionGuideHeading.closest('section')
|
||||
expect(homeDecisionGuideSection).toBeTruthy()
|
||||
expect(within(homeDecisionGuideSection as HTMLElement).getByText('Need the protected desktop-download lane?')).toBeTruthy()
|
||||
expect(within(homeDecisionGuideSection as HTMLElement).getByText('Need plan selection, billing, or operator provisioning?')).toBeTruthy()
|
||||
expect(within(homeDecisionGuideSection as HTMLElement).getByText('Need the desktop download?')).toBeTruthy()
|
||||
expect(within(homeDecisionGuideSection as HTMLElement).getByText('Need pricing, billing, or subscription help?')).toBeTruthy()
|
||||
expect(screen.getByText('Release references and source availability')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'https://docs.hypertwist.app' })).toBeTruthy()
|
||||
expect(screen.getByText('How a real first session flows')).toBeTruthy()
|
||||
expect(screen.getByText('What the first serious session should look like')).toBeTruthy()
|
||||
expect(screen.getByText('1. Resolve access and choose the right build')).toBeTruthy()
|
||||
expect(screen.getByText('Which public page should you open next?')).toBeTruthy()
|
||||
expect(screen.getByText('Offline operator manual')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Download offline operator manual' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getAllByText('Offline desktop guide').length).toBeGreaterThan(0)
|
||||
expect(screen.getByRole('link', { name: 'Download offline desktop guide' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getByText('Launch status')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(screen.getByText('Move into the protected dashboard')).toBeTruthy()
|
||||
expect(screen.getAllByText('Current surface authority map').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Open your account dashboard')).toBeTruthy()
|
||||
expect(screen.getAllByText('Where each part of HyperTwist lives').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Native Unreal desktop runtime').length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
|
|
@ -603,25 +603,25 @@ describe('public marketing pages', () => {
|
|||
renderWithProviders(<AboutPage />, ['/about'])
|
||||
|
||||
expect(screen.getByText('How a real HyperTwist session unfolds')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist keeps both a website and a desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist gives you both a website and a desktop app')).toBeTruthy()
|
||||
expect(screen.getAllByText('Common browser-versus-desktop questions').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Is the simulator fully in the browser?')).toBeTruthy()
|
||||
expect(screen.getByText(/The current shipping lane is desktop-first and Unreal-backed/i)).toBeTruthy()
|
||||
expect(screen.getByText('What the desktop runtime is for')).toBeTruthy()
|
||||
expect(screen.getByText('Can I use HyperTwist entirely in my browser?')).toBeTruthy()
|
||||
expect(screen.getByText(/current shipping product is desktop-first and Unreal-backed/i)).toBeTruthy()
|
||||
expect(screen.getByText('Why the desktop app exists')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(screen.getByText('Stay on the public website')).toBeTruthy()
|
||||
const aboutDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next release move' })
|
||||
expect(screen.getByText('Stay on the website')).toBeTruthy()
|
||||
const aboutDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next best step' })
|
||||
const aboutDecisionGuideSection = aboutDecisionGuideHeading.closest('section')
|
||||
expect(aboutDecisionGuideSection).toBeTruthy()
|
||||
expect(within(aboutDecisionGuideSection as HTMLElement).getByText('Need account state, pairing, or protected browser follow-through?')).toBeTruthy()
|
||||
expect(within(aboutDecisionGuideSection as HTMLElement).getByText('Need plan selection, billing, or operator provisioning?')).toBeTruthy()
|
||||
expect(within(aboutDecisionGuideSection as HTMLElement).getByText('Need account access, pairing, or dashboard help?')).toBeTruthy()
|
||||
expect(within(aboutDecisionGuideSection as HTMLElement).getByText('Need pricing, billing, or subscription help?')).toBeTruthy()
|
||||
expect(screen.getByText('Current desktop control and headset truth')).toBeTruthy()
|
||||
expect(screen.getByText('Selectable control and settings roster')).toBeTruthy()
|
||||
expect(screen.getByText('Shipped classic control profile')).toBeTruthy()
|
||||
expect(screen.getByText('Generates desktop-link tokens for safe browser-to-desktop handoff')).toBeTruthy()
|
||||
expect(screen.getByText('Would need its own runtime quality, backend contract, and product proof before launch')).toBeTruthy()
|
||||
expect(screen.getByText('Generates desktop-link tokens for safe website-to-desktop pairing.')).toBeTruthy()
|
||||
expect(screen.getByText('Future browser-only simulator')).toBeTruthy()
|
||||
expect(screen.getByText('Release and deployment maturity')).toBeTruthy()
|
||||
expect(screen.getByText('Current public rollout state')).toBeTruthy()
|
||||
expect(screen.getByText('Current public access state')).toBeTruthy()
|
||||
expect(screen.getByText('About-page launch and release access')).toBeTruthy()
|
||||
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
|
|
@ -725,31 +725,31 @@ describe('public marketing pages', () => {
|
|||
|
||||
renderWithProviders(<FeaturesPage />, ['/features'])
|
||||
|
||||
expect(screen.getByText('Feature truth without the guesswork.')).toBeTruthy()
|
||||
expect(screen.getByText('How to read the product truth')).toBeTruthy()
|
||||
expect(screen.getByText('Implemented now')).toBeTruthy()
|
||||
expect(screen.getByText('A deep training toolkit, not just a timer.')).toBeTruthy()
|
||||
expect(screen.getByText('How to read this feature map')).toBeTruthy()
|
||||
expect(screen.getByText('Available today')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist keeps both a website and a desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Current input, XR, and settings truth')).toBeTruthy()
|
||||
expect(screen.getAllByText('Current surface authority map').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Embedded simulator browser shell').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Current shipped capability')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist gives you both a website and a desktop app')).toBeTruthy()
|
||||
expect(screen.getByText('Controls and device support today')).toBeTruthy()
|
||||
expect(screen.getAllByText('Where everything happens').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Embedded simulator web tools').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('What you can use today').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Current selectable control roster')).toBeTruthy()
|
||||
expect(screen.getAllByText('Selectable immersive and family-specific settings').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('XR reopen requirements').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('The remaining reopen requirement is Windows packaged controller validation with controller truth on the shipping lane rather than only editor-time or config-only confidence.')).toBeTruthy()
|
||||
expect(screen.getByText('Runtime control guide')).toBeTruthy()
|
||||
expect(screen.getByText('Controls guide')).toBeTruthy()
|
||||
expect(screen.getByText('Native diagnostics check')).toBeTruthy()
|
||||
expect(screen.getByText('Common capability questions')).toBeTruthy()
|
||||
expect(screen.getByText('If the desktop app is primary, why keep the web version?')).toBeTruthy()
|
||||
expect(screen.getByText('Is VR/controller support already fully finished?')).toBeTruthy()
|
||||
expect(screen.getByText('Current public rollout state')).toBeTruthy()
|
||||
expect(screen.getByText('What is the website actually for?')).toBeTruthy()
|
||||
expect(screen.getByText('Is VR support already complete?')).toBeTruthy()
|
||||
expect(screen.getByText('Current availability')).toBeTruthy()
|
||||
expect(screen.getByText('Public feature and access status')).toBeTruthy()
|
||||
const featuresDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next release move' })
|
||||
const featuresDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next best step' })
|
||||
const featuresDecisionGuideSection = featuresDecisionGuideHeading.closest('section')
|
||||
expect(featuresDecisionGuideSection).toBeTruthy()
|
||||
expect(within(featuresDecisionGuideSection as HTMLElement).getByText('Need the protected desktop-download lane?')).toBeTruthy()
|
||||
expect(within(featuresDecisionGuideSection as HTMLElement).getByText('Need plan selection, billing, or operator provisioning?')).toBeTruthy()
|
||||
expect(within(featuresDecisionGuideSection as HTMLElement).getByText('Need the desktop download?')).toBeTruthy()
|
||||
expect(within(featuresDecisionGuideSection as HTMLElement).getByText('Need pricing, billing, or subscription help?')).toBeTruthy()
|
||||
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
expect(screen.getByText('Release references and source availability')).toBeTruthy()
|
||||
|
|
@ -931,22 +931,22 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getAllByText('Current input and runtime control truth').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('The current host decision remains desktop-first for broader native OpenXR/controller rollout until Windows packaged controller validation with controller truth is complete.')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
const pricingDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next release move' })
|
||||
const pricingDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next best step' })
|
||||
const pricingDecisionGuideSection = pricingDecisionGuideHeading.closest('section')
|
||||
expect(pricingDecisionGuideSection).toBeTruthy()
|
||||
expect(within(pricingDecisionGuideSection as HTMLElement).getByText('Need plan selection, billing, or operator provisioning?')).toBeTruthy()
|
||||
expect(within(pricingDecisionGuideSection as HTMLElement).getByText('Need account state, pairing, or protected browser follow-through?')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist keeps both a website and a desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Use the desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Current input, XR, and settings truth')).toBeTruthy()
|
||||
expect(within(pricingDecisionGuideSection as HTMLElement).getByText('Need pricing, billing, or subscription help?')).toBeTruthy()
|
||||
expect(within(pricingDecisionGuideSection as HTMLElement).getByText('Need account access, pairing, or dashboard help?')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist gives you both a website and a desktop app')).toBeTruthy()
|
||||
expect(screen.getByText('The desktop app stays primary because that is where practice starts to feel rich, responsive, and worth returning to every day.')).toBeTruthy()
|
||||
expect(screen.getByText('Controls and device support today')).toBeTruthy()
|
||||
expect(screen.getByRole('heading', { name: 'Browser account access methods' })).toBeTruthy()
|
||||
expect(screen.getByText('Auth methods ready')).toBeTruthy()
|
||||
expect(screen.getByText('Email and password')).toBeTruthy()
|
||||
expect(screen.getByText('GitHub sign-in')).toBeTruthy()
|
||||
expect(screen.getByText('ORCID sign-in')).toBeTruthy()
|
||||
expect(screen.queryByText('Google sign-in')).toBeNull()
|
||||
expect(screen.getByText('Shows account, auth, billing, and release readiness status')).toBeTruthy()
|
||||
expect(screen.getByText('Owns recognition, replay, coaching, and packaged training behavior')).toBeTruthy()
|
||||
expect(screen.getByText('Shows account, auth, billing, and release status.')).toBeTruthy()
|
||||
expect(screen.getByText('Owns recognition, replay, coaching, and packaged training behavior.')).toBeTruthy()
|
||||
expect(screen.getByText('Commercial distribution doctrine')).toBeTruthy()
|
||||
expect(screen.getByText('Public pages are distribution surfaces')).toBeTruthy()
|
||||
expect(screen.getByText('Higher-dimensional runtime ownership')).toBeTruthy()
|
||||
|
|
@ -1022,23 +1022,23 @@ describe('public marketing pages', () => {
|
|||
})
|
||||
renderWithProviders(<SupportPage />, ['/support?topic=launch-readiness'])
|
||||
|
||||
expect(screen.getAllByText('Selected help lane').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Selected help topic').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Launch readiness').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText(/turning early access into a public launch/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Selected lane next steps')).toBeTruthy()
|
||||
const selectedLaneHeading = screen.getByRole('heading', { name: 'Selected lane next steps' })
|
||||
expect(screen.getAllByText(/current website, pricing, download, and legal status before a wider public launch/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Selected topic next steps')).toBeTruthy()
|
||||
const selectedLaneHeading = screen.getByRole('heading', { name: 'Selected topic next steps' })
|
||||
const selectedLaneSection = selectedLaneHeading.closest('section')
|
||||
expect(selectedLaneSection).toBeTruthy()
|
||||
expect(within(selectedLaneSection as HTMLElement).getByText('Confirm public launch status, operator/studio checkout status, and support contact first.')).toBeTruthy()
|
||||
expect(within(selectedLaneSection as HTMLElement).getByRole('link', { name: 'Sign in for protected release lane' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Fdownloads%3Fplatform%3Dwindows')
|
||||
expect(within(selectedLaneSection as HTMLElement).getByRole('link', { name: 'Sign in for protected notices' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Fnotices')
|
||||
expect(within(selectedLaneSection as HTMLElement).getByText('Confirm public launch status, checkout readiness, and support contact first.')).toBeTruthy()
|
||||
expect(within(selectedLaneSection as HTMLElement).getByRole('link', { name: 'Sign in for desktop downloads' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Fdownloads%3Fplatform%3Dwindows')
|
||||
expect(within(selectedLaneSection as HTMLElement).getByRole('link', { name: 'Sign in for notices' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Fnotices')
|
||||
expect(within(selectedLaneSection as HTMLElement).getByRole('link', { name: 'Open pricing' }).getAttribute('href')).toBe('/pricing')
|
||||
expect(screen.getByText('If the desktop app is primary, why keep the web version?')).toBeTruthy()
|
||||
expect(screen.getByText('Is VR/controller support already fully finished?')).toBeTruthy()
|
||||
expect(screen.getByText('What is the website actually for?')).toBeTruthy()
|
||||
expect(screen.getByText('Is VR support already complete?')).toBeTruthy()
|
||||
expect(screen.getByText('Do higher-dimensional selector choices persist between sessions?')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist keeps both a website and a desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Why the browser is intentionally narrower')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist gives you both a website and a desktop app')).toBeTruthy()
|
||||
expect(screen.getByText('Why the website feels fast and focused')).toBeTruthy()
|
||||
expect(screen.getByText('Browser and desktop responsibilities')).toBeTruthy()
|
||||
expect(screen.getByRole('heading', { name: 'Current browser sign-in methods' })).toBeTruthy()
|
||||
expect(screen.getByText('Auth methods ready')).toBeTruthy()
|
||||
|
|
@ -1046,7 +1046,7 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('GitHub sign-in')).toBeTruthy()
|
||||
expect(screen.getByText('ORCID sign-in')).toBeTruthy()
|
||||
expect(screen.queryByText('Google sign-in')).toBeNull()
|
||||
expect(screen.getByText('Shows account, auth, billing, and release readiness status')).toBeTruthy()
|
||||
expect(screen.getByText('Shows account, auth, billing, and release status.')).toBeTruthy()
|
||||
expect(screen.getByText('Support lanes')).toBeTruthy()
|
||||
expect(screen.getByText('Issue reporting checklist')).toBeTruthy()
|
||||
expect(screen.getByText('Account, pairing, or entitlement issue report')).toBeTruthy()
|
||||
|
|
@ -1058,19 +1058,19 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('Current support-facing launch access')).toBeTruthy()
|
||||
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
const decisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next release move' })
|
||||
const decisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next best step' })
|
||||
const decisionGuideSection = decisionGuideHeading.closest('section')
|
||||
expect(decisionGuideSection).toBeTruthy()
|
||||
expect(within(decisionGuideSection as HTMLElement).getByText('Need the protected desktop-download lane?')).toBeTruthy()
|
||||
expect(within(decisionGuideSection as HTMLElement).getByText('Need plan selection, billing, or operator provisioning?')).toBeTruthy()
|
||||
expect(within(decisionGuideSection as HTMLElement).getByText('Need account state, pairing, or protected browser follow-through?')).toBeTruthy()
|
||||
expect(within(decisionGuideSection as HTMLElement).getByText('Need notices, corresponding source, or release follow-through?')).toBeTruthy()
|
||||
expect(within(decisionGuideSection as HTMLElement).getByRole('link', { name: 'Sign in for protected browser access' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Fbrowser-access')
|
||||
expect(within(decisionGuideSection as HTMLElement).getByText('Need the desktop download?')).toBeTruthy()
|
||||
expect(within(decisionGuideSection as HTMLElement).getByText('Need pricing, billing, or subscription help?')).toBeTruthy()
|
||||
expect(within(decisionGuideSection as HTMLElement).getByText('Need account access, pairing, or dashboard help?')).toBeTruthy()
|
||||
expect(within(decisionGuideSection as HTMLElement).getByText('Need legal details, release notes, or source links?')).toBeTruthy()
|
||||
expect(within(decisionGuideSection as HTMLElement).getByRole('link', { name: 'Sign in for browser access' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Fbrowser-access')
|
||||
expect(within(decisionGuideSection as HTMLElement).getByRole('link', { name: 'Review public notices' }).getAttribute('href')).toBe('/open-source-notices')
|
||||
expect(screen.getByText('Release references and source availability')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'https://docs.hypertwist.app' })).toBeTruthy()
|
||||
expect(screen.getByText('Digital delivery workflow')).toBeTruthy()
|
||||
expect(screen.getByText('Post-install operator workflow')).toBeTruthy()
|
||||
expect(screen.getByText('After-install rhythm')).toBeTruthy()
|
||||
expect(screen.getByText('Privacy and compliance boundary')).toBeTruthy()
|
||||
expect(screen.getByText('Browser identity and billing boundary')).toBeTruthy()
|
||||
expect(screen.getByText('Escalation map')).toBeTruthy()
|
||||
|
|
@ -1135,19 +1135,19 @@ describe('public marketing pages', () => {
|
|||
})
|
||||
|
||||
const browserView = renderWithProviders(<BrowserExperiencePage />, ['/browser'])
|
||||
expect(within(browserView.container).getByText('What the browser version is genuinely for')).toBeTruthy()
|
||||
expect(within(browserView.container).getByText('Why the downloadable remains much more powerful')).toBeTruthy()
|
||||
expect(within(browserView.container).getByText('What the website is genuinely great at')).toBeTruthy()
|
||||
expect(within(browserView.container).getByText('Why the desktop app goes much further')).toBeTruthy()
|
||||
expect(within(browserView.container).getByText('How the browser and downloadable work together')).toBeTruthy()
|
||||
expect(within(browserView.container).getByText('The browser version exists to support the downloadable, not replace it.')).toBeTruthy()
|
||||
expect(within(browserView.container).getByText('The website gets you in. The desktop app becomes the training room.')).toBeTruthy()
|
||||
|
||||
const helpView = renderWithProviders(<HelpCenterPage />, ['/help'])
|
||||
expect(within(helpView.container).getByText('What this help center covers')).toBeTruthy()
|
||||
expect(within(helpView.container).getByText('Pick the right help lane')).toBeTruthy()
|
||||
expect(within(helpView.container).getByText('Pick the right help path')).toBeTruthy()
|
||||
expect(within(helpView.container).getByText('Current controls and device state')).toBeTruthy()
|
||||
expect(within(helpView.container).getByText('Need escalation instead of guidance?')).toBeTruthy()
|
||||
|
||||
const contactView = renderWithProviders(<ContactPage />, ['/contact'])
|
||||
expect(within(contactView.container).getByText('Contact lanes')).toBeTruthy()
|
||||
expect(within(contactView.container).getByText('Contact paths')).toBeTruthy()
|
||||
expect(within(contactView.container).getByText('Primary contact route')).toBeTruthy()
|
||||
expect(within(contactView.container).getByText('What to include in your message')).toBeTruthy()
|
||||
expect(within(contactView.container).getByText('How HyperTwist classifies escalations')).toBeTruthy()
|
||||
|
|
@ -1262,26 +1262,26 @@ describe('public marketing pages', () => {
|
|||
).toBeTruthy()
|
||||
expect(screen.getAllByText('Support topic quick routes').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist keeps both a website and a desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Why the browser is intentionally narrower')).toBeTruthy()
|
||||
expect(screen.getByText('Why HyperTwist gives you both a website and a desktop app')).toBeTruthy()
|
||||
expect(screen.getByText('Why the website feels fast and focused')).toBeTruthy()
|
||||
expect(screen.getByText('Recovery and escalation')).toBeTruthy()
|
||||
expect(screen.getByText('Operator playbooks')).toBeTruthy()
|
||||
expect(screen.getByText('Training and team playbooks')).toBeTruthy()
|
||||
expect(screen.getByText('Simulator use today')).toBeTruthy()
|
||||
expect(screen.getByText('Higher-dimensional runtime guide')).toBeTruthy()
|
||||
expect(screen.getByText('Current control and device state')).toBeTruthy()
|
||||
expect(screen.getAllByText('Selectable control and settings roster').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText(/3 immersive-intensity presets and 3 reduced-distraction presets/i)).toBeTruthy()
|
||||
expect(screen.getByText('Deployment readiness snapshot')).toBeTruthy()
|
||||
expect(screen.getByText('Release snapshot')).toBeTruthy()
|
||||
expect(screen.getAllByText('XR groundwork exists, but the full VR lane is not finished').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Higher-dimensional runtime ownership')).toBeTruthy()
|
||||
expect(screen.getByText('Public manual route atlas')).toBeTruthy()
|
||||
expect(screen.getByText('Shipping & payment')).toBeTruthy()
|
||||
expect(screen.getByText('Offline operator manual')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Download offline operator manual' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getByText('Choose the next release move')).toBeTruthy()
|
||||
expect(screen.getByText('Need account state, pairing, or protected browser follow-through?')).toBeTruthy()
|
||||
expect(screen.getByText(/The web lane is not superfluous/i)).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Sign in for protected dashboard' }).getAttribute('href')).toBe('/login?next=%2Fapp')
|
||||
expect(screen.getAllByText('Offline desktop guide').length).toBeGreaterThan(0)
|
||||
expect(screen.getByRole('link', { name: 'Download offline desktop guide' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getByText('Choose the next best step')).toBeTruthy()
|
||||
expect(screen.getByText('Need account access, pairing, or dashboard help?')).toBeTruthy()
|
||||
expect(screen.getByText('Open your account dashboard')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Sign in for dashboard' }).getAttribute('href')).toBe('/login?next=%2Fapp')
|
||||
expect(screen.getByText('Release references and source availability')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'https://docs.hypertwist.app' })).toBeTruthy()
|
||||
})
|
||||
|
|
@ -1415,21 +1415,21 @@ describe('public marketing pages', () => {
|
|||
|
||||
expect(screen.getByText('Current shipped capability')).toBeTruthy()
|
||||
expect(screen.getByText('Native training and coaching core')).toBeTruthy()
|
||||
expect(screen.getByText('Operator manual')).toBeTruthy()
|
||||
expect(screen.getByText('How HyperTwist is actually used')).toBeTruthy()
|
||||
expect(screen.getByText('First real desktop session')).toBeTruthy()
|
||||
expect(screen.getByText('Operational verification checklist')).toBeTruthy()
|
||||
expect(screen.getByText('Recovery and degraded-state manual')).toBeTruthy()
|
||||
expect(screen.getByText('Issue reporting checklist')).toBeTruthy()
|
||||
expect(screen.getByText('Offline operator manual')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Download offline operator manual' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getAllByText('Offline desktop guide').length).toBeGreaterThan(0)
|
||||
expect(screen.getByRole('link', { name: 'Download offline desktop guide' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getByText('Support topic quick routes')).toBeTruthy()
|
||||
expect(screen.getAllByText('Current surface authority map').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Why HyperTwist keeps both a website and a desktop runtime')).toBeTruthy()
|
||||
expect(screen.getByText('Why the browser is intentionally narrower')).toBeTruthy()
|
||||
expect(screen.getAllByText('Where each part of HyperTwist lives').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Why HyperTwist gives you both a website and a desktop app')).toBeTruthy()
|
||||
expect(screen.getByText('Why the website feels fast and focused')).toBeTruthy()
|
||||
expect(screen.getByText('When to use browser versus desktop')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(screen.getAllByText('Protected browser dashboard').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('1. Start in the browser shell').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Signed-in dashboard').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('1. Start on the website').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Optional full-browser simulator branch').length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('Simulator manual')).toBeTruthy()
|
||||
expect(screen.getByText('Higher-dimensional family guide')).toBeTruthy()
|
||||
|
|
@ -1438,7 +1438,7 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getAllByText('Selectable control and settings roster').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText('Keyboard and mouse ship today').length).toBeGreaterThan(0)
|
||||
expect(screen.getAllByText(/classic-wca-keyboard\/v1/i).length).toBeGreaterThan(0)
|
||||
expect(screen.getByText('4. Open the higher-dimensional lanes and read the control boundary honestly')).toBeTruthy()
|
||||
expect(screen.getByText('4. Open the higher-dimensional families and read the control boundary honestly')).toBeTruthy()
|
||||
expect(screen.getByText(/I\/K = R\/R', J\/F = U\/U', H\/G = F\/F'/i)).toBeTruthy()
|
||||
expect(screen.getByText(/bounded OpenXR plugin stack, first-party runtime-owner substrate, and first-party controller settings\/rebinding ownership/i)).toBeTruthy()
|
||||
expect(screen.getByText(/R scramble, H hint, Enter submit, F mode, V hold-to-talk, C cycle voice/i)).toBeTruthy()
|
||||
|
|
@ -1449,13 +1449,13 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('Browser auth degraded or mixed')).toBeTruthy()
|
||||
expect(screen.getByText('Release authority temporarily unavailable')).toBeTruthy()
|
||||
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the next release move')).toBeTruthy()
|
||||
expect(screen.getByText('Need the protected desktop-download lane?')).toBeTruthy()
|
||||
expect(screen.getByText('Need notices, corresponding source, or release follow-through?')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the next best step')).toBeTruthy()
|
||||
expect(screen.getByText('Need the desktop download?')).toBeTruthy()
|
||||
expect(screen.getByText('Need legal details, release notes, or source links?')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Review public notices' }).getAttribute('href')).toBe('/open-source-notices')
|
||||
expect(screen.getByText('Common operator questions')).toBeTruthy()
|
||||
expect(screen.getByText('If the desktop app is primary, why keep the web version?')).toBeTruthy()
|
||||
expect(screen.getByText(/it does not own package-validated training behavior, low-latency native input, higher-dimensional packaged execution, or device\/runtime integration authority/i)).toBeTruthy()
|
||||
expect(screen.getByText('Common questions')).toBeTruthy()
|
||||
expect(screen.getByText('What is the website actually for?')).toBeTruthy()
|
||||
expect(screen.getByText('It does not pretend to be the full simulator, the advanced puzzle room, or the device-heavy practice surface.')).toBeTruthy()
|
||||
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
|
||||
expect(screen.getByText('Release references and source availability')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'https://docs.hypertwist.app' })).toBeTruthy()
|
||||
|
|
@ -1560,8 +1560,8 @@ describe('public marketing pages', () => {
|
|||
|
||||
expect(screen.getByText('The first serious HyperTwist session')).toBeTruthy()
|
||||
expect(screen.getByText('Operator path')).toBeTruthy()
|
||||
expect(screen.getByText('Offline operator manual')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Download offline operator manual' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getAllByText('Offline desktop guide').length).toBeGreaterThan(0)
|
||||
expect(screen.getByRole('link', { name: 'Download offline desktop guide' }).getAttribute('href')).toBe('/manual/hypertwist-operator-manual.md')
|
||||
expect(screen.getByText('Browser account access methods')).toBeTruthy()
|
||||
expect(screen.getByText('First launch and desktop setup')).toBeTruthy()
|
||||
expect(screen.getByText('First-session verification checklist')).toBeTruthy()
|
||||
|
|
@ -1686,7 +1686,7 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('Access support and escalation lanes')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the next access move')).toBeTruthy()
|
||||
expect(screen.getByText('Release, notice, and support bundle')).toBeTruthy()
|
||||
expect(screen.getByText('Why access authority stays in the browser shell')).toBeTruthy()
|
||||
expect(screen.getByText('Why access status lives on the website')).toBeTruthy()
|
||||
expect(screen.getAllByText('Launch readiness').length).toBeGreaterThan(0)
|
||||
expect(
|
||||
screen.getAllByRole('link', { name: 'Open launch status' }).some((link) => link.getAttribute('href') === '/launch-status'),
|
||||
|
|
@ -1797,10 +1797,10 @@ describe('public marketing pages', () => {
|
|||
expect(screen.getByText('Signed-in operator shell now mirrors the native control roster')).toBeTruthy()
|
||||
expect(screen.getByText('Native selector-recall diagnostics hardened and revalidated')).toBeTruthy()
|
||||
expect(screen.getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
const changelogDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next release move' })
|
||||
const changelogDecisionGuideHeading = screen.getByRole('heading', { name: 'Choose the next best step' })
|
||||
const changelogDecisionGuideSection = changelogDecisionGuideHeading.closest('section')
|
||||
expect(changelogDecisionGuideSection).toBeTruthy()
|
||||
expect(within(changelogDecisionGuideSection as HTMLElement).getByText('Need notices, corresponding source, or release follow-through?')).toBeTruthy()
|
||||
expect(within(changelogDecisionGuideSection as HTMLElement).getByText('Need legal details, release notes, or source links?')).toBeTruthy()
|
||||
expect(within(changelogDecisionGuideSection as HTMLElement).getByRole('link', { name: 'Review public notices' }).getAttribute('href')).toBe('/open-source-notices')
|
||||
expect(screen.getByText('Release rollout checklist')).toBeTruthy()
|
||||
expect(screen.getByText('Read the actual lane that changed')).toBeTruthy()
|
||||
|
|
@ -2050,10 +2050,10 @@ describe('public marketing pages', () => {
|
|||
expect((await within(noticesView.container).findAllByRole('link', { name: 'https://hypertwist.app/open-source/source.zip' })).length).toBeGreaterThan(0)
|
||||
expect((await within(noticesView.container).findAllByRole('link', { name: 'https://github.com/hypertwist/hypertwist' })).length).toBeGreaterThan(0)
|
||||
expect(within(noticesView.container).getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(within(noticesView.container).getByText('Choose the next release move')).toBeTruthy()
|
||||
expect(within(noticesView.container).getByText('Need notices, corresponding source, or release follow-through?')).toBeTruthy()
|
||||
expect(within(noticesView.container).getByText('Current surface authority map')).toBeTruthy()
|
||||
expect(within(noticesView.container).getByText('Offline operator manual')).toBeTruthy()
|
||||
expect(within(noticesView.container).getByText('Choose the next best step')).toBeTruthy()
|
||||
expect(within(noticesView.container).getByText('Need legal details, release notes, or source links?')).toBeTruthy()
|
||||
expect(within(noticesView.container).getByText('Where each part of HyperTwist lives')).toBeTruthy()
|
||||
expect(within(noticesView.container).getAllByText('Offline desktop guide').length).toBeGreaterThan(0)
|
||||
expect(within(noticesView.container).getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(within(noticesView.container).getByText('Packaged validation passed')).toBeTruthy()
|
||||
|
||||
|
|
@ -2061,10 +2061,10 @@ describe('public marketing pages', () => {
|
|||
expect(within(privacyView.container).getByText('Practical privacy boundary')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Browser identity and billing boundary')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Choose the next release move')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Need account state, pairing, or protected browser follow-through?')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Choose the next best step')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Need account access, pairing, or dashboard help?')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Browser and desktop responsibilities')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Current surface authority map')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Where each part of HyperTwist lives')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Support-safe escalation boundary')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(within(privacyView.container).getByText('Packaged validation passed')).toBeTruthy()
|
||||
|
|
@ -2075,10 +2075,10 @@ describe('public marketing pages', () => {
|
|||
expect(within(termsView.container).getByText('Terms in practice')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Access model')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Choose the next release move')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Need the protected desktop-download lane?')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Choose the next best step')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Need the desktop download?')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Browser and desktop responsibilities')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Current surface authority map')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Where each part of HyperTwist lives')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Release and redistribution checklist')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Current packaged desktop proof')).toBeTruthy()
|
||||
expect(within(termsView.container).getByText('Packaged validation passed')).toBeTruthy()
|
||||
|
|
@ -2087,15 +2087,15 @@ describe('public marketing pages', () => {
|
|||
|
||||
const shippingPaymentView = renderWithProviders(<ShippingPaymentPage />, ['/shipping-payment'])
|
||||
expect(within(shippingPaymentView.container).getByText('Digital delivery workflow')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Protected entitlement handoff')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Private download handoff')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('What happens after access is granted')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Choose the right HyperTwist surface')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Choose the next release move')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Need plan selection, billing, or operator provisioning?')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Choose the next best step')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Need pricing, billing, or subscription help?')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Release references and source availability')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByRole('link', { name: 'https://notes.hypertwist.app' })).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Current surface authority map')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Offline operator manual')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Where each part of HyperTwist lives')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getAllByText('Offline desktop guide').length).toBeGreaterThan(0)
|
||||
expect(within(shippingPaymentView.container).getByText('Terms of access in practice')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Distribution doctrine')).toBeTruthy()
|
||||
expect(within(shippingPaymentView.container).getByText('Release and redistribution checklist')).toBeTruthy()
|
||||
|
|
|
|||
|
|
@ -158,9 +158,9 @@ describe('website route and shell behavior', () => {
|
|||
|
||||
const { container } = renderProtectedRoute('/app/account')
|
||||
|
||||
expect(screen.getByText('Loading operator access...')).toBeTruthy()
|
||||
expect(screen.getByText('Browser account access')).toBeTruthy()
|
||||
expect(screen.getByText(/Checking your saved browser session, protected release status, and browser-to-desktop continuity/i)).toBeTruthy()
|
||||
expect(screen.getByText('Loading your account...')).toBeTruthy()
|
||||
expect(screen.getByText('Signed-in website access')).toBeTruthy()
|
||||
expect(screen.getByText(/Checking your saved session, download access, and desktop pairing/i)).toBeTruthy()
|
||||
expect(container.querySelector('.general-page-loader-shell--fullscreen')).not.toBeNull()
|
||||
expect(screen.getByAltText('HyperTwist')).toBeTruthy()
|
||||
expect(screen.getByRole('link', { name: 'Open sign-in' }).getAttribute('href')).toBe('/login?next=%2Fapp%2Faccount')
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ function applyAuthShellBackdropStyles(backdrop: HTMLElement) {
|
|||
backdrop.style.zIndex = '0'
|
||||
backdrop.style.pointerEvents = 'none'
|
||||
backdrop.style.background =
|
||||
'radial-gradient(circle at top, rgba(216,203,175,0.12), transparent 40%), linear-gradient(180deg, rgba(18,20,24,0.97), rgba(10,12,17,0.99))'
|
||||
'radial-gradient(circle at top, rgba(84,203,255,0.12), transparent 32%), radial-gradient(circle at 82% 14%, rgba(247,178,103,0.12), transparent 24%), linear-gradient(180deg, rgba(9,12,24,0.26), rgba(7,10,19,0.38))'
|
||||
}
|
||||
|
||||
export function isAuthShellRoute(pathname: string) {
|
||||
|
|
|
|||
|
|
@ -10,24 +10,24 @@ import { HyperTwistButton, HyperTwistButtonLink } from '../ui/HyperTwistButton'
|
|||
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>
|
||||
<p className="eyebrow">Main experience</p>
|
||||
<strong>Desktop app</strong>
|
||||
<p>Daily training, replay review, coaching, and the deepest puzzle families all live in the desktop app.</p>
|
||||
</article>
|
||||
<article className="product-reality-strip__item">
|
||||
<p className="eyebrow">Web purpose</p>
|
||||
<strong>Access and guidance</strong>
|
||||
<p>The browser owns docs, accounts, pricing, notices, protected downloads, and desktop pairing.</p>
|
||||
<p className="eyebrow">Website role</p>
|
||||
<strong>Account and launch hub</strong>
|
||||
<p>Create your account, compare plans, read updates, get help, and unlock desktop downloads from the website.</p>
|
||||
</article>
|
||||
<article className="product-reality-strip__item">
|
||||
<p className="eyebrow">Ready today</p>
|
||||
<p className="eyebrow">Ready now</p>
|
||||
<strong>Keyboard and mouse</strong>
|
||||
<p>Desktop controls support serious keyboard, mouse, touch, camera, replay, and training work without a headset.</p>
|
||||
<p>Train and explore with keyboard and mouse today, while broader headset support stays clearly marked as still in validation.</p>
|
||||
</article>
|
||||
<article className="product-reality-strip__item">
|
||||
<p className="eyebrow">Account delivery</p>
|
||||
<strong>Account-gated delivery</strong>
|
||||
<p>Public pages explain the product; signed-in dashboard routes handle release access and pairing.</p>
|
||||
<p className="eyebrow">Download path</p>
|
||||
<strong>Account-backed delivery</strong>
|
||||
<p>Start on the site, sign in once, and carry your account cleanly into the desktop build for access and first-launch pairing.</p>
|
||||
</article>
|
||||
</section>
|
||||
)
|
||||
|
|
@ -38,30 +38,30 @@ export const compactProductRealityStrip = (
|
|||
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>
|
||||
<p className="eyebrow">Main experience</p>
|
||||
<strong>Desktop app</strong>
|
||||
<p>Daily training, replay review, coaching, and the deepest puzzle families all live in the desktop app.</p>
|
||||
</article>
|
||||
<article className="product-reality-strip__item">
|
||||
<p className="eyebrow">Web purpose</p>
|
||||
<strong>Access and guidance</strong>
|
||||
<p>The browser owns docs, accounts, pricing, notices, protected downloads, and desktop pairing.</p>
|
||||
<p className="eyebrow">Website role</p>
|
||||
<strong>Account and launch hub</strong>
|
||||
<p>Create your account, compare plans, read updates, get help, and unlock desktop downloads from the website.</p>
|
||||
</article>
|
||||
<article className="product-reality-strip__item">
|
||||
<p className="eyebrow">Ready today</p>
|
||||
<p className="eyebrow">Ready now</p>
|
||||
<strong>Keyboard and mouse</strong>
|
||||
<p>Desktop controls support serious keyboard, mouse, touch, camera, replay, and training work without a headset.</p>
|
||||
<p>Train and explore with keyboard and mouse today, while broader headset support stays clearly marked as still in validation.</p>
|
||||
</article>
|
||||
<article className="product-reality-strip__item">
|
||||
<p className="eyebrow">Account delivery</p>
|
||||
<strong>Account-gated delivery</strong>
|
||||
<p>Public pages explain the product; signed-in dashboard routes handle release access and pairing.</p>
|
||||
<p className="eyebrow">Download path</p>
|
||||
<strong>Account-backed delivery</strong>
|
||||
<p>Start on the site, sign in once, and carry your account cleanly into the desktop build for access and first-launch pairing.</p>
|
||||
</article>
|
||||
</section>
|
||||
)
|
||||
|
||||
function useHideHeaderOnScroll() {
|
||||
const [hidden, setHidden] = useState(false)
|
||||
const [hidden, setHidden] = useState(() => (typeof window !== 'undefined' ? window.scrollY > 220 : false))
|
||||
|
||||
useEffect(() => {
|
||||
let previousScrollY = window.scrollY
|
||||
|
|
@ -69,12 +69,13 @@ function useHideHeaderOnScroll() {
|
|||
|
||||
function updateHeaderVisibility() {
|
||||
const nextScrollY = window.scrollY
|
||||
const isScrollingDown = nextScrollY > previousScrollY + 8
|
||||
const isScrollingUp = nextScrollY < previousScrollY - 8
|
||||
const delta = nextScrollY - previousScrollY
|
||||
const isScrollingDown = delta > 12
|
||||
const isNearTop = nextScrollY < 172
|
||||
|
||||
if (nextScrollY < 96 || isScrollingUp) {
|
||||
if (isNearTop) {
|
||||
setHidden(false)
|
||||
} else if (isScrollingDown && nextScrollY > 160) {
|
||||
} else if (isScrollingDown && nextScrollY > 220) {
|
||||
setHidden(true)
|
||||
}
|
||||
|
||||
|
|
@ -158,14 +159,23 @@ export function MarketingShell({
|
|||
onClick={toggleColorMode}
|
||||
className="theme-toggle"
|
||||
>
|
||||
{colorMode === 'dark' ? <Sun size={17} /> : <Moon size={17} />}
|
||||
<span>{colorMode === 'dark' ? 'Light' : 'Dark'}</span>
|
||||
<span className="theme-toggle__content">
|
||||
<span className="theme-toggle__icon">
|
||||
{colorMode === 'dark' ? <Sun size={17} /> : <Moon size={17} />}
|
||||
</span>
|
||||
<span className="theme-toggle__label">{colorMode === 'dark' ? 'Light' : 'Dark'}</span>
|
||||
</span>
|
||||
</HyperTwistButton>
|
||||
{!isAuthenticated ? (
|
||||
<HyperTwistButtonLink tone="secondary" to="/register?next=%2Fapp">
|
||||
Create account
|
||||
</HyperTwistButtonLink>
|
||||
) : null}
|
||||
<HyperTwistButtonLink tone="ghost" to={isAuthenticated ? '/app' : '/login'}>
|
||||
{isAuthenticated ? 'Open dashboard' : 'Log in'}
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink to={isAuthenticated ? '/app/downloads?platform=windows' : '/register?next=%2Fapp%2Fdownloads%3Fplatform%3Dwindows'}>
|
||||
Get desktop app
|
||||
Get the desktop app
|
||||
</HyperTwistButtonLink>
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -190,11 +200,16 @@ export function MarketingShell({
|
|||
</p>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonLink to={isAuthenticated ? '/app/downloads?platform=windows' : '/register?next=%2Fapp%2Fdownloads%3Fplatform%3Dwindows'}>
|
||||
Get desktop app
|
||||
Get the desktop app
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink tone="ghost" to={isAuthenticated ? '/app' : '/login'}>
|
||||
{isAuthenticated ? 'Account dashboard' : 'Account access'}
|
||||
{isAuthenticated ? 'Account overview' : 'Account sign-in'}
|
||||
</HyperTwistButtonLink>
|
||||
{!isAuthenticated ? (
|
||||
<HyperTwistButtonLink tone="ghost" to="/register?next=%2Fapp">
|
||||
Start free account
|
||||
</HyperTwistButtonLink>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="site-footer__grid">
|
||||
|
|
@ -215,7 +230,7 @@ export function MarketingShell({
|
|||
))}
|
||||
</div>
|
||||
<p className="site-footer__note">
|
||||
Public distribution surfaces must continue to expose open-source notices and corresponding-source guidance when shipped builds contain MPL-covered material.
|
||||
Downloadable releases include open-source notices and corresponding-source links whenever the shipped build requires them.
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ export function ProtectedRoute() {
|
|||
if (isLoading) {
|
||||
return (
|
||||
<GeneralPageLoader
|
||||
title="Loading operator access..."
|
||||
eyebrow="Browser account access"
|
||||
description="Checking your saved browser session, protected release status, and browser-to-desktop continuity before the operator shell opens."
|
||||
title="Loading your account..."
|
||||
eyebrow="Signed-in website access"
|
||||
description="Checking your saved session, download access, and desktop pairing so your dashboard opens in the right state."
|
||||
fullscreen
|
||||
showBrand
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useEffect } from 'react'
|
|||
import { brandConfig } from '../../site-config'
|
||||
|
||||
const DEFAULT_DESCRIPTION =
|
||||
'HyperTwist is a native cube and hypercube training environment for recognition, replay, coaching, higher-dimensional runtime ownership, and desktop-first operator workflows.'
|
||||
'HyperTwist is the desktop-first cube and hypercube simulator for guided practice, replay review, coaching, and serious 120‑cell and 5D study.'
|
||||
const DEFAULT_IMAGE_PATH = '/branding/hypertwist-3d-symbol.png'
|
||||
|
||||
function ensureMetaTag(attributeName: 'name' | 'property', attributeValue: string) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
isGoogleOAuthEnabled,
|
||||
isOrcidOAuthEnabled,
|
||||
} from '../../auth/supertokens-runtime'
|
||||
import { HyperTwistButtonLink } from './HyperTwistButton'
|
||||
|
||||
type BrowserAuthMethodCard = {
|
||||
title: string
|
||||
|
|
@ -16,11 +17,11 @@ function buildBrowserAuthMethodCards(): readonly BrowserAuthMethodCard[] {
|
|||
{
|
||||
title: 'Email and password',
|
||||
description:
|
||||
'This is the default browser-account lane and remains the most predictable shared-auth path across early access, mixed, and production deployments.',
|
||||
'This is the simplest way to create your HyperTwist account and unlock the dashboard, downloads, and desktop pairing tools.',
|
||||
bullets: [
|
||||
'Use it when you want the clearest route into the protected dashboard, protected downloads, and desktop-link pairing.',
|
||||
'It opens the same protected account and release surfaces as the optional provider buttons.',
|
||||
'It does not change the desktop-first simulator boundary or replace native pairing.',
|
||||
'Use it when you want the clearest route into your account, downloads, and first desktop launch.',
|
||||
'It opens the same dashboard and download tools as the optional provider buttons.',
|
||||
'It helps you reach the simulator; it does not replace the desktop app itself.',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
@ -29,10 +30,10 @@ function buildBrowserAuthMethodCards(): readonly BrowserAuthMethodCard[] {
|
|||
cards.push({
|
||||
title: 'GitHub sign-in',
|
||||
description:
|
||||
'GitHub is available in this build as an optional browser-account shortcut when the shared auth server has that provider configured.',
|
||||
'GitHub can be used as a faster sign-in route when this deployment has it enabled.',
|
||||
bullets: [
|
||||
'Use it to enter the same protected dashboard and release lanes without creating a separate password first.',
|
||||
'It still relies on the same shared auth runtime, entitlement checks, and desktop-link handoff as the email/password lane.',
|
||||
'You still land in the same dashboard, downloads, and pairing tools.',
|
||||
'It uses the same account and entitlement checks as email and password.',
|
||||
],
|
||||
})
|
||||
}
|
||||
|
|
@ -41,10 +42,10 @@ function buildBrowserAuthMethodCards(): readonly BrowserAuthMethodCard[] {
|
|||
cards.push({
|
||||
title: 'Google sign-in',
|
||||
description:
|
||||
'Google is available in this build as an optional shared-auth provider for browser account continuity.',
|
||||
'Google can be used for the same account and download flow when it is enabled on this deployment.',
|
||||
bullets: [
|
||||
'Use it when you want the same protected download, account, and rollout surfaces through a Google-backed sign-in flow.',
|
||||
'It does not widen browser ownership into simulator execution or change the desktop pairing boundary.',
|
||||
'It opens the same dashboard, downloads, and account tools as the default sign-in.',
|
||||
'It does not change the boundary between the website and the desktop simulator.',
|
||||
],
|
||||
})
|
||||
}
|
||||
|
|
@ -53,10 +54,10 @@ function buildBrowserAuthMethodCards(): readonly BrowserAuthMethodCard[] {
|
|||
cards.push({
|
||||
title: 'ORCID sign-in',
|
||||
description:
|
||||
'ORCID is available in this build as a first-party shared-auth provider, so research-oriented identity can route through the same protected operator lanes.',
|
||||
'ORCID can be used here when research-oriented identity needs to flow into the same HyperTwist account experience.',
|
||||
bullets: [
|
||||
'Use it when this deployment has ORCID configured and you want standards-backed browser sign-in continuity into the protected dashboard and download lanes.',
|
||||
'It remains a browser-account path only; it does not replace desktop-link pairing or the native simulator runtime.',
|
||||
'It opens the same dashboard and download tools when the deployment has ORCID configured.',
|
||||
'It remains an account-access path only; the desktop simulator still does the training work.',
|
||||
],
|
||||
})
|
||||
}
|
||||
|
|
@ -79,10 +80,10 @@ export function BrowserAuthMethodsGuide() {
|
|||
const summary = authRuntimeValidation.ready
|
||||
? (
|
||||
diagnostics.length === 0
|
||||
? 'The shared browser-auth runtime is configured cleanly in this build, so the methods below all route into the same protected account, release, and desktop-pairing surfaces.'
|
||||
: 'The shared browser-auth runtime is present in this build, but remaining warnings should be cleared before this surface is treated as fully production-ready.'
|
||||
? 'The sign-in options below all lead into the same HyperTwist account, download, and desktop-pairing experience.'
|
||||
: 'The sign-in options are available, but a few configuration warnings still remain in this deployment.'
|
||||
)
|
||||
: 'This build may still rely on bounded recovery mode until the pending shared-auth runtime values are configured, even though the method lineup below stays useful as operator guidance.'
|
||||
: 'This deployment may still be running with a limited auth configuration, but the sign-in lineup below remains the intended account path.'
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -112,6 +113,14 @@ export function BrowserAuthMethodsGuide() {
|
|||
</article>
|
||||
))}
|
||||
</div>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonLink to="/register?next=%2Fapp">
|
||||
Create account
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink tone="secondary" to="/login?next=%2Fapp">
|
||||
Log in
|
||||
</HyperTwistButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ function buildClassName({
|
|||
return [
|
||||
'button',
|
||||
`button--${tone}`,
|
||||
tone === 'secondary' ? 'button--primary' : '',
|
||||
full ? 'button--full' : '',
|
||||
className || '',
|
||||
].filter(Boolean).join(' ')
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import {
|
|||
} from '../auth/supertokens-runtime'
|
||||
import { SiteMetadata } from '../components/seo/SiteMetadata'
|
||||
import { compactProductRealityStrip } from '../components/layout/MarketingShell'
|
||||
import { HyperTwistButton } from '../components/ui/HyperTwistButton'
|
||||
import { HyperTwistButton, HyperTwistButtonLink } from '../components/ui/HyperTwistButton'
|
||||
import { OperationalStatusCallout } from '../components/ui/OperationalStatusCallout'
|
||||
import { browserDesktopRealityCards, deliverySurfaceCards, desktopWorkflowTracks, operatorManualTracks } from '../site-data'
|
||||
import { buildSupportPath, getDownloadPlatformLabel, normalizeDownloadPlatform } from '../site-routes'
|
||||
|
|
@ -62,50 +62,50 @@ function resolveAuthNextStep(nextPath: string): AuthNextStepDescriptor {
|
|||
const platformLabel = platform ? getDownloadPlatformLabel(platform) : 'desktop'
|
||||
|
||||
return {
|
||||
title: `Protected ${platformLabel} release lane`,
|
||||
title: `Signed-in ${platformLabel} downloads`,
|
||||
description:
|
||||
`After sign-in you will continue to the protected ${platformLabel} download center so entitlement, current package proof, and release-notice follow-through stay tied to your account instead of being exposed on an anonymous page.`,
|
||||
`After sign-in you will continue to the signed-in ${platformLabel} download center so entitlement, current package proof, and release notes stay tied to your account instead of being exposed on an anonymous page.`,
|
||||
bullets: [
|
||||
`Review the current ${platformLabel} target, package proof, and release notes before download.`,
|
||||
'Use the protected dashboard afterward if you need a desktop-link token for first launch.',
|
||||
'Return to protected or public notices if rollout or compliance status changes.',
|
||||
'Use the dashboard afterward if you need a desktop-link token for first launch.',
|
||||
'Return to notices if rollout or compliance status changes.',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/app/account') {
|
||||
return {
|
||||
title: 'Protected account lane',
|
||||
title: 'Signed-in account',
|
||||
description:
|
||||
'After sign-in you will continue to the protected account surface so session state, plan status, and browser-to-desktop access continuity can be confirmed before any download or rollout step.',
|
||||
'After sign-in you will continue to your account page so session state, plan status, and desktop access continuity can be confirmed before any download or rollout step.',
|
||||
bullets: [
|
||||
'Confirm your plan, role, and current release access first.',
|
||||
'Use the protected download center once desktop entitlement is resolved.',
|
||||
'Use the dashboard and notices lanes if account or release status changes later.',
|
||||
'Use the signed-in download center once desktop entitlement is resolved.',
|
||||
'Use the dashboard and notices pages if account or release status changes later.',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
if (pathname === '/app/notices') {
|
||||
return {
|
||||
title: 'Protected notices lane',
|
||||
title: 'Signed-in notices',
|
||||
description:
|
||||
'After sign-in you will continue to the protected notices surface so distribution references, corresponding-source status, and release-target notice truth remain attached to the signed-in operator lane.',
|
||||
'After sign-in you will continue to the signed-in notices page so distribution references, source status, and release details remain attached to your account.',
|
||||
bullets: [
|
||||
'Review notices before redistributing any covered downloadable build.',
|
||||
'Use the protected download center when you need the entitled package itself.',
|
||||
'Keep browser access, native package proof, and legal follow-through synchronized.',
|
||||
'Use the signed-in download center when you need the entitled package itself.',
|
||||
'Keep account access, native package proof, and legal follow-through synchronized.',
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
title: 'Protected operator dashboard',
|
||||
title: 'Signed-in dashboard',
|
||||
description:
|
||||
'After sign-in you will continue to the protected operator dashboard so browser auth, billing, release status, and desktop-link pairing are resolved before the native simulator lane takes over.',
|
||||
'After sign-in you will continue to the signed-in dashboard so browser auth, billing, release status, and desktop-link pairing are resolved before the native simulator takes over.',
|
||||
bullets: [
|
||||
'Confirm account, plan, and release readiness first.',
|
||||
'Open the protected download center when you need the entitled desktop build.',
|
||||
'Open the signed-in download center when you need the entitled desktop build.',
|
||||
'Generate a desktop-link token there or from the dashboard before first native launch.',
|
||||
],
|
||||
}
|
||||
|
|
@ -129,8 +129,8 @@ function AuthNextStepNotice({ nextPath }: { nextPath: string }) {
|
|||
|
||||
function AuthAccessGuide({ nextPath }: { nextPath: string }) {
|
||||
const nextStep = resolveAuthNextStep(nextPath)
|
||||
const dashboardCard = deliverySurfaceCards.find((card) => card.title === 'Protected browser dashboard')
|
||||
const runtimeCard = deliverySurfaceCards.find((card) => card.title === 'Native Unreal desktop runtime')
|
||||
const dashboardCard = deliverySurfaceCards.find((card) => card.title === 'Signed-in dashboard')
|
||||
const runtimeCard = deliverySurfaceCards.find((card) => card.title === 'Desktop simulator')
|
||||
const pairingTrack = operatorManualTracks.find((track) => track.title === '3. Pair the installed desktop runtime')
|
||||
|
||||
return (
|
||||
|
|
@ -242,21 +242,21 @@ function AuthRuntimeNotice() {
|
|||
}
|
||||
summary={
|
||||
authRuntimeValidation.ready
|
||||
? 'You can still sign in, create an account, and continue into protected dashboard routes. Operator diagnostics keep the deeper auth warnings visible after sign-in.'
|
||||
: 'If shared auth is temporarily unavailable, HyperTwist keeps account routing recoverable so you can still review product access and support paths.'
|
||||
? 'You can still sign in, create an account, and continue into your dashboard, downloads, and desktop pairing flow.'
|
||||
: 'If shared auth is temporarily unavailable, HyperTwist still keeps account routing recoverable so you can review access, downloads, and support paths.'
|
||||
}
|
||||
sections={[
|
||||
{
|
||||
title: 'What still works',
|
||||
items: [
|
||||
'Login, registration, and safe next-step routing remain available.',
|
||||
'The protected dashboard can still recover bounded account, release, and desktop-pairing status after sign-in.',
|
||||
'The dashboard can still recover your account, release, and desktop-pairing status after sign-in.',
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'What stays intentionally bounded',
|
||||
title: 'What may still need follow-through',
|
||||
items: [
|
||||
'Shared production auth, live entitlement authority, and deployment-grade session guarantees are not claimed until this auth surface is fully green.',
|
||||
'Some shared-auth or billing details may still need finishing work before this deployment is fully launch-ready.',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -264,11 +264,11 @@ function AuthRuntimeNotice() {
|
|||
items: diagnostics,
|
||||
},
|
||||
{
|
||||
title: 'Recommended recovery order',
|
||||
title: 'Best next step',
|
||||
items: [
|
||||
authRuntimeValidation.ready
|
||||
? 'Continue into the dashboard for account-aware status, downloads, and desktop-link pairing.'
|
||||
: 'Use account creation for orientation now; contact support if you need production release access immediately.',
|
||||
? 'Continue into the dashboard for account status, downloads, and desktop pairing.'
|
||||
: 'Create an account for orientation now, then contact support if you need release access immediately.',
|
||||
],
|
||||
},
|
||||
]}
|
||||
|
|
@ -337,7 +337,7 @@ export function LoginPage() {
|
|||
/>
|
||||
<AuthShell
|
||||
title="Log in to HyperTwist"
|
||||
subtitle="Use HyperTwist browser sign-in to reach the protected operator surfaces, then hand off to the desktop runtime when needed."
|
||||
subtitle="Sign in to reach your dashboard, desktop downloads, release notes, and first-launch pairing tools."
|
||||
footer={<p>Need access? <Link to={`/register?next=${encodeURIComponent(nextPath)}`}>Create an account</Link>.</p>}
|
||||
>
|
||||
<AuthRuntimeNotice />
|
||||
|
|
@ -377,6 +377,14 @@ export function LoginPage() {
|
|||
<HyperTwistButton full disabled={isSubmitting} type="submit">
|
||||
{isSubmitting ? 'Signing in...' : 'Log in'}
|
||||
</HyperTwistButton>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonLink full tone="secondary" to={`/register?next=${encodeURIComponent(nextPath)}`}>
|
||||
Create account
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink full tone="ghost" to="/browser">
|
||||
Learn how the website and desktop app work together
|
||||
</HyperTwistButtonLink>
|
||||
</div>
|
||||
</form>
|
||||
<div className="provider-row">
|
||||
{isGoogleOAuthEnabled() ? (
|
||||
|
|
@ -461,7 +469,7 @@ export function RegisterPage() {
|
|||
/>
|
||||
<AuthShell
|
||||
title="Create a HyperTwist account"
|
||||
subtitle="Create an account to reach the protected dashboard, desktop download center, release notes, entitlement state, and browser-to-desktop pairing."
|
||||
subtitle="Create your account to unlock the dashboard, desktop downloads, release notes, and browser-to-desktop pairing."
|
||||
footer={<p>Already have access? <Link to={`/login?next=${encodeURIComponent(nextPath)}`}>Log in</Link>.</p>}
|
||||
>
|
||||
<AuthRuntimeNotice />
|
||||
|
|
@ -513,6 +521,14 @@ export function RegisterPage() {
|
|||
<HyperTwistButton full disabled={isSubmitting} type="submit">
|
||||
{isSubmitting ? 'Creating account...' : 'Create account'}
|
||||
</HyperTwistButton>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonLink full tone="secondary" to={`/login?next=${encodeURIComponent(nextPath)}`}>
|
||||
Already have an account? Log in
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink full tone="ghost" to="/pricing">
|
||||
Compare plans first
|
||||
</HyperTwistButtonLink>
|
||||
</div>
|
||||
</form>
|
||||
<div className="provider-row">
|
||||
{isGoogleOAuthEnabled() ? (
|
||||
|
|
|
|||
|
|
@ -52,61 +52,61 @@ export const supportTopicGuidance: Record<string, SupportTopicGuide> = {
|
|||
'launch-readiness': {
|
||||
title: 'Launch readiness',
|
||||
description:
|
||||
'Need help turning early access into a public launch? We can walk through checkout wiring, download release targets, notices, and corresponding-source publication.',
|
||||
'Use this path when you need the current website, pricing, download, and legal status before a wider public launch.',
|
||||
steps: [
|
||||
'Confirm public launch status, operator/studio checkout status, and support contact first.',
|
||||
'Review the current desktop target plus packaged proof before promising a release lane externally.',
|
||||
'Keep notices and corresponding-source status live before widening rollout.',
|
||||
'Confirm public launch status, checkout readiness, and support contact first.',
|
||||
'Review the current desktop target plus packaged proof before promising a release externally.',
|
||||
'Keep notices and corresponding-source links visible before a wider launch.',
|
||||
],
|
||||
routeFocus: [
|
||||
'Launch-status, pricing, and download pages explain public launch status and target selection.',
|
||||
'Public notices and terms pages carry the distribution and legal follow-through.',
|
||||
'The public manual explains the browser-versus-desktop product split that launch copy must preserve.',
|
||||
'Public notices and terms pages carry the distribution and legal details.',
|
||||
'The public manual explains how the website and desktop app work together so launch messaging stays accurate.',
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Open launch status', to: '/launch-status' },
|
||||
{ label: 'Sign in for protected release lane', to: buildLoginPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Sign in for protected notices', to: buildLoginPath('/app/notices') },
|
||||
{ label: 'Sign in for desktop downloads', to: buildLoginPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Sign in for notices', to: buildLoginPath('/app/notices') },
|
||||
{ label: 'Open pricing', to: '/pricing' },
|
||||
],
|
||||
},
|
||||
'operator-access': {
|
||||
title: 'Operator access',
|
||||
title: 'Account access',
|
||||
description:
|
||||
'Use this lane when you need operator checkout, entitlement enablement, or help reaching the protected desktop-download surface.',
|
||||
'Use this path when you need sign-up help, account approval, subscription access, or the signed-in desktop download.',
|
||||
steps: [
|
||||
'Start with sign-in or registration, then confirm whether the issue is auth, entitlement, or desktop-link pairing.',
|
||||
'Use the protected dashboard and protected download lane once sign-in succeeds so account and release status stay attached.',
|
||||
'Start with sign-in or signup, then confirm whether the issue is account access, entitlement, or desktop pairing.',
|
||||
'Use the signed-in dashboard and download center once sign-in succeeds so your account and release status stay attached.',
|
||||
'Escalate with the affected plan, requested platform, and whether access, pairing, or package delivery failed.',
|
||||
],
|
||||
routeFocus: [
|
||||
'Login and register preserve safe next-target routing into the protected operator lanes.',
|
||||
'The public download page shows release status without exposing raw delivery authority.',
|
||||
'The public manual explains when the browser dashboard owns the next step and when the native runtime takes over.',
|
||||
'Login and signup preserve your next destination so you land in the right dashboard or download view.',
|
||||
'The public download page shows release status without exposing raw delivery links.',
|
||||
'The public manual explains when the website owns the next step and when the desktop app takes over.',
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Sign in for protected downloads', to: buildLoginPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Sign in for desktop downloads', to: buildLoginPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Create account for desktop access', to: buildRegisterPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Open protected dashboard', to: buildLoginPath('/app') },
|
||||
{ label: 'Open dashboard', to: buildLoginPath('/app') },
|
||||
],
|
||||
},
|
||||
'studio-rollout': {
|
||||
title: 'Studio rollout',
|
||||
title: 'Teams and studios',
|
||||
description:
|
||||
'Use this lane for higher-dimensional rollout planning, deployment coordination, or production-lane package and notice readiness.',
|
||||
'Use this path for studio, classroom, or team planning, especially when higher-dimensional training and release readiness need to be reviewed together.',
|
||||
steps: [
|
||||
'Treat packaged proof, notices, pricing, and delivery status as one rollout packet instead of four disconnected checks.',
|
||||
'Keep browser/operator coordination separate from native simulator execution during deployment planning.',
|
||||
'Carry higher-dimensional dedicated-family proof and the current XR/controller boundary into rollout decisions explicitly.',
|
||||
'Review packaged proof, notices, pricing, and delivery status together instead of as separate disconnected checks.',
|
||||
'Keep planning and account coordination separate from the simulator itself.',
|
||||
'Carry higher-dimensional readiness and the current XR/controller boundary into team decisions explicitly.',
|
||||
],
|
||||
routeFocus: [
|
||||
'Release notes and the public manual explain recent browser, package, and control-boundary changes.',
|
||||
'Release notes and the public manual explain recent website, desktop, and controls changes.',
|
||||
'Download and notices pages carry the current distribution status plus legal follow-through.',
|
||||
'Resources summarize higher-dimensional runtime lanes and operator playbooks for wider team review.',
|
||||
'Resources summarize higher-dimensional training paths and team playbooks for wider review.',
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Sign in for protected notices', to: buildLoginPath('/app/notices') },
|
||||
{ label: 'Sign in for protected browser access', to: buildLoginPath('/app/browser-access') },
|
||||
{ label: 'Sign in for notices', to: buildLoginPath('/app/notices') },
|
||||
{ label: 'Sign in for browser access', to: buildLoginPath('/app/browser-access') },
|
||||
{ label: 'Open release notes', to: '/changelog' },
|
||||
{ label: 'Open resources', to: '/resources' },
|
||||
],
|
||||
|
|
@ -239,8 +239,8 @@ export function DeliverySurfaceResponsibilitiesGrid({ limit }: { limit?: number
|
|||
}
|
||||
|
||||
export function BrowserDesktopRealitySection({
|
||||
title = 'Why HyperTwist keeps both a website and a desktop runtime',
|
||||
description = 'The browser shell is intentionally narrower than the simulator. That is what keeps access, release, billing, notices, and native execution from collapsing into one confused surface.',
|
||||
title = 'Why HyperTwist gives you both a website and a desktop app',
|
||||
description = 'The website gets you in, keeps your account and downloads organized, and handles plans, updates, and help. The desktop app is where the full simulator experience lives.',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -272,12 +272,12 @@ export function BrowserDesktopRealitySection({
|
|||
|
||||
const surfaceChoiceGuideCards = [
|
||||
{
|
||||
title: 'Stay on the public website',
|
||||
posture: 'Public release shell',
|
||||
description: 'Use this surface when you are still evaluating HyperTwist, reading docs, checking access status, or reviewing pricing, notices, and rollout guidance.',
|
||||
title: 'Stay on the website',
|
||||
posture: 'Public website',
|
||||
description: 'Use this when you are still exploring HyperTwist, reading the manual, comparing plans, or checking release status.',
|
||||
bullets: [
|
||||
'Best for product discovery, public documentation, pricing, support, and legal follow-through.',
|
||||
'Keeps launch-readiness and packaged proof visible without exposing protected download authority.',
|
||||
'Best for product discovery, documentation, pricing, support, and release updates.',
|
||||
'Keeps launch details visible without exposing protected downloads on anonymous pages.',
|
||||
],
|
||||
action: {
|
||||
to: '/docs',
|
||||
|
|
@ -285,25 +285,25 @@ const surfaceChoiceGuideCards = [
|
|||
},
|
||||
},
|
||||
{
|
||||
title: 'Move into the protected dashboard',
|
||||
posture: 'Signed-in operator shell',
|
||||
description: 'Use this surface when identity, entitlement, billing, protected notices, or browser-to-desktop pairing starts to matter.',
|
||||
title: 'Open your account dashboard',
|
||||
posture: 'Signed-in dashboard',
|
||||
description: 'Use this when your identity, plan, downloads, billing status, or desktop pairing starts to matter.',
|
||||
bullets: [
|
||||
'Best for account state, protected downloads, desktop-link pairing, and release-manifest viewer truth.',
|
||||
'Keeps the operator inside direct signed-in routes instead of routing them back through anonymous guidance.',
|
||||
'Best for account state, protected downloads, desktop-link pairing, and release updates tied to your plan.',
|
||||
'Keeps you inside signed-in routes instead of bouncing you back through anonymous guidance.',
|
||||
],
|
||||
action: {
|
||||
to: '/app',
|
||||
label: 'Open operator dashboard',
|
||||
label: 'Open dashboard',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Use the desktop runtime',
|
||||
posture: 'Simulator authority',
|
||||
description: 'Use this surface when you need the actual training loop, packaged higher-dimensional maps, replay/coaching behavior, or simulator-side diagnostics.',
|
||||
title: 'Launch the desktop app',
|
||||
posture: 'Desktop simulator',
|
||||
description: 'Use this when you want the actual training loop, replay review, higher-dimensional maps, or the full simulator experience.',
|
||||
bullets: [
|
||||
'Best for classic-cube execution, dedicated-family Magic120Cell and MagicCube5D work, and packaged validation truth.',
|
||||
'Owns the real simulator behavior that the public and protected browser surfaces intentionally do not claim.',
|
||||
'Best for classic-cube execution, dedicated 120‑cell and 5D sessions, and the strongest training fidelity.',
|
||||
'This is the real simulator experience that the website intentionally does not pretend to replace.',
|
||||
],
|
||||
action: {
|
||||
to: '/download',
|
||||
|
|
@ -346,7 +346,7 @@ export function SurfaceChoiceGuideSection({
|
|||
|
||||
export function OperatorDesktopQuickstartSection({
|
||||
title = 'First real desktop session',
|
||||
description = 'This is the shortest honest path from browser discovery into the native simulator: resolve access, pair the installed runtime safely, verify the first training lane, and keep the current XR/controller boundary visible.',
|
||||
description = 'This is the shortest path from website discovery into the real simulator: create your account, pair your install, verify the first session, and know exactly what is ready today.',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -371,7 +371,7 @@ export function OperatorDesktopQuickstartSection({
|
|||
Open download center
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink tone="ghost" to="/app">
|
||||
Open operator dashboard
|
||||
Open dashboard
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink tone="ghost" to="/docs">
|
||||
Open public manual
|
||||
|
|
@ -382,8 +382,8 @@ export function OperatorDesktopQuickstartSection({
|
|||
}
|
||||
|
||||
export function DownloadableOperatorManualSection({
|
||||
title = 'Offline operator manual',
|
||||
description = 'HyperTwist now ships a same-origin downloadable operator manual so the desktop-first onboarding, control-boundary truth, and rollout guidance can travel with the product instead of living only in live browser routes.',
|
||||
title = 'Offline desktop guide',
|
||||
description = 'HyperTwist ships a downloadable guide so onboarding, controls, advanced puzzle guidance, and release notes can travel with the app instead of living only on the website.',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -392,18 +392,18 @@ export function DownloadableOperatorManualSection({
|
|||
<Section title={title} description={description}>
|
||||
<article className="card">
|
||||
<p>
|
||||
The downloadable manual bundles the current first-session path, browser-versus-desktop
|
||||
boundary, protected dashboard follow-through, control roster, higher-dimensional runtime
|
||||
guide, and the current XR/controller truth into one offline reference.
|
||||
The downloadable guide bundles the first-session path, the website-versus-desktop split,
|
||||
signed-in access, controls, higher-dimensional guidance, and the current
|
||||
XR/controller status into one offline reference.
|
||||
</p>
|
||||
<ul className="list top-gap">
|
||||
<li>Use it for desktop-side onboarding, rollout review, and operator support when the browser shell is not the best reading surface.</li>
|
||||
<li>It stays aligned with the current public route atlas instead of inventing a separate product story.</li>
|
||||
<li>It keeps the current headset/controller validation boundary explicit instead of flattening it into generic VR support language.</li>
|
||||
<li>Use it for desktop-side onboarding, launch review, and support when the website is not the best reading surface.</li>
|
||||
<li>It stays aligned with the public manual instead of inventing a separate product story.</li>
|
||||
<li>It keeps the current headset and controller status explicit instead of flattening it into vague VR claims.</li>
|
||||
</ul>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonAnchor href={downloadablePublicManualHref} download>
|
||||
Download offline operator manual
|
||||
Download offline desktop guide
|
||||
</HyperTwistButtonAnchor>
|
||||
<HyperTwistButtonLink tone="ghost" to="/docs">
|
||||
Open public manual
|
||||
|
|
@ -442,7 +442,7 @@ export function PrincipleCardSection({
|
|||
|
||||
export function SimulatorManualSection({
|
||||
title = 'Simulator manual',
|
||||
description = 'These are the current product-safe usage tracks for the native runtime itself.',
|
||||
description = 'These are the current usage tracks for the desktop app itself.',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -452,7 +452,7 @@ export function SimulatorManualSection({
|
|||
|
||||
export function HigherDimensionalRuntimeGuideSection({
|
||||
title = 'Higher-dimensional family guide',
|
||||
description = 'These are the currently represented higher-dimensional lanes and the truthful host/runtime model for each.',
|
||||
description = 'These are the higher-dimensional puzzle families already represented in HyperTwist and where each one actually lives today.',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -468,7 +468,7 @@ export function HigherDimensionalRuntimeGuideSection({
|
|||
|
||||
export function InputAndDevicePostureSection({
|
||||
title = 'Input and device state',
|
||||
description = 'Public documentation should be explicit about the current control/runtime truth instead of blurring groundwork and finished VR claims together.',
|
||||
description = 'Public documentation should be explicit about the controls, devices, and current VR status instead of blurring groundwork and finished claims together.',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -484,7 +484,7 @@ export function InputAndDevicePostureSection({
|
|||
|
||||
export function ControlProfileRosterSection({
|
||||
title = 'Selectable control and settings roster',
|
||||
description = 'This manual section answers the concrete user question: what settings, profiles, selectors, and persistence surfaces are already real today?',
|
||||
description = 'This answers the practical user question: which settings, profiles, selectors, and saved preferences are already real today?',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -500,7 +500,7 @@ export function ControlProfileRosterSection({
|
|||
|
||||
export function RuntimeControlGuideSection({
|
||||
title = 'Runtime control guide',
|
||||
description = 'This manual section explains how to approach the current desktop runtime without pretending the currently closed XR/controller branch has already been reopened.',
|
||||
description = 'This manual section explains how to use the current desktop app without pretending the unfinished XR/controller branch is already complete.',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -622,7 +622,7 @@ export function FaqCardSection({
|
|||
|
||||
export function BrowserDesktopDecisionFaqSection({
|
||||
title = 'Common browser-versus-desktop questions',
|
||||
description = 'These are the direct product-boundary answers most operators want before deciding whether the browser lane is enough on its own or whether they should move into the native runtime.',
|
||||
description = 'These are the direct product-boundary answers most people want before deciding whether the website is enough on its own or whether they should move into the native runtime.',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -658,7 +658,7 @@ export function SupportTopicDirectorySection({
|
|||
key={topic.topicKey}
|
||||
className={`card${isSelected ? ' card--selected' : ''}`}
|
||||
>
|
||||
{isSelected ? <p className="status-pill status-pill--info">Selected help lane</p> : null}
|
||||
{isSelected ? <p className="status-pill status-pill--info">Selected help topic</p> : null}
|
||||
<h3>{topic.title}</h3>
|
||||
<p>{topic.description}</p>
|
||||
<p className="eyebrow top-gap">Next steps</p>
|
||||
|
|
@ -690,7 +690,7 @@ export function SupportTopicDirectorySection({
|
|||
|
||||
export function PublicManualRouteAtlasSection({
|
||||
title = 'Public manual route atlas',
|
||||
description = 'Each public page has a different job inside HyperTwist. This atlas keeps the route set readable as a real manual rather than a flat marketing shell.',
|
||||
description = 'Each public page has a different job inside HyperTwist. This atlas keeps the route set readable as a real manual instead of a flat landing site.',
|
||||
cards = publicManualRouteAtlasCards,
|
||||
limit,
|
||||
}: {
|
||||
|
|
@ -728,7 +728,7 @@ export function PublicManualRouteAtlasSection({
|
|||
|
||||
export function BrowserAuthMethodsSection({
|
||||
title = 'Browser account access methods',
|
||||
description = 'The public browser-account provider lineup should stay visible wherever operators are being asked to sign in, buy access, pair the desktop runtime, or recover access state.',
|
||||
description = 'Keep the available sign-in options visible wherever people are being asked to create an account, buy access, pair the desktop app, or recover access.',
|
||||
}: {
|
||||
title?: string
|
||||
description?: string
|
||||
|
|
@ -780,7 +780,7 @@ type ReleaseAuthorityReference = {
|
|||
export function ReleaseAuthorityBundleSection({
|
||||
releaseManifest,
|
||||
title = 'Release references and source availability',
|
||||
description = 'Treat public docs, release notes, corresponding source, notices repository, and operator contact as one launch bundle so distribution-critical pages do not fragment the release story.',
|
||||
description = 'Keep docs, release notes, source links, notices, and support in one place so buyers and teams can always see the full delivery story.',
|
||||
}: {
|
||||
releaseManifest: ReturnType<typeof resolveReleaseManifestView>
|
||||
title?: string
|
||||
|
|
@ -788,19 +788,19 @@ export function ReleaseAuthorityBundleSection({
|
|||
}) {
|
||||
const references: readonly ReleaseAuthorityReference[] = [
|
||||
{
|
||||
title: 'Downloadable operator manual',
|
||||
title: 'Offline desktop guide',
|
||||
description:
|
||||
'A same-origin offline manual ships with the website so desktop-first onboarding, protected-release follow-through, and rollout review do not depend on live browsing alone.',
|
||||
'A same-origin offline manual ships with the website so desktop-first onboarding, download help, and launch review do not depend on live browsing alone.',
|
||||
href: downloadablePublicManualHref,
|
||||
configuredLabel: 'Included',
|
||||
missingLabel: 'Add the downloadable operator manual before wider launch',
|
||||
linkLabel: 'Download operator manual (.md)',
|
||||
missingLabel: 'Add the offline desktop guide before wider launch',
|
||||
linkLabel: 'Download offline desktop guide (.md)',
|
||||
download: true,
|
||||
},
|
||||
{
|
||||
title: 'Public manual',
|
||||
description:
|
||||
'The public docs portal is the product-safe manual for browser-versus-desktop boundaries, rollout status, and simulator truth.',
|
||||
'The public docs portal is the main manual for browser-versus-desktop boundaries, launch status, and simulator truth.',
|
||||
href: releaseManifest.public_docs_url,
|
||||
configuredLabel: 'Configured',
|
||||
missingLabel: 'Add public docs URL before wider launch',
|
||||
|
|
@ -828,16 +828,16 @@ export function ReleaseAuthorityBundleSection({
|
|||
{
|
||||
title: 'Open-source repo and notices reference',
|
||||
description:
|
||||
'The public repository and notices reference keeps release, legal, and redistribution follow-through visible outside the simulator.',
|
||||
'The public repository and notices reference keeps release, legal, and redistribution details visible outside the simulator.',
|
||||
href: releaseManifest.open_source_repo_url,
|
||||
configuredLabel: 'Configured',
|
||||
missingLabel: 'Add public repo/notices URL before wider launch',
|
||||
linkLabel: releaseManifest.open_source_repo_url || 'Add public repo/notices URL before wider launch',
|
||||
},
|
||||
{
|
||||
title: 'Operator support contact',
|
||||
title: 'Support contact',
|
||||
description:
|
||||
'Support contact belongs in the same bundle so release, entitlement, notice, and rollout questions do not force operators to improvise escalation paths.',
|
||||
'Support contact belongs in the same bundle so account, download, notice, and launch questions never need improvised escalation paths.',
|
||||
href: releaseManifest.support_email ? `mailto:${releaseManifest.support_email}` : null,
|
||||
configuredLabel: 'Ready',
|
||||
missingLabel: 'Add support email before wider launch',
|
||||
|
|
@ -930,34 +930,34 @@ function buildPublicReleaseDecisionCards(releaseManifest: ReleaseManifestView):
|
|||
const releaseTargetCard: PublicReleaseDecisionCard = primaryTarget?.configured
|
||||
? {
|
||||
badge: 'Desktop release target published',
|
||||
title: 'Need the protected desktop-download lane?',
|
||||
title: 'Need the desktop download?',
|
||||
summary:
|
||||
'The public site can confirm the current desktop release target, but actual package delivery still belongs to the signed-in protected release lane.',
|
||||
'The public site can confirm the current desktop release target, while the actual package still arrives through your signed-in download area.',
|
||||
bullets: [
|
||||
`Current public target: ${describePublicReleaseTarget(primaryTarget)}`,
|
||||
`Configured release targets: ${configuredTargets.length}/${releaseManifest.platforms.length}`,
|
||||
primaryTarget.validation_summary
|
||||
? 'Public packaged proof is already attached to this lane.'
|
||||
: 'This lane is published, but public packaged proof is not attached yet.',
|
||||
? 'Public package proof is already attached to this release.'
|
||||
: 'This release is published, but public package proof is not attached yet.',
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Sign in for protected downloads', to: buildLoginPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Sign in for desktop downloads', to: buildLoginPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Create account for desktop access', to: buildRegisterPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Open download center', to: '/download' },
|
||||
],
|
||||
}
|
||||
: {
|
||||
badge: 'Package publication pending',
|
||||
title: 'Need the protected desktop-download lane?',
|
||||
title: 'Need the desktop download?',
|
||||
summary:
|
||||
'The public site can still explain the release lane, but direct package delivery belongs in protected follow-through until a published desktop target is active.',
|
||||
'The public site can still explain the release path, but direct package delivery stays inside the signed-in flow until a published desktop target is active.',
|
||||
bullets: [
|
||||
`Configured release targets: ${configuredTargets.length}/${releaseManifest.platforms.length}`,
|
||||
`Primary default lane: ${primaryTarget ? primaryTarget.platform : 'Windows desktop lane pending publication'}`,
|
||||
`Primary default target: ${primaryTarget ? primaryTarget.platform : 'Windows desktop release pending publication'}`,
|
||||
`Support contact: ${supportEmail}`,
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Sign in for protected downloads', to: buildLoginPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Sign in for desktop downloads', to: buildLoginPath(buildProtectedDownloadPath('windows')) },
|
||||
{ label: 'Open download center', to: '/download' },
|
||||
{ label: 'Open launch-readiness support', to: buildSupportPath('launch-readiness') },
|
||||
],
|
||||
|
|
@ -966,56 +966,56 @@ function buildPublicReleaseDecisionCards(releaseManifest: ReleaseManifestView):
|
|||
const provisioningCard: PublicReleaseDecisionCard = checkoutConfigured
|
||||
? {
|
||||
badge: 'Checkout or provisioning route ready',
|
||||
title: 'Need plan selection, billing, or operator provisioning?',
|
||||
title: 'Need pricing, billing, or subscription help?',
|
||||
summary:
|
||||
'The website is not a brochure-only surface here: pricing is the public handoff into plan selection, then the protected dashboard resolves entitlement, downloads, and operator follow-through.',
|
||||
'The website is where pricing starts. After that, the signed-in dashboard resolves plan access, downloads, and account follow-through.',
|
||||
bullets: [
|
||||
`Operator checkout: ${releaseCommerce.operator_checkout_url ? 'configured' : 'manual help required'}`,
|
||||
`Studio checkout: ${releaseCommerce.studio_checkout_url ? 'configured' : 'manual help required'}`,
|
||||
'Keep pricing on the public site, then move into the protected dashboard for account-aware release work.',
|
||||
`Desktop plan checkout: ${releaseCommerce.operator_checkout_url ? 'configured' : 'manual help required'}`,
|
||||
`Studio plan checkout: ${releaseCommerce.studio_checkout_url ? 'configured' : 'manual help required'}`,
|
||||
'Keep pricing on the public site, then move into the signed-in dashboard for account-aware release work.',
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Open pricing', to: '/pricing' },
|
||||
{ label: 'Sign in for protected dashboard', to: buildLoginPath('/app') },
|
||||
{ label: 'Sign in for dashboard', to: buildLoginPath('/app') },
|
||||
],
|
||||
}
|
||||
: {
|
||||
badge: 'Manual provisioning route',
|
||||
title: 'Need plan selection, billing, or operator provisioning?',
|
||||
title: 'Need pricing, billing, or subscription help?',
|
||||
summary:
|
||||
'Pricing can still explain the real delivery model, while account or support follow-through remains the truthful path until live checkout targets are active.',
|
||||
'Pricing can still explain the delivery model, while account or support follow-through remains the right path until live checkout targets are active.',
|
||||
bullets: [
|
||||
`Operator checkout: ${releaseCommerce.operator_checkout_url ? 'configured' : 'manual help required'}`,
|
||||
`Studio checkout: ${releaseCommerce.studio_checkout_url ? 'configured' : 'manual help required'}`,
|
||||
`Desktop plan checkout: ${releaseCommerce.operator_checkout_url ? 'configured' : 'manual help required'}`,
|
||||
`Studio plan checkout: ${releaseCommerce.studio_checkout_url ? 'configured' : 'manual help required'}`,
|
||||
`Support contact: ${supportEmail}`,
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Open pricing', to: '/pricing' },
|
||||
{ label: 'Open operator-access support', to: buildSupportPath('operator-access') },
|
||||
{ label: 'Open access support', to: buildSupportPath('operator-access') },
|
||||
],
|
||||
}
|
||||
|
||||
const browserContinuityCard: PublicReleaseDecisionCard = {
|
||||
badge: 'Protected browser shell',
|
||||
title: 'Need account state, pairing, or protected browser follow-through?',
|
||||
badge: 'Signed-in website follow-through',
|
||||
title: 'Need account access, pairing, or dashboard help?',
|
||||
summary:
|
||||
'The web lane is not superfluous: it owns identity, entitlement, billing status, protected notices, and browser-to-desktop pairing while the native runtime keeps simulator authority.',
|
||||
'The website handles your account, downloads, billing, legal details, and desktop pairing while the desktop app stays focused on training.',
|
||||
bullets: [
|
||||
'Use the protected dashboard for account state, auth/runtime truth, and release-manifest follow-through.',
|
||||
'Use the protected browser-access lane when you need account-aware browser continuity rather than simulator execution.',
|
||||
'Move into the desktop runtime for the actual training loop, coaching, and higher-dimensional interaction.',
|
||||
'Use the signed-in dashboard for account status, release updates, and pairing follow-through.',
|
||||
'Use the signed-in browser-access route when you need account continuity rather than simulator execution.',
|
||||
'Move into the desktop app for the actual training loop, coaching, and higher-dimensional interaction.',
|
||||
],
|
||||
actions: [
|
||||
{ label: 'Sign in for protected dashboard', to: buildLoginPath('/app') },
|
||||
{ label: 'Sign in for protected browser access', to: buildLoginPath('/app/browser-access') },
|
||||
{ label: 'Sign in for dashboard', to: buildLoginPath('/app') },
|
||||
{ label: 'Sign in for browser access', to: buildLoginPath('/app/browser-access') },
|
||||
],
|
||||
}
|
||||
|
||||
const noticesCard: PublicReleaseDecisionCard = {
|
||||
badge: configuredReferenceCount === 5 ? 'Release references configured' : 'Some release references are still pending',
|
||||
title: 'Need notices, corresponding source, or release follow-through?',
|
||||
title: 'Need legal details, release notes, or source links?',
|
||||
summary:
|
||||
'Public notices and release references stay on the website first, while the protected notices lane remains the signed-in follow-through surface when account context matters.',
|
||||
'Public notices and release references stay on the website first, while the signed-in notices area helps when those details need to stay attached to your account.',
|
||||
bullets: [
|
||||
`Configured public release references: ${configuredReferenceCount}/5`,
|
||||
'Keep notices, release notes, and corresponding-source status visible anywhere downloadable distribution is discussed.',
|
||||
|
|
@ -1024,7 +1024,7 @@ function buildPublicReleaseDecisionCards(releaseManifest: ReleaseManifestView):
|
|||
actions: [
|
||||
{ label: 'Review public notices', to: '/open-source-notices' },
|
||||
{ label: 'Review release notes', to: '/changelog' },
|
||||
{ label: 'Sign in for protected notices', to: buildLoginPath('/app/notices') },
|
||||
{ label: 'Sign in for notices', to: buildLoginPath('/app/notices') },
|
||||
],
|
||||
}
|
||||
|
||||
|
|
@ -1057,8 +1057,8 @@ function SurfaceActionLink({
|
|||
|
||||
export function PublicReleaseDecisionGuideSection({
|
||||
releaseManifest,
|
||||
title = 'Choose the next release move',
|
||||
description = 'These cards keep the public pages practical: they show whether the next honest step is pricing, protected browser/account work, protected desktop access, or notices/source follow-through.',
|
||||
title = 'Choose the next best step',
|
||||
description = 'These cards keep the public pages practical: they show whether the next step is pricing, account access, the desktop download, or the release details around it.',
|
||||
}: {
|
||||
releaseManifest: ReleaseManifestView
|
||||
title?: string
|
||||
|
|
|
|||
|
|
@ -80,13 +80,13 @@ export function PricingPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist pricing"
|
||||
description="View HyperTwist browser-access, operator, and studio pricing with Paddle-ready checkout, account-gated downloads, and launch-honest legal guidance."
|
||||
description="View HyperTwist pricing for individuals and studios with account-backed downloads, Paddle-ready checkout, and clear desktop delivery guidance."
|
||||
canonicalPath="/pricing"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Paddle-ready plans"
|
||||
title="Pricing that matches the actual delivery model."
|
||||
lede="The browser shell handles account, release, and billing access. The simulator remains desktop-first. Prices and checkouts can be switched live through HyperTwist's own Paddle-ready runtime commerce structure."
|
||||
lede="Choose a plan, unlock your account, and move into the full desktop simulator. Billing lives on the website so downloads, subscriptions, and support stay simple while the desktop app stays focused on training."
|
||||
>
|
||||
<Section title="Plan lineup" description={paddleReadyDescription}>
|
||||
<div className="card-grid card-grid--pricing">
|
||||
|
|
@ -107,8 +107,8 @@ export function PricingPage() {
|
|||
</Section>
|
||||
|
||||
<BulletCardSection
|
||||
title="Why plan access starts on the web but training stays in the downloadable"
|
||||
description="Pricing should explain the product boundary directly instead of letting buyers infer it from scattered support notes."
|
||||
title="Why accounts live on the website while training lives in the desktop app"
|
||||
description="Pricing should explain the product clearly instead of making buyers infer it from scattered support notes."
|
||||
cards={browserLimitCards}
|
||||
/>
|
||||
|
||||
|
|
@ -126,16 +126,16 @@ export function PricingPage() {
|
|||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Pricing is a distribution surface, so its launch truth should carry the same docs, release, source, and escalation bundle as the rest of the public release lane."
|
||||
description="Pricing is part of the real delivery path, so it should carry the same docs, release notes, source links, and support bundle as the rest of the public site."
|
||||
/>
|
||||
|
||||
<BrowserAuthMethodsSection
|
||||
description="Pricing decisions are safer when the current shared-auth sign-in lineup is visible before checkout, protected release access, or browser-account follow-through."
|
||||
description="Pricing decisions are easier to trust when the current sign-in options are visible before checkout, desktop access, or account follow-through."
|
||||
/>
|
||||
|
||||
<StepCardSection
|
||||
title="What happens after access is granted"
|
||||
description="Pricing only stays professional when it explains the real path from browser entitlement into the packaged simulator instead of stopping at the checkout button."
|
||||
description="Pricing only stays professional when it explains the real path from account access into the desktop simulator instead of stopping at the checkout button."
|
||||
cards={operatorManualTracks.slice(0, 3)}
|
||||
/>
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ export function PricingPage() {
|
|||
|
||||
<BulletCardSection
|
||||
title="What those plans unlock in practice"
|
||||
description="Commercial copy is stronger when it points at the concrete simulator lanes the downloadable already owns."
|
||||
description="Pricing gets stronger when it points at the concrete experiences the desktop app already owns."
|
||||
cards={puzzleCatalogCards}
|
||||
/>
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ export function PricingPage() {
|
|||
|
||||
<BulletCardSection
|
||||
title="Current input and runtime control truth"
|
||||
description="The pricing page should also be explicit about the present control quality bar so buyers can see what is real today and what remains in later device validation."
|
||||
description="The pricing page should also be explicit about current controls so buyers can see what is ready today and what still needs more device validation."
|
||||
cards={[...inputAndDevicePostureCards, ...runtimeControlGuideCards]}
|
||||
/>
|
||||
|
||||
|
|
@ -165,35 +165,35 @@ export function PricingPage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Pricing should also say plainly whether the next honest operator move is checkout, protected desktop access, browser/account continuity, or notices/source follow-through."
|
||||
description="Pricing should also say plainly whether the next best move is checkout, the desktop download, account continuity, or the release details around it."
|
||||
/>
|
||||
|
||||
<BrowserDesktopRealitySection />
|
||||
|
||||
<Section
|
||||
title="Why plans live in the browser while training stays native"
|
||||
description="Commercial access, entitlement, and launch-readiness status belong to the browser shell so the simulator can stay focused on training quality."
|
||||
title="Why plans live on the website while training stays native"
|
||||
description="Accounts, billing, and release status belong on the website so the simulator can stay focused on training quality."
|
||||
>
|
||||
<DeliverySurfaceResponsibilitiesGrid limit={3} />
|
||||
</Section>
|
||||
|
||||
<BulletCardSection
|
||||
title="Commercial distribution doctrine"
|
||||
description="Commercial pages should stay as explicit about release and legal follow-through as the rest of the public site."
|
||||
description="Commercial pages should stay as explicit about release and legal details as the rest of the public site."
|
||||
cards={distributionDoctrineCards}
|
||||
/>
|
||||
|
||||
<BulletCardSection
|
||||
title="Terms of access in practice"
|
||||
description="Pricing is easier to trust when the access model, simulator boundary, and operator obligations stay visible before checkout."
|
||||
description="Pricing is easier to trust when the access model, simulator boundary, and buyer obligations stay visible before checkout."
|
||||
cards={termsBoundaryCards}
|
||||
/>
|
||||
|
||||
<Section title="Important launch note">
|
||||
<article className="callout">
|
||||
<p>
|
||||
Public pricing, checkout, and download pages are distribution surfaces. Before external launch,
|
||||
keep their legal footer and open-source notices link live and ensure the corresponding-source URL is configured for any downloadable build containing MPL-covered material.
|
||||
Public pricing, checkout, and download pages are part of the real product delivery story. That is why HyperTwist keeps release notes,
|
||||
notices, and source links close to the buying path whenever a desktop build includes open-source obligations.
|
||||
</p>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonLink to="/browser" tone="ghost">
|
||||
|
|
@ -240,7 +240,7 @@ function ReleaseManifestStatusSections({
|
|||
{
|
||||
title: 'What still works',
|
||||
items: [
|
||||
'Supported targets, packaged proof, and public rollout guidance remain visible.',
|
||||
'Supported targets, packaged proof, and public release guidance remain visible.',
|
||||
'Platform selection can still be preserved into the protected sign-in and download handoff.',
|
||||
],
|
||||
},
|
||||
|
|
@ -254,8 +254,8 @@ function ReleaseManifestStatusSections({
|
|||
{
|
||||
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 recovery persists during rollout or purchase work.',
|
||||
'Use the signed-in dashboard once the auth server recovers if you need actual package delivery.',
|
||||
'Open support if release-authority recovery persists during purchase or release work.',
|
||||
],
|
||||
},
|
||||
]}
|
||||
|
|
@ -315,13 +315,13 @@ export function DownloadPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="Download HyperTwist desktop"
|
||||
description="Download the HyperTwist desktop build, preserve your target platform into the protected release surface, and pair the installed app with your browser account."
|
||||
description="Download the HyperTwist desktop build, preserve your target platform into the signed-in release path, and pair the installed app with your browser account."
|
||||
canonicalPath="/download"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Desktop distribution"
|
||||
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 into every protected download."
|
||||
lede="The website provides account, release, and legal surfaces. The actual simulator ships through the desktop app, with package validation and release discipline carried into every signed-in download."
|
||||
>
|
||||
<ReleaseManifestStatusSections
|
||||
isLoading={releaseManifestQuery.isLoading}
|
||||
|
|
@ -343,27 +343,27 @@ export function DownloadPage() {
|
|||
|
||||
<Section
|
||||
title="Need the web-version explanation before you install?"
|
||||
description="The download lane should still link back to the public routes that explain what the browser shell is for and how the protected handoff works."
|
||||
description="The download page should still link back to the public routes that explain what the website is for and how the signed-in handoff works."
|
||||
>
|
||||
<div className="feature-band">
|
||||
<Link to="/browser" className="feature-band__card feature-band__card--link">
|
||||
<h3>Browser guide</h3>
|
||||
<p>Read why HyperTwist keeps the web version, what it does well, and where it is intentionally weaker than the downloadable.</p>
|
||||
<p>Read what the web experience is for, what it does well, and where the desktop app goes deeper.</p>
|
||||
</Link>
|
||||
<Link to="/help" className="feature-band__card feature-band__card--link">
|
||||
<h3>Help center</h3>
|
||||
<p>Open practical onboarding, controls, and rollout-safe troubleshooting guidance before or after the first install.</p>
|
||||
<p>Open practical onboarding, controls, and troubleshooting guidance before or after the first install.</p>
|
||||
</Link>
|
||||
<Link to="/contact" className="feature-band__card feature-band__card--link">
|
||||
<h3>Contact</h3>
|
||||
<p>Open the human contact lane when the next move is support, rollout coordination, or pricing follow-through.</p>
|
||||
<p>Open the human contact lane when the next move is support, team coordination, or pricing follow-through.</p>
|
||||
</Link>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<SimulatorManualSection
|
||||
title="What the installed runtime already owns"
|
||||
description="The download page is stronger when it also explains the real software lane operators receive after the package handoff."
|
||||
description="The download page is stronger when it also explains the real software experience you receive after the package handoff."
|
||||
/>
|
||||
|
||||
<HigherDimensionalRuntimeGuideSection
|
||||
|
|
@ -383,11 +383,11 @@ export function DownloadPage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The download page should also say whether the next honest move is protected desktop access, pricing/provisioning, browser/account continuity, or notices/source follow-through."
|
||||
description="The download page should also say whether the next best move is the desktop download, pricing, account continuity, or the release details around it."
|
||||
/>
|
||||
|
||||
<BrowserAuthMethodsSection
|
||||
description="Download guidance is clearer when the current shared-auth sign-in lineup is visible before operators cross into the protected entitlement and package-delivery lane."
|
||||
description="Download guidance is clearer when the current sign-in options are visible before you cross into protected access and package delivery."
|
||||
/>
|
||||
|
||||
<BulletCardSection
|
||||
|
|
@ -398,12 +398,12 @@ export function DownloadPage() {
|
|||
|
||||
<BulletCardSection
|
||||
title="First-session verification checklist"
|
||||
description="The download lane is stronger when it also tells operators exactly what to confirm before they treat a fresh install as a trustworthy training runtime."
|
||||
description="The download lane is stronger when it also tells you exactly what to confirm before treating a fresh install as a trustworthy training runtime."
|
||||
cards={firstSessionVerificationCards}
|
||||
/>
|
||||
|
||||
<DownloadableOperatorManualSection
|
||||
description="The download lane now also includes a same-origin offline manual so the first-session path, current controls, higher-dimensional runtime guide, and rollout boundary can be carried alongside the desktop build."
|
||||
description="The download lane now also includes a same-origin offline manual so the first-session path, current controls, higher-dimensional runtime guide, and release boundary can be carried alongside the desktop build."
|
||||
/>
|
||||
|
||||
<OperatorDesktopQuickstartSection title="First desktop session after install" />
|
||||
|
|
@ -452,7 +452,7 @@ export function DownloadPage() {
|
|||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Download guidance is more trustworthy when docs, release notes, corresponding source, notices repository, and operator escalation remain visible as one coherent release bundle."
|
||||
description="Download guidance is more trustworthy when docs, release notes, corresponding source, notices, and support remain visible as one coherent release bundle."
|
||||
/>
|
||||
|
||||
<Section title="Why this page does not expose raw download URLs">
|
||||
|
|
@ -460,11 +460,11 @@ export function DownloadPage() {
|
|||
<p>
|
||||
HyperTwist treats desktop distribution as an account-gated release surface.
|
||||
Public pages can describe supported targets and release status, but the actual
|
||||
download links live behind the protected dashboard where plan and entitlement
|
||||
download links live behind the signed-in dashboard where plan and entitlement
|
||||
state are resolved.
|
||||
</p>
|
||||
<HyperTwistButtonLink to={buildProtectedDownloadPath('windows')} tone="ghost">
|
||||
Open protected download lane
|
||||
Open protected download center
|
||||
</HyperTwistButtonLink>
|
||||
</article>
|
||||
</Section>
|
||||
|
|
@ -472,11 +472,11 @@ export function DownloadPage() {
|
|||
<Section title="Pair the desktop app with your browser account">
|
||||
<article className="callout">
|
||||
<p>
|
||||
After sign-in, open the operator dashboard to generate a desktop-link token.
|
||||
After sign-in, open the dashboard to generate a desktop-link token.
|
||||
That token is designed to hand browser identity and plan status over to the local desktop app without exposing your password.
|
||||
</p>
|
||||
<HyperTwistButtonLink to={buildLoginPath('/app')} tone="ghost">
|
||||
Open protected dashboard
|
||||
Open dashboard
|
||||
</HyperTwistButtonLink>
|
||||
</article>
|
||||
</Section>
|
||||
|
|
@ -500,7 +500,7 @@ export function OpenSourceNoticesPage() {
|
|||
<MarketingShell
|
||||
eyebrow="Legal and notices"
|
||||
title="Open-source notices for public distribution surfaces."
|
||||
lede="HyperTwist pricing, checkout, release, and download pages must make legal and corresponding-source guidance visible whenever shipped builds include MPL-covered material."
|
||||
lede="HyperTwist pricing, checkout, release, and download pages keep legal and corresponding-source guidance visible whenever a shipped build requires it."
|
||||
>
|
||||
<Section title="Key components">
|
||||
<div className="card-grid">
|
||||
|
|
@ -549,27 +549,27 @@ export function OpenSourceNoticesPage() {
|
|||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
title="Release and source references"
|
||||
description="The notices lane is stronger when corresponding source, release notes, docs, repo/notices references, and operator escalation stay visible together instead of being scattered across separate public pages."
|
||||
description="The notices page is stronger when corresponding source, release notes, docs, repo/notices references, and support escalation stay visible together instead of being scattered across separate public pages."
|
||||
/>
|
||||
|
||||
<Section title="Distribution readiness">
|
||||
<PublicLaunchStatus title="Notices and corresponding-source readiness" />
|
||||
</Section>
|
||||
|
||||
<SurfaceChoiceGuideSection description="Legal pages are more useful when they still tell the operator where to go next: public for release-safe notice context, protected for account-aware release work, and desktop for the actual shipped build." />
|
||||
<SurfaceChoiceGuideSection description="Legal pages are more useful when they still tell you where to go next: public for release-safe notice context, protected for account-aware release work, and desktop for the actual shipped build." />
|
||||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The notices lane should also say plainly whether the next honest move is protected desktop access, pricing/provisioning, browser/account continuity, or notices/source follow-through."
|
||||
description="The notices lane should say plainly whether you need the desktop download, pricing help, account continuity, or the legal details around a release."
|
||||
/>
|
||||
|
||||
<BrowserDesktopRealitySection
|
||||
title="Why notices stay in the browser shell while execution stays native"
|
||||
description="Legal and corresponding-source follow-through belong to the public/protected browser surfaces even when the actual simulator remains a desktop-first runtime."
|
||||
title="Why notices stay on the website while execution stays native"
|
||||
description="Legal and corresponding-source follow-through belong to the website and signed-in dashboard even when the actual simulator remains a desktop-first runtime."
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Current surface authority map"
|
||||
title="Where each part of HyperTwist lives"
|
||||
description="Distribution and corresponding-source language stays safer when the live public, protected, embedded-browser, and native simulator boundaries remain explicit."
|
||||
>
|
||||
<ProductSurfaceMatrix rows={productSurfaceMatrixRows} />
|
||||
|
|
@ -604,7 +604,7 @@ export function OpenSourceNoticesPage() {
|
|||
|
||||
<Section
|
||||
title="Release and redistribution checklist"
|
||||
description="Legal pages stay more useful when they explain the concrete release follow-through that should happen before broader redistribution."
|
||||
description="Legal pages stay more useful when they explain the concrete release checks that should happen before broader redistribution."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{releaseRolloutChecklist.map((card) => (
|
||||
|
|
@ -622,18 +622,18 @@ export function OpenSourceNoticesPage() {
|
|||
|
||||
<PublicManualRouteAtlasSection
|
||||
title="Which public route should you open next?"
|
||||
description="The notices lane is part of the same operator manual family as the rest of the public site, so it should keep the strongest adjacent public routes visible instead of acting like an isolated compliance stub."
|
||||
description="The notices lane is part of the same public guide family as the rest of the site, so it should keep the strongest adjacent routes visible instead of acting like an isolated compliance stub."
|
||||
limit={6}
|
||||
/>
|
||||
|
||||
<DownloadableOperatorManualSection
|
||||
description="The legal/distribution lane now also keeps the offline operator manual reachable so corresponding-source, rollout, download, and native-runtime boundary guidance can travel together."
|
||||
description="The legal and distribution lane now also keeps the offline desktop guide reachable so source links, release notes, downloads, and native-runtime boundary guidance can travel together."
|
||||
/>
|
||||
|
||||
<Section title="Questions about notices or redistribution?">
|
||||
<article className="callout">
|
||||
<p>
|
||||
Contact <a href={brandConfig.contact.emailHref}>{brandConfig.contact.email}</a> when the next question is redistribution, corresponding-source follow-through, or release-surface notices rather than simulator behavior.
|
||||
Contact <a href={brandConfig.contact.emailHref}>{brandConfig.contact.email}</a> when the next question is redistribution, corresponding source, or release-surface notices rather than simulator behavior.
|
||||
</p>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonLink to="/support?topic=launch-readiness" tone="ghost">
|
||||
|
|
@ -663,13 +663,13 @@ export function PrivacyPage() {
|
|||
<MarketingShell
|
||||
eyebrow="Privacy"
|
||||
title="Privacy model"
|
||||
lede="HyperTwist keeps the browser shell narrow and the simulator desktop-first. Privacy descriptions must reflect that separation clearly."
|
||||
lede="HyperTwist keeps the website focused on account and delivery work while the simulator stays desktop-first. Privacy descriptions should reflect that split clearly."
|
||||
>
|
||||
<Section title="Core points">
|
||||
<article className="card">
|
||||
<ul className="list">
|
||||
<li>The public website stores account/session data needed for authentication, plan access, and desktop-link issuance.</li>
|
||||
<li>The browser shell does not claim ownership over the full simulator runtime state unless a future browser-client packet is explicitly opened.</li>
|
||||
<li>The website does not claim ownership over the full simulator runtime state unless a future browser-client branch is explicitly opened.</li>
|
||||
<li>Support, billing, and release operations should collect only the data required to deliver digital access and maintain legal compliance.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
|
@ -677,7 +677,7 @@ export function PrivacyPage() {
|
|||
|
||||
<Section
|
||||
title="Practical privacy boundary"
|
||||
description="Privacy wording should follow the actual product split instead of flattening the browser shell and native simulator into one vague surface."
|
||||
description="Privacy wording should follow the actual product split instead of flattening the website and native simulator into one vague surface."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{privacyBoundaryCards.map((card) => (
|
||||
|
|
@ -698,7 +698,7 @@ export function PrivacyPage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Privacy guidance is stronger when it also says whether the next honest move is protected desktop access, pricing/provisioning, browser/account continuity, or notices/source follow-through."
|
||||
description="Privacy guidance is stronger when it also says whether you need the desktop download, pricing help, account continuity, or the legal details around a release."
|
||||
/>
|
||||
|
||||
<Section
|
||||
|
|
@ -709,7 +709,7 @@ export function PrivacyPage() {
|
|||
</Section>
|
||||
|
||||
<Section
|
||||
title="Current surface authority map"
|
||||
title="Where each part of HyperTwist lives"
|
||||
description="This keeps privacy expectations grounded in the live product topology instead of a vague all-in-one app claim."
|
||||
>
|
||||
<ProductSurfaceMatrix rows={productSurfaceMatrixRows} />
|
||||
|
|
@ -725,7 +725,7 @@ export function PrivacyPage() {
|
|||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Privacy guidance remains more actionable when docs, release notes, corresponding source, notices repository, and operator support stay reachable from the same public boundary page."
|
||||
description="Privacy guidance remains more actionable when docs, release notes, corresponding source, notices, and support stay reachable from the same public page."
|
||||
/>
|
||||
|
||||
<Section
|
||||
|
|
@ -750,7 +750,7 @@ export function PrivacyPage() {
|
|||
<Section title="Privacy contact and policy help">
|
||||
<article className="callout">
|
||||
<p>
|
||||
For browser-account, billing, release-access, or policy questions, contact <a href={brandConfig.contact.emailHref}>{brandConfig.contact.email}</a> and include whether the concern is public, protected, or native-runtime context.
|
||||
For account, billing, release-access, or policy questions, contact <a href={brandConfig.contact.emailHref}>{brandConfig.contact.email}</a> and include whether the concern is on the public site, inside your account, or inside the desktop app.
|
||||
</p>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonLink to="/help" tone="ghost">
|
||||
|
|
@ -785,8 +785,8 @@ export function TermsPage() {
|
|||
<Section title="Service model">
|
||||
<article className="card">
|
||||
<ul className="list">
|
||||
<li>Browser access covers public pages, account, release, download, and operator/dashboard surfaces.</li>
|
||||
<li>The simulator itself is delivered through the desktop lane unless a later browser-client branch is explicitly opened.</li>
|
||||
<li>Browser access covers the public site, your account, release updates, download management, and dashboard tools.</li>
|
||||
<li>The simulator itself is delivered through the desktop app unless a later browser-client branch is explicitly opened.</li>
|
||||
<li>Downloaded builds and their public distribution pages remain subject to open-source notice and corresponding-source disclosure rules where applicable.</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
|
@ -815,7 +815,7 @@ export function TermsPage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The terms lane should also make the next honest operator move explicit: protected desktop access, pricing/provisioning, browser/account continuity, or notices/source follow-through."
|
||||
description="The terms lane should also make the next best move explicit: desktop access, pricing help, account continuity, or legal details around a release."
|
||||
/>
|
||||
|
||||
<Section
|
||||
|
|
@ -826,7 +826,7 @@ export function TermsPage() {
|
|||
</Section>
|
||||
|
||||
<Section
|
||||
title="Current surface authority map"
|
||||
title="Where each part of HyperTwist lives"
|
||||
description="This is the quickest way to see which live surface owns access, execution, and future validation branches today."
|
||||
>
|
||||
<ProductSurfaceMatrix rows={productSurfaceMatrixRows} />
|
||||
|
|
@ -842,7 +842,7 @@ export function TermsPage() {
|
|||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Terms and access language are easier to trust when they keep public docs, release notes, corresponding source, notices references, and support follow-through in one visible release bundle."
|
||||
description="Terms and access language are easier to trust when public docs, release notes, corresponding source, notices, and support stay together in one visible release bundle."
|
||||
/>
|
||||
|
||||
<Section
|
||||
|
|
@ -911,7 +911,7 @@ export function ShippingPaymentPage() {
|
|||
|
||||
<Section
|
||||
title="Digital delivery workflow"
|
||||
description="A professional digital-delivery lane does more than expose a buy button. It keeps release status, entitlement, and the desktop handoff coherent."
|
||||
description="A professional digital-delivery lane does more than expose a buy button. It keeps release status, account access, and the desktop handoff coherent."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{digitalDeliveryCards.map((card) => (
|
||||
|
|
@ -930,7 +930,7 @@ export function ShippingPaymentPage() {
|
|||
|
||||
<Section
|
||||
title="What happens after access is granted"
|
||||
description="Payment surfaces are stronger when they explain the next real operator steps instead of stopping at checkout language."
|
||||
description="Payment surfaces are stronger when they explain the real next steps instead of stopping at checkout language."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorManualTracks.slice(1, 4).map((track) => (
|
||||
|
|
@ -951,27 +951,27 @@ export function ShippingPaymentPage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Shipping and payment guidance is clearer when it also says whether the next honest move is protected desktop access, pricing/provisioning, browser/account continuity, or notices/source follow-through."
|
||||
description="Shipping and payment guidance is clearer when it also says whether you need desktop access, pricing help, account continuity, or legal details around a release."
|
||||
/>
|
||||
|
||||
<BrowserAuthMethodsSection
|
||||
description="Payment and entitlement language is easier to trust when the current shared-auth sign-in lineup stays visible before protected release access and desktop provisioning."
|
||||
description="Payment and access language is easier to trust when the current sign-in options stay visible before private downloads and desktop provisioning."
|
||||
/>
|
||||
|
||||
<BrowserDesktopRealitySection
|
||||
title="Why checkout and entitlement stay on the web while training stays native"
|
||||
description="The commercial/browser shell owns access, billing, and release status so the packaged simulator can remain focused on execution quality instead of becoming a confused storefront."
|
||||
title="Why checkout and access stay on the website while training stays native"
|
||||
description="The website owns access, billing, and release status so the packaged simulator can remain focused on execution quality instead of becoming a confused storefront."
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Browser and desktop responsibilities"
|
||||
description="Shipping and payment surfaces should explain why checkout, entitlement, and release status stay on the web while simulator execution stays native."
|
||||
description="Shipping and payment surfaces should explain why checkout, account access, and release status stay on the web while simulator execution stays native."
|
||||
>
|
||||
<DeliverySurfaceResponsibilitiesGrid />
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Current surface authority map"
|
||||
title="Where each part of HyperTwist lives"
|
||||
description="Digital-delivery pages stay stronger when the live public, protected, embedded-browser, and native simulator responsibilities are visible in one place."
|
||||
>
|
||||
<ProductSurfaceMatrix rows={productSurfaceMatrixRows} />
|
||||
|
|
@ -992,7 +992,7 @@ export function ShippingPaymentPage() {
|
|||
|
||||
<Section
|
||||
title="Terms of access in practice"
|
||||
description="The shipping/payment page should still tell operators what they are actually buying access to and what remains outside the current browser scope."
|
||||
description="The shipping/payment page should still tell people what they are actually buying access to and what remains outside the current browser scope."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{termsBoundaryCards.map((card) => (
|
||||
|
|
@ -1053,13 +1053,13 @@ export function ShippingPaymentPage() {
|
|||
/>
|
||||
|
||||
<DownloadableOperatorManualSection
|
||||
description="The delivery lane also keeps the offline operator manual attached so first-session setup, rollout guidance, higher-dimensional runtime guidance, and browser-versus-desktop truth can accompany the purchase and download path."
|
||||
description="The delivery lane also keeps the offline desktop guide attached so first-session setup, release guidance, higher-dimensional runtime guidance, and browser-versus-desktop truth can accompany the purchase and download path."
|
||||
/>
|
||||
|
||||
<Section title="Need billing, rollout, or plan help?">
|
||||
<Section title="Need billing, team setup, or plan help?">
|
||||
<article className="callout">
|
||||
<p>
|
||||
Write to <a href={brandConfig.contact.emailHref}>{brandConfig.contact.email}</a> for operator provisioning, studio rollout, payment, or release-surface billing questions.
|
||||
Write to <a href={brandConfig.contact.emailHref}>{brandConfig.contact.email}</a> for account provisioning, team setup, payment, or release-surface billing questions.
|
||||
</p>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonLink to="/pricing" tone="ghost">
|
||||
|
|
|
|||
|
|
@ -36,65 +36,65 @@ export function FeaturesPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist features"
|
||||
description="Explore the HyperTwist feature atlas: desktop runtime ownership, browser account and release surfaces, higher-dimensional capability, and clear product boundaries."
|
||||
description="Explore the full HyperTwist feature set across classic training, replay, coaching, higher-dimensional study, browser access, and desktop delivery."
|
||||
canonicalPath="/features"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Capability atlas"
|
||||
title="Feature truth without the guesswork."
|
||||
lede="This page condenses the real HyperTwist product surface into one public map: what ships now, what stays desktop-first, what the browser shell owns, and which branches remain deliberately future-facing."
|
||||
eyebrow="Features"
|
||||
title="A deep training toolkit, not just a timer."
|
||||
lede="HyperTwist brings classic-cube practice, replay review, recognition-assisted recovery, guided training, coaching, and serious higher-dimensional study together in one product. This page shows what you can use today and where each part lives."
|
||||
>
|
||||
<BulletCardSection
|
||||
title="How to read the product truth"
|
||||
description="The public website now mirrors the same discipline used in the internal feature registry so advanced readers do not have to guess which surfaces are live, retained, or reserved for later validation."
|
||||
title="How to read this feature map"
|
||||
description="This page keeps available features, planned additions, and future ideas clearly separated so you always know what is ready today."
|
||||
cards={featureRegistryTierCards}
|
||||
/>
|
||||
|
||||
<BulletCardSection
|
||||
title="Current shipped capability"
|
||||
description="These are the major product tracks that are already first-party owned and safe to describe as current HyperTwist capability."
|
||||
title="What you can use today"
|
||||
description="These are the major experiences HyperTwist already delivers right now."
|
||||
cards={featureAtlasCurrentTracks}
|
||||
/>
|
||||
|
||||
<BulletCardSection
|
||||
title="Advanced live adjunct families"
|
||||
description="The current product is broader than the core simulator loop alone. These adjacent operator-grade families are already implemented and should stay visible in public truth."
|
||||
title="Power-user companion systems"
|
||||
description="HyperTwist also includes companion systems for voice, continuity, and service routing that deepen day-to-day training."
|
||||
cards={advancedOperatorAdjunctTracks}
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Current surface authority map"
|
||||
description="This is the clean public answer to the architecture question: public website, protected dashboard, embedded browser shell, native runtime, and future browser-client branch each have different ownership."
|
||||
title="Where everything happens"
|
||||
description="The website, signed-in dashboard, in-app web tools, and desktop simulator each have a different job. This guide keeps that split easy to understand."
|
||||
>
|
||||
<ProductSurfaceMatrix rows={productSurfaceMatrixRows} />
|
||||
</Section>
|
||||
|
||||
<SurfaceChoiceGuideSection description="This keeps the feature atlas actionable: use the public site for product truth, the protected dashboard for account-aware release work, and the desktop runtime for the actual simulator." />
|
||||
<SurfaceChoiceGuideSection description="Use the public site when you need guidance, updates, or account entry. Use the signed-in dashboard when your plan or downloads matter. Use the desktop app when it is time to train." />
|
||||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The feature atlas should also tell operators whether the next honest move is protected desktop access, pricing, browser/account continuity, or notices/source follow-through."
|
||||
description="The feature page should also tell you whether the next best move is account creation, pricing, the desktop download, or the release details around it."
|
||||
/>
|
||||
|
||||
<BrowserDesktopRealitySection />
|
||||
|
||||
<HigherDimensionalRuntimeGuideSection
|
||||
title="Higher-dimensional families and runtime host"
|
||||
description="These are the current public-safe explanations of the serious hypercubing lanes and the runtime host each one actually uses."
|
||||
title="Higher-dimensional families and where they live"
|
||||
description="These are the serious hypercubing experiences already in HyperTwist and where each one actually runs today."
|
||||
/>
|
||||
|
||||
<InputAndDevicePostureSection
|
||||
title="Current control and device state"
|
||||
description="The public product surface stays stronger when it is explicit about what input quality exists now and what still belongs to a later native completion packet."
|
||||
description="This section shows what control quality you can rely on now and what still needs more device validation."
|
||||
/>
|
||||
|
||||
<ControlProfileRosterSection
|
||||
title="Current selectable control roster"
|
||||
description="This keeps the feature atlas concrete about the shipped keyboard profile, scenic presets, dedicated-family selectors, and the current persistence boundary."
|
||||
description="This keeps the feature atlas concrete about the shipped keyboard profile, scene presets, dedicated-family selectors, and the current persistence boundary."
|
||||
/>
|
||||
|
||||
<RuntimeControlGuideSection
|
||||
title="Runtime control guide"
|
||||
title="Controls guide"
|
||||
description="The feature atlas is more useful when it also teaches practical desktop controls instead of stopping at capability labels and roster summaries."
|
||||
/>
|
||||
|
||||
|
|
@ -105,8 +105,8 @@ export function FeaturesPage() {
|
|||
/>
|
||||
|
||||
<Section
|
||||
title="Release and distribution maturity"
|
||||
description="Production readiness in HyperTwist is not just code quality. It also includes package proof, launch status, account access, and legal/distribution discipline."
|
||||
title="Release readiness"
|
||||
description="Great software also needs clean delivery, package proof, account access, and trustworthy release notes."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{releaseStoryCards.map((card) => (
|
||||
|
|
@ -134,8 +134,8 @@ export function FeaturesPage() {
|
|||
</Section>
|
||||
|
||||
<Section
|
||||
title="Current public rollout state"
|
||||
description="Capability and launch status are related but not identical. This section keeps the current public release lane honest."
|
||||
title="Current availability"
|
||||
description="Capabilities and live access are related but different. This section shows the current public release picture clearly."
|
||||
>
|
||||
<PublicLaunchStatus title="Public feature and access status" />
|
||||
</Section>
|
||||
|
|
@ -150,12 +150,12 @@ export function FeaturesPage() {
|
|||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The feature atlas is part of the rollout story too, so it should keep docs, release notes, source, notices, and support references attached to the capability map."
|
||||
description="The feature map stays more useful when docs, release notes, source links, notices, and support stay attached to it."
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Explicit boundaries"
|
||||
description="These are the product claims HyperTwist refuses to blur. Keeping them visible is part of the product quality bar."
|
||||
title="What stays clear on purpose"
|
||||
description="These are the expectations HyperTwist keeps visible on purpose so the product stays easy to trust."
|
||||
>
|
||||
<div className="split-grid">
|
||||
{roadmapHonestyCards.map((item) => (
|
||||
|
|
|
|||
|
|
@ -42,41 +42,44 @@ export function BrowserExperiencePage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist browser access"
|
||||
description="Learn what the HyperTwist browser experience is for, why it remains intentionally narrower than the downloadable, and when to move into the desktop runtime."
|
||||
description="Learn what the HyperTwist website is for, why the desktop app remains more powerful, and when to move from browser access into real simulator training."
|
||||
canonicalPath="/browser"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Web version and protected browser shell"
|
||||
title="The browser version exists to support the downloadable, not replace it."
|
||||
lede="HyperTwist keeps a real browser experience because account access, pricing, release status, notices, pairing, and operator support should stay outside the simulator. That does make the browser version narrower and less powerful for training work, and this page says exactly how."
|
||||
eyebrow="Website and browser companion"
|
||||
title="The website gets you in. The desktop app becomes the training room."
|
||||
lede="Use the website for accounts, pricing, release notes, legal details, pairing, and support. Use the desktop app when you want the full simulator experience: deeper controls, replay review, recognition workflows, and advanced puzzle families."
|
||||
>
|
||||
<Section
|
||||
title="Start with the right expectation"
|
||||
description="This is the shortest honest description of the web version: useful, production-relevant, and intentionally weaker than the downloadable for the core simulator job."
|
||||
title="Start with the clearest picture"
|
||||
description="The browser experience is useful, polished, and valuable. It is also intentionally lighter than the desktop app for the core simulator job."
|
||||
>
|
||||
<div className="feature-band">
|
||||
<article className="feature-band__card">
|
||||
<Globe size={22} />
|
||||
<h3>The browser is the public and protected operator shell</h3>
|
||||
<p>Use it for docs, pricing, release status, account access, notices, and browser-to-desktop pairing.</p>
|
||||
<h3>The website is your public and signed-in home base</h3>
|
||||
<p>Use it for docs, pricing, release status, account access, legal details, and website-to-desktop pairing.</p>
|
||||
</article>
|
||||
<article className="feature-band__card">
|
||||
<MonitorSmartphone size={22} />
|
||||
<h3>The downloadable is the actual simulator</h3>
|
||||
<p>Use the desktop runtime for recognition, replay, coaching, higher-dimensional families, and packaged proof.</p>
|
||||
<h3>The desktop app is the real simulator</h3>
|
||||
<p>Use the desktop app for recognition, replay, coaching, higher-dimensional families, and the full training experience.</p>
|
||||
</article>
|
||||
<article className="feature-band__card">
|
||||
<Headset size={22} />
|
||||
<h3>The current XR boundary is still explicit</h3>
|
||||
<p>The browser does not hide the fact that full headset/controller validation remains a separate native device packet.</p>
|
||||
<h3>Headset support is still being validated</h3>
|
||||
<p>The website does not hide the fact that broader headset and controller support still belongs to a later native device pass.</p>
|
||||
</article>
|
||||
</div>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonLink to="/register?next=%2Fapp" tone="secondary">
|
||||
Create account
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink to="/download">
|
||||
Open desktop download
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink to="/app" tone="ghost">
|
||||
Open protected dashboard
|
||||
Open dashboard
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink to="/help" tone="ghost">
|
||||
Open help center
|
||||
|
|
@ -85,39 +88,39 @@ export function BrowserExperiencePage() {
|
|||
</Section>
|
||||
|
||||
<BulletCardSection
|
||||
title="What the browser version is genuinely for"
|
||||
description="This is the browser lane at its strongest: account-aware operator and distribution work around the simulator."
|
||||
title="What the website is genuinely great at"
|
||||
description="This is where the website shines: access, support, release updates, and account-aware work around the simulator."
|
||||
cards={browserPurposeCards}
|
||||
/>
|
||||
|
||||
<BulletCardSection
|
||||
title="Why the downloadable remains much more powerful"
|
||||
description="This is the direct statement the public site should not bury: the web version is intentionally narrower and materially less capable for simulator execution."
|
||||
title="Why the desktop app goes much further"
|
||||
description="This is the direct statement the public site should not bury: the website is intentionally narrower and materially less capable for actual simulator execution."
|
||||
cards={browserLimitCards}
|
||||
/>
|
||||
|
||||
<StepCardSection
|
||||
title="How the browser and downloadable work together"
|
||||
description="Use this lane when a buyer, operator, or studio wants the real sequence instead of generic browser-versus-native rhetoric."
|
||||
description="Use this section when a buyer, team, or studio wants the real sequence instead of generic browser-versus-native rhetoric."
|
||||
cards={browserWorkflowTracks}
|
||||
/>
|
||||
|
||||
<BrowserDesktopRealitySection
|
||||
title="What stays on the website and what stays in the downloadable"
|
||||
description="The browser shell remains valuable because it stays on its own side of the product boundary instead of quietly absorbing simulator claims it cannot prove."
|
||||
title="What stays on the website and what moves into the desktop app"
|
||||
description="The website stays valuable because it does its own job well instead of quietly claiming simulator features it cannot prove."
|
||||
/>
|
||||
|
||||
<BrowserDesktopDecisionFaqSection
|
||||
description="These are the direct questions people ask as soon as they realize HyperTwist has both a website and a downloadable."
|
||||
description="These are the direct questions people ask as soon as they realize HyperTwist has both a website and a desktop app."
|
||||
/>
|
||||
|
||||
<BrowserAuthMethodsSection
|
||||
description="The browser lane is easier to understand when real sign-in options are visible next to the release, support, and download decisions it controls."
|
||||
description="The browser experience is easier to trust when the real sign-in options are visible next to the release, support, and download decisions they unlock."
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="What changes after you install the desktop build"
|
||||
description="The browser remains part of the product after installation, but the center of gravity moves into the downloadable immediately."
|
||||
title="What changes after installation"
|
||||
description="The website remains part of the product after installation, but the center of gravity moves into the desktop app immediately."
|
||||
>
|
||||
<DeliverySurfaceResponsibilitiesGrid />
|
||||
</Section>
|
||||
|
|
@ -126,7 +129,7 @@ export function BrowserExperiencePage() {
|
|||
|
||||
<SupportTopicDirectorySection
|
||||
title="Need a browser or handoff answer fast?"
|
||||
description="These routes keep the next operator move explicit when the web version raises a launch, access, or rollout question."
|
||||
description="These routes keep the next step explicit when the website raises an access, setup, or release question."
|
||||
topics={supportTopicDirectory}
|
||||
selectedTopicKey="operator-access"
|
||||
/>
|
||||
|
|
@ -135,7 +138,7 @@ export function BrowserExperiencePage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="This keeps the web-version story operationally useful too: the next honest move might be protected downloads, pricing, notices, or browser-account continuity."
|
||||
description="This keeps the web-version story practical too: the next move might be the desktop download, pricing, account continuity, or the release details around it."
|
||||
/>
|
||||
|
||||
<PublicPackagedDesktopProofSection
|
||||
|
|
@ -148,7 +151,7 @@ export function BrowserExperiencePage() {
|
|||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The browser lane is part of the release story, so this page keeps docs, release notes, source, notices, and support contact bundled with the downloadable handoff."
|
||||
description="The browser experience is part of the release story, so this page keeps docs, release notes, source links, legal pages, and support bundled with the desktop handoff."
|
||||
/>
|
||||
</MarketingShell>
|
||||
</>
|
||||
|
|
@ -162,23 +165,23 @@ export function HelpCenterPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist help center"
|
||||
description="Use the HyperTwist help center for onboarding, controls, higher-dimensional runtime guidance, browser-to-desktop handoff help, and rollout-safe troubleshooting."
|
||||
description="Use the HyperTwist help center for onboarding, controls, higher-dimensional guidance, browser-to-desktop handoff help, and practical troubleshooting."
|
||||
canonicalPath="/help"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Help center"
|
||||
title="Production-grade help for the routes, runtime, and rollout you actually have."
|
||||
lede="The help center is where HyperTwist answers practical questions before they become support tickets: how the browser handoff works, how the downloadable should behave on first launch, what controls are real today, and how to escalate when the next step is genuinely human."
|
||||
title="Practical help for setup, controls, downloads, and launch."
|
||||
lede="The help center answers the questions people actually have before they turn into support tickets: how the website handoff works, how the desktop app should behave on first launch, what controls are real today, and when to escalate."
|
||||
>
|
||||
<BulletCardSection
|
||||
title="What this help center covers"
|
||||
description="These are the main categories the public site should answer directly instead of forcing operators into support for everything."
|
||||
description="These are the main categories the public site should answer directly instead of forcing people into support for everything."
|
||||
cards={helpCenterCards}
|
||||
/>
|
||||
|
||||
<SupportTopicDirectorySection
|
||||
title="Pick the right help lane"
|
||||
description="These cards keep account, rollout, release, and runtime questions separated before escalation begins."
|
||||
title="Pick the right help path"
|
||||
description="These cards keep account, release, pricing, and runtime questions separated before escalation begins."
|
||||
topics={supportTopicDirectory}
|
||||
/>
|
||||
|
||||
|
|
@ -193,36 +196,36 @@ export function HelpCenterPage() {
|
|||
/>
|
||||
|
||||
<HigherDimensionalRuntimeGuideSection
|
||||
title="Higher-dimensional runtime help"
|
||||
title="Higher-dimensional family help"
|
||||
description="This guide keeps the serious family lanes accessible to public readers without pretending they run in the browser."
|
||||
/>
|
||||
|
||||
<BulletCardSection
|
||||
title="Issue-report packet"
|
||||
description="When self-service stops being enough, these prompts keep issue reports high-signal and surface-aware."
|
||||
description="When self-service stops being enough, these prompts keep issue reports clear, useful, and quick to act on."
|
||||
cards={issueReportingChecklistCards}
|
||||
/>
|
||||
|
||||
<FaqCardSection
|
||||
title="Common operator questions"
|
||||
description="These public-safe answers should settle the most common browser, download, controls, and scope questions quickly."
|
||||
title="Common questions"
|
||||
description="These answers should settle the most common browser, download, controls, and scope questions quickly."
|
||||
cards={supportFaqs}
|
||||
/>
|
||||
|
||||
<BrowserDesktopRealitySection
|
||||
title="Why help still separates browser from downloadable"
|
||||
description="Good help is clearer when it mirrors the live product topology instead of flattening everything into one vague app."
|
||||
description="Help is clearer when it mirrors the real product instead of flattening everything into one vague app."
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Need escalation instead of guidance?"
|
||||
description="Move into the human lane when the question has already become account, package, runtime, or rollout blocking."
|
||||
description="Move into the human path when the question has already become account, build, app, or launch blocking."
|
||||
>
|
||||
<div className="feature-band">
|
||||
<Link to="/support" className="feature-band__card feature-band__card--link">
|
||||
<LifeBuoy size={22} />
|
||||
<h3>Open support</h3>
|
||||
<p>Use the escalation route when the next job is launch recovery, entitlement review, or runtime issue routing.</p>
|
||||
<p>Use the escalation route when the next job is launch recovery, account review, or desktop-app issue routing.</p>
|
||||
</Link>
|
||||
<Link to="/contact" className="feature-band__card feature-band__card--link">
|
||||
<Mail size={22} />
|
||||
|
|
@ -236,7 +239,7 @@ export function HelpCenterPage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Help gets stronger when it also tells operators the next honest move instead of stopping at explanation alone."
|
||||
description="Help gets stronger when it also tells you the next best move instead of stopping at explanation alone."
|
||||
/>
|
||||
|
||||
<PublicPackagedDesktopProofSection
|
||||
|
|
@ -249,7 +252,7 @@ export function HelpCenterPage() {
|
|||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The help center should still carry the same docs, release notes, notices, and support bundle as the rest of the public manual so operators can act on what they just learned."
|
||||
description="The help center should still carry the same docs, release notes, legal pages, and support bundle as the rest of the public manual so you can act on what you just learned."
|
||||
/>
|
||||
</MarketingShell>
|
||||
</>
|
||||
|
|
@ -263,31 +266,31 @@ export function ContactPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="Contact HyperTwist"
|
||||
description="Contact HyperTwist for operator support, desktop rollout help, download issues, pricing questions, and notices or release follow-through."
|
||||
description="Contact HyperTwist for support, team setup help, download issues, pricing questions, and release details."
|
||||
canonicalPath="/contact"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Contact"
|
||||
title="Contact the team with the right packet the first time."
|
||||
lede={`Write to ${brandConfig.contact.email} when the next step is genuinely human: access problems, rollout questions, runtime issue packets, pricing follow-through, or legal-distribution contact.`}
|
||||
title="Contact the team with the right details the first time."
|
||||
lede={`Write to ${brandConfig.contact.email} when the next step is genuinely human: access problems, team setup questions, desktop-app issues, pricing follow-through, or release and legal contact.`}
|
||||
>
|
||||
<BulletCardSection
|
||||
title="Contact lanes"
|
||||
description="The public site should make the human lanes explicit too, not only the self-service documentation."
|
||||
title="Contact paths"
|
||||
description="The public site should make the human paths explicit too, not only the self-service documentation."
|
||||
cards={contactChannelCards}
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Primary contact route"
|
||||
description="HyperTwist keeps one direct contact path visible, but the fastest answer still depends on naming the right surface and packet."
|
||||
description="HyperTwist keeps one direct contact path visible, but the fastest answer still depends on naming the right part of the product and the right issue."
|
||||
>
|
||||
<article className="callout">
|
||||
<p>
|
||||
Email <a href={brandConfig.contact.emailHref}>{brandConfig.contact.email}</a> with the route,
|
||||
platform, plan or account state, and whether the question is public, protected, or native runtime work.
|
||||
platform, plan or account state, and whether the question is public, signed-in, or desktop-app work.
|
||||
</p>
|
||||
<p>
|
||||
The clearer the surface ownership is up front, the faster HyperTwist can separate account, package, runtime, and rollout questions.
|
||||
The clearer the context is up front, the faster HyperTwist can separate account, package, runtime, and team questions.
|
||||
</p>
|
||||
<div className="button-row top-gap">
|
||||
<HyperTwistButtonAnchor href={brandConfig.contact.emailHref}>
|
||||
|
|
@ -302,7 +305,7 @@ export function ContactPage() {
|
|||
|
||||
<SupportTopicDirectorySection
|
||||
title="Choose the right escalation lane"
|
||||
description="These topic cards help you attach the message to the real account, rollout, or simulator surface before you send it."
|
||||
description="These topic cards help you attach the message to the real account, team, or simulator surface before you send it."
|
||||
topics={supportTopicDirectory}
|
||||
/>
|
||||
|
||||
|
|
@ -319,8 +322,8 @@ export function ContactPage() {
|
|||
/>
|
||||
|
||||
<BrowserDesktopRealitySection
|
||||
title="Why contact still depends on the browser-versus-downloadable split"
|
||||
description="The contact lane should reflect the real product topology too: account and release issues stay in the browser family, while simulator issues stay attached to the downloadable."
|
||||
title="Why contact still depends on the website-versus-downloadable split"
|
||||
description="The contact page should reflect the real product too: account and release issues stay on the web side, while simulator issues stay attached to the desktop app."
|
||||
/>
|
||||
|
||||
<Section
|
||||
|
|
@ -335,13 +338,13 @@ export function ContactPage() {
|
|||
</article>
|
||||
<article className="feature-band__card">
|
||||
<MonitorSmartphone size={22} />
|
||||
<h3>The downloadable remains the runtime authority</h3>
|
||||
<p>Runtime issue reports should name the exact simulator family and build, not only the website route that led there.</p>
|
||||
<h3>The downloadable remains the source of simulator truth</h3>
|
||||
<p>Desktop-app issue reports should name the exact simulator family and build, not only the website route that led there.</p>
|
||||
</article>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<SurfaceChoiceGuideSection description="This page should still point operators to the right next surface too: public for context, protected for account-aware release work, and desktop for actual simulator confirmation." />
|
||||
<SurfaceChoiceGuideSection description="This page should still point people to the right next surface too: public for context, signed-in for account-aware release work, and desktop for actual simulator confirmation." />
|
||||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
|
|
|
|||
|
|
@ -22,30 +22,30 @@ export function LaunchStatusPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist access status"
|
||||
description="Check how HyperTwist public pages, account access, protected downloads, notices, and desktop delivery fit together."
|
||||
description="Check how HyperTwist public pages, account access, private downloads, legal details, and desktop delivery fit together right now."
|
||||
canonicalPath="/launch-status"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Access status"
|
||||
title="See how public pages, account access, and desktop delivery work together."
|
||||
lede="This is the canonical public status surface for HyperTwist access: release targets, checkout/account flow, notices, corresponding source, and protected desktop handoff stay visible in one place instead of being reconstructed from scattered callouts."
|
||||
lede="This is the public status page for HyperTwist access: release targets, account flow, legal details, source links, and desktop handoff stay visible in one place."
|
||||
>
|
||||
<Section
|
||||
title="Current public access status"
|
||||
description="Use this page when rollout language, pricing, download, and legal/distribution follow-through need one authoritative browser-owned status surface."
|
||||
description="Use this page when pricing, download, release, and legal details need one clear browser-owned status surface."
|
||||
>
|
||||
<PublicLaunchStatus title="Current account and release access" />
|
||||
</Section>
|
||||
|
||||
<StepOnlyCardSection
|
||||
title="What must be true before wider public launch"
|
||||
description="These are the bounded rollout checks that keep external launch language tied to real release authority instead of aspiration."
|
||||
description="These are the checks that keep public launch language tied to real release proof instead of aspiration."
|
||||
cards={releaseRolloutChecklist}
|
||||
/>
|
||||
|
||||
<StepOnlyCardSection
|
||||
title="Launch hardening tracks"
|
||||
description="These are the practical work tracks for turning account-gated early access into a production-grade public release lane."
|
||||
description="These are the practical work tracks for turning account-gated early access into a production-grade public release."
|
||||
cards={deploymentReadinessTracks}
|
||||
/>
|
||||
|
||||
|
|
@ -59,7 +59,7 @@ export function LaunchStatusPage() {
|
|||
|
||||
<SupportTopicDirectorySection
|
||||
title="Access support and escalation lanes"
|
||||
description="Access status only helps when the next human lane is explicit too, so these support routes stay attached to the same authority surface."
|
||||
description="Access status only helps when the next human lane is explicit too, so these support routes stay attached to the same status page."
|
||||
topics={supportTopicDirectory}
|
||||
selectedTopicKey="launch-readiness"
|
||||
/>
|
||||
|
|
@ -67,20 +67,20 @@ export function LaunchStatusPage() {
|
|||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
title="Choose the next access move"
|
||||
description="This page should not stop at checklist language. It should also say whether the next honest move is protected desktop access, pricing/provisioning, browser/account follow-through, or notices/source review."
|
||||
description="This page should not stop at checklist language. It should also say whether the next best move is the desktop download, pricing, account follow-through, or notice review."
|
||||
/>
|
||||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
title="Release, notice, and support bundle"
|
||||
description="Launch readiness is only trustworthy when docs, release notes, corresponding source, public notices, and operator contact remain visible as one release bundle."
|
||||
description="Launch readiness is only trustworthy when docs, release notes, source links, public notices, and support contact remain visible as one release bundle."
|
||||
/>
|
||||
|
||||
<SurfaceChoiceGuideSection description="This keeps access status grounded in the real product topology too: public for product truth, protected for account-aware release work, and native for the simulator itself." />
|
||||
|
||||
<BrowserDesktopRealitySection
|
||||
title="Why access authority stays in the browser shell"
|
||||
description="Launch-readiness belongs to the distribution shell because checkout, notices, release references, and account access are browser-owned even while the actual training runtime remains native."
|
||||
title="Why access status lives on the website"
|
||||
description="Launch readiness belongs to the website because checkout, notices, release references, and account access are web-owned even while the actual training runtime remains native."
|
||||
/>
|
||||
</MarketingShell>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -73,24 +73,27 @@ function HomeHeroPanel() {
|
|||
<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 status, support, and pairing so the
|
||||
downloadable can stay focused on the simulator itself.
|
||||
HyperTwist is built for people who want more than a timer. Start on the website
|
||||
when you want the story, plans, release notes, and setup guidance. Move into the
|
||||
desktop app when you want the real experience: fast daily solves, guided recovery,
|
||||
replay study, coaching, and deep 120‑cell or 5D sessions that feel like a place
|
||||
you can keep returning to.
|
||||
</p>
|
||||
<div className="button-row">
|
||||
<HyperTwistButtonLink to="/download">
|
||||
Download desktop app
|
||||
<HyperTwistButtonLink to="/register?next=%2Fapp">
|
||||
Create account
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink to="/download" tone="secondary">
|
||||
Get the desktop app
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink to="/browser" tone="ghost">
|
||||
Why keep the web version?
|
||||
See what the website is for
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink to="/help" tone="ghost">
|
||||
Open help center
|
||||
Explore the help center
|
||||
</HyperTwistButtonLink>
|
||||
<HyperTwistButtonLink to="/app" tone="ghost">
|
||||
Open operator dashboard
|
||||
Open your dashboard
|
||||
</HyperTwistButtonLink>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -101,12 +104,12 @@ function HomeHeroPanel() {
|
|||
className="hero-visual-card__image"
|
||||
/>
|
||||
<p className="hero-visual-card__caption">
|
||||
Browser shell for access, rollout, and support. Native Unreal runtime for the simulator.
|
||||
One product, two roles: a polished front door on the web and a far deeper training room on desktop.
|
||||
</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 headset and controller support as a native device-validation track, not as a browser feature claim.</li>
|
||||
<li>Use the website for pricing, docs, release notes, legal details, and private downloads.</li>
|
||||
<li>Use the desktop app for real cube-state workflows, replay review, and higher-dimensional maps.</li>
|
||||
<li>Use keyboard and mouse today, with XR kept honest until packaged device proof is complete.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -126,7 +129,7 @@ function HomeShippingNowSection() {
|
|||
return (
|
||||
<Section
|
||||
title="What ships now"
|
||||
description="The public site only describes current product truth or explicitly marked retained/spec-only branches."
|
||||
description="Every item here is already part of the product today."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{shippingNowCards.map((item) => (
|
||||
|
|
@ -144,7 +147,7 @@ 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."
|
||||
description="HyperTwist combines training depth, higher-dimensional study, and a clean access experience across web and desktop."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{capabilityPillars.map((pillar) => (
|
||||
|
|
@ -161,8 +164,8 @@ function HomeCapabilityPillarsSection() {
|
|||
function HomeRoadmapHonestySection() {
|
||||
return (
|
||||
<Section
|
||||
title="Current product boundaries"
|
||||
description="Important limits stay visible in plain product language instead of being blurred into vague claims."
|
||||
title="What to expect from web and desktop"
|
||||
description="Clear expectations make the product easier to trust."
|
||||
>
|
||||
<div className="split-grid">
|
||||
{roadmapHonestyCards.map((item) => (
|
||||
|
|
@ -178,30 +181,30 @@ function HomeRoadmapHonestySection() {
|
|||
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."
|
||||
title="How HyperTwist comes together"
|
||||
description="Start on the website. Train in the desktop app. Both parts matter, but each one exists to do a different job well."
|
||||
>
|
||||
<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 status, desktop pairing, notices, and operator state.</p>
|
||||
<h3>Website account and dashboard</h3>
|
||||
<p>Signed-in web access for release status, desktop pairing, notices, and account management.</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 guidance for the native Unreal build, with legal linkage already wired in.</p>
|
||||
<h3>Desktop download and release path</h3>
|
||||
<p>Download guidance for the native Unreal build, with release notes and notices kept close to the build itself.</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>
|
||||
<h3>Plans, checkout, and notices</h3>
|
||||
<p>Pricing, checkout, and public notices stay visible together so buying and download decisions are easy to understand.</p>
|
||||
<Link to="/open-source-notices" className="inline-link">
|
||||
Review notices <ArrowRight size={15} />
|
||||
</Link>
|
||||
|
|
@ -215,7 +218,7 @@ 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."
|
||||
description="This is the real path from curiosity into the actual simulator, without pretending the website already replaced the desktop app."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorManualTracks.slice(0, 3).map((track) => (
|
||||
|
|
@ -263,14 +266,14 @@ function ResourcesFinderSection({
|
|||
onQueryChange: (value: string) => void
|
||||
}) {
|
||||
return (
|
||||
<Section title="Resource finder" description="Search the public resource categories that matter to operators and buyers.">
|
||||
<Section title="Resource finder" description="Search the guides, references, and training materials that matter to players, teams, 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..."
|
||||
placeholder="Search training, downloads, notices..."
|
||||
/>
|
||||
<div className="card-grid top-gap">
|
||||
{filteredCollections.map((collection) => (
|
||||
|
|
@ -295,42 +298,42 @@ function ResourcesDirectRoutesSection() {
|
|||
<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>
|
||||
<p>The public explanation of what the web experience is for, where the desktop app goes deeper, and when to move into it.</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 status.</p>
|
||||
<p>One public page for puzzle families, controls, higher-dimensional depth, and what ships today.</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>
|
||||
<p>Knowledge-base style guidance for onboarding, controls, release handoff, and practical 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>
|
||||
<p>The clearest first-session path from website account setup into the downloadable app.</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 early access, release authority, and rollout follow-through.</p>
|
||||
<p>The public checklist for early access, release readiness, and current availability.</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>
|
||||
<p>Product-facing documentation, controls, setup, and deeper 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>
|
||||
<p>Contact, account help, download help, and setup questions.</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>
|
||||
<p>The direct contact route for access, desktop-app issues, pricing, and team planning.</p>
|
||||
</Link>
|
||||
<Link to="/changelog" className="feature-band__card feature-band__card--link">
|
||||
<Sparkles size={22} />
|
||||
|
|
@ -345,8 +348,8 @@ function ResourcesDirectRoutesSection() {
|
|||
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."
|
||||
title="Training and team playbooks"
|
||||
description="These are the practical patterns that matter once a player, team, or studio moves from curiosity into real use."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorPlaybooks.map((playbook) => (
|
||||
|
|
@ -367,8 +370,8 @@ function ResourcesOperatorPlaybooksSection() {
|
|||
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."
|
||||
title="Release snapshot"
|
||||
description="The public site stays easier to trust when account access, package proof, and legal guidance stay clearly separated."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deploymentReadinessTracks.map((track) => (
|
||||
|
|
@ -393,40 +396,40 @@ export function HomeLanding() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist"
|
||||
description="HyperTwist is a native cube and hypercube training environment for recognition, replay, coaching, higher-dimensional runtime ownership, and desktop-first operator workflows."
|
||||
description="HyperTwist is a desktop-first cube and hypercube training environment for recognition, replay, coaching, and higher-dimensional puzzle study."
|
||||
canonicalPath="/"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Desktop-first simulator. Browser-first access and rollout."
|
||||
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 status, 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."
|
||||
eyebrow="Desktop-first simulator. Website-first access."
|
||||
title="One serious home for classic cubes, 120‑cell, 5D, and deep daily practice."
|
||||
lede="HyperTwist gives you a polished website for account access, plans, guides, and release updates, then hands you into a much deeper desktop app for recognition, replay, guided training, and higher-dimensional puzzle study."
|
||||
>
|
||||
<HomeHeroPanel />
|
||||
|
||||
<Section
|
||||
title="Current public site status"
|
||||
description="The homepage explains the current account-gated access model that governs release, download, and pricing paths."
|
||||
title="Current access snapshot"
|
||||
description="See what is available right now before you decide whether to create an account, compare plans, or download the app."
|
||||
>
|
||||
<PublicLaunchStatus title="Public website and release access" />
|
||||
</Section>
|
||||
|
||||
<BulletCardSection
|
||||
title="Why the downloadable remains the main product"
|
||||
description="The homepage should answer this immediately instead of leaving it hidden inside support or legal copy."
|
||||
title="Why the desktop app does the heavy lifting"
|
||||
description="This is the honest answer to the obvious question: why the downloadable remains the strongest, richest way to experience HyperTwist."
|
||||
cards={browserLimitCards}
|
||||
/>
|
||||
|
||||
<BrowserDesktopRealitySection />
|
||||
|
||||
<BrowserDesktopDecisionFaqSection
|
||||
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."
|
||||
description="First-time visitors should not have to decode the split themselves. These answers explain what the website is for and why the desktop app stays central."
|
||||
/>
|
||||
|
||||
<HomeShippingNowSection />
|
||||
|
||||
<BulletCardSection
|
||||
title="Puzzle families and study lanes"
|
||||
description="HyperTwist is broader than one timer or one browser widget. These are the concrete practice and puzzle lanes the product already owns."
|
||||
description="HyperTwist is broader than one timer or one browser widget. These are the real practice and puzzle experiences already waiting inside the product."
|
||||
cards={puzzleCatalogCards}
|
||||
/>
|
||||
|
||||
|
|
@ -434,7 +437,7 @@ export function HomeLanding() {
|
|||
|
||||
<BulletCardSection
|
||||
title="Who HyperTwist is for"
|
||||
description="The browser and downloadable work together differently depending on whether the next job is practice, rollout, or higher-dimensional study."
|
||||
description="The website and desktop app work together differently depending on whether the next job is practice, team setup, or higher-dimensional study."
|
||||
cards={audienceFitCards}
|
||||
/>
|
||||
|
||||
|
|
@ -444,12 +447,12 @@ export function HomeLanding() {
|
|||
|
||||
<PublicManualRouteAtlasSection
|
||||
title="Which public page should you open next?"
|
||||
description="The public website is a real product surface, not one generic brochure page. This atlas keeps the strongest next manual routes visible from the homepage."
|
||||
description="The website is more than a landing page. This atlas points you to the next guide, plan, or download step that fits what you want to do."
|
||||
limit={8}
|
||||
/>
|
||||
|
||||
<DownloadableOperatorManualSection
|
||||
description="The homepage now also exposes a same-origin offline manual so the desktop-first product story, first-session path, and current runtime boundary can leave the browser cleanly when needed."
|
||||
description="The homepage also exposes an offline guide so the desktop-first product story, first-session path, and current runtime boundary can leave the browser cleanly when needed."
|
||||
/>
|
||||
|
||||
<PublicPackagedDesktopProofSection
|
||||
|
|
@ -462,16 +465,16 @@ export function HomeLanding() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The homepage should not stop at proof. It should also say whether the next useful move is protected desktop access, pricing, browser/account continuity, or notices/source follow-through."
|
||||
description="The homepage should not stop at explanation alone. It should also show whether your next best move is to create an account, compare plans, download the app, or review the release details."
|
||||
/>
|
||||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The homepage is part of the real release lane, so it keeps docs, release notes, source, notices, and operator support references bundled instead of scattering them behind later pages."
|
||||
description="The homepage is part of the real release story, so it keeps docs, release notes, source links, notices, and support references bundled instead of scattering them behind later pages."
|
||||
/>
|
||||
|
||||
<BrowserAuthMethodsSection
|
||||
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."
|
||||
description="The homepage also makes the available sign-in methods visible before you commit to signup, pricing, or downloads."
|
||||
/>
|
||||
|
||||
<HomeFirstSessionSection />
|
||||
|
|
@ -484,11 +487,11 @@ export function HomeLanding() {
|
|||
|
||||
<OperatorDesktopQuickstartSection title="What the first serious session should look like" />
|
||||
|
||||
<SurfaceChoiceGuideSection description="This is the shortest practical answer to the browser-versus-desktop question: stay public for release context, move protected for account-aware access, and move native for the actual simulator." />
|
||||
<SurfaceChoiceGuideSection description="This is the shortest practical answer to the website-versus-desktop question: stay public for release context, move signed-in for account-aware access, and move native for the actual simulator." />
|
||||
|
||||
<Section
|
||||
title="Current surface authority map"
|
||||
description="HyperTwist really has five distinct surfaces today. This shared map keeps the public site, embedded browser shell, protected dashboard, desktop runtime, and future browser-client branch from being blurred together."
|
||||
title="Where each part of HyperTwist lives"
|
||||
description="HyperTwist spans a website, a signed-in dashboard, in-app web tools, and a desktop simulator. This map keeps each part clear."
|
||||
>
|
||||
<ProductSurfaceMatrix rows={productSurfaceMatrixRows} />
|
||||
</Section>
|
||||
|
|
@ -504,13 +507,13 @@ export function AboutPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="About HyperTwist"
|
||||
description="Learn why HyperTwist exists, how it treats higher-dimensional training as first-class work, and why the public site stays honest about desktop-first simulator truth."
|
||||
description="Learn why HyperTwist exists, how it treats higher-dimensional training as first-class work, and why the website stays honest about a desktop-first simulator."
|
||||
canonicalPath="/about"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Why HyperTwist exists"
|
||||
title="A training stack serious enough for higher-dimensional cubing."
|
||||
lede="HyperTwist exists because cubers deserve one coherent system for physical recognition, explanation, practice, replay, analytics, serious higher-dimensional runtime ownership, and an operator shell that does not blur account access with simulator execution."
|
||||
title="A training stack built for people who want more than a timer."
|
||||
lede="HyperTwist exists because cubers deserve one coherent system for practice, replay, explanation, recognition-assisted recovery, and serious higher-dimensional exploration without stitching together five different tools."
|
||||
>
|
||||
<Section title="Mission">
|
||||
<div className="card">
|
||||
|
|
@ -523,14 +526,14 @@ export function AboutPage() {
|
|||
<BrowserDesktopRealitySection />
|
||||
|
||||
<Section
|
||||
title="Why the web surface remains necessary"
|
||||
description="Keeping the website does not weaken the desktop-first thesis. It keeps public distribution, release, and operator-governance work outside the simulator proper."
|
||||
title="Why the website still matters"
|
||||
description="Keeping the website does not weaken the desktop-first vision. It gives HyperTwist a clean place for accounts, plans, release updates, downloads, and a product story people can actually understand before they install."
|
||||
>
|
||||
<DeliverySurfaceResponsibilitiesGrid />
|
||||
</Section>
|
||||
|
||||
<BrowserDesktopDecisionFaqSection
|
||||
description="The about page should also answer the recurring boundary questions directly: what the browser can do, what it should not claim, and why keeping both surfaces is the more trustworthy product choice."
|
||||
description="The about page should answer the recurring web-versus-desktop questions directly: what the website can do, what it should not claim, and why both parts exist."
|
||||
/>
|
||||
|
||||
<Section title="What makes the product different">
|
||||
|
|
@ -538,7 +541,7 @@ export function AboutPage() {
|
|||
<article className="card">
|
||||
<Boxes size={22} />
|
||||
<h3>It treats higher-dimensional puzzles as first-class work</h3>
|
||||
<p>120-cell and 5D runtime ownership are not hand-wavy aspirations. They are part of the current product truth.</p>
|
||||
<p><span className="no-break">120‑cell</span> and 5D study are not hand-wavy aspirations. They are part of the current product.</p>
|
||||
</article>
|
||||
<article className="card">
|
||||
<BookOpenText size={22} />
|
||||
|
|
@ -547,8 +550,8 @@ export function AboutPage() {
|
|||
</article>
|
||||
<article className="card">
|
||||
<MonitorCog size={22} />
|
||||
<h3>It separates browser shell from simulator truth</h3>
|
||||
<p>The public web surface helps operators access the product without pretending the browser already replaces the desktop runtime.</p>
|
||||
<h3>It keeps the website and simulator honest</h3>
|
||||
<p>The public website helps people access the product without pretending the browser already replaces the desktop app.</p>
|
||||
</article>
|
||||
</div>
|
||||
</Section>
|
||||
|
|
@ -561,13 +564,13 @@ export function AboutPage() {
|
|||
|
||||
<BulletCardSection
|
||||
title="Who the product is built for"
|
||||
description="HyperTwist is not one flat audience product. The browser shell and downloadable serve different needs across these groups."
|
||||
description="HyperTwist is not one flat audience product. The website companion and desktop app serve different needs across these groups."
|
||||
cards={audienceFitCards}
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="How a real HyperTwist session unfolds"
|
||||
description="The product story is strongest when the public site explains the genuine operator path instead of flattening browser access and simulator use into one vague promise."
|
||||
description="The product story is strongest when the public site explains the genuine journey instead of flattening website access and simulator use into one vague promise."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorManualTracks.map((track) => (
|
||||
|
|
@ -586,7 +589,7 @@ export function AboutPage() {
|
|||
|
||||
<StepCardSection
|
||||
title="How the software is actually used"
|
||||
description="These are the practical desktop and operator workflows that already exist behind the product narrative."
|
||||
description="These are the practical desktop and training workflows that already exist behind the product narrative."
|
||||
cards={desktopWorkflowTracks}
|
||||
/>
|
||||
|
||||
|
|
@ -594,7 +597,7 @@ export function AboutPage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The about page should also say what the next honest operator move is, not only why the product exists."
|
||||
description="The about page should also say what the next move is, not only why the product exists."
|
||||
/>
|
||||
|
||||
<InputAndDevicePostureSection
|
||||
|
|
@ -609,7 +612,7 @@ export function AboutPage() {
|
|||
|
||||
<Section
|
||||
title="Release and deployment maturity"
|
||||
description="Production seriousness comes from clean separation between identity, package proof, legal follow-through, and runtime execution."
|
||||
description="Production seriousness comes from clean separation between identity, package proof, legal details, and runtime execution."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{deploymentReadinessTracks.map((track) => (
|
||||
|
|
@ -626,7 +629,7 @@ export function AboutPage() {
|
|||
</Section>
|
||||
|
||||
<Section
|
||||
title="Current public rollout state"
|
||||
title="Current public access state"
|
||||
description="Narrative claims stay more credible when this page also shows live public access status and current packaged desktop proof."
|
||||
>
|
||||
<PublicLaunchStatus title="About-page launch and release access" />
|
||||
|
|
@ -642,7 +645,7 @@ export function AboutPage() {
|
|||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The about page should keep its product narrative tied to the same docs, release, source, notices, and support references that govern real rollout decisions."
|
||||
description="The about page should keep its product narrative tied to the same docs, release notes, source links, legal pages, and support references people need in real use."
|
||||
/>
|
||||
</MarketingShell>
|
||||
</>
|
||||
|
|
@ -660,13 +663,13 @@ export function ResourcesPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist resources"
|
||||
description="Browse product-safe HyperTwist resources for rollout, release notes, documentation, operator onboarding, and higher-dimensional product truth."
|
||||
description="Browse HyperTwist resources for release notes, documentation, onboarding, controls, and higher-dimensional product guidance."
|
||||
canonicalPath="/resources"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Public resources"
|
||||
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."
|
||||
title="Every guide you need to start, train, and grow with HyperTwist."
|
||||
lede="This resource library brings together product guides, onboarding help, release references, higher-dimensional study material, and the practical routes that carry a new player or a whole team from curiosity into real use."
|
||||
>
|
||||
<ResourcesFinderSection
|
||||
query={query}
|
||||
|
|
@ -677,33 +680,33 @@ export function ResourcesPage() {
|
|||
<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."
|
||||
description="The resource portal gets much more useful when it also explains what each public page is for, so teams can move from reading into the exact route that owns the next question."
|
||||
/>
|
||||
|
||||
<DownloadableOperatorManualSection
|
||||
description="Resources now also expose the downloadable manual directly so rollout teams can carry one offline operator reference beside the richer live route atlas."
|
||||
description="Resources now also expose the downloadable manual directly so teams can carry one offline guide beside the richer live route atlas."
|
||||
/>
|
||||
|
||||
<SurfaceChoiceGuideSection description="The resource portal is most useful when it also tells operators where to go next: stay public for references, move protected for release access, and move native for simulator execution." />
|
||||
<SurfaceChoiceGuideSection description="The resource portal is most useful when it also tells people where to go next: stay public for references, move signed-in for release access, and move native for simulator execution." />
|
||||
|
||||
<BrowserDesktopRealitySection />
|
||||
|
||||
<SupportTopicDirectorySection
|
||||
title="Support topic quick routes"
|
||||
description="These topic cards bridge the public resource portal into the concrete help lanes without making operators guess which surface owns the next step."
|
||||
description="These topic cards bridge the public resource portal into concrete help paths without making people guess which part of the product owns the next step."
|
||||
topics={supportTopicDirectory}
|
||||
/>
|
||||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
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."
|
||||
description="Resources are stronger when they do not stop at explanation: this guide says plainly whether the next move is the desktop download, pricing, account access, or release details."
|
||||
/>
|
||||
|
||||
<ResourcesOperatorPlaybooksSection />
|
||||
|
||||
<StepCardSection
|
||||
title="Practical software workflows"
|
||||
description="These cards focus on how the actual software is used today once the browser shell has already done its access and rollout job."
|
||||
description="These cards focus on how the actual software is used today once the website has already done its access job."
|
||||
cards={desktopWorkflowTracks}
|
||||
/>
|
||||
|
||||
|
|
@ -713,7 +716,7 @@ export function ResourcesPage() {
|
|||
/>
|
||||
|
||||
<BulletCardSection
|
||||
title="Advanced operator adjuncts"
|
||||
title="Advanced companion systems"
|
||||
description="The resources portal should also surface the live speech, provider, and continuity families that surround the simulator, so teams do not mistake them for hidden internal-only work."
|
||||
cards={advancedOperatorAdjunctTracks}
|
||||
/>
|
||||
|
|
@ -750,13 +753,13 @@ export function ResourcesPage() {
|
|||
platform={windowsValidationPlatform}
|
||||
actions={[
|
||||
{ to: '/download', label: 'Open download center' },
|
||||
{ to: '/app', label: 'Open operator dashboard' },
|
||||
{ to: '/app', label: 'Open dashboard' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The resources portal is stronger when docs, release notes, corresponding source, notices, and support contact remain visible as part of the same operator reference bundle."
|
||||
description="The resources portal is stronger when docs, release notes, source links, legal pages, and support contact remain visible as part of the same reference bundle."
|
||||
/>
|
||||
</MarketingShell>
|
||||
</>
|
||||
|
|
@ -771,12 +774,12 @@ function GettingStartedPageContent({
|
|||
<>
|
||||
<OperatorDesktopQuickstartSection
|
||||
title="The first serious HyperTwist session"
|
||||
description="This quickstart stays faithful to the current product topology: browser first for identity and release access, desktop first for the actual simulator."
|
||||
description="This quickstart stays faithful to the current product shape: browser first for identity and release access, desktop first for the actual simulator."
|
||||
/>
|
||||
|
||||
<StepCardSection
|
||||
title="Operator path"
|
||||
description="These are the current first-party working stages from public discovery through protected release access and into the native simulator."
|
||||
description="These are the current working stages from public discovery through signed-in access and into the native simulator."
|
||||
cards={operatorManualTracks}
|
||||
/>
|
||||
|
||||
|
|
@ -791,7 +794,7 @@ function GettingStartedPageContent({
|
|||
/>
|
||||
|
||||
<BrowserAuthMethodsSection
|
||||
description="The onboarding path is easier to trust when the shared-auth provider lineup is visible before operators cross into the protected shell."
|
||||
description="The onboarding path is easier to trust when the shared-auth provider lineup is visible before people cross into the signed-in account area."
|
||||
/>
|
||||
|
||||
<Section
|
||||
|
|
@ -843,33 +846,33 @@ function GettingStartedPageContent({
|
|||
|
||||
<RuntimeControlGuideSection
|
||||
title="Runtime control guide"
|
||||
description="This onboarding route should also show practical desktop controls so operators do not have to leave the canonical first-session page just to find the real input and diagnostics guidance."
|
||||
description="This onboarding route should also show practical desktop controls so people do not have to leave the canonical first-session page just to find the real input and diagnostics guidance."
|
||||
/>
|
||||
|
||||
<SurfaceChoiceGuideSection description="Onboarding is smoother when the right next surface is explicit too: public for orientation, protected for entitled access, and desktop for simulator execution." />
|
||||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="This onboarding lane should also tell operators whether the next honest move is protected desktop access, pricing/provisioning, browser/account continuity, or notices/source follow-through."
|
||||
description="This onboarding lane should also tell people whether the next move is the desktop download, pricing, account access, or the release details around it."
|
||||
/>
|
||||
|
||||
<PublicPackagedDesktopProofSection
|
||||
platform={windowsValidationPlatform}
|
||||
actions={[
|
||||
{ to: '/download', label: 'Open download center' },
|
||||
{ to: '/app', label: 'Open operator dashboard' },
|
||||
]}
|
||||
/>
|
||||
actions={[
|
||||
{ to: '/download', label: 'Open download center' },
|
||||
{ to: '/app', label: 'Open dashboard' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<SupportTopicDirectorySection
|
||||
title="Need help on the way in?"
|
||||
description="Use these quick routes when the next onboarding step turns into a launch, access, or rollout issue."
|
||||
description="Use these quick routes when the next onboarding step turns into a launch, access, or release issue."
|
||||
topics={supportTopicDirectory}
|
||||
/>
|
||||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="A professional onboarding page should keep docs, release notes, corresponding source, notices, and operator support visible as part of the same release story."
|
||||
description="A professional onboarding page should keep docs, release notes, source links, legal pages, and support visible as part of the same release story."
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
|
@ -882,13 +885,13 @@ export function GettingStartedPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="Getting started with HyperTwist"
|
||||
description="Follow the real first-session path in HyperTwist: browser account access, protected release, desktop pairing, first launch, and current simulator boundary truth."
|
||||
description="Follow the real first-session path in HyperTwist: browser account access, signed-in release access, desktop pairing, first launch, and current simulator boundary truth."
|
||||
canonicalPath="/getting-started"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Operator onboarding"
|
||||
title="Start in the browser. Train in the desktop runtime."
|
||||
lede="This page is the shortest complete operator path through the current product: resolve access, pair the installed runtime safely, verify the first session, and keep the real XR/controller boundary visible."
|
||||
eyebrow="Getting started"
|
||||
title="Start on the website. Train in the desktop app."
|
||||
lede="This page gives you the cleanest path into HyperTwist: get access, pair your install, verify the first session, and understand exactly what is ready today."
|
||||
>
|
||||
<GettingStartedPageContent
|
||||
releaseManifest={releaseManifest}
|
||||
|
|
@ -911,20 +914,20 @@ export function DocsPage() {
|
|||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Documentation"
|
||||
title="HyperTwist documentation stays capability-accurate."
|
||||
lede="The public docs surface now functions as a real product manual: current feature truth, browser-versus-downloadable boundaries, first-session guidance, control truth, higher-dimensional runtime state, and release-safe distribution guidance."
|
||||
title="The complete HyperTwist manual."
|
||||
lede="This manual covers what HyperTwist does today, how the website and desktop app work together, how the controls behave, and how to move from first launch into real training."
|
||||
>
|
||||
<PrincipleCardSection title="Public documentation lanes" cards={publicDocumentationPrinciples} />
|
||||
<PrincipleCardSection title="Documentation principles" cards={publicDocumentationPrinciples} />
|
||||
|
||||
<Section
|
||||
title="Manual entry points"
|
||||
description="The docs surface is now only one part of the public manual family. These entry points help operators choose the right reading depth quickly."
|
||||
description="These entry points help you choose the right reading depth quickly, whether you need a quick answer or a deeper manual pass."
|
||||
>
|
||||
<div className="feature-band">
|
||||
<Link to="/browser" className="feature-band__card feature-band__card--link">
|
||||
<MonitorCog size={22} />
|
||||
<h3>Browser guide</h3>
|
||||
<p>Open the public explanation of the web version, protected dashboard, and downloadable boundary.</p>
|
||||
<p>Open the public explanation of the website companion, account dashboard, and desktop app.</p>
|
||||
</Link>
|
||||
<Link to="/help" className="feature-band__card feature-band__card--link">
|
||||
<BookOpenText size={22} />
|
||||
|
|
@ -941,11 +944,11 @@ export function DocsPage() {
|
|||
|
||||
<PublicManualRouteAtlasSection
|
||||
title="Documentation and route atlas"
|
||||
description="The manual is easier to use when it also says which public route owns which question, so operators do not have to infer structure from navigation labels alone."
|
||||
description="The manual is easier to use when it also says which public route owns which question, so people do not have to infer structure from navigation labels alone."
|
||||
/>
|
||||
|
||||
<DownloadableOperatorManualSection
|
||||
description="The docs lane now also provides a same-origin offline manual asset for operators who want the current route atlas, first-session flow, and runtime boundary in one downloadable reference."
|
||||
description="The docs page now also provides a same-origin offline manual asset for people who want the current route atlas, first-session flow, and control boundary in one downloadable reference."
|
||||
/>
|
||||
|
||||
<BulletCardSection
|
||||
|
|
@ -955,14 +958,14 @@ export function DocsPage() {
|
|||
/>
|
||||
|
||||
<BulletCardSection
|
||||
title="Advanced operator and continuity adjuncts"
|
||||
description="The public manual is more truthful when it also surfaces the already-live speech, provider, and continuity families that support serious operator work around the simulator."
|
||||
title="Advanced companion and continuity systems"
|
||||
description="The public manual is more truthful when it also surfaces the already-live speech, provider, and continuity families that support deeper work around the simulator."
|
||||
cards={advancedOperatorAdjunctTracks}
|
||||
/>
|
||||
|
||||
<StepCardSection
|
||||
title="Operator manual"
|
||||
description="This is the public-facing manual for how HyperTwist is actually used today: browser first for identity and release access, desktop first for the simulator."
|
||||
title="How HyperTwist is actually used"
|
||||
description="This is the public-facing manual for how HyperTwist is actually used today: website first for access and release updates, desktop first for the simulator."
|
||||
cards={operatorManualTracks}
|
||||
/>
|
||||
|
||||
|
|
@ -981,7 +984,7 @@ export function DocsPage() {
|
|||
<OperatorDesktopQuickstartSection />
|
||||
|
||||
<BrowserAuthMethodsSection
|
||||
description="The public manual now makes the current shared-auth lineup explicit too, so operators do not have to infer provider truth only from the sign-in form buttons."
|
||||
description="The public manual now makes the current sign-in lineup explicit too, so people do not have to infer provider support only from the sign-in form buttons."
|
||||
/>
|
||||
|
||||
<StepCardSection
|
||||
|
|
@ -992,19 +995,19 @@ export function DocsPage() {
|
|||
|
||||
<BulletCardSection
|
||||
title="Issue reporting checklist"
|
||||
description="This is the public-safe reporting packet for account, release, runtime, and rollout issues so support receives higher-signal operator reports."
|
||||
description="This is the public-safe reporting packet for account, release, app, and launch issues so support receives higher-signal reports."
|
||||
cards={issueReportingChecklistCards}
|
||||
/>
|
||||
|
||||
<SupportTopicDirectorySection
|
||||
title="Support topic quick routes"
|
||||
description="These are the public docs entry points for the concrete help lanes HyperTwist already recognizes: launch readiness, operator access, and studio rollout."
|
||||
description="These are the public docs entry points for the concrete help paths HyperTwist already recognizes: launch readiness, account access, and team rollout."
|
||||
topics={supportTopicDirectory}
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Current surface authority map"
|
||||
description="This is the quickest way to understand which live surface owns what today, including the difference between the public website and the embedded browser shell that ships inside the desktop runtime."
|
||||
title="Where each part of HyperTwist lives"
|
||||
description="This is the quickest way to understand which part of HyperTwist owns what today, including the difference between the public website and the in-app web tools that ship with the desktop runtime."
|
||||
>
|
||||
<ProductSurfaceMatrix rows={productSurfaceMatrixRows} />
|
||||
</Section>
|
||||
|
|
@ -1022,7 +1025,7 @@ export function DocsPage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The public manual should not only explain the surface split. It should also tell operators whether the next honest move is pricing, protected browser/account work, protected desktop access, or notices/source follow-through."
|
||||
description="The public manual should not only explain the surface split. It should also tell people whether the next move is pricing, account access, the desktop download, or the release details around it."
|
||||
/>
|
||||
|
||||
<SimulatorManualSection />
|
||||
|
|
@ -1031,7 +1034,7 @@ export function DocsPage() {
|
|||
|
||||
<StepOnlyCardSection
|
||||
title="Deployment readiness manual"
|
||||
description="This is the public-safe checklist for moving from account-gated early access to operator-grade rollout."
|
||||
description="This is the public-safe checklist for moving from account-gated early access to a broader rollout."
|
||||
cards={deploymentReadinessTracks}
|
||||
/>
|
||||
|
||||
|
|
@ -1051,12 +1054,12 @@ export function DocsPage() {
|
|||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="The public manual stays more useful when it ends with the same docs, release, source, notices, and support bundle operators need for real rollout follow-through."
|
||||
description="The public manual stays more useful when it ends with the same docs, release notes, source links, legal pages, and support bundle teams need in real use."
|
||||
/>
|
||||
|
||||
<FaqCardSection
|
||||
title="Common operator questions"
|
||||
description="These answers keep the public manual direct about the current browser shell, downloadable runtime, and bounded XR/settings truth."
|
||||
title="Common questions"
|
||||
description="These answers keep the public manual direct about the current website companion, desktop runtime, and bounded XR/settings truth."
|
||||
cards={supportFaqs}
|
||||
/>
|
||||
|
||||
|
|
@ -1083,16 +1086,16 @@ export function SupportPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist support"
|
||||
description="Get help with HyperTwist rollout, desktop downloads, pricing, legal readiness, and browser-to-desktop operator access."
|
||||
description="Get help with HyperTwist setup, desktop downloads, pricing, legal readiness, and website-to-desktop account access."
|
||||
canonicalPath="/support"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Operator help"
|
||||
title="Support for rollout, downloads, pricing, and browser-to-desktop access."
|
||||
lede="Support is focused on getting operators unstuck without flattening the product topology: browser and release issues stay browser-side, simulator issues stay attached to the downloadable, and rollout or legal questions stay distinct from both."
|
||||
eyebrow="Support"
|
||||
title="Help for access, downloads, setup, and launch."
|
||||
lede="Support is focused on getting people unstuck without flattening the product: website and release issues stay website-side, simulator issues stay attached to the desktop app, and launch questions stay distinct from both."
|
||||
>
|
||||
{selectedSupportTopic ? (
|
||||
<Section title="Selected help lane">
|
||||
<Section title="Selected help topic">
|
||||
<article className="callout">
|
||||
<p className="status-pill status-pill--info">{selectedSupportTopic.title}</p>
|
||||
<p>{selectedSupportTopic.description}</p>
|
||||
|
|
@ -1102,8 +1105,8 @@ export function SupportPage() {
|
|||
|
||||
{selectedSupportTopicKey ? (
|
||||
<SupportTopicDirectorySection
|
||||
title="Selected lane next steps"
|
||||
description="This is the concrete recovery and routing shape for the currently selected support lane."
|
||||
title="Selected topic next steps"
|
||||
description="This is the concrete recovery and routing shape for the currently selected support topic."
|
||||
topics={supportTopicDirectory.filter((topic) => topic.topicKey === selectedSupportTopicKey)}
|
||||
selectedTopicKey={selectedSupportTopicKey}
|
||||
/>
|
||||
|
|
@ -1126,12 +1129,12 @@ export function SupportPage() {
|
|||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Support is faster when the current next move is explicit too: pricing, protected downloads, browser/account follow-through, and notices/source work all stay separated here."
|
||||
description="Support is faster when the current next move is explicit too: pricing, downloads, account follow-through, and release details all stay separated here."
|
||||
/>
|
||||
|
||||
<ReleaseAuthorityBundleSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Support conversations move faster when docs, release notes, source, notices, and operator contact stay visible beside the launch and package evidence."
|
||||
description="Support conversations move faster when docs, release notes, source, notices, and contact stay visible beside the launch and build evidence."
|
||||
/>
|
||||
|
||||
<BrowserAuthMethodsSection
|
||||
|
|
@ -1167,20 +1170,20 @@ export function SupportPage() {
|
|||
</div>
|
||||
</Section>
|
||||
|
||||
<SurfaceChoiceGuideSection description="Most support confusion disappears once the operator picks the right surface first: public for guidance, protected for account-aware release work, desktop for simulator execution." />
|
||||
<SurfaceChoiceGuideSection description="Most support confusion disappears once people pick the right surface first: public for guidance, signed-in for account-aware release work, desktop for simulator execution." />
|
||||
|
||||
<BrowserDesktopRealitySection />
|
||||
|
||||
<Section
|
||||
title="Browser and desktop responsibilities"
|
||||
description="This keeps support conversations anchored to the real live surfaces so operators know whether they need public guidance, protected account access, or native runtime follow-through."
|
||||
description="This keeps support conversations anchored to the real live surfaces so people know whether they need public guidance, signed-in account access, or desktop-app follow-through."
|
||||
>
|
||||
<DeliverySurfaceResponsibilitiesGrid limit={3} />
|
||||
</Section>
|
||||
|
||||
<Section
|
||||
title="Support lanes"
|
||||
description="Support works best when operators know whether they need public guidance, protected browser access, or native desktop follow-through."
|
||||
description="Support works best when people know whether they need public guidance, signed-in website access, or native desktop follow-through."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{operatorPlaybooks.map((playbook) => (
|
||||
|
|
@ -1198,13 +1201,13 @@ export function SupportPage() {
|
|||
|
||||
<BulletCardSection
|
||||
title="Issue reporting checklist"
|
||||
description="These reporting prompts keep support requests concrete and route-aware so rollout, account, package, and runtime issues do not blur together."
|
||||
description="These reporting prompts keep support requests concrete and route-aware so launch, account, package, and runtime issues do not blur together."
|
||||
cards={issueReportingChecklistCards}
|
||||
/>
|
||||
|
||||
<SupportTopicDirectorySection
|
||||
title="Support topic quick routes"
|
||||
description="Use these cards when the issue already has a clear help lane and you want the shortest path through public, protected, and rollout-safe surfaces."
|
||||
description="Use these cards when the issue already has a clear help path and you want the shortest route through public, signed-in, and release-safe surfaces."
|
||||
topics={supportTopicDirectory}
|
||||
selectedTopicKey={selectedSupportTopicKey}
|
||||
/>
|
||||
|
|
@ -1217,7 +1220,7 @@ export function SupportPage() {
|
|||
|
||||
<Section
|
||||
title="Digital delivery workflow"
|
||||
description="Support can move faster when the operator-facing browser shell and the installed desktop runtime are described as one continuous delivery lane."
|
||||
description="Support can move faster when the website and installed desktop app are described as one continuous delivery experience."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{digitalDeliveryCards.map((card) => (
|
||||
|
|
@ -1236,7 +1239,7 @@ export function SupportPage() {
|
|||
|
||||
<Section
|
||||
title="Privacy and compliance boundary"
|
||||
description="Support and rollout guidance should stay clear about what belongs to browser identity, what belongs to the desktop runtime, and what remains legal/compliance follow-through."
|
||||
description="Support and launch guidance should stay clear about what belongs to browser identity, what belongs to the desktop runtime, and what remains legal/compliance follow-through."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{privacyBoundaryCards.map((card) => (
|
||||
|
|
@ -1255,7 +1258,7 @@ export function SupportPage() {
|
|||
|
||||
<Section
|
||||
title="Escalation map"
|
||||
description="This keeps support conversations concrete by separating account, package, runtime, and rollout/compliance problems instead of flattening them together."
|
||||
description="This keeps support conversations concrete by separating account, package, runtime, and launch/compliance problems instead of flattening them together."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{supportEscalationCards.map((card) => (
|
||||
|
|
@ -1283,17 +1286,17 @@ export function ChangelogPage() {
|
|||
<>
|
||||
<SiteMetadata
|
||||
title="HyperTwist release notes"
|
||||
description="Review recent public-facing HyperTwist changes across browser operator access, package hardening, diagnostics, and distribution readiness."
|
||||
description="Review recent public-facing HyperTwist changes across website access, desktop improvements, packaging, and release readiness."
|
||||
canonicalPath="/changelog"
|
||||
/>
|
||||
<MarketingShell
|
||||
eyebrow="Release notes"
|
||||
title="Release notes you can actually use for rollout."
|
||||
lede="This page combines grouped release packets with the chronological feed so operators can understand what changed in the browser shell, what changed in the downloadable, and what changed in release or legal follow-through without digging through internal planning notes."
|
||||
title="Release notes for players, teams, and studios."
|
||||
lede="This page combines grouped release stories with the chronological feed so you can see what changed on the website, what changed in the desktop app, and what changed in release delivery without digging through internal notes."
|
||||
>
|
||||
<Section
|
||||
title="Recent release packets"
|
||||
description="These grouped cards are the higher-signal public version of the release story: what changed, where it changed, and why it matters operationally."
|
||||
description="These grouped cards are the higher-signal public version of the release story: what changed, where it changed, and why it matters."
|
||||
>
|
||||
<div className="timeline">
|
||||
{releasePacketCards.map((entry) => (
|
||||
|
|
@ -1313,7 +1316,7 @@ export function ChangelogPage() {
|
|||
|
||||
<Section
|
||||
title="Detailed chronological feed"
|
||||
description="The detailed feed keeps route-by-route and surface-by-surface chronology visible once you need the finer-grained public history."
|
||||
description="The detailed feed keeps the finer-grained public history visible once you want the full story behind the latest updates."
|
||||
>
|
||||
<div className="timeline">
|
||||
{changelogEntries.map((entry) => (
|
||||
|
|
@ -1328,7 +1331,7 @@ export function ChangelogPage() {
|
|||
|
||||
<Section
|
||||
title="How to read the release feed"
|
||||
description="HyperTwist release notes stay useful when they distinguish simulator/runtime work, browser-operator work, and distribution/legal hardening instead of collapsing them together."
|
||||
description="HyperTwist release notes stay useful when they distinguish desktop-app work, website work, and distribution follow-through instead of collapsing everything together."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{releaseStoryCards.map((card) => (
|
||||
|
|
@ -1345,16 +1348,16 @@ export function ChangelogPage() {
|
|||
</div>
|
||||
</Section>
|
||||
|
||||
<SurfaceChoiceGuideSection description="Release notes are easier to use when the next surface is explicit too: public for change context, protected for release follow-through, and desktop for the actual runtime under discussion." />
|
||||
<SurfaceChoiceGuideSection description="Release notes are easier to use when the next surface is explicit too: public for change context, signed-in access for account-aware release details, and desktop for the actual runtime under discussion." />
|
||||
|
||||
<PublicReleaseDecisionGuideSection
|
||||
releaseManifest={releaseManifest}
|
||||
description="Release notes become more actionable when they also say whether the next move is protected desktop access, pricing/provisioning, browser/account continuity, or notices/source follow-through."
|
||||
description="Release notes become more actionable when they also say whether the next move is the desktop download, pricing, account continuity, or the release details around it."
|
||||
/>
|
||||
|
||||
<Section
|
||||
title="Release rollout checklist"
|
||||
description="Use the public release notes as an operator tool, not just a chronology. This keeps each change tied to package proof, entitlement status, and distribution follow-through."
|
||||
description="Use the public release notes as a real rollout tool, not just a chronology. This keeps each change tied to build proof, account access, and distribution follow-through."
|
||||
>
|
||||
<div className="card-grid">
|
||||
{releaseRolloutChecklist.map((card) => (
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
"path": "/",
|
||||
"label": "Home",
|
||||
"loaderTitle": "Loading HyperTwist...",
|
||||
"loaderEyebrow": "Public release shell",
|
||||
"loaderDescription": "Loading the public product story, current launch status, and browser-versus-desktop authority map.",
|
||||
"loaderEyebrow": "Public website",
|
||||
"loaderDescription": "Loading the full HyperTwist story, the current access snapshot, and the easiest path into the desktop app.",
|
||||
"nav": true,
|
||||
"footer": false,
|
||||
"crawlable": true
|
||||
|
|
@ -13,8 +13,8 @@
|
|||
"path": "/browser",
|
||||
"label": "Browser",
|
||||
"loaderTitle": "Browser access guide",
|
||||
"loaderEyebrow": "Web version role",
|
||||
"loaderDescription": "Loading the browser-versus-downloadable boundary, protected dashboard handoff, and the current web-lane purpose.",
|
||||
"loaderEyebrow": "Browser companion",
|
||||
"loaderDescription": "Loading how the website, account access, and desktop app work together.",
|
||||
"nav": true,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
"label": "Features",
|
||||
"loaderTitle": "Feature atlas",
|
||||
"loaderEyebrow": "Capability atlas",
|
||||
"loaderDescription": "Loading the shipped feature map, higher-dimensional runtime state, and explicit desktop-first boundaries.",
|
||||
"loaderDescription": "Loading the full feature lineup across classic cubes, 120‑cell, 5D, replay, coaching, and guided training.",
|
||||
"nav": true,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
"label": "About",
|
||||
"loaderTitle": "About HyperTwist",
|
||||
"loaderEyebrow": "Product narrative",
|
||||
"loaderDescription": "Loading the mission, higher-dimensional seriousness, and current control-boundary truth behind the product.",
|
||||
"loaderDescription": "Loading the HyperTwist story, the training vision, and why the product spans both web and desktop.",
|
||||
"nav": true,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -43,8 +43,8 @@
|
|||
"path": "/resources",
|
||||
"label": "Resources",
|
||||
"loaderTitle": "Resources",
|
||||
"loaderEyebrow": "Public reference portal",
|
||||
"loaderDescription": "Loading rollout-safe resources, simulator guidance, and current higher-dimensional operator references.",
|
||||
"loaderEyebrow": "Resource library",
|
||||
"loaderDescription": "Loading guides, manuals, and training references for players, teams, and studios.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -53,8 +53,8 @@
|
|||
"path": "/help",
|
||||
"label": "Help Center",
|
||||
"loaderTitle": "Help center",
|
||||
"loaderEyebrow": "Operator knowledge base",
|
||||
"loaderDescription": "Loading onboarding, control truth, browser-to-desktop guidance, and rollout-safe troubleshooting.",
|
||||
"loaderEyebrow": "Help center",
|
||||
"loaderDescription": "Loading setup, controls, downloads, and practical troubleshooting.",
|
||||
"nav": true,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -63,8 +63,8 @@
|
|||
"path": "/getting-started",
|
||||
"label": "Getting Started",
|
||||
"loaderTitle": "Getting started",
|
||||
"loaderEyebrow": "Operator onboarding",
|
||||
"loaderDescription": "Loading the first-session quickstart, browser-to-desktop pairing flow, and the current desktop-first runtime boundary.",
|
||||
"loaderEyebrow": "Getting started",
|
||||
"loaderDescription": "Loading the first-session path from account setup to the desktop app.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -73,8 +73,8 @@
|
|||
"path": "/launch-status",
|
||||
"label": "Launch Status",
|
||||
"loaderTitle": "Launch status",
|
||||
"loaderEyebrow": "Launch authority",
|
||||
"loaderDescription": "Loading the canonical public launch-readiness checklist, release status, and early-access authority map.",
|
||||
"loaderEyebrow": "Access status",
|
||||
"loaderDescription": "Loading current availability, release progress, and access options.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -84,7 +84,7 @@
|
|||
"label": "Docs",
|
||||
"loaderTitle": "Documentation",
|
||||
"loaderEyebrow": "Public manual",
|
||||
"loaderDescription": "Loading the feature-registry-backed manual, rollout doctrine, and browser-versus-desktop usage guide.",
|
||||
"loaderDescription": "Loading the complete HyperTwist manual, controls, and setup guidance.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -93,8 +93,8 @@
|
|||
"path": "/support",
|
||||
"label": "Support",
|
||||
"loaderTitle": "Support",
|
||||
"loaderEyebrow": "Operator help lane",
|
||||
"loaderDescription": "Loading rollout guidance, account and entitlement help, and browser-to-desktop escalation routes.",
|
||||
"loaderEyebrow": "Support",
|
||||
"loaderDescription": "Loading help routes for account access, downloads, pricing, and setup.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -103,8 +103,8 @@
|
|||
"path": "/contact",
|
||||
"label": "Contact",
|
||||
"loaderTitle": "Contact HyperTwist",
|
||||
"loaderEyebrow": "Human help lane",
|
||||
"loaderDescription": "Loading operator contact paths, escalation packets, and release-aware support follow-through.",
|
||||
"loaderEyebrow": "Contact",
|
||||
"loaderDescription": "Loading direct contact routes for support, teams, and release questions.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -114,7 +114,7 @@
|
|||
"label": "Release notes",
|
||||
"loaderTitle": "Release notes",
|
||||
"loaderEyebrow": "Public release history",
|
||||
"loaderDescription": "Loading recent browser, package, control-boundary, and rollout changes from the active shipping lane.",
|
||||
"loaderDescription": "Loading recent product updates across the website, desktop app, controls, and releases.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -123,8 +123,8 @@
|
|||
"path": "/pricing",
|
||||
"label": "Pricing",
|
||||
"loaderTitle": "Pricing",
|
||||
"loaderEyebrow": "Paddle-ready plans",
|
||||
"loaderDescription": "Loading plan access, public launch readiness, and the current desktop-first distribution model.",
|
||||
"loaderEyebrow": "Pricing",
|
||||
"loaderDescription": "Loading plans, checkout status, and what each plan unlocks.",
|
||||
"nav": true,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -134,7 +134,7 @@
|
|||
"label": "Download",
|
||||
"loaderTitle": "Download center",
|
||||
"loaderEyebrow": "Desktop distribution",
|
||||
"loaderDescription": "Loading the current release manifest status, package proof, and browser-to-desktop pairing guidance.",
|
||||
"loaderDescription": "Loading release availability, desktop build details, and install guidance.",
|
||||
"nav": true,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -144,7 +144,7 @@
|
|||
"label": "Open Source Notices",
|
||||
"loaderTitle": "Open source notices",
|
||||
"loaderEyebrow": "Distribution references",
|
||||
"loaderDescription": "Loading notices, corresponding-source status, and public legal follow-through for downloadable builds.",
|
||||
"loaderDescription": "Loading open-source notices, source links, and release-related legal information.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -153,8 +153,8 @@
|
|||
"path": "/privacy",
|
||||
"label": "Privacy",
|
||||
"loaderTitle": "Privacy",
|
||||
"loaderEyebrow": "Public policy surface",
|
||||
"loaderDescription": "Loading the browser-account privacy model, desktop-link boundary, and distribution-safe data handling notes.",
|
||||
"loaderEyebrow": "Privacy",
|
||||
"loaderDescription": "Loading how account data, billing, and the desktop app stay clearly separated.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -163,8 +163,8 @@
|
|||
"path": "/terms",
|
||||
"label": "Terms",
|
||||
"loaderTitle": "Terms",
|
||||
"loaderEyebrow": "Public policy surface",
|
||||
"loaderDescription": "Loading access terms, protected download boundaries, and the current desktop-first simulator model.",
|
||||
"loaderEyebrow": "Terms",
|
||||
"loaderDescription": "Loading access terms for accounts, downloads, and desktop use.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -174,7 +174,7 @@
|
|||
"label": "Shipping & Payment",
|
||||
"loaderTitle": "Shipping & payment",
|
||||
"loaderEyebrow": "Distribution policy",
|
||||
"loaderDescription": "Loading the digital-delivery model, Paddle-ready billing, and protected release follow-through.",
|
||||
"loaderDescription": "Loading digital delivery, billing, and purchase guidance.",
|
||||
"nav": false,
|
||||
"footer": true,
|
||||
"crawlable": true
|
||||
|
|
@ -183,8 +183,8 @@
|
|||
"path": "/login",
|
||||
"label": "Log in",
|
||||
"loaderTitle": "Log in",
|
||||
"loaderEyebrow": "Browser account access",
|
||||
"loaderDescription": "Preparing sign-in, safe next-step routing, and protected release continuity before the operator shell opens.",
|
||||
"loaderEyebrow": "Account access",
|
||||
"loaderDescription": "Preparing sign-in, account access, and the path into your dashboard and downloads.",
|
||||
"nav": false,
|
||||
"footer": false,
|
||||
"crawlable": false
|
||||
|
|
@ -193,8 +193,8 @@
|
|||
"path": "/register",
|
||||
"label": "Create account",
|
||||
"loaderTitle": "Create account",
|
||||
"loaderEyebrow": "Browser account access",
|
||||
"loaderDescription": "Preparing account creation, protected release follow-through, and browser-to-desktop continuity for first launch.",
|
||||
"loaderEyebrow": "Create account",
|
||||
"loaderDescription": "Preparing account creation, dashboard access, and your first desktop download.",
|
||||
"nav": false,
|
||||
"footer": false,
|
||||
"crawlable": false
|
||||
|
|
|
|||
|
|
@ -15,39 +15,39 @@ const NoticesPage = lazy(() => import('../pages/app-pages').then((m) => ({ defau
|
|||
const protectedAppLoaders = {
|
||||
dashboard: {
|
||||
title: 'Loading dashboard...',
|
||||
eyebrow: 'Protected operator shell',
|
||||
eyebrow: 'Signed-in dashboard',
|
||||
description:
|
||||
'Restoring account state, release authority, package proof, and browser-to-desktop pairing inside the signed-in operator lane.',
|
||||
'Restoring your account, release status, desktop pairing, and download readiness.',
|
||||
},
|
||||
downloads: {
|
||||
title: 'Loading downloads...',
|
||||
eyebrow: 'Protected release lane',
|
||||
eyebrow: 'Signed-in downloads',
|
||||
description:
|
||||
'Resolving entitled desktop targets, current package proof, and rollout references before the signed-in download surface opens.',
|
||||
'Preparing your available desktop builds, package checks, and release notes.',
|
||||
},
|
||||
launchStatus: {
|
||||
title: 'Loading launch status...',
|
||||
eyebrow: 'Protected rollout authority',
|
||||
eyebrow: 'Launch status',
|
||||
description:
|
||||
'Restoring the signed-in launch checklist, release references, and packaged proof before protected rollout follow-through resumes.',
|
||||
'Restoring the current release checklist, launch notes, and package proof.',
|
||||
},
|
||||
browserAccess: {
|
||||
title: 'Loading browser access...',
|
||||
eyebrow: 'Protected browser shell',
|
||||
eyebrow: 'Browser companion',
|
||||
description:
|
||||
'Restoring the signed-in browser state, release guidance, and explicit browser-versus-desktop boundaries.',
|
||||
'Restoring the signed-in website experience and the current web-versus-desktop guide.',
|
||||
},
|
||||
account: {
|
||||
title: 'Loading account...',
|
||||
eyebrow: 'Protected account lane',
|
||||
eyebrow: 'Account',
|
||||
description:
|
||||
'Refreshing the signed-in account view, current plan state, and release-access continuity before desktop follow-through resumes.',
|
||||
'Refreshing your plan, access, and release details.',
|
||||
},
|
||||
notices: {
|
||||
title: 'Loading notices...',
|
||||
eyebrow: 'Protected distribution references',
|
||||
eyebrow: 'Release notices',
|
||||
description:
|
||||
'Restoring signed-in notices, corresponding-source references, and download/distribution guidance inside the protected release shell.',
|
||||
'Restoring notices, source links, and release references for the current desktop build.',
|
||||
},
|
||||
} as const
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const brandConfig = {
|
|||
brandName: 'HyperTwist',
|
||||
legalName: 'HyperTwist',
|
||||
domain: 'hypertwist.app',
|
||||
tagline: 'Native cube and hypercube training, from first solve to 120-cell.',
|
||||
tagline: 'Desktop cubing and hypercubing, from first solves to 120‑cell mastery.',
|
||||
contact: {
|
||||
email: readTrimmedEnv('VITE_SUPPORT_EMAIL', 'hello@hypertwist.app'),
|
||||
emailHref: `mailto:${readTrimmedEnv('VITE_SUPPORT_EMAIL', 'hello@hypertwist.app')}`,
|
||||
|
|
@ -55,7 +55,7 @@ export const planCatalog = [
|
|||
price: 'Free',
|
||||
ctaLabel: 'Create account',
|
||||
ctaHref: buildRegisterPath('/app'),
|
||||
notes: 'Create a HyperTwist account to read the operator dashboard, product manual, release notes, and desktop onboarding flow before you subscribe.',
|
||||
notes: 'Create your HyperTwist account to open the dashboard, read the manual, follow release notes, and get ready for your first desktop session.',
|
||||
features: [
|
||||
'Browser account and dashboard access',
|
||||
'Public manual, release notes, and resource center',
|
||||
|
|
@ -69,51 +69,51 @@ export const planCatalog = [
|
|||
price: readTrimmedEnv('VITE_PLAN_PRICE_OPERATOR', 'Launch pricing via Paddle'),
|
||||
ctaLabel: operatorCheckoutUrl ? 'Subscribe with Paddle' : 'Create account for download access',
|
||||
ctaHref: operatorCheckoutUrl || buildProtectedDownloadPath('windows'),
|
||||
notes: 'Desktop-first recognition, replay, training, higher-dimensional runtime ownership, and protected Windows package access for active solvers.',
|
||||
notes: 'Unlock the desktop simulator for recognition, replay review, guided training, and higher-dimensional practice on Windows.',
|
||||
features: [
|
||||
'Protected desktop download access',
|
||||
'Browser-issued desktop-link token handoff',
|
||||
'Recognition, replay, and coaching ownership',
|
||||
'Classic-cube package and validation lane access',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'studio',
|
||||
name: 'Studio',
|
||||
price: readTrimmedEnv('VITE_PLAN_PRICE_STUDIO', 'Contact for launch readiness'),
|
||||
ctaLabel: studioCheckoutUrl ? 'Open studio checkout' : 'Talk to HyperTwist',
|
||||
ctaHref: studioCheckoutUrl || '/contact',
|
||||
notes: 'Higher-dimensional training programs, deployment support, and release/package coordination for studios, educators, and rollout owners.',
|
||||
features: [
|
||||
'Magic120Cell and 5D operator status',
|
||||
'Release planning and deployment coordination',
|
||||
'Desktop distribution and legal-notice readiness',
|
||||
'Custom support for rollout and training programs',
|
||||
'Recognition, replay review, and coaching tools',
|
||||
'Classic-cube training plus verified package delivery',
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'studio',
|
||||
name: 'Studio',
|
||||
price: readTrimmedEnv('VITE_PLAN_PRICE_STUDIO', 'Contact for launch readiness'),
|
||||
ctaLabel: studioCheckoutUrl ? 'Open studio checkout' : 'Talk to HyperTwist',
|
||||
ctaHref: studioCheckoutUrl || '/contact',
|
||||
notes: 'For studios, educators, and advanced teams who want guided onboarding, higher-dimensional programs, and hands-on release support.',
|
||||
features: [
|
||||
'Magic120Cell and 5D training access planning',
|
||||
'Release planning and deployment coordination',
|
||||
'Desktop distribution and notice-readiness support',
|
||||
'Custom onboarding for classes, labs, and training teams',
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
export const downloadTargets = [
|
||||
{
|
||||
platformKey: 'windows',
|
||||
platform: 'Windows',
|
||||
subtitle: 'Primary shipping lane',
|
||||
subtitle: 'Primary desktop release',
|
||||
configured: readBooleanEnv('VITE_WINDOWS_RELEASE_CONFIGURED', 'VITE_WINDOWS_DOWNLOAD_CONFIGURED'),
|
||||
details: 'Current packaged validation is strongest on the Windows Unreal lane.',
|
||||
details: 'The current packaged build and validation proof are strongest on Windows.',
|
||||
},
|
||||
{
|
||||
platformKey: 'macos',
|
||||
platform: 'macOS',
|
||||
subtitle: 'Planned distribution surface',
|
||||
subtitle: 'Planned release',
|
||||
configured: readBooleanEnv('VITE_MAC_RELEASE_CONFIGURED', 'VITE_MAC_DOWNLOAD_CONFIGURED'),
|
||||
details: 'macOS distribution will appear here when a signed desktop build is ready for account-gated delivery.',
|
||||
},
|
||||
{
|
||||
platformKey: 'linux',
|
||||
platform: 'Linux',
|
||||
subtitle: 'Operator-targeted later lane',
|
||||
subtitle: 'Later desktop release',
|
||||
configured: readBooleanEnv('VITE_LINUX_RELEASE_CONFIGURED', 'VITE_LINUX_DOWNLOAD_CONFIGURED'),
|
||||
details: 'Linux distribution will appear here when the operator-targeted package lane is ready.',
|
||||
details: 'Linux distribution will appear here when a supported desktop package is ready.',
|
||||
},
|
||||
] as const satisfies readonly DownloadTarget[]
|
||||
|
||||
|
|
@ -138,4 +138,4 @@ export const launchReadiness = {
|
|||
} as const
|
||||
|
||||
export const paddleReadyDescription =
|
||||
'HyperTwist is a desktop-first training environment for classic cube practice, recognition-assisted reconstruction, replay explanation, coaching, 120-cell and 5D exploration, and browser-based operator access. Delivery is digital-only: public pages explain the product, account routes manage subscription and entitlement, and the downloadable desktop runtime performs the real simulator work. Pricing and checkout are wired for Paddle, while public legal pages keep open-source notices and corresponding-source disclosure attached to shipped downloadable builds.'
|
||||
'Choose a plan, unlock your account, and keep downloads, billing, release notes, and support in one place. Then move into the desktop app for the full HyperTwist simulator, from guided classic-cube practice to serious 120‑cell and 5D sessions.'
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -15,6 +15,22 @@
|
|||
--ht-grid: rgba(148, 201, 255, 0.06);
|
||||
--font-display: "Space Grotesk", "IBM Plex Sans", sans-serif;
|
||||
--font-body: "IBM Plex Sans", system-ui, sans-serif;
|
||||
--brand-logo-default-size: clamp(1.6rem, 1.95vw, 1.9rem);
|
||||
--brand-logo-scale: 3.5;
|
||||
--pui-grad-from: #48d7ff;
|
||||
--pui-grad-mid: #3da8ff;
|
||||
--pui-grad-to: #f7b267;
|
||||
--pui-wave-text: #d9ecff;
|
||||
--pui-wave-text-hover: #f5fbff;
|
||||
--pui-wave-border: rgba(84, 203, 255, 0.28);
|
||||
--pui-wave-border-hover: rgba(84, 203, 255, 0.44);
|
||||
--pui-glow: 0 0 26px rgba(72, 215, 255, 0.22);
|
||||
--pui-glow-strong: 0 0 52px rgba(72, 215, 255, 0.28), 0 0 96px rgba(247, 178, 103, 0.16);
|
||||
--pui-button-dark-fill: linear-gradient(180deg, rgba(10, 18, 34, 0.96), rgba(8, 13, 24, 0.94));
|
||||
--pui-button-dark-popover-fill: linear-gradient(180deg, rgba(9, 16, 28, 0.98), rgba(7, 11, 20, 0.98));
|
||||
--pui-button-dark-border: rgba(84, 203, 255, 0.16);
|
||||
--pui-shimmer-sweep: rgba(255, 255, 255, 0.34);
|
||||
--pui-shimmer-sweep-edge: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
@property --glow-angle {
|
||||
|
|
@ -50,6 +66,20 @@ html[data-theme="light"] {
|
|||
--ht-amber-soft: rgba(200, 104, 15, 0.12);
|
||||
--ht-success: #0f8b56;
|
||||
--ht-grid: rgba(35, 90, 167, 0.08);
|
||||
--pui-grad-from: #0c8dd6;
|
||||
--pui-grad-mid: #2f93f6;
|
||||
--pui-grad-to: #d67a12;
|
||||
--pui-wave-text: #164776;
|
||||
--pui-wave-text-hover: #0b2445;
|
||||
--pui-wave-border: rgba(32, 105, 184, 0.3);
|
||||
--pui-wave-border-hover: rgba(32, 105, 184, 0.48);
|
||||
--pui-glow: 0 0 24px rgba(32, 105, 184, 0.1);
|
||||
--pui-glow-strong: 0 0 48px rgba(32, 105, 184, 0.14), 0 0 80px rgba(214, 122, 18, 0.1);
|
||||
--pui-button-dark-fill: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(246, 250, 255, 0.96));
|
||||
--pui-button-dark-popover-fill: linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(245, 249, 255, 0.98));
|
||||
--pui-button-dark-border: rgba(12, 86, 152, 0.14);
|
||||
--pui-shimmer-sweep: rgba(6, 25, 49, 0.22);
|
||||
--pui-shimmer-sweep-edge: rgba(6, 25, 49, 0.06);
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
@ -96,14 +126,19 @@ img {
|
|||
.site-shell,
|
||||
.app-shell {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
.site-header,
|
||||
.page-main,
|
||||
.site-footer,
|
||||
.app-topbar,
|
||||
.app-main {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.page-main,
|
||||
.site-footer {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
|
|
@ -119,35 +154,81 @@ img {
|
|||
radial-gradient(circle at top, rgba(84, 203, 255, 0.08), transparent 46%),
|
||||
linear-gradient(180deg, rgba(8, 12, 22, 0.82), rgba(10, 16, 30, 0.72));
|
||||
position: sticky;
|
||||
top: 0;
|
||||
top: 0.75rem;
|
||||
z-index: 9999;
|
||||
isolation: isolate;
|
||||
contain: paint;
|
||||
overflow: clip;
|
||||
backface-visibility: hidden;
|
||||
transform: translateZ(0);
|
||||
margin: 0.75rem auto 0;
|
||||
width: min(1260px, calc(100vw - 2rem));
|
||||
border-radius: 1.6rem;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.04),
|
||||
0 22px 60px rgba(0, 0, 0, 0.22);
|
||||
transition: transform 220ms ease, opacity 220ms ease, border-color 220ms ease, box-shadow 220ms ease;
|
||||
transition: transform 220ms ease, opacity 220ms ease, border-color 220ms ease, box-shadow 220ms ease, background 220ms ease;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.site-header--hidden {
|
||||
transform: translateY(calc(-100% - 1.25rem));
|
||||
opacity: 0;
|
||||
.site-header::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
pointer-events: none;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(84, 203, 255, 0.18), transparent 30%),
|
||||
linear-gradient(315deg, rgba(247, 178, 103, 0.16), transparent 34%);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.site-header > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.site-header--hidden {
|
||||
transform: translate3d(0, calc(-100% - 3rem), 0) scale(0.985);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
max-height: 0;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-top: 0;
|
||||
border-width: 0;
|
||||
clip-path: inset(0 0 100% 0 round 1.6rem);
|
||||
backdrop-filter: none;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
overflow: hidden;
|
||||
content-visibility: hidden;
|
||||
}
|
||||
|
||||
.site-header--hidden::before {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.9rem;
|
||||
flex: 1 1 24rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.brand-mark__image {
|
||||
width: clamp(6.5rem, 9vw, 9.72rem);
|
||||
height: clamp(6.5rem, 9vw, 9.72rem);
|
||||
width: calc(var(--brand-logo-default-size) * var(--brand-logo-scale));
|
||||
height: calc(var(--brand-logo-default-size) * var(--brand-logo-scale));
|
||||
min-width: 0;
|
||||
flex: none;
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 0 34px rgba(84, 203, 255, 0.3));
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.app-sidebar__brand-image {
|
||||
|
|
@ -159,7 +240,8 @@ img {
|
|||
|
||||
.brand-mark__copy {
|
||||
display: grid;
|
||||
gap: 0.2rem;
|
||||
gap: 0.3rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.brand-mark strong,
|
||||
|
|
@ -167,7 +249,7 @@ img {
|
|||
display: block;
|
||||
font-family: var(--font-display);
|
||||
letter-spacing: 0.02em;
|
||||
font-size: clamp(1.35rem, 2vw, 1.7rem);
|
||||
font-size: clamp(1.5rem, 2.35vw, 1.98rem);
|
||||
line-height: 0.96;
|
||||
}
|
||||
|
||||
|
|
@ -180,7 +262,7 @@ img {
|
|||
}
|
||||
|
||||
.brand-mark small {
|
||||
font-size: 0.98rem;
|
||||
font-size: 1rem;
|
||||
max-width: 29rem;
|
||||
}
|
||||
|
||||
|
|
@ -200,12 +282,16 @@ img {
|
|||
}
|
||||
|
||||
.site-nav {
|
||||
flex: 1 1 26rem;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.site-header__actions {
|
||||
flex: 0 1 auto;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
|
|
@ -220,9 +306,11 @@ img {
|
|||
.site-nav__link,
|
||||
.app-nav__link {
|
||||
padding: 0.7rem 0.95rem;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 999px;
|
||||
color: var(--ht-muted);
|
||||
font-weight: 600;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
transition: 160ms ease;
|
||||
}
|
||||
|
||||
|
|
@ -231,6 +319,7 @@ img {
|
|||
.app-nav__link:hover,
|
||||
.app-nav__link.is-active {
|
||||
color: var(--ht-text);
|
||||
border-color: var(--ht-border);
|
||||
background: var(--ht-cyan-soft);
|
||||
}
|
||||
|
||||
|
|
@ -238,48 +327,229 @@ img {
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
min-height: 2.8rem;
|
||||
padding: 0.82rem 1.18rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
gap: 0.58rem;
|
||||
min-height: 3rem;
|
||||
padding: 0.88rem 1.22rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid transparent;
|
||||
font-family: var(--font-display);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.1;
|
||||
cursor: pointer;
|
||||
transition: transform 160ms ease, background 160ms ease, border-color 160ms ease, color 160ms ease, box-shadow 160ms ease;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.button::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
border-radius: inherit;
|
||||
background: linear-gradient(115deg, transparent 10%, rgba(255, 255, 255, 0.22) 42%, transparent 70%);
|
||||
opacity: 0.55;
|
||||
transform: translateX(-140%);
|
||||
transition: transform 240ms ease;
|
||||
}
|
||||
|
||||
.button > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.button:hover::after {
|
||||
transform: translateX(120%);
|
||||
}
|
||||
|
||||
.button.pui-btn {
|
||||
font-family: var(--font-body);
|
||||
background: var(--pui-button-dark-fill);
|
||||
border-color: var(--pui-button-dark-border);
|
||||
box-shadow:
|
||||
0 16px 34px rgba(8, 14, 28, 0.22),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.button.pui-btn > span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.58rem;
|
||||
white-space: nowrap;
|
||||
line-height: 1.05;
|
||||
}
|
||||
|
||||
.button svg {
|
||||
flex: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.button--primary {
|
||||
background: linear-gradient(120deg, var(--ht-amber), #ffc788);
|
||||
color: #09111d;
|
||||
border-color: rgba(247, 178, 103, 0.72);
|
||||
color: var(--ht-text);
|
||||
border-color: rgba(84, 203, 255, 0.18);
|
||||
box-shadow:
|
||||
0 14px 34px rgba(247, 178, 103, 0.24),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.08) inset;
|
||||
0 18px 40px rgba(33, 121, 199, 0.18),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05) inset;
|
||||
}
|
||||
|
||||
.button--ghost {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.07), rgba(255, 255, 255, 0.03));
|
||||
border-color: var(--ht-border);
|
||||
color: var(--ht-text);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.button--secondary {
|
||||
border-color: rgba(84, 203, 255, 0.3);
|
||||
color: var(--ht-text);
|
||||
box-shadow:
|
||||
0 14px 30px rgba(84, 203, 255, 0.14),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.04) inset;
|
||||
}
|
||||
|
||||
.button.pui-btn--glow {
|
||||
color: var(--ht-text);
|
||||
background:
|
||||
radial-gradient(circle at var(--glow-x) var(--glow-y), rgba(84, 203, 255, 0.34), transparent 38%),
|
||||
linear-gradient(135deg, rgba(76, 212, 255, 0.2), rgba(12, 20, 36, 0.96) 42%, rgba(247, 178, 103, 0.22));
|
||||
border-color: rgba(84, 203, 255, 0.28);
|
||||
box-shadow: var(--pui-glow-strong), inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
||||
backdrop-filter: blur(14px) saturate(120%);
|
||||
}
|
||||
|
||||
.button.pui-btn--glow::before {
|
||||
opacity: 0.72;
|
||||
filter: blur(14px);
|
||||
}
|
||||
|
||||
.button.pui-btn--wave {
|
||||
min-height: 3rem;
|
||||
padding-top: 0.72rem;
|
||||
padding-bottom: 0.78rem;
|
||||
color: var(--pui-wave-text);
|
||||
background:
|
||||
linear-gradient(135deg, rgba(84, 203, 255, 0.18), rgba(9, 16, 28, 0.96) 42%, rgba(247, 178, 103, 0.18)),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(255, 255, 255, 0));
|
||||
border-color: var(--pui-wave-border);
|
||||
box-shadow:
|
||||
0 14px 28px rgba(8, 14, 28, 0.18),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.button.pui-btn--ghost:hover {
|
||||
border-color: rgba(84, 203, 255, 0.32);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .button--primary {
|
||||
color: #081426;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .button--ghost {
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(240, 247, 255, 0.9));
|
||||
}
|
||||
|
||||
html[data-theme="light"] .button--secondary {
|
||||
color: #093055;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .button.pui-btn {
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(245, 249, 255, 0.98));
|
||||
border-color: rgba(18, 82, 145, 0.14);
|
||||
box-shadow:
|
||||
0 18px 34px rgba(26, 78, 132, 0.08),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.88);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .button.pui-btn--glow {
|
||||
color: #081426;
|
||||
background:
|
||||
radial-gradient(circle at var(--glow-x) var(--glow-y), rgba(69, 178, 233, 0.28), transparent 40%),
|
||||
linear-gradient(135deg, rgba(76, 197, 255, 0.24), rgba(255, 255, 255, 0.98) 42%, rgba(247, 178, 103, 0.22));
|
||||
border-color: rgba(24, 96, 165, 0.24);
|
||||
box-shadow:
|
||||
0 24px 46px rgba(28, 92, 151, 0.12),
|
||||
0 0 40px rgba(96, 196, 245, 0.12);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .button.pui-btn--wave {
|
||||
color: #11355b;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(82, 195, 248, 0.14), rgba(255, 255, 255, 0.98) 38%, rgba(247, 178, 103, 0.16)),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(245, 249, 255, 0.94));
|
||||
border-color: rgba(26, 96, 164, 0.24);
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
min-width: 6.2rem;
|
||||
min-width: 7.15rem;
|
||||
padding-inline: 0.95rem;
|
||||
}
|
||||
|
||||
.theme-toggle__content {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.theme-toggle__icon,
|
||||
.theme-toggle__label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.theme-toggle__icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.theme-toggle__icon svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.theme-toggle__label {
|
||||
min-width: 0;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .theme-toggle__icon,
|
||||
html[data-theme="light"] .theme-toggle__label {
|
||||
color: #0d3d67;
|
||||
}
|
||||
|
||||
.page-main h1,
|
||||
.page-main h2,
|
||||
.page-main h3,
|
||||
.page-main strong,
|
||||
.page-main .eyebrow,
|
||||
.metric-card strong,
|
||||
.metric-card span,
|
||||
.site-nav__link,
|
||||
.button {
|
||||
word-break: keep-all;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.button--full {
|
||||
|
|
@ -341,6 +611,7 @@ img {
|
|||
font-family: var(--font-display);
|
||||
font-size: clamp(2.4rem, 6vw, 4.8rem);
|
||||
line-height: 0.98;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.page-hero__lede,
|
||||
|
|
@ -353,8 +624,10 @@ img {
|
|||
.auth-card__subtitle {
|
||||
color: var(--ht-muted);
|
||||
line-height: 1.7;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: normal;
|
||||
text-wrap: pretty;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.page-hero__lede {
|
||||
|
|
@ -707,14 +980,32 @@ img {
|
|||
|
||||
.list li,
|
||||
.auth-card__footer {
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: normal;
|
||||
text-wrap: pretty;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.button-row .button.pui-btn {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-row .button.pui-btn > span {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.feature-band {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.no-break {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
text-wrap: nowrap;
|
||||
word-break: keep-all;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
.feature-band__card {
|
||||
flex: 1 1 280px;
|
||||
display: block;
|
||||
|
|
@ -726,6 +1017,88 @@ img {
|
|||
rgba(10, 16, 30, 0.56);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .site-header {
|
||||
border-color: rgba(20, 78, 136, 0.12);
|
||||
background:
|
||||
radial-gradient(circle at top, rgba(75, 194, 245, 0.14), transparent 44%),
|
||||
radial-gradient(circle at 88% 16%, rgba(247, 178, 103, 0.12), transparent 32%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(244, 249, 255, 0.9));
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.82),
|
||||
0 20px 44px rgba(28, 88, 147, 0.12);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .site-nav__link,
|
||||
html[data-theme="light"] .app-nav__link {
|
||||
background: rgba(17, 92, 163, 0.05);
|
||||
color: #2f4f73;
|
||||
}
|
||||
|
||||
html[data-theme="light"] .site-nav__link:hover,
|
||||
html[data-theme="light"] .site-nav__link.is-active,
|
||||
html[data-theme="light"] .app-nav__link:hover,
|
||||
html[data-theme="light"] .app-nav__link.is-active {
|
||||
color: #0f2b48;
|
||||
border-color: rgba(25, 95, 163, 0.16);
|
||||
background: rgba(72, 181, 235, 0.12);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .page-hero {
|
||||
background:
|
||||
radial-gradient(circle at top, rgba(76, 197, 255, 0.2), transparent 42%),
|
||||
radial-gradient(circle at 82% 18%, rgba(247, 178, 103, 0.14), transparent 30%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(240, 246, 255, 0.96));
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.92),
|
||||
0 28px 72px rgba(28, 88, 147, 0.12);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .hero-panel,
|
||||
html[data-theme="light"] .page-section,
|
||||
html[data-theme="light"] .site-footer,
|
||||
html[data-theme="light"] .auth-card,
|
||||
html[data-theme="light"] .app-sidebar,
|
||||
html[data-theme="light"] .app-topbar,
|
||||
html[data-theme="light"] .panel,
|
||||
html[data-theme="light"] .hero-visual-card,
|
||||
html[data-theme="light"] .metric-card,
|
||||
html[data-theme="light"] .card,
|
||||
html[data-theme="light"] .callout,
|
||||
html[data-theme="light"] .timeline-entry {
|
||||
border-color: rgba(20, 78, 136, 0.12);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(244, 249, 255, 0.96));
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.92),
|
||||
0 22px 54px rgba(28, 88, 147, 0.08);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .hero-panel {
|
||||
background:
|
||||
radial-gradient(circle at top left, rgba(76, 197, 255, 0.14), transparent 34%),
|
||||
radial-gradient(circle at bottom right, rgba(247, 178, 103, 0.12), transparent 30%),
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.97), rgba(243, 249, 255, 0.97));
|
||||
}
|
||||
|
||||
html[data-theme="light"] .hero-visual-card,
|
||||
html[data-theme="light"] .feature-band__card,
|
||||
html[data-theme="light"] .metric-card {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.94), rgba(239, 246, 255, 0.96)),
|
||||
rgba(242, 248, 255, 0.96);
|
||||
border-color: rgba(20, 78, 136, 0.12);
|
||||
}
|
||||
|
||||
html[data-theme="light"] .feature-band__card:hover,
|
||||
html[data-theme="light"] .feature-band__card--link:hover,
|
||||
html[data-theme="light"] .card:hover,
|
||||
html[data-theme="light"] .callout:hover,
|
||||
html[data-theme="light"] .timeline-entry:hover {
|
||||
border-color: rgba(214, 122, 18, 0.28);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.92),
|
||||
0 24px 60px rgba(28, 88, 147, 0.12);
|
||||
}
|
||||
|
||||
.feature-band__card--link {
|
||||
transition: border-color 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
|
@ -824,11 +1197,13 @@ img {
|
|||
|
||||
.auth-shell {
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
isolation: isolate;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
|
|
@ -853,14 +1228,17 @@ img {
|
|||
}
|
||||
|
||||
.auth-card {
|
||||
width: min(760px, 100%);
|
||||
width: 100%;
|
||||
max-width: 760px;
|
||||
padding: 1.6rem;
|
||||
display: grid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.auth-form {
|
||||
display: grid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
|
|
@ -929,7 +1307,8 @@ img {
|
|||
}
|
||||
|
||||
.provider-row {
|
||||
display: grid;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
|
|
@ -1197,8 +1576,8 @@ code {
|
|||
}
|
||||
|
||||
.brand-mark__image {
|
||||
width: 6.2rem;
|
||||
height: 6.2rem;
|
||||
width: 7rem;
|
||||
height: 7rem;
|
||||
}
|
||||
|
||||
.button-row > * {
|
||||
|
|
@ -1211,6 +1590,12 @@ code {
|
|||
white-space: normal;
|
||||
}
|
||||
|
||||
.button-row .button.pui-btn > span,
|
||||
.provider-row .button.pui-btn > span {
|
||||
white-space: normal;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.product-reality-strip,
|
||||
.product-reality-strip--compact {
|
||||
grid-template-columns: 1fr;
|
||||
|
|
|
|||
|
|
@ -11,24 +11,24 @@ type PublicRouteExpectation = {
|
|||
const responsivePublicRoutes: readonly PublicRouteExpectation[] = [
|
||||
{
|
||||
path: '/',
|
||||
heading: 'From first solves to 120-cell, HyperTwist keeps the real simulator in the downloadable.',
|
||||
cta: 'Download desktop app',
|
||||
heading: 'One serious home for classic cubes, 120‑cell, 5D, and deep daily practice.',
|
||||
cta: 'Create account',
|
||||
},
|
||||
{
|
||||
path: '/features',
|
||||
heading: 'Feature truth without the guesswork.',
|
||||
heading: 'A deep training toolkit, not just a timer.',
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
heading: 'A training stack serious enough for higher-dimensional cubing.',
|
||||
heading: 'A training stack built for people who want more than a timer.',
|
||||
},
|
||||
{
|
||||
path: '/resources',
|
||||
heading: 'Resources that explain the product without leaking operator-only internals.',
|
||||
heading: 'Every guide you need to start, train, and grow with HyperTwist.',
|
||||
},
|
||||
{
|
||||
path: '/docs',
|
||||
heading: 'HyperTwist documentation stays capability-accurate.',
|
||||
heading: 'The complete HyperTwist manual.',
|
||||
},
|
||||
{
|
||||
path: '/pricing',
|
||||
|
|
@ -40,7 +40,7 @@ const responsivePublicRoutes: readonly PublicRouteExpectation[] = [
|
|||
},
|
||||
{
|
||||
path: '/getting-started',
|
||||
heading: 'Start in the browser. Train in the desktop runtime.',
|
||||
heading: 'Start on the website. Train in the desktop app.',
|
||||
},
|
||||
{
|
||||
path: '/launch-status',
|
||||
|
|
@ -48,7 +48,7 @@ const responsivePublicRoutes: readonly PublicRouteExpectation[] = [
|
|||
},
|
||||
{
|
||||
path: '/support',
|
||||
heading: 'Support for rollout, downloads, pricing, and browser-to-desktop access.',
|
||||
heading: 'Help for access, downloads, setup, and launch.',
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
|
|
@ -57,7 +57,7 @@ const responsivePublicRoutes: readonly PublicRouteExpectation[] = [
|
|||
},
|
||||
{
|
||||
path: '/changelog',
|
||||
heading: 'Release notes you can actually use for rollout.',
|
||||
heading: 'Release notes for players, teams, and studios.',
|
||||
},
|
||||
{
|
||||
path: '/open-source-notices',
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue