dm / commit
feat: expand data, add abilities sidebar, setting-filtered foci, quick actions
plan.md | 38 +++++++++++ public/index.html | 198 ++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 202 insertions(+), 34 deletions(-) diff --git a/plan.md b/plan.md index ab7219f..f318048 100644 --- a/plan.md +++ b/plan.md @@ -175,9 +175,47 @@ Cypher System is genre-neutral. Offer a setting choice at creation that adjusts --- +--- + +## Phase 5 — Skill system + +The `skills` array already exists in `gameState.character` as `[{ name, rank: 'trained'|'specialized'|'inability' }]` but nothing populates or displays it yet. Claude knows about skills in the system prompt (trained = difficulty −1, specialized = −2, inability = +1) but the player has no visibility or control. + +### 5a — Display + +- [ ] New "Skills" section in the sidebar (between Abilities and Equipment) +- [ ] Each skill shows name + rank badge: green "Trained" / gold "Specialized" / red "Inability" +- [ ] Empty state: "No skills yet" + +### 5b — Type and Descriptor starting skills + +Several Type Tier 1 abilities grant skill training ("Physical Skills", "Interaction Skills", "Danger Sense", etc.) but currently these are stored as narrative text in `abilities`, not as actual entries in `skills`. Fix by: + +- [ ] Add a `startingSkills` field to each Type (parallel to `startingAbilities`) +- [ ] Add `descriptorSkills` field to each Descriptor for their trained skills +- [ ] Populate `character.skills` from both in `beginAdventure()` + +### 5c — Skill advancement + +The XP advancement path ("train a skill") is currently only narrated. To make it mechanical: + +- [ ] Add a "Train a Skill" option to the Spend 4 XP flow — prompts for skill name, adds it as trained +- [ ] Allow upgrading trained → specialized for another advancement step +- [ ] Specialization cap at 2 levels; inability cannot be trained away (only reduced to trained via advancement) + +### 5d — Skill lookup in task resolution + +When a player's message mentions an action, Claude already references skills in its narration. Future enhancement: + +- [ ] Pass `character.skills` in the system prompt so Claude can cite which skills apply to a given roll +- [ ] Expose "I am trained in X" as a quick action or structured input so the player can assert relevant skills before rolling + +--- + ## Notes - Phases 1–2 land together (one `SAVE_VERSION` bump to 2) — they're too intertwined to separate safely - Phase 3 can ship independently after Phase 2 (UI only, no `gameState` shape change) - Phase 4 is cosmetic and can go in any order +- Phase 5 requires a `SAVE_VERSION` bump when 5b lands (new `startingSkills` field changes character shape) - The CSRD is available at [cypher-srd.com](https://cypher-srd.com) for rules reference diff --git a/public/index.html b/public/index.html index 5242711..e7c8ade 100644 --- a/public/index.html +++ b/public/index.html @@ -616,6 +616,12 @@ body { .cypher-use-btn:hover { border-color: var(--gold-dim); color: var(--gold); } .cypher-limit-warn { color: #d97706; font-size: 0.72rem; } +/* Abilities section */ +.ability-entry { border: 1px solid var(--border); border-radius: 5px; padding: 6px 9px; margin-bottom: 5px; background: var(--bg-card); } +.ability-name { display:block; font-family:'Cinzel',serif; font-size:0.68rem; color:var(--gold-light); margin-bottom:2px; } +.ability-cost { color:var(--text-dim); font-weight:400; } +.ability-desc { display:block; font-size:0.75rem; color:var(--text-dim); line-height:1.4; } + /* Advancement row */ .adv-row { display:flex; align-items:center; gap:8px; margin:4px 0 6px; flex-wrap:wrap; } .adv-dots { display:flex; gap:4px; } @@ -1302,11 +1308,11 @@ body { <div id="messages"></div> <div class="quick-actions" id="quick-actions"> - <button class="quick-btn" data-action="I look around carefully, taking stock of my surroundings.">👁 Look around</button> - <button class="quick-btn" data-action="I find a safe spot to rest and recover my strength.">🛌 Rest & recover</button> - <button class="quick-btn" data-action="I check my inventory and equipment.">🎒 Check inventory</button> - <button class="quick-btn" data-action="I carefully search the area for traps or hidden dangers.">🔍 Search for traps</button> - <button class="quick-btn" data-action="I approach the nearest person and try to start a conversation.">💬 Talk to NPC</button> + <button class="quick-btn" data-action="I take a moment to assess the situation — scanning for threats, opportunities, and anything unusual.">👁 Assess</button> + <button class="quick-btn" data-action="I take a recovery roll to restore my pools. Please narrate a brief rest and tell me which pool benefits most.">🎲 Recovery roll</button> + <button class="quick-btn" data-action="I check my equipment and cyphers, taking stock of what I have available.">🎒 Check gear</button> + <button class="quick-btn" data-action="I carefully search the area for hidden things — secrets, traps, valuables, or clues.">🔍 Search</button> + <button class="quick-btn" data-action="I try to talk my way through this — using whatever approach seems most likely to work.">💬 Talk</button> </div> <div class="input-area"> @@ -1377,6 +1383,12 @@ body { <div id="cyphers-list"><span style="font-size:0.78rem;color:var(--text-dim);font-style:italic">No cyphers yet.</span></div> </div> + <!-- Abilities --> + <div class="sidebar-section"> + <div class="sidebar-title">Abilities</div> + <div id="abilities-list"></div> + </div> + <!-- Equipment --> <div class="sidebar-section"> <div class="sidebar-title">Equipment</div> @@ -1720,71 +1732,158 @@ const TYPES = [ edge: { might: 1, speed: 0, intellect: 0 }, cypherLimit: 2, note: 'Practiced with all weapons. Might Edge 1.', - equipment: ['Weapon of choice', 'Light armor', "Explorer's pack", 'Moderate-priced item'] }, + equipment: ['Weapon of choice', 'Light armor', "Explorer's pack", 'Moderate-priced item'], + startingAbilities: [ + { name: 'Practiced with All Weapons', cost: null, description: 'You can use any weapon without penalty.' }, + { name: 'Physical Skills', cost: null, description: 'Trained in two physical skills of your choice (e.g. climbing, swimming, jumping, running).' }, + { name: 'Bash', cost: '1 Might', description: 'Your attack inflicts 2 additional points of damage.' }, + ] }, { name: 'Adept', icon: '🔮', pools: { might: 7, speed: 9, intellect: 12 }, edge: { might: 0, speed: 0, intellect: 1 }, cypherLimit: 3, note: 'Masters esoteries (special abilities). Intellect Edge 1. Cypher Limit 3.', - equipment: ['Clothing', 'Book of lore', 'Two expensive items', 'Moderate-priced item'] }, + equipment: ['Clothing', 'Book of lore', 'Two expensive items', 'Moderate-priced item'], + startingAbilities: [ + { name: 'Practiced with Light Weapons', cost: null, description: 'You can use light weapons without penalty.' }, + { name: 'Magical Training', cost: null, description: 'Trained in all esoteries as if they were skills.' }, + { name: 'Hedge Magic', cost: '1 Intellect', description: 'Perform a minor magical trick — small light, move a tiny object, clean something, etc.' }, + { name: 'Ward', cost: null, description: 'A constant magical field grants you +1 Armor at all times.' }, + ] }, { name: 'Explorer', icon: '🧭', pools: { might: 10, speed: 9, intellect: 9 }, edge: { might: 1, speed: 0, intellect: 0 }, cypherLimit: 2, note: 'Balanced adventurer; light/medium weapons. Might Edge 1.', - equipment: ['Weapon of choice', 'Light armor', 'Exploration gear', 'Moderate-priced item'] }, + equipment: ['Weapon of choice', 'Light armor', 'Exploration gear', 'Moderate-priced item'], + startingAbilities: [ + { name: 'Practiced with Light and Medium Weapons', cost: null, description: 'You can use light and medium weapons without penalty.' }, + { name: 'Practiced in Armor', cost: null, description: 'Wear light armor without any Speed Effort cost.' }, + { name: 'Danger Sense', cost: null, description: 'Your initiative tasks are eased.' }, + { name: 'Physical Skills', cost: null, description: 'Trained in two physical skills of your choice.' }, + ] }, { name: 'Speaker', icon: '🗣️', pools: { might: 8, speed: 9, intellect: 11 }, edge: { might: 0, speed: 0, intellect: 1 }, cypherLimit: 2, note: 'Social and deceptive abilities. Intellect Edge 1.', - equipment: ['Light weapon', 'Stylish clothing', 'Two expensive items', 'Moderate-priced item'] }, + equipment: ['Light weapon', 'Stylish clothing', 'Two expensive items', 'Moderate-priced item'], + startingAbilities: [ + { name: 'Practiced with Light Weapons', cost: null, description: 'You can use light weapons without penalty.' }, + { name: 'Spin Identity', cost: '2+ Intellect', description: 'Convince others you are someone you are not; lasts the duration of the interaction.' }, + { name: 'Interaction Skills', cost: null, description: 'Trained in deception, persuasion, and intimidation.' }, + { name: 'Babel', cost: null, description: 'After listening to any spoken language briefly, you can understand it and make yourself understood.' }, + ] }, ]; const DESCRIPTORS = [ + { name: 'Appealing', icon: '😊', poolBonus: { speed: 2 }, note: 'Your presence puts others at ease. Trained in pleasant social interactions.' }, + { name: 'Brash', icon: '😤', poolBonus: { speed: 2 }, note: 'Act first, think later. Trained in initiative; +2 Speed pool.' }, + { name: 'Calm', icon: '🧘', poolBonus: { intellect: 2 }, note: 'Unshakeable under pressure. Trained in resisting persuasion and panic.' }, + { name: 'Charming', icon: '💫', poolBonus: { intellect: 2 }, note: 'Hard to dislike and easy to trust. Trained in persuasion and performance.' }, { name: 'Clever', icon: '🧠', poolBonus: { intellect: 2 }, note: 'Skilled at lies, trickery, and assessing danger.' }, + { name: 'Creative', icon: '🎨', poolBonus: { intellect: 2 }, note: 'Finds unexpected solutions. Trained in all creative tasks (art, music, writing).' }, + { name: 'Empathic', icon: '💗', poolBonus: { intellect: 2 }, note: 'Reads emotional states with ease. Trained in sensing motives and first-aid.' }, { name: 'Fast', icon: '💨', poolBonus: { speed: 2 }, note: 'Can move and act in the same round.' }, { name: 'Graceful', icon: '🌸', poolBonus: { speed: 2 }, note: 'Trained in balance, performance, and Speed defense.' }, - { name: 'Hardy', icon: '🪨', poolBonus: { might: 4 }, note: 'Functions one step better on damage track.' }, + { name: 'Hardy', icon: '🪨', poolBonus: { might: 4 }, note: 'Functions one step better on the damage track.' }, { name: 'Honorable', icon: '🛡️', poolBonus: { might: 2 }, note: 'Trained in pleasant social interactions and detecting lies.' }, + { name: 'Impulsive', icon: '⚡', poolBonus: { speed: 2 }, note: 'You go first and think later. Trained in initiative and Speed defense.' }, { name: 'Inquisitive', icon: '🔍', poolBonus: { intellect: 4 }, note: 'Trained in learning, geography, and history.' }, { name: 'Intelligent', icon: '📚', poolBonus: { intellect: 2 }, note: 'Trained in one knowledge area and memorization.' }, { name: 'Intuitive', icon: '✨', poolBonus: { intellect: 2 }, note: 'Trained in perception; can act out of turn once per cycle.' }, + { name: 'Kind', icon: '🕊️', poolBonus: { intellect: 2 }, note: 'Genuine warmth that opens doors. Trained in healing and social tasks.' }, + { name: 'Learned', icon: '🎓', poolBonus: { intellect: 4 }, note: 'A walking library. Trained in two knowledge areas of your choice.' }, + { name: 'Loyal', icon: '🤝', poolBonus: { might: 2 }, note: 'Your allies inspire you. Trained in tasks that help companions directly.' }, { name: 'Lucky', icon: '🍀', poolBonus: {}, note: 'Gains a 3-point Luck Pool; bonuses to rerolls.' }, { name: 'Mighty', icon: '💪', poolBonus: { might: 4 }, note: 'Trained in lifting/throwing; +1 to recovery rolls.' }, { name: 'Mysterious', icon: '🌑', poolBonus: {}, note: 'Trained in stealth and resisting interrogation.' }, { name: 'Mystical', icon: '🌙', poolBonus: { intellect: 2 }, note: 'Can detect and cast minor magic; trained in the supernatural.' }, { name: 'Perceptive', icon: '👁️', poolBonus: { intellect: 2 }, note: 'Trained in noticing details and recalling trivia.' }, { name: 'Resilient', icon: '🌳', poolBonus: { might: 2, intellect: 2 }, note: 'Trained in both Might and Intellect defense; extra recovery roll.' }, - { name: 'Sharp-Eyed', icon: '🦅', poolBonus: {}, note: 'Trained in initiative and perception; can find foe weaknesses.' }, + { name: 'Rugged', icon: '🏔️', poolBonus: { might: 2, speed: 2 }, note: 'Built for hard conditions. Light armor without penalties; trained in navigation.' }, + { name: 'Sharp-Eyed', icon: '🦅', poolBonus: {}, note: 'Trained in initiative and perception; can spot a foe\'s weakness.' }, { name: 'Stealthy', icon: '🌫️', poolBonus: { speed: 2 }, note: 'Trained in stealth, trickery, and illusions.' }, { name: 'Strong', icon: '🪓', poolBonus: { might: 4 }, note: 'Trained in breaking objects and jumping.' }, { name: 'Strong-Willed',icon: '🔮', poolBonus: { intellect: 4 }, note: 'Trained in resisting mental effects and concentration.' }, + { name: 'Swift', icon: '🐆', poolBonus: { speed: 4 }, note: 'Trained in running and escape; movement is never restricted.' }, { name: 'Tough', icon: '🦾', poolBonus: { might: 2 }, note: '+1 Armor; +1 to recovery rolls.' }, + { name: 'Vicious', icon: '🩸', poolBonus: { might: 2 }, note: 'You fight to hurt. Attacks deal 1 additional point of damage.' }, + { name: 'Virtuous', icon: '⭐', poolBonus: { intellect: 2 }, note: 'Trained in resisting temptation and corruption; allies trust you deeply.' }, { name: 'Weird', icon: '🌀', poolBonus: { intellect: 2 }, note: 'Trained in supernatural knowledge; senses nearby strange phenomena.' }, ]; +// settings: which genres this focus fits. 'all' = any setting. const FOCI = [ - { name: 'Controls Beasts', icon: '🦁', playstyle: 'Communicate with and command animals.' }, - { name: 'Crafts Illusions', icon: '🎭', playstyle: 'Fashion images from light so real they deceive all senses.' }, - { name: 'Crafts Unique Objects', icon: '⚙️', playstyle: 'Inventor of strange gadgets, gear, and contraptions.' }, - { name: 'Explores Dark Places', icon: '🕯️', playstyle: 'Treasure hunter and scavenger of lost and forgotten things.' }, - { name: 'Fights With Panache', icon: '🎪', playstyle: 'Swashbuckling daredevil who wins through flamboyant style.' }, - { name: 'Howls at the Moon', icon: '🐺', playstyle: 'Transforms into a fearsome beast with dangerous control issues.' }, - { name: 'Hunts', icon: '🏹', playstyle: 'Stalking hunter who tracks and takes down a chosen quarry.' }, - { name: 'Infiltrates', icon: '🕵️', playstyle: 'Uses subtlety, guile, and stealth to enter impossible places.' }, - { name: 'Leads', icon: '👑', playstyle: 'Natural commander who maintains a loyal band of followers.' }, - { name: 'Lives in the Wilderness',icon: '🌲', playstyle: 'Survivalist who thrives where others perish.' }, - { name: 'Masters Spells', icon: '📜', playstyle: 'Memorizes and rapidly deploys a variety of arcane spells.' }, - { name: 'Masters Weaponry', icon: '🗡️', playstyle: 'Grandmaster of one weapon type, surpassing all others.' }, - { name: 'Murders', icon: '🩸', playstyle: 'Assassin specialising in lethal surprise and clean eliminations.' }, - { name: 'Never Says Die', icon: '💀', playstyle: 'Unstoppable survivor who keeps fighting past all reason.' }, - { name: 'Rides the Lightning', icon: '⚡', playstyle: 'Creates and discharges electrical power to shock and energize.' }, - { name: 'Slays Monsters', icon: '🐉', playstyle: 'Dedicated monster hunter with lethal knowledge of creatures.' }, - { name: 'Solves Mysteries', icon: '🔎', playstyle: 'Master of deduction who reads evidence to find hidden answers.' }, - { name: 'Wears a Sheen of Ice', icon: '❄️', playstyle: 'Commands the wintry power of cold and ice.' }, - { name: 'Wields Two Weapons', icon: '🤺', playstyle: 'Dual-wielder who overwhelms enemies with relentless twin attacks.' }, - { name: 'Works Miracles', icon: '💫', playstyle: 'Healer and time-bender who restores the injured.' }, + { name: 'Abides in Stone', icon: '🗿', settings: ['fantasy','numenera'], playstyle: 'Your body can merge with rock; you are patient, enduring, and terrifying in close combat.' }, + { name: 'Acts Without Consequence', icon: '😏', settings: ['all'], playstyle: 'Rules, ethics, and consequences never seem to stick to you — a charmed manipulator.' }, + { name: 'Battles Robots', icon: '🤖', settings: ['numenera','scifi'], playstyle: 'Specialised in locating and destroying mechanical threats of all kinds.' }, + { name: 'Carries a Quiver', icon: '🏹', settings: ['all'], playstyle: 'Master archer who never misses and always has the right arrow for the moment.' }, + { name: 'Commands Mental Powers', icon: '🧿', settings: ['all'], playstyle: 'Wields psychic force — telepathy, telekinesis, and mind-shattering blasts.' }, + { name: 'Controls Beasts', icon: '🦁', settings: ['fantasy','numenera','modern'], playstyle: 'Communicate with and command animals, from pets to apex predators.' }, + { name: 'Crafts Illusions', icon: '🎭', settings: ['fantasy','numenera'], playstyle: 'Fashion images from light so real they deceive all senses.' }, + { name: 'Crafts Unique Objects', icon: '⚙️', settings: ['fantasy','numenera','scifi'], playstyle: 'Inventor of strange gadgets, gear, and one-of-a-kind contraptions.' }, + { name: 'Dances with Dark Matter', icon: '🌌', settings: ['numenera','scifi'], playstyle: 'Shape the unseen fabric of reality into shields, weapons, and impossible structures.' }, + { name: 'Defends the Weak', icon: '🛡️', settings: ['all'], playstyle: 'Selfless protector who interposes and shields those who cannot protect themselves.' }, + { name: 'Doesn\'t Do Much', icon: '😴', settings: ['all'], playstyle: 'Outwardly lazy — secretly formidable. Passive abilities accumulate into devastating effect.' }, + { name: 'Employs Magnetism', icon: '🧲', settings: ['numenera','scifi'], playstyle: 'Attract, repel, and reshape metal at will; a terrifying force on any battlefield.' }, + { name: 'Entertains', icon: '🎶', settings: ['all'], playstyle: 'Your art — music, acrobatics, oration — captivates crowds and shapes hearts.' }, + { name: 'Exists Partially Out of Phase', icon: '👻', settings: ['numenera','scifi'], playstyle: 'Slip through walls and dodge attacks by stepping slightly outside reality.' }, + { name: 'Explores Dark Places', icon: '🕯️', settings: ['all'], playstyle: 'Treasure hunter and delver of forgotten ruins, always one step from disaster.' }, + { name: 'Fights Dirty', icon: '🤛', settings: ['all'], playstyle: 'Kicks, bites, throws sand — any tactic that wins is a valid tactic.' }, + { name: 'Fights With Panache', icon: '🎪', settings: ['all'], playstyle: 'Swashbuckling daredevil who wins through flamboyant style over brute force.' }, + { name: 'Focuses Mind Over Matter', icon: '🔵', settings: ['all'], playstyle: 'Forged by mental discipline; your will shapes the physical world around you.' }, + { name: 'Fuses Flesh and Steel', icon: '🦾', settings: ['numenera','scifi'], playstyle: 'Cybernetic enhancements blur the line between human and machine.' }, + { name: 'Fuses Mind and Machine', icon: '💻', settings: ['numenera','scifi'], playstyle: 'Interface directly with computers, drones, and AI — your mind is the terminal.' }, + { name: 'Grows to Towering Heights',icon: '🗼', settings: ['numenera','scifi','fantasy'], playstyle: 'Expand to enormous size, crushing opposition beneath your feet.' }, + { name: 'Howls at the Moon', icon: '🐺', settings: ['fantasy','numenera'], playstyle: 'Transforms into a fearsome beast — power beyond control, control beyond reach.' }, + { name: 'Hunts', icon: '🦌', settings: ['all'], playstyle: 'Stalking hunter who tracks and takes down a chosen quarry across any terrain.' }, + { name: 'Infiltrates', icon: '🕵️', settings: ['all'], playstyle: 'Uses subtlety, guile, and disguise to enter impossible places unseen.' }, + { name: 'Interprets the Law', icon: '⚖️', settings: ['all'], playstyle: 'Expert in rules, codes, and loopholes — the letter of the law is your weapon.' }, + { name: 'Is Licensed to Carry', icon: '🔫', settings: ['modern','scifi'], playstyle: 'Expert marksman trained in firearms, their maintenance, and lethal application.' }, + { name: 'Leads', icon: '👑', settings: ['all'], playstyle: 'Natural commander who earns loyalty and turns individuals into a deadly unit.' }, + { name: 'Lives in the Wilderness', icon: '🌲', settings: ['fantasy','numenera','modern'], playstyle: 'Survivalist who reads terrain, weather, and prey — thrives where others perish.' }, + { name: 'Looks for Trouble', icon: '😈', settings: ['all'], playstyle: 'Brawler who wades into any conflict and relishes the chaos of open combat.' }, + { name: 'Masters Defense', icon: '🛡️', settings: ['all'], playstyle: 'Immovable wall who absorbs punishment and keeps allies safe.' }, + { name: 'Masters Spells', icon: '📜', settings: ['fantasy','numenera'], playstyle: 'Memorizes and rapidly deploys a repertoire of arcane spells.' }, + { name: 'Masters the Swarm', icon: '🐝', settings: ['fantasy','numenera','scifi'], playstyle: 'Commands insects, vermin, or nanites as an extension of your will.' }, + { name: 'Masters Weaponry', icon: '🗡️', settings: ['all'], playstyle: 'Grandmaster of one weapon type — you wield it better than anyone alive.' }, + { name: 'Metes Out Justice', icon: '⚖️', settings: ['all'], playstyle: 'Vigilante who punishes the guilty and rights wrongs outside official channels.' }, + { name: 'Moves Like a Cat', icon: '🐈', settings: ['all'], playstyle: 'Acrobat and parkour artist; gravity is a suggestion, not a rule.' }, + { name: 'Moves Like the Wind', icon: '🌬️', settings: ['all'], playstyle: 'Blur of speed — strikes before enemies react and vanishes before they recover.' }, + { name: 'Murders', icon: '🩸', settings: ['all'], playstyle: 'Assassin specialising in lethal surprise and untraceable eliminations.' }, + { name: 'Needs No Weapon', icon: '👊', settings: ['all'], playstyle: 'Your body is the only weapon you need — fists, feet, and iron will.' }, + { name: 'Never Says Die', icon: '💀', settings: ['all'], playstyle: 'Unstoppable survivor who keeps fighting long past the point of reason.' }, + { name: 'Operates Undercover', icon: '🎭', settings: ['all'], playstyle: 'Deep-cover operative; multiple identities, zero trace, infinite patience.' }, + { name: 'Performs Feats of Strength',icon: '🏋️', settings: ['all'], playstyle: 'Immense physical power applied with precision — lift, throw, and crush at will.' }, + { name: 'Pilots Starcraft', icon: '🚀', settings: ['numenera','scifi'], playstyle: 'Born in the cockpit; fly, fight, and fix any spacecraft under any conditions.' }, + { name: 'Plays Too Many Games', icon: '♟️', settings: ['all'], playstyle: 'Master tactician whose life is one long game — always thinking three moves ahead.' }, + { name: 'Rages', icon: '😡', settings: ['all'], playstyle: 'Berserker who trades control for devastating fury, unstoppable in the heat of battle.' }, + { name: 'Rides the Lightning', icon: '⚡', settings: ['all'], playstyle: 'Creates and discharges electrical power to shock, stun, and energize.' }, + { name: 'Runs Away', icon: '🏃', settings: ['all'], playstyle: 'Escape artist who knows when to fight and when to vanish — and survives because of it.' }, + { name: 'Sailed Beneath the Jolly Roger', icon: '☠️', settings: ['fantasy','modern'], playstyle: 'Pirate captain: seafaring, swashbuckling, plundering — and proud of every bit of it.' }, + { name: 'Scavenges', icon: '♻️', settings: ['all'], playstyle: 'Improviser who finds tools, weapons, and value where others see only junk.' }, + { name: 'Sees Beyond', icon: '🔮', settings: ['fantasy','numenera'], playstyle: 'Peer into futures, pasts, and hidden truths — visions come unbidden and change everything.' }, + { name: 'Separates Mind From Body', icon: '🕯️', settings: ['all'], playstyle: 'Astral projection lets your mind travel freely while your body waits in safety.' }, + { name: 'Shreds the Walls of the World', icon: '🌀', settings: ['numenera','scifi','fantasy'], playstyle: 'Tear open dimensional rifts; step between spaces or hurl enemies into the void.' }, + { name: 'Shrinks to Microscopic Size', icon: '🔬', settings: ['numenera','scifi'], playstyle: 'Reduce to molecular scale to infiltrate, sabotage, and strike unseen.' }, + { name: 'Siphons Power', icon: '🌊', settings: ['fantasy','numenera','scifi'], playstyle: 'Drain energy from enemies, machines, or the environment and redirect it as your own.' }, + { name: 'Slays Monsters', icon: '🐉', settings: ['fantasy','numenera'], playstyle: 'Dedicated monster hunter with encyclopaedic knowledge of creature weaknesses.' }, + { name: 'Solves Mysteries', icon: '🔎', settings: ['all'], playstyle: 'Master of deduction who reads evidence to reconstruct hidden truths.' }, + { name: 'Speaks for the Land', icon: '🌿', settings: ['fantasy','numenera'], playstyle: 'Voice of nature itself; plants, winds, and stones obey your call.' }, + { name: 'Stands Like a Bastion', icon: '🏰', settings: ['all'], playstyle: 'An unmovable point of order amid chaos — your resolve is physically manifest.' }, + { name: 'Takes Animal Shape', icon: '🦊', settings: ['fantasy','numenera'], playstyle: 'Shapeshifter who borrows the bodies of beasts at will.' }, + { name: 'Talks to Machines', icon: '🖥️', settings: ['numenera','scifi'], playstyle: 'Converse with and command technology as naturally as breathing.' }, + { name: 'Throws with Deadly Accuracy', icon: '🎯', settings: ['all'], playstyle: 'Any object becomes a lethal projectile in your hands; range is not a problem.' }, + { name: 'Thunders', icon: '🔊', settings: ['all'], playstyle: 'Sonic blasts shatter stone, stun enemies, and shake the air for miles.' }, + { name: 'Travels Through Time', icon: '⏳', settings: ['numenera','scifi','fantasy'], playstyle: 'Skip forward, reach back, and observe branching moments of history.' }, + { name: 'Wears a Sheen of Ice', icon: '❄️', settings: ['fantasy','numenera','scifi'], playstyle: 'Commands the wintry power of cold, frost, and absolute zero.' }, + { name: 'Wields Power with Precision', icon: '🎯', settings: ['all'], playstyle: 'Your special abilities are calibrated to a razor edge — max effect, minimal collateral.' }, + { name: 'Wields Two Weapons', icon: '🤺', settings: ['all'], playstyle: 'Dual-wielder who overwhelms enemies with relentless twin-blade attacks.' }, + { name: 'Works Miracles', icon: '💫', settings: ['fantasy','numenera'], playstyle: 'Healer and restorer who defies fatal wounds and brings the near-dead back.' }, + { name: 'Works the Back Alleys', icon: '🌆', settings: ['all'], playstyle: 'Street-level operative: fences, fixers, and the shadow economy are your domain.' }, + { name: 'Worships the Lunar Nodes', icon: '🌕', settings: ['fantasy','numenera'], playstyle: 'Moon-bound cultist whose power waxes and wanes with the celestial cycle.' }, + { name: 'Would Rather Be Reading', icon: '📖', settings: ['all'], playstyle: 'Bookish scholar who always knows exactly the right fact at exactly the right moment.' }, ]; const DIE_TYPES = [4, 6, 8, 10, 12, 20, 100]; @@ -2088,9 +2187,10 @@ function updateCharSentence() { function makeSelGrid(gridId, items, getCurrent, setCurrent) { const grid = document.getElementById(gridId); + grid.innerHTML = ''; items.forEach(item => { const card = document.createElement('div'); - card.className = 'sel-card'; + card.className = 'sel-card' + (getCurrent()?.name === item.name ? ' selected' : ''); card.innerHTML = `<span class="icon">${item.icon}</span><span class="name">${item.name}</span>`; card.title = item.note || item.playstyle || ''; card.addEventListener('click', () => { @@ -2104,10 +2204,24 @@ function makeSelGrid(gridId, items, getCurrent, setCurrent) { }); } +function filteredFoci() { + return FOCI.filter(f => f.settings.includes('all') || f.settings.includes(selectedSetting)); +} + +function rebuildFocusGrid() { + const filtered = filteredFoci(); + if (selectedFocus && !filtered.find(f => f.name === selectedFocus.name)) { + selectedFocus = null; + } + makeSelGrid('focus-grid', filtered, () => selectedFocus, v => { selectedFocus = v; }); + updateCharSentence(); + checkBeginReady(); +} + async function buildCreationScreen() { makeSelGrid('descriptor-grid', DESCRIPTORS, () => selectedDescriptor, v => { selectedDescriptor = v; }); makeSelGrid('type-grid', TYPES, () => selectedType, v => { selectedType = v; }); - makeSelGrid('focus-grid', FOCI, () => selectedFocus, v => { selectedFocus = v; }); + rebuildFocusGrid(); document.getElementById('begin-btn').addEventListener('click', () => beginAdventure()); document.getElementById('change-key-btn').addEventListener('click', () => openSetupModal(getSharedBlob() ? 'shared' : 'own')); } @@ -2156,7 +2270,7 @@ async function beginAdventure() { cypherLimit: selectedType.cypherLimit, cyphers: [], skills: [], - abilities: [], + abilities: [...(selectedType.startingAbilities || [])], equipment: [...selectedType.equipment], shins: 10, xp: 0, @@ -2263,6 +2377,21 @@ function updateSidebar() { cypherList.classList.remove('cypher-limit-warn'); } + // Abilities + const abList = document.getElementById('abilities-list'); + abList.innerHTML = ''; + if (c.abilities?.length) { + c.abilities.forEach(ab => { + const div = document.createElement('div'); + div.className = 'ability-entry'; + div.innerHTML = `<span class="ability-name">${ab.name}${ab.cost ? ` <span class="ability-cost">(${ab.cost})</span>` : ''}</span>` + + `<span class="ability-desc">${ab.description}</span>`; + abList.appendChild(div); + }); + } else { + abList.innerHTML = '<span style="font-size:0.78rem;color:var(--text-dim);font-style:italic">None yet.</span>'; + } + // Equipment const list = document.getElementById('inventory-list'); list.innerHTML = ''; @@ -2523,6 +2652,7 @@ document.getElementById('setting-row').addEventListener('click', e => { document.querySelectorAll('.setting-btn').forEach(b => b.classList.remove('selected')); btn.classList.add('selected'); selectedSetting = btn.dataset.setting; + rebuildFocusGrid(); }); function rollDice() {