amerc/src/main.jsx
artheru dbe3c383e3 branded app loading screen (0.78.0)
- the Suspense fallback (shown on every cold load while the app chunk loads,
  longer on slow/mobile connections) was a plain monospace 'Loading amerc…'
- replace with a branded screen: a pulsing cyan gem over 'amerc' in gold
  Georgia on a dark radial background, matching the logo + error-boundary look
- respects prefers-reduced-motion
- also audited the public mobile surfaces (agent modal, status, changelog) —
  all mobile-safe (modal is width:min(640px,96vw) + scroll)
- verified the loading screen renders (blocked the app chunk to capture it)
2026-06-10 11:20:37 +08:00

70 lines
2.5 KiB
JavaScript

import React, { Suspense, lazy } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';
// One SPA, served from every *.amerc.ai docroot; pick the app by hostname.
const host = window.location.hostname;
const DocsApp = lazy(() => import('./DocsApp.jsx'));
const PmApp = lazy(() => import('./PmApp.jsx'));
const Scene2DApp = lazy(() => import('./Scene2D.jsx'));
let App = Scene2DApp;
if (host.startsWith('docs.')) { App = DocsApp; document.title = 'Docs · amerc'; }
else if (host.startsWith('pm.')) { App = PmApp; document.title = 'PM · amerc'; }
// Preload the tavern's canvas tiles on the main domain so the scene paints as
// soon as the app mounts — they download in parallel with the JS chunk instead
// of only after TavernGame runs. Not needed on docs/pm.
if (App === Scene2DApp) {
for (const n of ['inside', 'castlefloors', 'victoria', 'barrel', 'princess', 'soldier', 'soldier_altcolor']) {
const im = new Image(); im.src = `/scene2d/lpc/${n}.png`;
}
}
// Catch render errors so a single bad component can't blank the whole app.
class ErrorBoundary extends React.Component {
constructor(props) { super(props); this.state = { error: null }; }
static getDerivedStateFromError(error) { return { error }; }
render() {
if (this.state.error) {
return (
<div className="eb">
<div className="eb-card">
<span className="eb-gem" aria-hidden="true" />
<h1>Something went off-script.</h1>
<p>The tavern hit an unexpected snag. A reload usually sorts it out.</p>
<div className="eb-actions">
<button type="button" onClick={() => window.location.reload()}>Reload</button>
<a href="https://amerc.ai/#/status">System status</a>
</div>
</div>
</div>
);
}
return this.props.children;
}
}
const Loading = () => (
<div className="app-loading">
<span className="app-loading-gem" aria-hidden="true" />
<span className="app-loading-word">amerc</span>
</div>
);
createRoot(document.getElementById('root')).render(
<React.StrictMode>
<ErrorBoundary>
<Suspense fallback={<Loading />}>
<App />
</Suspense>
</ErrorBoundary>
</React.StrictMode>,
);
// Progressive Web App: register the service worker on production amerc.ai hosts only
// (skip the Vite dev server so HMR keeps working).
if ('serviceWorker' in navigator && host.endsWith('amerc.ai')) {
window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js').catch(() => {}); });
}