- 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)
70 lines
2.5 KiB
JavaScript
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(() => {}); });
|
|
}
|