Fix the header on phones: credits and clock no longer overlap
CI / check (push) Successful in 4m37s
Details
CI / check (push) Successful in 4m37s
Details
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
754242dde6
commit
621eecb737
|
|
@ -1059,3 +1059,9 @@ Implementation decisions not spelled out in `memory-bank/implementation-plan.md`
|
||||||
- **Game-time spans read "4.3 game h"** so they can't be mistaken for real hours next to a real countdown.
|
- **Game-time spans read "4.3 game h"** so they can't be mistaken for real hours next to a real countdown.
|
||||||
- **Reduced motion has two sources**: the OS media query (unless the player turned motion back on) and the player's override. The map keeps moving ships but stills its background.
|
- **Reduced motion has two sources**: the OS media query (unless the player turned motion back on) and the player's override. The map keeps moving ships but stills its background.
|
||||||
- **Pure helpers read preferences directly** (`displayPreferences()`) where they format strings outside React (the dispatch sheet's problem lines); those re-render every second with the clock anyway.
|
- **Pure helpers read preferences directly** (`displayPreferences()`) where they format strings outside React (the dispatch sheet's problem lines); those re-render every second with the clock anyway.
|
||||||
|
|
||||||
|
## Fix: header on phones (user report during Phase 10K)
|
||||||
|
|
||||||
|
- **User report:** "On a phone screen the credits and time line can't be read" (screenshot at about 430 px): the credits chip was drawn over the DEADHEAD wordmark. The wordmark showed from 420 px, and 10K.2's gear plus 10K.3's device-locale clock ("2145-07-11 7:18 PM") made the right side wider than the space left.
|
||||||
|
- **Fix:** the wordmark now shows from 640 px (`sm`), the logo side clips instead of overflowing, the game clock shows only the time below 640 px (the date returns above), chips sit a little closer, and the gear is 32 px on phones. The gear icon is now a cog (it read as a sun).
|
||||||
|
- **Checked** in headless Chrome at 375, 390 and 430 px with a 1,234,567 cr balance and a 12-hour clock: no overlapping header items and no horizontal scroll (`docs/screenshots/header-phone-390.png`).
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,12 @@ export { formatGameTime };
|
||||||
export function GameClock() {
|
export function GameClock() {
|
||||||
const now = useClock((state) => state.now);
|
const now = useClock((state) => state.now);
|
||||||
const clock = usePreferences((p) => p.clock);
|
const clock = usePreferences((p) => p.clock);
|
||||||
return <time>{formatGameTime(realToGame(now, content.config), clock)}</time>;
|
const [date, ...time] = formatGameTime(realToGame(now, content.config), clock).split(" ");
|
||||||
|
// Phones show the time only; the date joins it from 640 px (it no longer fit).
|
||||||
|
return (
|
||||||
|
<time>
|
||||||
|
<span className="hidden sm:inline">{date} </span>
|
||||||
|
{time.join(" ")}
|
||||||
|
</time>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,16 +28,17 @@ export function Layout({
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-dvh flex-col text-ink-50 [--header-h:3.5rem] [--nav-h:calc(4.25rem+env(safe-area-inset-bottom))] md:pl-24 md:[--nav-h:0px]">
|
<div className="flex min-h-dvh flex-col text-ink-50 [--header-h:3.5rem] [--nav-h:calc(4.25rem+env(safe-area-inset-bottom))] md:pl-24 md:[--nav-h:0px]">
|
||||||
<header className="sticky top-0 z-10 flex h-(--header-h) items-center justify-between gap-2 border-b border-space-700/80 bg-space-950/80 px-3 backdrop-blur-xl sm:px-4">
|
<header className="sticky top-0 z-10 flex h-(--header-h) items-center justify-between gap-2 border-b border-space-700/80 bg-space-950/80 px-3 backdrop-blur-xl sm:px-4">
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
{/* The wordmark only fits beside the chips from 640 px; on phones the mark alone. */}
|
||||||
|
<div className="flex min-w-0 items-center gap-2 overflow-hidden">
|
||||||
<LogoMark />
|
<LogoMark />
|
||||||
<span className="hidden font-display text-lg font-bold tracking-[0.28em] min-[420px]:inline">
|
<span className="hidden font-display text-lg font-bold tracking-[0.28em] sm:inline">
|
||||||
DEADHEAD
|
DEADHEAD
|
||||||
</span>
|
</span>
|
||||||
<span className="hidden font-display text-[10px] tracking-[0.3em] text-cyan-300/70 uppercase lg:inline">
|
<span className="hidden font-display text-[10px] tracking-[0.3em] text-cyan-300/70 uppercase lg:inline">
|
||||||
Space Freight
|
Space Freight
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex shrink-0 items-center gap-2">
|
<div className="flex shrink-0 items-center gap-1.5 sm:gap-2">
|
||||||
<div className="flex items-center gap-1.5 rounded-lg border border-amber-400/45 bg-amber-500/10 px-2.5 py-1 text-amber-200 shadow-glow-amber">
|
<div className="flex items-center gap-1.5 rounded-lg border border-amber-400/45 bg-amber-500/10 px-2.5 py-1 text-amber-200 shadow-glow-amber">
|
||||||
<Icon name="credits" className="size-4 text-amber-300" />
|
<Icon name="credits" className="size-4 text-amber-300" />
|
||||||
<span aria-label="Credits" className="font-display text-sm font-bold tabular-nums">
|
<span aria-label="Credits" className="font-display text-sm font-bold tabular-nums">
|
||||||
|
|
@ -53,7 +54,7 @@ export function Layout({
|
||||||
<Link
|
<Link
|
||||||
to="/settings"
|
to="/settings"
|
||||||
aria-label="Settings"
|
aria-label="Settings"
|
||||||
className="grid size-9 place-items-center rounded-lg border border-space-600 bg-space-900/80 text-ink-300 transition hover:border-amber-400/60 hover:text-amber-200"
|
className="grid size-8 place-items-center rounded-lg border border-space-600 bg-space-900/80 text-ink-300 transition hover:border-amber-400/60 hover:text-amber-200 sm:size-9"
|
||||||
>
|
>
|
||||||
<Icon name="gear" className="size-5" />
|
<Icon name="gear" className="size-5" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -82,10 +82,11 @@ const PATHS = {
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
close: <path d="M6 6l12 12M18 6 6 18" />,
|
close: <path d="M6 6l12 12M18 6 6 18" />,
|
||||||
|
// A cog (outline after Lucide "settings", ISC licence).
|
||||||
gear: (
|
gear: (
|
||||||
<>
|
<>
|
||||||
|
<path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" />
|
||||||
<circle cx="12" cy="12" r="3" />
|
<circle cx="12" cy="12" r="3" />
|
||||||
<path d="M12 2.5v3M12 18.5v3M2.5 12h3M18.5 12h3M5.3 5.3l2.1 2.1M16.6 16.6l2.1 2.1M5.3 18.7l2.1-2.1M16.6 7.4l2.1-2.1" />
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
plus: <path d="M12 5v14M5 12h14" />,
|
plus: <path d="M12 5v14M5 12h14" />,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { content } from "@deadhead/content";
|
import { content } from "@deadhead/content";
|
||||||
import { realToGame } from "@deadhead/sim";
|
import { realToGame } from "@deadhead/sim";
|
||||||
import { act, render, screen } from "@testing-library/react";
|
import { act, render } from "@testing-library/react";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import type { ApiClient } from "../src/api/client";
|
import type { ApiClient } from "../src/api/client";
|
||||||
import { serverNow, startClockTicker, syncClock, useClock } from "../src/clock/clock";
|
import { serverNow, startClockTicker, syncClock, useClock } from "../src/clock/clock";
|
||||||
|
|
@ -51,14 +51,16 @@ describe("server-synced clock", () => {
|
||||||
it("shows the in-game time in the game clock and keeps it ticking", async () => {
|
it("shows the in-game time in the game clock and keeps it ticking", async () => {
|
||||||
await syncClock(serverAhead(0));
|
await syncClock(serverAhead(0));
|
||||||
const stop = startClockTicker();
|
const stop = startClockTicker();
|
||||||
render(<GameClock />);
|
const { container } = render(<GameClock />);
|
||||||
|
// Date and time are separate spans (the date hides on phones).
|
||||||
|
const clock = () => container.querySelector("time")!;
|
||||||
// The default clock follows the device locale (CR-011).
|
// The default clock follows the device locale (CR-011).
|
||||||
const at = (real: number) => formatGameTime(realToGame(real, content.config), "locale");
|
const at = (real: number) => formatGameTime(realToGame(real, content.config), "locale");
|
||||||
expect(screen.getByText(at(DEVICE_NOW))).toBeInTheDocument();
|
expect(clock()).toHaveTextContent(at(DEVICE_NOW));
|
||||||
|
|
||||||
// One real minute is one game hour.
|
// One real minute is one game hour.
|
||||||
act(() => vi.advanceTimersByTime(60_000));
|
act(() => vi.advanceTimersByTime(60_000));
|
||||||
expect(screen.getByText(at(DEVICE_NOW + 60_000))).toBeInTheDocument();
|
expect(clock()).toHaveTextContent(at(DEVICE_NOW + 60_000));
|
||||||
stop();
|
stop();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Loading…
Reference in New Issue