Files
vending-machine-game/src/game/interfaceBridge.js
T
HamedandClaude Opus 5.5 d9ae8a45da feat: add landing-style start screen with four start modes
Phase 3 of turning the game into the company landing page.

- Replace the Iran-map briefing with a brand start screen over the
  orbiting 3D factory: logo, tagline and four ways in.
- Real world: safety notice, then GPS; if location is denied or
  missing, a friendly dialog offers offline play instead.
- Offline: start at Park Mellat beside the family car with the route
  to the factory, where the first purchase mission is.
- About Us: start in the factory yard. Products: start at the machine
  samples with the showroom open.
- Contact, address with map links, FAQ, careers, gallery and social
  links open in a sheet (bottom sheet on phones) from company config.
- Restart replays the chosen mode. Layout scrolls on short screens and
  respects safe areas.
- Add tests for the start screen, map links and theme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 08:39:22 +03:30

77 lines
2.0 KiB
JavaScript

import { publishInterface } from '../store/slices/interfaceSlice.js';
import { publishProgress } from '../store/slices/gameSlice.js';
/** Bridges the high-frequency renderer and React using serializable, batched snapshots.
* @param {object} store - Application Redux store.
* @returns {object} View publication and action registration API.
*/
export function createInterfaceBridge(store) {
let nodes = {
landing: {
hidden: false,
},
hud: {
hidden: true,
},
},
actions = new Map(),
timer = null,
disposed = false;
/** Publishes at most ten interface frames per second, independently of WebGL FPS. */
function flush() {
timer = null;
if (!disposed) store.dispatch(publishInterface(structuredClone(nodes)));
}
/** Merges a node view model, scheduling only actual changes. */
function patch(id, values) {
const previous = nodes[id] ?? {};
if (Object.keys(values).every((key) => Object.is(previous[key], values[key]))) return;
nodes = {
...nodes,
[id]: {
...previous,
...values,
},
};
if (timer === null && !disposed) timer = setTimeout(flush, 100);
}
return {
activate() {
disposed = false;
},
patch,
read: (id) => nodes[id] ?? {},
open: (id) =>
patch(id, {
open: true,
}),
close: (id) =>
patch(id, {
open: false,
}),
on: (id, handler) => actions.set(id, handler),
emit: (id, value) => actions.get(id)?.(value),
style: (id, style) =>
patch(id, {
style: {
...nodes[id]?.style,
...style,
},
}),
classState: (id, name, on) =>
patch(id, {
classes: {
...nodes[id]?.classes,
[name]: on,
},
}),
publishGame: (state) => store.dispatch(publishProgress(structuredClone(state))),
clearActions: () => actions.clear(),
dispose() {
disposed = true;
clearTimeout(timer);
actions.clear();
},
};
}