Sheet: large portrait slot, writable notes in summary and lobby

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
This commit is contained in:
Claude
2026-10-24 18:10:12 +00:00
parent 7940fcfa3f
commit a2103566a0
8 changed files with 83 additions and 25 deletions
+36 -15
View File
@@ -31,6 +31,8 @@ export function printSheet(root, title) {
const win = window.open('', '_blank')
if (!win) { window.alert('Please allow pop-ups for this site to print the sheet.'); return }
const clone = root.cloneNode(true)
const src = root.querySelectorAll('textarea'), dst = clone.querySelectorAll('textarea')
dst.forEach((t, i) => { t.textContent = src[i].value }) // typed text does not survive cloning on its own
clone.style.zoom = ''
clone.classList.remove('dark') // printed pages are always light
const head = [...document.querySelectorAll('link[rel=stylesheet], style')].map((n) => n.outerHTML).join('')
@@ -47,6 +49,17 @@ export function printSheet(root, title) {
links.forEach((l) => { l.addEventListener('load', () => { if (--pending === 0) setTimeout(go, 150) }); l.addEventListener('error', () => { if (--pending === 0) go() }) })
}
// Ruled note area. With onChange (own sheet in the summary or the lobby) it can be typed into, otherwise it just shows the text.
function Notes({ title, value, onChange }) {
return (
<div className="psec pnotes"><h3>{title}</h3>
{onChange
? <textarea className="plines pwrite" value={value || ''} maxLength={5000} onChange={(e) => onChange(e.target.value)} placeholder="Click here to write …" aria-label={title} />
: <div className="plines pwrite">{value}</div>}
</div>
)
}
// Scales the A4 pages down when the space is narrower than the paper
function Fit({ children, rootRef, dark }) {
const wrap = useRef(null)
@@ -68,7 +81,7 @@ 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, bare, onRoll }) {
export default function PaperSheet({ data, baseRules, tools, bare, onRoll, onNotes }) {
const ch = useMemo(() => normalize(data, baseRules), [data, baseRules])
const rules = useMemo(() => effectiveRules(baseRules, ch), [baseRules, ch])
const c = useMemo(() => compute(ch, rules), [ch, rules])
@@ -108,18 +121,26 @@ export default function PaperSheet({ data, baseRules, tools, bare, onRoll }) {
)}
<Fit rootRef={root} dark={dark}>
<section className="paper">
<header className="phead">
<div className="pname">{name}</div>
{ch.portrait && <img className="pportrait" src={ch.portrait} alt="" />}
<div className="pbrand">Arcane Wasteland<span>Character sheet</span></div>
</header>
<div className="pfields">
<Field label="Player">{ch.player}</Field>
<Field label="Concept">{ch.concept}</Field>
<Field label="Class">{cls && [cls.name, sub?.name].filter(Boolean).join(' / ')}</Field>
<Field label="Profession">{prof?.name}</Field>
<Field label="Rules">{preset}</Field>
</div>
{(() => {
const head = (
<header className="phead">
<div className="pname">{name}</div>
<div className="pbrand">Arcane Wasteland<span>Character sheet</span></div>
</header>
)
const fields = (
<div className="pfields">
<Field label="Player">{ch.player}</Field>
<Field label="Concept">{ch.concept}</Field>
<Field label="Class">{cls && [cls.name, sub?.name].filter(Boolean).join(' / ')}</Field>
<Field label="Profession">{prof?.name}</Field>
<Field label="Rules">{preset}</Field>
</div>
)
return ch.portrait
? <div className="pidentity"><img className="pportrait" src={ch.portrait} alt="" /><div className="pidright">{head}{fields}</div></div>
: <>{head}{fields}</>
})()}
<div className="pattrs">
{rules.attributes.map((a) => (
@@ -165,7 +186,7 @@ export default function PaperSheet({ data, baseRules, tools, bare, onRoll }) {
{data.loot && <><h3>Loot</h3><p className="pre">{data.loot}</p></>}
</div>
)}
<div className="psec pnotes"><h3>Conditions &amp; notes</h3><div className="plines" /></div>
<Notes title="Conditions &amp; notes" value={ch.conditions} onChange={onNotes && ((v) => onNotes({ conditions: v }))} />
<Foot n={1} />
</section>
@@ -208,7 +229,7 @@ export default function PaperSheet({ data, baseRules, tools, bare, onRoll }) {
{ch.background && <div className="psec"><h3>Background</h3><p className="pbg">{ch.background}</p></div>}
<div className="psec pnotes"><h3>Notes</h3><div className="plines" /></div>
<Notes title="Notes" value={ch.notes} onChange={onNotes && ((v) => onNotes({ notes: v }))} />
<Foot n={2} />
</section>
</Fit>