diff --git a/index.html b/index.html index 5880668..8edfef4 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - + diff --git a/package.json b/package.json index 2926609..36c7d05 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amerc-site", - "version": "0.63.0-docsaccuracy", + "version": "0.64.0-resilience", "private": true, "type": "module", "scripts": { diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index f6e4513..5c085bd 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -7,7 +7,7 @@ import Account from './Account.jsx'; import TavernGame from './TavernGame.jsx'; import { api } from './api.js'; -const RELEASE = '0.63.0-docsaccuracy'; +const RELEASE = '0.64.0-resilience'; const NAV = [ { id: 'home', label: 'Tavern' }, diff --git a/src/main.jsx b/src/main.jsx index 2b1bd53..43c96ca 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -12,11 +12,37 @@ let App = Scene2DApp; if (host.startsWith('docs.')) App = DocsApp; else if (host.startsWith('pm.')) App = PmApp; +// 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 ( +
+
+
+
+ ); + } + return this.props.children; + } +} + createRoot(document.getElementById('root')).render( - Loading amerc…}> - - + + Loading amerc…}> + + + , ); diff --git a/src/styles.css b/src/styles.css index 5619c50..67d2228 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1435,3 +1435,14 @@ button { /* themed task-list checkboxes in docs (0.63) */ .docs-render input[type="checkbox"] { accent-color: var(--accent); width: 14px; height: 14px; vertical-align: -2px; margin-right: 6px; } .docs-render li:has(> input[type="checkbox"]) { list-style: none; margin-left: -20px; } + +/* error boundary fallback (0.64) */ +.eb { position: fixed; inset: 0; display: grid; place-items: center; background: radial-gradient(circle at 50% 28%, #160d22, #07060c 70%); padding: 24px; } +.eb-card { max-width: 420px; text-align: center; background: linear-gradient(180deg, #160f24, #110b1d); border: 1px solid #2a1f3e; border-radius: 16px; padding: 32px 28px; box-shadow: 0 20px 60px rgba(0,0,0,0.5); font-family: Georgia, "Times New Roman", serif; } +.eb-gem { width: 26px; height: 26px; display: inline-block; background: linear-gradient(135deg, #5fe6ff, #1f86c4); transform: rotate(45deg); box-shadow: 0 0 0 3px #07273a, 0 0 24px rgba(39,216,255,0.5); margin-bottom: 20px; } +.eb-card h1 { color: #f3d27a; font-size: 22px; margin: 0 0 10px; } +.eb-card p { color: #b6a7d4; font-size: 14px; line-height: 1.6; margin: 0 0 22px; } +.eb-actions { display: flex; gap: 12px; justify-content: center; align-items: center; } +.eb-actions button { font-family: Georgia, serif; font-size: 14px; padding: 9px 22px; border-radius: 10px; border: none; cursor: pointer; background: linear-gradient(180deg, #f3d27a, #d9a948); color: #2a1303; font-weight: 600; } +.eb-actions a { font-family: Georgia, serif; font-size: 14px; color: #cfe6ff; text-decoration: none; } +.eb-actions a:hover { color: #7fe9ff; }