feat: minimal HUD with one floating menu and objective chip
Phase 4 of turning the game into the company landing page. - Only the menu button, a compact objective chip and the touch controls stay on screen; speed shows only while driving. - The objective chip shows the next goal in one line, its distance and an arrow that turns towards it (replaces the compass strip); tapping it opens the mission list. - One floating menu holds map, missions, status, products, real-world, camera, stable mode, sound, graphics, minimap, fullscreen, help, restart and back to the start screen. It closes on a second tap, a tap outside, Escape, or after an action. - Missions, status and help open as sheets; street label, rank bar, status panel, toolbar and compass are gone. The minimap is optional and off by default. - Share the icon set and sheet dialog between start screen and HUD. - Layout checked for phone portrait, landscape and safe areas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { Icon } from './Icon.jsx';
|
||||
|
||||
/** @typedef {{id: string, open: boolean, title: string, closeLabel: string, onClose: function,
|
||||
* children?: import('react').ReactNode}} SheetProps */
|
||||
/**
|
||||
* Modal panel built on the native dialog (focus trap and Escape for free). Styled by the
|
||||
* `.info-sheet` rules: a centred card on wide screens, a bottom sheet on phones.
|
||||
* @param {SheetProps} props - Visibility, title, close label and callback, and content.
|
||||
* @returns {import('react').ReactElement} Dialog element.
|
||||
*/
|
||||
export function Sheet({ id, open, title, closeLabel, onClose, children }) {
|
||||
const dialog = useRef(null);
|
||||
|
||||
// Keep the native dialog in step with `open`; closing by Escape reports back through onClose.
|
||||
useEffect(() => {
|
||||
const element = dialog.current;
|
||||
if (!element) return;
|
||||
if (open && !element.open) element.showModal();
|
||||
if (!open && element.open) element.close();
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<dialog
|
||||
ref={dialog}
|
||||
id={id}
|
||||
aria-labelledby={`${id}-title`}
|
||||
onClose={onClose}
|
||||
className="info-sheet"
|
||||
>
|
||||
<header className="mb-4 flex items-center justify-between gap-4">
|
||||
<h2 id={`${id}-title`} className="text-lg font-bold">
|
||||
{title}
|
||||
</h2>
|
||||
<button
|
||||
type="button"
|
||||
className="start-icon-button"
|
||||
aria-label={closeLabel}
|
||||
onClick={onClose}
|
||||
>
|
||||
<Icon name="close" className="h-5 w-5" />
|
||||
</button>
|
||||
</header>
|
||||
{open && children}
|
||||
</dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user