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 (