Add luck token toggle, color picker, and click-to-edit ability scores
- Luck token: star toggle (filled/empty) in header, always clickable - Color picker: input[type=color] next to name in edit mode - Ability scores: InlineNumber click-to-edit replaces +/- buttons - Server: added color and luck_token to allowed update fields - DB: luck_token column migration (default 1) - Fix dice color for hex color values (was only handling HSL) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
51ffb0033a
commit
b88fa0cb3e
8 changed files with 87 additions and 52 deletions
|
|
@ -104,6 +104,37 @@
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
.luckBtn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1;
|
||||
padding: 0.1rem;
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
|
||||
.luckBtn:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.colorPicker {
|
||||
width: 2rem;
|
||||
height: 1.5rem;
|
||||
border: 1px solid rgba(var(--gold-rgb), 0.3);
|
||||
border-radius: 3px;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.colorRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
margin-top: 0.3rem;
|
||||
}
|
||||
|
||||
.nameInput {
|
||||
font-family: "Cinzel", Georgia, serif;
|
||||
font-size: 1.3rem;
|
||||
|
|
|
|||
|
|
@ -89,11 +89,22 @@ export default function CharacterSheet({
|
|||
<div className={styles.identity}>
|
||||
{mode === "edit" ? (
|
||||
<div>
|
||||
<div className={styles.colorRow}>
|
||||
<input
|
||||
className={styles.nameInput}
|
||||
defaultValue={character.name}
|
||||
onChange={(e) => handleNameField("name", e.target.value)}
|
||||
/>
|
||||
<input
|
||||
type="color"
|
||||
className={styles.colorPicker}
|
||||
value={character.color || "#888888"}
|
||||
onChange={(e) =>
|
||||
onUpdate(character.id, { color: e.target.value })
|
||||
}
|
||||
title="Character color"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
className={styles.titleInput}
|
||||
defaultValue={character.title}
|
||||
|
|
@ -169,6 +180,25 @@ export default function CharacterSheet({
|
|||
<span className={styles.xpThreshold}>/ {xpThreshold}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Luck token — always toggleable */}
|
||||
<div className={styles.vital}>
|
||||
<button
|
||||
className={styles.luckBtn}
|
||||
onClick={() =>
|
||||
onUpdate(character.id, {
|
||||
luck_token: character.luck_token ? 0 : 1,
|
||||
})
|
||||
}
|
||||
title={
|
||||
character.luck_token
|
||||
? "Luck token available"
|
||||
: "Luck token spent"
|
||||
}
|
||||
>
|
||||
{character.luck_token ? "\u2605" : "\u2606"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -138,9 +138,12 @@ function buildNotation(expression: string, rolls: number[]): string | null {
|
|||
return `${count}d${sides}@${values}`;
|
||||
}
|
||||
|
||||
/** Convert an HSL string like "hsl(210, 70%, 50%)" to a hex color */
|
||||
function hslToHex(hsl: string): string {
|
||||
const match = hsl.match(/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/);
|
||||
/** Convert a color string (HSL or hex) to hex */
|
||||
function hslToHex(color: string): string {
|
||||
// Already hex — pass through
|
||||
if (color.startsWith("#")) return color;
|
||||
|
||||
const match = color.match(/hsl\((\d+),\s*(\d+)%,\s*(\d+)%\)/);
|
||||
if (!match) return "#8B2020";
|
||||
|
||||
const h = parseInt(match[1]) / 360;
|
||||
|
|
|
|||
|
|
@ -54,30 +54,3 @@
|
|||
color: var(--hp);
|
||||
font-size: 0.65rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(var(--gold-rgb), 0.25);
|
||||
background: var(--bg-inset);
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
transition:
|
||||
border-color 0.15s,
|
||||
color 0.15s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
border-color: var(--gold);
|
||||
color: var(--gold);
|
||||
}
|
||||
|
||||
.rollSpace {
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { Stat } from "../types";
|
||||
import { getModifier, formatModifier } from "../utils/modifiers";
|
||||
import InlineNumber from "./InlineNumber";
|
||||
import DiceButton from "./DiceButton";
|
||||
import styles from "./StatBlock.module.css";
|
||||
|
||||
|
|
@ -39,14 +40,6 @@ export default function StatBlock({
|
|||
<div key={name} className={styles.stat}>
|
||||
<span className={styles.statName}>{name}</span>
|
||||
<div className={styles.statRow}>
|
||||
{mode === "edit" && (
|
||||
<button
|
||||
className={styles.btn}
|
||||
onClick={() => onStatChange(name, baseValue - 1)}
|
||||
>
|
||||
−
|
||||
</button>
|
||||
)}
|
||||
<span className={styles.modifier}>{formatModifier(mod)}</span>
|
||||
{mode === "view" && campaignId && (
|
||||
<DiceButton
|
||||
|
|
@ -59,17 +52,18 @@ export default function StatBlock({
|
|||
label={`${name} check`}
|
||||
/>
|
||||
)}
|
||||
{mode === "edit" && (
|
||||
<button
|
||||
className={styles.btn}
|
||||
onClick={() => onStatChange(name, baseValue + 1)}
|
||||
>
|
||||
+
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<span className={styles.score}>
|
||||
{value}
|
||||
{mode === "edit" ? (
|
||||
<InlineNumber
|
||||
value={baseValue}
|
||||
onChange={(v) => onStatChange(name, v)}
|
||||
min={1}
|
||||
max={30}
|
||||
/>
|
||||
) : (
|
||||
value
|
||||
)}
|
||||
{bonus > 0 && <span className={styles.bonus}> (+{bonus})</span>}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export interface Character {
|
|||
gear_slots_max: number;
|
||||
overrides: Record<string, unknown>;
|
||||
color: string;
|
||||
luck_token: number;
|
||||
stats: Stat[];
|
||||
gear: Gear[];
|
||||
talents: Talent[];
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ const v2Columns: Array<[string, string, string]> = [
|
|||
["character_gear", "effects", "TEXT DEFAULT '{}'"],
|
||||
["character_talents", "game_talent_id", "INTEGER"],
|
||||
["characters", "color", "TEXT DEFAULT ''"],
|
||||
["characters", "luck_token", "INTEGER DEFAULT 1"],
|
||||
["roll_log", "nat20", "INTEGER DEFAULT 0"],
|
||||
["roll_log", "character_color", "TEXT DEFAULT ''"],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -148,6 +148,8 @@ router.patch("/:id", (req, res) => {
|
|||
"cp",
|
||||
"gear_slots_max",
|
||||
"overrides",
|
||||
"color",
|
||||
"luck_token",
|
||||
];
|
||||
|
||||
const updates: string[] = [];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue