From 59df681cfaa10297b9020d8628e170ceefdf40a8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 22 Oct 2026 11:12:32 +0000 Subject: [PATCH] Lobby: sheet theme switch in the tab bar, remove extra toolbar Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K --- README.md | 2 +- frontend/src/Lobbies.jsx | 12 +++------- frontend/src/PaperSheet.jsx | 44 ++++++++++++++++++++++++++----------- frontend/src/styles.css | 7 ++++++ 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 1c69659..d9711cd 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Each wizard page has a **Reset this page** button that clears only that page's d ### Character sheet (A4) -The Summary page and the sheet tabs in the lobbies show the character as **A4 pages**: page 1 has name, attributes, derived stats and all skills (proficiency dots, attribute, points, total), page 2 has talents, mutations and implants, equipment, background and a ruled notes area. **Print / PDF (A4)** opens just the pages in a clean window; choose "Save as PDF" in the print dialog (margins none, background graphics on if your browser asks). Hover or tap a value to see how it is made up (not printed). **Light / Dark** switches the colours of the sheet on screen (remembered in the browser; printed pages are always light). The Word and PDF downloads on the Summary page are separate exports with their own layout. +The Summary page and the sheet tabs in the lobbies show the character as **A4 pages**: page 1 has name, attributes, derived stats and all skills (proficiency dots, attribute, points, total), page 2 has talents, mutations and implants, equipment, background and a ruled notes area. On the Summary page, **Print / PDF (A4)** opens just the pages in a clean window; choose "Save as PDF" in the print dialog (margins none, background graphics on if your browser asks). Hover or tap a value to see how it is made up (not printed). **Light / Dark** (in the lobby it sits in the tab bar) switches the colours of the sheet on screen (remembered in the browser; printed pages are always light). The Word and PDF downloads on the Summary page are separate exports with their own layout. ### Lobbies diff --git a/frontend/src/Lobbies.jsx b/frontend/src/Lobbies.jsx index 0ec7bd8..a245563 100644 --- a/frontend/src/Lobbies.jsx +++ b/frontend/src/Lobbies.jsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef, useState } from 'react' -import PaperSheet from './PaperSheet.jsx' +import PaperSheet, { ThemeSwitch } from './PaperSheet.jsx' const J = { 'Content-Type': 'application/json' } const send = (url, method, body) => fetch(url, { method, headers: J, body: body === undefined ? undefined : JSON.stringify(body) }) @@ -323,14 +323,7 @@ function CharTab({ cid, owner, rules, onOpen, name }) { return (
{err &&

{err}

} - {data && ( - - {owner ? `Played by ${owner}` : 'Your character'} ยท read-only, hover or tap a value for details - - - } /> - )} + {data && }
) } @@ -400,6 +393,7 @@ function LobbyView({ id, rules, user, onOpen }) { ))} + {active.startsWith('sheet:') && } {active.startsWith('sheet:') && (() => { diff --git a/frontend/src/PaperSheet.jsx b/frontend/src/PaperSheet.jsx index 1d80188..3c3f899 100644 --- a/frontend/src/PaperSheet.jsx +++ b/frontend/src/PaperSheet.jsx @@ -1,10 +1,30 @@ -import { useLayoutEffect, useMemo, useRef, useState } from 'react' +import { useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react' import Tip from './Tip.jsx' import { compute, effectiveRules, kitItems, normalize } from './calc.js' import { explainAttr, explainCapacity, explainCarry, explainDerived, explainSkill } from './explain.js' const PAPER_PX = 794 // 210 mm at 96 dpi +// Light/dark choice for the sheet, shared by all sheets and switches on the page and remembered in the browser +const THEME_KEY = 'aw_sheettheme' +const listeners = new Set() +const readTheme = () => { try { return localStorage.getItem(THEME_KEY) === 'dark' } catch { return false } } +let darkNow = readTheme() +export function useSheetTheme() { + const dark = useSyncExternalStore((cb) => { listeners.add(cb); return () => listeners.delete(cb) }, () => darkNow) + const set = (d) => { darkNow = d; try { localStorage.setItem(THEME_KEY, d ? 'dark' : 'light') } catch { /* storage may be blocked */ } listeners.forEach((f) => f()) } + return [dark, set] +} +export function ThemeSwitch() { + const [dark, setDark] = useSheetTheme() + return ( + + + + + ) +} + // Opens the sheet pages in a clean window (only the paper, A4, no margins) and starts the print dialog: // "Save as PDF" there gives an A4 PDF that looks exactly like the screen. export function printSheet(root, title) { @@ -48,14 +68,13 @@ const Pips = ({ n, locked }) => ( ) // The character sheet as A4 pages (page 1: attributes and skills, page 2: talents, mutations, equipment, notes). -export default function PaperSheet({ data, baseRules, tools }) { +export default function PaperSheet({ data, baseRules, tools, bare }) { const ch = useMemo(() => normalize(data, baseRules), [data, baseRules]) const rules = useMemo(() => effectiveRules(baseRules, ch), [baseRules, ch]) const c = useMemo(() => compute(ch, rules), [ch, rules]) const x = { ch, rules, c } const root = useRef(null) - const [dark, setDark] = useState(() => { try { return localStorage.getItem('aw_sheettheme') === 'dark' } catch { return false } }) - const pickTheme = (d) => { setDark(d); try { localStorage.setItem('aw_sheettheme', d ? 'dark' : 'light') } catch { /* storage may be blocked */ } } + const [dark] = useSheetTheme() const prof = rules.professions.find((p) => p.id === ch.profession) const cls = rules.classes.find((k) => k.id === ch.class_id) @@ -73,16 +92,15 @@ export default function PaperSheet({ data, baseRules, tools }) { return (
-
- {tools} - - - - + {!bare && ( +
+ {tools} + + + - - -
+
+ )}
diff --git a/frontend/src/styles.css b/frontend/src/styles.css index c998d54..5bd8d66 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -328,3 +328,10 @@ body:has(.lobbychat) main { max-width: none; margin: 0; padding-right: calc(var( .lobbyhead h1 { font-size: 1.3rem; margin: 0; } .lobbypage .tabs { margin: .4rem 0 .7rem; } .lobbypage .tabs button { padding: .3rem .8rem; } + +.tabs { align-items: center; } +.tabs .tabright { margin-left: auto; align-self: center; margin-bottom: .3rem; } +.tabs .tabright button { padding: .25rem .75rem; margin-bottom: 0; border-radius: 0; border-bottom: 1px solid var(--line); } +.tabs .tabright button.on { border-color: var(--noxium); } +.tabs .tabright button:first-child { border-radius: var(--radius) 0 0 var(--radius); } +.tabs .tabright button:last-child { border-radius: 0 var(--radius) var(--radius) 0; }