7 Commits

Author SHA1 Message Date
9ec45ad556 fix: Format Page 72
Was *really* wonky because of a flowchart that just didn't get copied
correctly by the AI
2026-06-26 21:01:59 -04:00
5327b524d2 feat: Multiple book improvements
- Format spell tables like they are in the book
- Add permalinks to headers
- Formatting
- Book-wide search
2026-06-26 20:50:36 -04:00
7f81f85735 feat: Add template spell picker from spells.yml; polish spell table
All checks were successful
Deploy / deploy (push) Successful in 1m20s
- Add yaml-loader; import data/spells.yml at build time
- Add SpellTemplate/SpellsFile types to globals.d.ts
- Add 'Add Template Spell' button that opens a modal picker pre-filling
  all spell fields from the YAML data
- Move spell data files into data/ directory
- Split spell rows into inputs row + full-width notes row (colspan=6)
- Shrink delete column to fit-content; bold spell name input
- Add class column to spell table; change MP cost to free-text input
- Auto-resize spell notes textarea on load and on input
- Add 10px padding between spells for visual separation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 17:54:36 -04:00
d655ad4afc fix: Expand spell notes textarea full-width; add inter-spell padding
Split each spell into two table rows: the inputs row (name, MP, targets,
duration, delete) and a full-width notes row (colspan=5). Adds 10px
padding above/below each spell for visual separation between entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 17:54:36 -04:00
5643b31fb0 fix: Auto-resize spell/class textareas on load; shrink delete column
- Add autoResize helper and wire it via ref callback (mount) and onInput
  (typing) to spell notes and class skills textareas
- Change .spell-del-col from width:40px to width:1px + white-space:nowrap
  so the delete button column shrink-wraps to its content

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-26 17:54:36 -04:00
85c02d7f7f ci: Atomically replace share-svc binary on deploy
All checks were successful
Deploy / deploy (push) Successful in 48s
scp overwrites in place, which fails with ETXTBSY ("text file busy") on
repeat deploys because systemd is executing /usr/local/bin/share-svc.
Copy to a temp path, chmod, then mv it over the destination so rename(2)
swaps the dir entry without touching the busy inode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 21:48:21 -04:00
de37809eec fix: Copy share URL over HTTP via execCommand fallback
Some checks failed
Deploy / deploy (push) Failing after 44s
navigator.clipboard is only available in a secure context (HTTPS or
localhost), so the Copy URL button threw over plain HTTP. Fall back to
the deprecated document.execCommand("copy") when the Clipboard API is
unavailable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 01:41:17 +00:00
40 changed files with 26524 additions and 21270 deletions

View File

@@ -64,8 +64,16 @@ jobs:
- name: Copy backend binary + unit file
run: |
# Copy to a temp path then atomically rename into place. You can't
# overwrite a running executable in place (ETXTBSY / "Text file busy"),
# but rename(2) just swaps the directory entry while the old inode keeps
# serving the running process until the restart step picks up the new
# binary. chmod first so the exec bit survives the rename (scp doesn't
# preserve mode without -p).
scp -i ~/.ssh/deploy_key server/share-svc \
"$DEPLOY_USER@$DEPLOY_HOST:/usr/local/bin/share-svc"
"$DEPLOY_USER@$DEPLOY_HOST:/usr/local/bin/share-svc.new"
ssh -i ~/.ssh/deploy_key "$DEPLOY_USER@$DEPLOY_HOST" \
'chmod 0755 /usr/local/bin/share-svc.new && mv -f /usr/local/bin/share-svc.new /usr/local/bin/share-svc'
scp -i ~/.ssh/deploy_key server/share-svc.service \
"$DEPLOY_USER@$DEPLOY_HOST:/etc/systemd/system/share-svc.service"

View File

@@ -18,17 +18,26 @@
<tr>
<td>Minor</td>
<td>5</td>
<td>Enemy commanders, unique and infamous monsters, and antagonists causing trouble in small settlements.</td>
<td>
Enemy commanders, unique and infamous monsters, and antagonists causing
trouble in small settlements.
</td>
</tr>
<tr>
<td>Major</td>
<td>10</td>
<td>Tyrants, powerful mages, legendary creatures, and entities that threaten entire countries.</td>
<td>
Tyrants, powerful mages, legendary creatures, and entities that threaten
entire countries.
</td>
</tr>
<tr>
<td>Supreme</td>
<td>15</td>
<td>Archdemons, immortal entities, and alien deities bent on destruction or domination.</td>
<td>
Archdemons, immortal entities, and alien deities bent on destruction or
domination.
</td>
</tr>
</tbody>
</table>
@@ -44,25 +53,33 @@
<tbody>
<tr>
<td>Escape</td>
<td>A Villain may spend 1 Ultima Point to safely leave the scene — the Game Master describes how they do so,
perhaps leaving a few henchmen behind to "keep the heroes company".</td>
<td>
A Villain may spend 1 Ultima Point to safely leave the scene — the Game
Master describes how they do so, perhaps leaving a few henchmen behind
to "keep the heroes company".
</td>
</tr>
<tr>
<td>Invoke Trait</td>
<td>After performing a Check, a Villain may spend 1 Ultima Point to invoke one of their Traits and reroll one or
both dice (this follows the same rules seen on <a href="/books/core/#page-46">page 46</a>).</td>
<td>
After performing a Check, a Villain may spend 1 Ultima Point to invoke
one of their Traits and reroll one or both dice (this follows the same
rules seen on <a href="/books/core/#page-46">page 46</a>).
</td>
</tr>
<tr>
<td>Recovery</td>
<td>A Villain may use an action and spend 1 Ultima Point to recover from all status effects and also recover 50
Mind Points.</td>
<td>
A Villain may use an action and spend 1 Ultima Point to recover from all
status effects and also recover 50 Mind Points.
</td>
</tr>
</tbody>
</table>
<p>
While Player Characters can gain Fabula Points in several ways, Villains
cannot recharge their Ultima Points — in a way, they represent the Villain's
determination and will to pursue their objectives. A Villain with no
remaining Ultima Points is no longer a Villain and becomes equivalent to any
other NPC; alternatively, they may choose to escalate (see next page).
</p>
determination and will to pursue their objectives. A Villain with no remaining
Ultima Points is no longer a Villain and becomes equivalent to any other NPC;
alternatively, they may choose to escalate (see next page).
</p>

View File

@@ -1,22 +1,20 @@
<h2>SHOULD THIS VILLAIN ESCALATE?</h2>
<p>
When choosing whether a Villain will escalate, the Game Master should
consider the antagonist's importance within the story, their personality,
and the way the heroes chose to confront them: a Villain's escalation shows
that the heroes might win a conflict but still make the situation worse.
When choosing whether a Villain will escalate, the Game Master should consider
the antagonist's importance within the story, their personality, and the way
the heroes chose to confront them: a Villain's escalation shows that the
heroes might win a conflict but still make the situation worse.
</p>
<p>
Most of the time it's best to simply let a Villain be defeated: maybe their
resolve is broken, maybe they even realized they were wrong and will turn
into an ally.
resolve is broken, maybe they even realized they were wrong and will turn into
an ally.
</p>
<p>
Escalating your Villains sparingly will make them more memorable and also
ensure that Players feel like their actions have an impact on the world.
</p>
<p>
In general, you should only have two to four escalations per campaign.
</p>
<p>In general, you should only have two to four escalations per campaign.</p>
<h2>VILLAIN AND FABULA POINTS</h2>
<p>
@@ -26,32 +24,32 @@
<ul>
<li>
<strong>Multiple identities.</strong> If a Villain has more than one
identity, the heroes only receive Fabula Points when they appear using
their "antagonist persona". Once the deception is exposed, however, they
will begin awarding Fabula Points every time they appear on a scene,
whether disguised or else.
identity, the heroes only receive Fabula Points when they appear using their
"antagonist persona". Once the deception is exposed, however, they will
begin awarding Fabula Points every time they appear on a scene, whether
disguised or else.
</li>
<li>
<strong>Multiple Villains.</strong> If two or more Villains appear during
the same scene, each Player Character will gain 1 Fabula Point per
Villain, up to a maximum of 3 Fabula Points per Player Character earned
this way during the same scene.
the same scene, each Player Character will gain 1 Fabula Point per Villain,
up to a maximum of 3 Fabula Points per Player Character earned this way
during the same scene.
</li>
<li>
<strong>Back-to-back scenes with Villains.</strong> If a Villain's
presence extends for several consecutive scenes, the Player Characters
only gain 1 Fabula Point at the beginning, and no additional Fabula Points
for the following scenes.
<strong>Back-to-back scenes with Villains.</strong> If a Villain's presence
extends for several consecutive scenes, the Player Characters only gain 1
Fabula Point at the beginning, and no additional Fabula Points for the
following scenes.
</li>
</ul>
<p>
Whenever the heroes are running low on Fabula Points, the Game Master should
run a Game Master scene and show a Villain in action — this is a great way
to push the story forward while also giving the heroes a bit more "fuel".
run a Game Master scene and show a Villain in action — this is a great way to
push the story forward while also giving the heroes a bit more "fuel".
</p>
<blockquote>
<p>
Great evil is born when good intentions are met with mistrust and despair.
</p>
</blockquote>
</blockquote>

View File

@@ -1,89 +1,95 @@
<h1>ELEMENTALIST SPELLS</h1>
<h2>SPELL</h2>
<div class="spell-entry">
<h3>Elemental Shroud</h3>
<p><strong>Cost:</strong> 5</p>
<p><strong>Targets:</strong> T</p>
<p><strong>Duration:</strong> Up to three creatures</p>
<p><strong>Action:</strong> Scene</p>
<p>
You weave magical energy and protect the targets from the fury of the
elements.
</p>
<p>
Choose a damage type: air, bolt, earth, fire or ice. Until this spell ends,
each target gains Resistance against the chosen damage type.
</p>
</div>
<div class="spell-entry">
<h3>Elemental Weapon</h3>
<p><strong>Cost:</strong> 10</p>
<p><strong>Targets:</strong> One weapon</p>
<p><strong>Duration:</strong> Scene</p>
<p>
You imbue a weapon with elemental energy. Choose a damage type: air, bolt,
earth, fire, or ice. Until this spell ends, all damage dealt by the weapon
becomes of the chosen damage type.
</p>
<ul>
<li>
If you have that weapon equipped while you cast this spell, you may
perform a free attack with it as part of the same action.
</li>
<li>
This spell can only be cast on a weapon equipped by a willing creature.
</li>
</ul>
</div>
<div class="spell-entry">
<h3>Flare</h3>
<p><strong>Cost:</strong> rr 20</p>
<p><strong>Targets:</strong> One creature</p>
<p><strong>Duration:</strong> Instantaneous</p>
<p>
You channel a single ray of fire towards your foe, its temperature so high
that it will pierce through most defenses. The target suffers
<strong>【HR + 25】</strong> fire damage.
</p>
<p>Damage dealt by this spell ignores Resistances.</p>
</div>
<div class="spell-entry">
<h3>Fulgur</h3>
<p><strong>Cost:</strong> rr 10</p>
<p><strong>Targets:</strong> × T</p>
<p><strong>Duration:</strong> Up to three creatures</p>
<p><strong>Action:</strong> Instantaneous</p>
<p>
You weave electricity into a wave of crackling bolts. Each target hit by
this spell suffers <strong>【HR + 15】</strong> bolt damage.
</p>
<p class="opportunity">
<strong>Opportunity:</strong> Each target hit by this spell suffers dazed.
</p>
</div>
<div class="spell-entry">
<h3>Glacies</h3>
<p><strong>Cost:</strong> rr 10</p>
<p><strong>Targets:</strong> × T</p>
<p><strong>Duration:</strong> Up to three creatures</p>
<p><strong>Action:</strong> Instantaneous</p>
<p>
You coat your foes under a thick layer of frost. Each target hit by this
spell suffers <strong>【HR + 15】</strong> ice damage.
</p>
<p class="opportunity">
<strong>Opportunity:</strong> Each target hit by this spell suffers slow.
</p>
</div>
<div class="spell-entry">
<h3>Iceberg</h3>
<p><strong>Cost:</strong> rr 20</p>
<p><strong>Targets:</strong> One creature</p>
<p><strong>Duration:</strong> Instantaneous</p>
<p>
A pillar of ice magic envelops your foe, suddenly dropping their body
temperature to a critical level. The target suffers
<strong>【HR + 25】</strong> ice damage.
</p>
<p>Damage dealt by this spell ignores Resistances.</p>
</div>
<h2>Elementalist Spells</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Cost</th>
<th>Targets</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Elemental Shroud</td>
<td>5 x T</td>
<td>Up to three creatures</td>
<td>scene</td>
</tr>
<tr>
<td colspan="4">
You weave magical energy and protect the targets from the fury of the
elements. Choose a damage type: air, bolt, earth, fire or ice. Until
this spell ends, each target gains Resistance against the chosen damage
type.
</td>
</tr>
<tr>
<td>Elemental Weapon</td>
<td>10</td>
<td>One weapon</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You imbue a weapon with elemental energy. Choose a damage type: air,
bolt, earth, fire, or ice. Until this spell ends, all damage dealt by
the weapon becomes of the chosen damage type. If you have that weapon
equipped while you cast this spell, you may perform a free attack with
it as part of the same action. This spell can only be cast on a weapon
equipped by a willing creature.
</td>
</tr>
<tr>
<td>Flare</td>
<td>20</td>
<td>One creature</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You channel a single ray of fire towards your foe, its temperature so
high that it will pierce through most defenses. The target suffers【 HR
+ 25】 fire damage. Damage dealt by this spell ignores Resistances.
</td>
</tr>
<tr>
<td>Fulgur</td>
<td>10 x T</td>
<td>Up to three creatures</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You weave electricity into a wave of crackling bolts. Each target hit by
this spell suffers 【HR + 15】 bolt damage. Opportunity: Each target hit
by this spell suffers dazed.
</td>
</tr>
<tr>
<td>Glacies</td>
<td>10 x T</td>
<td>Up to three creatures</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You coat your foes under a thick layer of frost. Each target hit by this
spell suffers【 HR + 15】 ice damage. Opportunity: Each target hit by
this spell suffers slow.
</td>
</tr>
<tr>
<td>Iceberg</td>
<td>20</td>
<td>One creature</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
A pillar of ice magic envelops your foe, suddenly dropping their body
temperature to a critical level. The target suffers【 HR + 25】 ice
damage. Damage dealt by this spell ignores Resistances.
</td>
</tr>
</tbody>
</table>

View File

@@ -1,91 +1,97 @@
<div>
<h2>Spell Catalogue</h2>
<section>
<h3>Ignis</h3>
<p>
<strong>Metadata:</strong> rr 10 × T | Up to three creatures |
Instantaneous
</p>
<p>
You unleash a searing barrage against your foes, conjuring flames out of
thin air. Each target hit by this spell suffers
<strong>【HR + 15】 fire damage</strong>.
</p>
<p><em>Opportunity:</em> Each target hit by this spell suffers shaken.</p>
</section>
<section>
<h3>Soaring Strike</h3>
<p><strong>Metadata:</strong> 10 | Self | Instantaneous</p>
<p>
The wind carries your strikes across the battlefield. You may immediately
perform a free attack with a melee weapon you have equipped. This attack
may target creatures that can only be targeted by ranged attacks.
</p>
<ul>
<li>
If you used a weapon belonging to the brawling or spear Category for
this attack, it deals 5 extra damage.
</li>
<li>
If you hit a flying target with this attack, you may force them to land
immediately.
</li>
</ul>
</section>
<section>
<h3>Terra</h3>
<p>
<strong>Metadata:</strong> rr 10 × T | Up to three creatures |
Instantaneous
</p>
<p>
Spires of jagged rock erupt from the ground beneath your foes, closing
around them. Each target hit by this spell suffers
<strong>【HR + 15】 earth damage</strong>. This spell cannot target
creatures who are flying, floating, falling, or otherwise in mid-air.
</p>
<p>
<em>Opportunity:</em> Each target hit by this spell performs one fewer
action on their next turn (to a minimum of 0 actions).
</p>
</section>
<section>
<h3>Thunderbolt</h3>
<p><strong>Metadata:</strong> rr 20 | One creature | Instantaneous</p>
<p>
You send lightning striking at your foe. The target suffers
<strong>【HR + 25】 bolt damage</strong>.
</p>
<p>Damage dealt by this spell ignores Resistances.</p>
</section>
<section>
<h3>Ventus</h3>
<p>
<strong>Metadata:</strong> rr 10 × T | Up to three creatures |
Instantaneous
</p>
<p>
You summon the power of winds against your enemy. Each target hit by this
spell suffers <strong>【HR + 15】 air damage</strong>.
</p>
<p>
<em>Opportunity:</em> Each flying target hit by this spell is forced to
land immediately.
</p>
</section>
<section>
<h3>Vortex</h3>
<p><strong>Metadata:</strong> 10 | Self | Scene</p>
<p>
A roaring gale surrounds you, blowing away arrows and bullets. Until this
spell ends, you gain a +2 bonus to your Defense against ranged attacks.
</p>
</section>
<aside>
<h4>Notes</h4>
<p>
<strong>cc</strong> Spells marked with <code>rr</code> are offensive
spells and require Magic Checks!
</p>
</aside>
</div>
<h2>Elementalist Spells (Cont.)</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Cost</th>
<th>Targets</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ignis</td>
<td>10 x T</td>
<td>Up to three creatures</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You unleash a searing barrage against your foes, conjuring flames out of
thin air. Each target hit by this spell suffers【 HR + 15】 fire damage.
Opportunity: Each target hit by this spell suffers shaken.
</td>
</tr>
<tr>
<td>Soaring Strike</td>
<td>10</td>
<td>Self</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
The wind carries your strikes across the battlefield. You may
immediately perform a free attack with a melee weapon you have equipped.
This attack may target creatures that can only be targeted by ranged
attacks. If you used a weapon belonging to the brawling or spear
Category for this attack, it deals 5 extra damage. If you hit a flying
target with this attack, you may force them to land immediately.
</td>
</tr>
<tr>
<td>Terra</td>
<td>10 x T</td>
<td>Up to three creatures</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
Spires of jagged rock erupt from the ground beneath your foes, closing
around them. Each target hit by this spell suffers【 HR + 15】 earth
damage. This spell cannot target creatures who are flying, floating,
falling, or otherwise in mid-air. Opportunity: Each target hit by this
spell performs one fewer action on their next turn (to a minimum of 0
actions).
</td>
</tr>
<tr>
<td>Thunderbolt</td>
<td>20</td>
<td>One creature</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You send lightning striking at your foe. The target suffers【 HR + 25】
bolt damage. Damage dealt by this spell ignores Resistances.
</td>
</tr>
<tr>
<td>Ventus</td>
<td>10 x T</td>
<td>One creature</td>
<td>Instantaneosu</td>
</tr>
<tr>
<td colspan="4">
You summon the power of winds against your enemy. Each target hit by
this spell suffers【 HR + 15】 air damage. Opportunity: Each flying
target hit by this spell is forced to land immediately.
</td>
</tr>
<tr>
<td>Vortex</td>
<td>10</td>
<td>Self</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
A roaring gale surrounds you, blowing away arrows and bullets. Until
this spell ends, you gain a +2 bonus to your Defense against ranged
attacks.
</td>
</tr>
</tbody>
</table>

View File

@@ -1,98 +1,112 @@
<h1>Spell List</h1>
<section>
<h2>Acceleration</h2>
<p>
<strong>Cost:</strong> 20 | <strong>Type:</strong> One creature |
<strong>Scene:</strong>
</p>
<p>
You bend the fabric of time. Until this spell ends, the target gains the
ability to perform a single additional action during each of their turns.
Once the target has performed a total of two additional actions granted by
this spell, this spell ends.
</p>
</section>
<section>
<h2>Anomaly</h2>
<p>
<strong>Cost:</strong> 20 | <strong>Type:</strong> One creature |
<strong>Scene:</strong>
</p>
<p>
You alter the very nature of your target. Until this spell ends, if the
target would suffer damage of a type they Absorb or are Immune to, they are
instead treated as if they were Vulnerable to that damage type. Once that
happens, this spell ends.
</p>
</section>
<section>
<h2>Dark Weapon</h2>
<p>
<strong>Cost:</strong> 10 | <strong>Type:</strong> One equipped weapon |
<strong>Scene:</strong>
</p>
<p>
You imbue a weapon with dark energy. Until this spell ends, all damage dealt
by the weapon becomes of the dark type. If you have that weapon equipped
while you cast this spell, you may perform a free attack with it as part of
the same action.
</p>
<p class="restrictions">
<em
>This spell can only be cast on a weapon equipped by a willing
creature.</em
>
</p>
</section>
<section>
<h2>Dispel</h2>
<p>
<strong>Cost:</strong> 10 | <strong>Type:</strong> One creature |
<strong>Instantaneous:</strong>
</p>
<p>
You release a wave of negative energy and cleanse all magic from a creature.
If the target is affected by one or more spells with a duration of Scene,
they are no longer affected by any of those spells instead.
</p>
</section>
<section>
<h2>Divination</h2>
<p>
<strong>Cost:</strong> 10 | <strong>Type:</strong> Self |
<strong>Scene:</strong>
</p>
<p>
You glimpse briefly into the future. Until this spell ends, after a creature
you can see performs a Check, if it was not a fumble nor a critical success,
you may force that creature to reroll both dice. Once you have forced two
rerolls this way, this spell ends.
</p>
</section>
<section>
<h2>Drain Spirit</h2>
<p>
<strong>Cost:</strong> 5 | <strong>Type:</strong> One creature |
<strong>Instantaneous:</strong>
</p>
<p>
You consume a creature's psyche. The target loses 【HR + 15】 Mind Points.
Then, you recover an amount of Mind Points equal to half the Mind Points
loss they suffered (if the loss was reduced to 0 in some way, you recover
none).
</p>
</section>
<section>
<h2>Drain Vigor</h2>
<p>
<strong>Cost:</strong> 10 | <strong>Type:</strong> One creature |
<strong>Instantaneous:</strong>
</p>
<p>
You steal another creature's life force. The target suffers 【HR + 15 】
dark damage. Then, you recover an amount of Hit Points equal to half the Hit
Points loss they suffered (if the loss was reduced to 0 in some way, you
recover none).
</p>
</section>
<aside class="collection-info"><h3>Entropist Spells</h3></aside>
<h2>Entropist Spells</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Cost</th>
<th>Targets</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Acceleration</td>
<td>20</td>
<td>One creature</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You bend the fabric of time. Until this spell ends, the target gains the
ability to perform a single additional action during each of their
turns. Once the target has performed a total of two additional actions
granted by this spell, this spell ends.
</td>
</tr>
<tr>
<td>Anomaly</td>
<td>20</td>
<td>One creature</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You alter the very nature of your target. Until this spell ends, if the
target would suffer damage of a type they Absorb or are Immune to, they
are instead treated as if they were Vulnerable to that damage type. Once
that happens, this spell ends.
</td>
</tr>
<tr>
<td>Dark Weapon</td>
<td>10</td>
<td>One equipped weapon</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You imbue a weapon with dark energy. Until this spell ends, all damage
dealt by the weapon becomes of the dark type. If you have that weapon
equipped while you cast this spell, you may perform a free attack with
it as part of the same action. This spell can only be cast on a weapon
equipped by a willing creature.
</td>
</tr>
<tr>
<td>Dispel</td>
<td>10</td>
<td>One creature</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You release a wave of negative energy and cleanse all magic from a
creature. If the target is affected by one or more spells with a
duration of Scene, they are no longer affected by any of those spells
instead.
</td>
</tr>
<tr>
<td>Divination</td>
<td>10</td>
<td>Self</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You glimpse briefly into the future. Until this spell ends, after a
creature you can see performs a Check, if it was not a fumble nor a
critical success, you may force that creature to reroll both dice. Once
you have forced two rerolls this way, this spell ends.
</td>
</tr>
<tr>
<td>Drain Spirit</td>
<td>5</td>
<td>One creature</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You consume a creature's psyche. The target loses【 HR + 15】 Mind
Points. Then, you recover an amount of Mind Points equal to half the
Mind Points loss they suffered (if the loss was reduced to 0 in some
way, you recover none).
</td>
</tr>
<tr>
<td>Drain Vigor</td>
<td>10</td>
<td>One creature</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You steal another creature's life force. The target suffers【 HR + 15】
dark damage. Then, you recover an amount of Hit Points equal to half the
Hit Points loss they suffered (if the loss was reduced to 0 in some way,
you recover none).
</td>
</tr>
</tbody>
</table>

View File

@@ -1,75 +1,102 @@
<h2>Gamble up to 20</h2>
<p><strong>Type:</strong> Special</p>
<p><strong>Target:</strong> Instantaneous</p>
<p class="duration">
You summon a vortex of chaotic energy. Roll your current Willpower die once
for every 10 Mind Points spent while casting this spell, then keep the single
die you prefer: the number on that die determines the effects of this spell.
</p>
<ol>
<li>
<strong>1:</strong> You lose half of your current Hit Points and half of
your current Mind Points.
</li>
<li>
<strong>2-3:</strong> Each creature present on the scene, including
yourself, suffers poisoned.
</li>
<li>
<strong>4-6:</strong> Each creature present on the scene, including
yourself, suffers slow.
</li>
<li>
<strong>7-8:</strong> Choose up to three creatures you can see: each of them
recovers 50 Hit Points and also recovers from all status effects.
</li>
<li>
<strong>9+:</strong> Choose any number of creatures you can see: each of
them suffers 30 damage. The damage type is determined randomly by rolling a
d6:
</li>
<ul>
<li>1. air</li>
<li>2. bolt</li>
<li>3. dark</li>
<li>4. earth</li>
<li>5. fire</li>
<li>6. poison</li>
</ul>
</ol>
<h2>Mirror 10</h2>
<p><strong>Type:</strong> One creature</p>
<p><strong>Scene Duration:</strong> Until this spell ends</p>
<p>
You twist the laws of magic. Until this spell ends, if an offensive spell is
cast on the target, the creature who cast that offensive spell will be
targeted in their stead (any other targets of the offensive spell will be
targeted as normal). Once that happens, this spell ends.
</p>
<h2>Omega rr 20</h2>
<p><strong>Type:</strong> One creature</p>
<p><strong>Duration:</strong> Instantaneous</p>
<p>
You invoke doom on your foe, turning strength into frailty. The target loses
an amount of Hit Points equal to
<code>【20 + half the target's level】</code>.
</p>
<h2>Stop rr 10</h2>
<p><strong>Type:</strong> One creature</p>
<p><strong>Duration:</strong> Instantaneous</p>
<p>
You trap a foe inside a circle of altered time and space. The target will
perform one fewer action on their next turn (to a minimum of 0 actions).
</p>
<h2>Umbra rr 10 × T</h2>
<p><strong>Type:</strong> Up to three creatures</p>
<p><strong>Duration:</strong> Instantaneous</p>
<p>
A storm of dark energy turns matter into ash. Each target hit by this spell
suffers <code>【HR + 15】</code> dark damage.
</p>
<p><em>Opportunity:</em> Each target hit by this spell suffers weak.</p>
<h3>Notes</h3>
<ul>
<li>Spells marked with rr are offensive spells and require Magic Checks!</li>
</ul>
<h2>Entropist Spells (Cont.)</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Cost</th>
<th>Targets</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Gamble</td>
<td>Up to 20</td>
<td>Special</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You summon a vortex of chaotic energy. Roll your current Willpower die
once for every 10 Mind Points spent while casting this spell, then keep
the single die you prefer: the number on that die determines the effects
of this spell.
<ul>
<li>
1) You lose half of your current Hit Points and half of your current
Mind Points.
</li>
<li>
2-3) Each creature present on the scene, including yourself, suffers
poisoned.
</li>
<li>
4-6) Each creature present on the scene, including yourself, suffers
slow.
</li>
<li>
7-8) Choose up to three creatures you can see: each of them recovers
50 Hit Points and also recovers from all status effects.
</li>
<li>
9+) Choose any number of creatures you can see: each of them suffers
30 damage. The damage type is determined randomly by rolling a d6:
1. air 2. bolt 3. dark 4. earth 5. fire 6. poison
</li>
</ul>
</td>
</tr>
<tr>
<td>Mirror</td>
<td>10</td>
<td>One creature</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You twist the laws of magic. Until this spell ends, if an offensive (r)
spell is cast on the target, the creature who cast that offensive spell
will be targeted in their stead (any other targets of the offensive
spell will be targeted as normal). Once that happens, this spell ends.
</td>
</tr>
<tr>
<td>Omega</td>
<td>20</td>
<td>One creature</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You invoke doom on your foe, turning strength into frailty. The target
loses an amount of Hit Points equal to【 20 + half the target's level】
</td>
</tr>
<tr>
<td>Stop</td>
<td>10</td>
<td>One creature</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You trap a foe inside a circle of altered time and space. The target
will perform one fewer action on their next turn (to a minimum of 0
actions).
</td>
</tr>
<tr>
<td>Umbra</td>
<td>10 x T</td>
<td>Up to three creatures</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
A storm of dark energy turns matter into ash. Each target hit by this
spell suffers 【HR + 15】 dark damage. Opportunity: Each target hit by
this spell suffers weak.
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1 @@
<em> This page is intentionally left blank. </em>

View File

@@ -1,20 +1,20 @@
<h1>Spiritist</h1>
<section>
<h2>Related Terms</h2>
<p><strong>ALSO:</strong> Healer, Priest, Witch</p>
<div class="motto-block"><em>"Show me the truths of your heart."</em></div>
<h3>Definition</h3>
<p>
Spiritists have developed a powerful connection with the raw aspects of
soul: emotion, energy, life, and death. They wield power both miraculous and
frightening, and are quite often affiliated with cults or religions.
</p>
</section>
<section class="further-details">
<h3>Nature of Abilities</h3>
<p>
Several worlds see the powers of Spiritism as gifts from a deity or proof
that a person's heart is pure; however, there is no definite proof of these
abilities coming from anything but discipline, empathy, and focus.
</p>
</section>
<p><strong>ALSO:</strong> Healer, Priest, Witch</p>
<h3>Definition</h3>
<p>
Spiritists have developed a powerful connection with the raw aspects of soul:
emotion, energy, life, and death. They wield power both miraculous and
frightening, and are quite often affiliated with cults or religions.
</p>
<h3>Nature of Abilities</h3>
<p>
Several worlds see the powers of Spiritism as gifts from a deity or proof that
a person's heart is pure; however, there is no definite proof of these
abilities coming from anything but discipline, empathy, and focus.
</p>
<blockquote>Show me the truths of your heart.</blockquote>

View File

@@ -1,14 +1,17 @@
<h1>SPIRITIST BENEFITS AND SKILLS</h1>
<h2>Free Benefits</h2>
<h2>SPIRITIST BENEFITS AND SKILLS</h2>
<h3>Free Benefits:</h3>
<ul>
<li>Permanently increase your maximum Mind Points by 5.</li>
<li>
You may perform Rituals whose effects fall within the Ritualism discipline.
</li>
</ul>
<hr />
<h2>SPIRITIST SKILLS</h2>
<h3>HEALING POWER (çç2)</h3>
<h3>SPIRITIST SKILLS</h3>
<h4>HEALING POWER</h4>
<p>
When you cast a spell that targets one or more allies, if you have an arcane
weapon equipped, you may have each of those allies recover an amount of Hit
@@ -16,25 +19,29 @@
<strong>【SL, multiplied by the number of Bonds you have】</strong>. This
healing is separate from any healing caused by the effects of the spell.
</p>
<h3>RITUAL SPIRITISM</h3>
<h4>RITUAL SPIRITISM</h4>
<p>
You may perform Rituals whose effects fall within the Spiritism discipline.
Spiritism Rituals use <strong>【INS + WLP】</strong> for the Magic Check.
</p>
<h3>SPIRITUAL MAGIC (çç10)</h3>
<h4>SPIRITUAL MAGIC (çç10)</h4>
<p>
Each time you acquire this Skill, learn one Spiritist spell (see next two
pages). Offensive Spiritist spells use <strong>【INS + WLP】</strong> for the
Magic Check.
</p>
<h3>SUPPORT MAGIC</h3>
<h4>SUPPORT MAGIC</h4>
<p>
When you cast a spell that targets one or more allies, if you have an arcane
weapon equipped, you may choose one of those allies you have a Bond towards.
If you do, that ally gains a bonus to the next Check they perform during the
current scene; this bonus is equal to the strength of your Bond towards them.
</p>
<h3>VISMAGUS</h3>
<h4>VISMAGUS</h4>
<p>
When you cast a spell, if you don't have enough Mind Points to pay for its
total cost, you may choose to spend twice as many Hit Points instead. You
@@ -42,7 +49,7 @@
cast this way would cause you to recover Hit Points, you instead recover no
Hit Points (the spell functions normally on any other target).
</p>
<hr />
<h2>Background Prompts</h2>
<ul>
<li>
@@ -58,4 +65,5 @@
</li>
<li>What does your magic look like?</li>
</ul>
<p>Show me the truths of your heart.</p>
<blockquote>Show me the truths of your heart.</blockquote>

View File

@@ -1,63 +1,95 @@
<h1>SPIRITIST SPELLS</h1>
<h2>Aura</h2>
<p>
<strong>Cost:</strong> 5 × T | <strong>Targets:</strong> Up to three creatures
| <strong>Duration:</strong> Scene
</p>
<p>
You project your soul outside your body and direct it to surround the targets,
shielding them from dangerous magic. Until this spell ends, each target may
treat their Magic Defense as being equal to 12 against any effects that target
it (they are still free to use their normal Defense score if higher than 12).
</p>
<h2>Awaken</h2>
<p>
<strong>Cost:</strong> 20 | <strong>Targets:</strong> One creature |
<strong>Duration:</strong> Scene
</p>
<p>
You allow a creature to focus their vital energy into accomplishing what they
previously could not. Choose one Attribute: Dexterity, Insight, Might, or
Willpower. Until this spell ends, the target treats the chosen Attribute as if
it were one die size higher (up to a maximum of d12).
</p>
<h2>Barrier</h2>
<p>
<strong>Cost:</strong> 5 × T | <strong>Targets:</strong> Up to three creatures
| <strong>Duration:</strong> Scene
</p>
<p>
You project your soul outside your body and weave it into a barrier to protect
the targets from attacks. Until this spell ends, each target may treat their
Defense as being equal to 12 against any effects that target it (they are
still free to use their normal Defense score if higher than 12).
</p>
<h2>Cleanse</h2>
<p>
<strong>Cost:</strong> 5 × T | <strong>Targets:</strong> Up to three creatures
| <strong>Duration:</strong> Instantaneous
</p>
<p>
You strengthen and purify the soul energy coursing through your companions.
Each target recovers from all status effects.
</p>
<h2>Enrage</h2>
<p>
<strong>Cost:</strong> rr 10 | <strong>Targets:</strong> One creature |
<strong>Duration:</strong> Instantaneous
</p>
<p>
You cause a creature to lose any semblance of temper and act brazenly. The
target suffers enraged and cannot perform the Guard or Spell actions during
their next turn.
</p>
<h2>Hallucination</h2>
<p>
<strong>Cost:</strong> rr 5 × T | <strong>Targets:</strong> Up to three
creatures | <strong>Duration:</strong> Instantaneous
</p>
<p>
You alter the senses of your enemies, causing them to experience bizarre or
frightening hallucinations. Choose dazed or shaken: you inflict the chosen
status effect on each target hit by this spell.
</p>
<h2>Spiritist Spells</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Cost</th>
<th>Targets</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Aura</td>
<td>5 x T</td>
<td>Up to three creatures</td>
<td>scene</td>
</tr>
<tr>
<td colspan="4">
You project your soul outside your body and direct it to surround the
targets, shielding them from dangerous magic. Until this spell ends,
each target may treat their Magic Defense as being equal to 12 against
any effects that target it (they are still free to use their normal
Defense score if higher than 12).
</td>
</tr>
<tr>
<td>Awaken</td>
<td>20</td>
<td>One creature</td>
<td>scene</td>
</tr>
<tr>
<td colspan="4">
You allow a creature to focus their vital energy into accomplishing what
they previously could not. Choose one Attribute: Dexterity, Insight,
Might, or Willpower. Until this spell ends, the target treats the chosen
Attribute as if it were one die size higher (up to a maximum of d12).
</td>
</tr>
<tr>
<td>Barrier</td>
<td>5 x T</td>
<td>Up to three creatures</td>
<td>scene</td>
</tr>
<tr>
<td colspan="4">
You project your soul outside your body and weave it into a barrier to
protect the targets from attacks. Until this spell ends, each target may
treat their Defense as being equal to 12 against any effects that target
it (they are still free to use their normal Defense score if higher than
12).
</td>
</tr>
<tr>
<td>Cleanse</td>
<td>5 x T</td>
<td>Up to three creatures</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You strengthen and purify the soul energy coursing through your
companions. Each target recovers from all status effects.
</td>
</tr>
<tr>
<td>Enrage</td>
<td>rr 10</td>
<td>One creature</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You cause a creature to lose any semblance of temper and act brazenly.
The target suffers enraged and cannot perform the Guard or Spell actions
during their next turn.
</td>
</tr>
<tr>
<td>Hallucination</td>
<td>rr 5 x T</td>
<td>Up to three creatures</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You alter the senses of your enemies, causing them to experience bizarre
or frightening hallucinations. Choose dazed or shaken: you inflict the
chosen status effect on each target hit by this spell.
</td>
</tr>
</tbody>
</table>

View File

@@ -0,0 +1,95 @@
<h2>Spiritist Spells (Cont.)</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Cost</th>
<th>Targets</th>
<th>Duration</th>
</tr>
</thead>
<tbody>
<tr>
<td>Heal</td>
<td>10 x T</td>
<td>Up to three creatures</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You invigorate your companions, soothing their pain and healing their
fatigue. Each target recovers 40 Hit Points. This amount increases to 50
Hit Points if you are level 20 or higher, or to 60 Hit Points if you are
level 40 or higher.
</td>
</tr>
<tr>
<td>Lux</td>
<td>10 x T</td>
<td>Up to three creatures</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You focus your inner energy into a barrage of blinding soul rays. Each
target hit by this spell suffers【 HR + 15】 light damage. Opportunity:
Each target hit by this spell suffers dazed.
</td>
</tr>
<tr>
<td>Mercy</td>
<td>20</td>
<td>One creature</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You strengthen the heart of a creature against suffering and despair.
Until this spell ends, if the target would be reduced to 0 Hit Points,
they are instead left standing with exactly 1 Hit Point. Once that
happens, this spell ends.
</td>
</tr>
<tr>
<td>Reinforce</td>
<td>5 x T</td>
<td>Up to three creatures</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You protect the targets from attacks that would corrupt their body and
spirit. Choose dazed, enraged, poisoned, shaken, slow, or weak. Until
this spell ends, each target becomes immune to the chosen status effect.
</td>
</tr>
<tr>
<td>Soul Weapon</td>
<td>10</td>
<td>One equipped weapon</td>
<td>Scene</td>
</tr>
<tr>
<td colspan="4">
You imbue a weapon with the cleansing energy of your spirit. Until this
spell ends, all damage dealt by the weapon becomes of the light type. If
you have that weapon equipped while you cast this spell, you may perform
a free attack with it as part of the same action. This spell can only be
cast on a weapon equipped by a willing creature.
</td>
</tr>
<tr>
<td>Torpor</td>
<td>5 x T</td>
<td>Up to three creatures</td>
<td>Instantaneous</td>
</tr>
<tr>
<td colspan="4">
You smother the soul energy coursing through the bodies of your foes,
hindering their movements. Choose slow or weak: you inflict the chosen
status effect on each target hit by this spell.
</td>
</tr>
</tbody>
</table>

View File

@@ -41,7 +41,6 @@
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Blessed Mace</b></td>
@@ -72,8 +71,11 @@
<td>【HR + 6】light</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ You gain a +2 bonus to Magic Checks against <b>demons</b>, as well as a
<b>+2</b> bonus to Opposed Checks against <b>demons</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ You gain a +2 bonus to Magic Checks against
<b>demons</b>, as well as a <b>+2</b> bonus to Opposed Checks against
<b>demons</b>.
</td>
</tr>
<tr class="main">
@@ -84,7 +86,9 @@
<td>【HR + 2】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You gain a +1 bonus to your Magic Checks.</td>
<td colspan="4">
One-handed ◆ Melee ◆ You gain a +1 bonus to your Magic Checks.
</td>
</tr>
<tr class="main">
@@ -95,8 +99,10 @@
<td>【HR + 6】dark</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Whenever you hit one or more creatures with this weapon, each of them loses
10 Mind Points.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Whenever you hit one or more creatures with this
weapon, each of them loses 10 Mind Points.
</td>
</tr>
<tr class="main">
@@ -107,8 +113,10 @@
<td>【HR + 6】air</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ When you hit one or more creatures with this weapon, you may recover 5 Hit
Points.</td>
<td colspan="4">
One-handed ◆ Melee ◆ When you hit one or more creatures with this
weapon, you may recover 5 Hit Points.
</td>
</tr>
<tr class="main">
@@ -119,8 +127,10 @@
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Spells you cast whose effects restore Hit Points will restore 5 extra Hit
Points.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Spells you cast whose effects restore Hit Points
will restore 5 extra Hit Points.
</td>
</tr>
<tr class="main">
@@ -131,8 +141,10 @@
<td>【HR + 6】dark</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ When you hit one or more creatures with an offensive (⚡) spell, each of them
suffers <b>shaken</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ When you hit one or more creatures with an
offensive (⚡) spell, each of them suffers <b>shaken</b>.
</td>
</tr>
<tr class="main">
@@ -143,7 +155,9 @@
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Spells you cast deal 5 extra damage.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Spells you cast deal 5 extra damage.
</td>
</tr>
<tr class="main">
@@ -154,9 +168,10 @@
<td>【HR + 10】poison</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ When you hit one or more creatures with an offensive (⚡) spell, each of them
suffers <b>poisoned</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ When you hit one or more creatures with an
offensive (⚡) spell, each of them suffers <b>poisoned</b>.
</td>
</tr>
</tbody>
</table>
</table>

View File

@@ -47,7 +47,6 @@
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Hand Crossbow</b></td>
@@ -78,7 +77,9 @@
<td>【HR + 12】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ Damage dealt by this weapon ignores Resistances.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ Damage dealt by this weapon ignores Resistances.
</td>
</tr>
<tr class="main">
@@ -89,7 +90,9 @@
<td>【HR + 8】air</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ You are immune to <b>shaken</b>.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ You are immune to <b>shaken</b>.
</td>
</tr>
<tr class="main">
@@ -100,7 +103,9 @@
<td>【HR + 8】bolt</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ You have Resistance to <b>bolt</b> damage.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ You have Resistance to <b>bolt</b> damage.
</td>
</tr>
<tr class="main">
@@ -111,8 +116,10 @@
<td>【HR + 8】fire</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ When you reduce a creature to 0 Hit Points with this weapon, you may
immediately recover 2 Inventory Points.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ When you reduce a creature to 0 Hit Points with
this weapon, you may immediately recover 2 Inventory Points.
</td>
</tr>
<tr class="main">
@@ -123,7 +130,9 @@
<td>【HR + 12】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ Attacks with this weapon have <b>multi (2)</b>.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ Attacks with this weapon have <b>multi (2)</b>.
</td>
</tr>
<tr class="main">
@@ -134,8 +143,10 @@
<td>【HR + 12】earth</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ When you hit a flying target with this weapon, you may force that target to
land immediately.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ When you hit a flying target with this weapon, you
may force that target to land immediately.
</td>
</tr>
<tr class="main">
@@ -146,8 +157,11 @@
<td>【HR + 12】ice</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ When you hit one or more creatures with this weapon, if you have at least
one <b>Bond</b> of <b>inferiority</b>, you may recover 5 MP.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ When you hit one or more creatures with this
weapon, if you have at least one <b>Bond</b> of <b>inferiority</b>, you
may recover 5 MP.
</td>
</tr>
<tr class="main">
@@ -158,7 +172,10 @@
<td>【HR + 12】poison</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ Each target hit by this weapon suffers <b>slow</b>.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ Each target hit by this weapon suffers
<b>slow</b>.
</td>
</tr>
<tr class="main">
@@ -169,8 +186,9 @@
<td>【HR + 12】light</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ You have Immunity to <b>dark</b> damage.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ You have Immunity to <b>dark</b> damage.
</td>
</tr>
</tbody>
</table>
</table>

View File

@@ -1,186 +1,207 @@
<meta charset="UTF-8">
<meta charset="UTF-8" />
<title>Sample Rare Brawling Weapons</title>
<style>
table {
border-collapse: collapse;
width: 100%;
}
table {
border-collapse: collapse;
width: 100%;
}
th {
/* background-color: #2d5a4e;
th {
/* background-color: #2d5a4e;
color: white; */
padding: 8px 12px;
text-align: left;
}
padding: 8px 12px;
text-align: left;
}
td {
padding: 6px 12px;
vertical-align: top;
}
td {
padding: 6px 12px;
vertical-align: top;
}
tr.desc td {
/* background-color: #f0f4f0; */
font-size: 0.95em;
}
tr.desc td {
/* background-color: #f0f4f0; */
font-size: 0.95em;
}
tr.main td {
border-top: 1px solid #ccc;
}
tr.main td {
border-top: 1px solid #ccc;
}
.icon {
width: 48px;
text-align: center;
}
.icon {
width: 48px;
text-align: center;
}
.rare {
color: #c0392b;
}
.rare {
color: #c0392b;
}
</style>
<h2>SAMPLE RARE BRAWLING WEAPONS</h2>
<table>
<thead>
<tr>
<th colspan="2">WEAPON</th>
<th>COST</th>
<th>ACCURACY</th>
<th>DAMAGE</th>
</tr>
</thead>
<tbody>
<thead>
<tr>
<th colspan="2">WEAPON</th>
<th>COST</th>
<th>ACCURACY</th>
<th>DAMAGE</th>
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Nekode</b></td>
<td>250 z</td>
<td>【DEX + MIG】+1</td>
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ No Quality.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Nekode</b></td>
<td>250 z</td>
<td>【DEX + MIG】+1</td>
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ No Quality.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Hellfist</b></td>
<td>350 z</td>
<td>【DEX + MIG】</td>
<td>【HR + 6】dark</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Attacks with this weapon target Magic Defense.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Hellfist</b></td>
<td>350 z</td>
<td>【DEX + MIG】</td>
<td>【HR + 6】dark</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Attacks with this weapon target Magic Defense.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Frozen Grasp</b></td>
<td>750 z</td>
<td>【DEX + MIG】</td>
<td>【HR + 6】ice</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ You are immune to <b>enraged</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Frozen Grasp</b></td>
<td>750 z</td>
<td>【DEX + MIG】</td>
<td>【HR + 6】ice</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You are immune to <b>enraged</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Bear Paw</b> <span class="rare"></span></td>
<td>850 z</td>
<td>【DEX + MIG】</td>
<td>【HR + 10】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You are immune to <b>weak</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Bear Paw</b> <span class="rare"></span></td>
<td>850 z</td>
<td>【DEX + MIG】</td>
<td>【HR + 10】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You are immune to <b>weak</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Fuel Knuckle</b></td>
<td>950 z</td>
<td>【DEX + MIG】</td>
<td>【HR + 6】fire</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ You have Resistance to <b>fire</b> damage.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Fuel Knuckle</b></td>
<td>950 z</td>
<td>【DEX + MIG</td>
<td>【HR + 6】fire</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You have Resistance to <b>fire</b> damage.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Silver Talon</b></td>
<td>1100 z</td>
<td>【DEX + DEX</td>
<td>【HR + 6】light</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ You gain a +1 bonus to Magic Defense.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Silver Talon</b></td>
<td>1100 z</td>
<td>【DEX + DEX】</td>
<td>【HR + 6】light</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You gain a +1 bonus to Magic Defense.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Old Bandages</b></td>
<td>1250 z</td>
<td>【DEX + MIG】+1</td>
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ You have Resistance to <b>dark</b> and
<b>poison</b> damage.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Old Bandages</b></td>
<td>1250 z</td>
<td>DEX + MIG】+1</td>
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You have Resistance to <b>dark</b> and <b>poison</b> damage.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Storm Fist</b></td>
<td>1300 z</td>
<td>MIG + MIG】</td>
<td>【HR + 6】bolt</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (2)</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Storm Fist</b></td>
<td>1300 z</td>
<td>MIG + MIG】</td>
<td>【HR + 6】bolt</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (2)</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Lobster Claw</b> <span class="rare"></span></td>
<td>1950 z</td>
<td>DEX + MIG】+1</td>
<td>【HR + 10】physical</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>slow</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Lobster Claw</b> <span class="rare"></span></td>
<td>1950 z</td>
<td>DEX + MIG】+1</td>
<td>【HR + 10】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>slow</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Dozer Glove</b> <span class="rare"></span></td>
<td>2000 z</td>
<td>MIG + MIG】</td>
<td>【HR + 10】earth</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Each target hit by this weapon suffers
<b>dazed</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Dozer Glove</b> <span class="rare"></span></td>
<td>2000 z</td>
<td>MIG + MIG】</td>
<td>【HR + 10】earth</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>dazed</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Venom Claw</b></td>
<td>2250 z</td>
<td>DEX + MIG】+1</td>
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Each target hit by this weapon suffers
<b>poisoned</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Venom Claw</b></td>
<td>2250 z</td>
<td>【DEX + MIG】+1</td>
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>poisoned</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>God Hand</b> <span class="rare"></span></td>
<td>2550 z</td>
<td>【DEX + MIG】+1</td>
<td>【HR + 10】light</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Damage dealt by this weapon ignores Immunities.</td>
</tr>
</tbody>
</table>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>God Hand</b> <span class="rare"></span></td>
<td>2550 z</td>
<td>【DEX + MIG】+1</td>
<td>【HR + 10】light</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Damage dealt by this weapon ignores Immunities.
</td>
</tr>
</tbody>
</table>

View File

@@ -1,176 +1,196 @@
<style>
table {
border-collapse: collapse;
width: 100%;
}
table {
border-collapse: collapse;
width: 100%;
}
th {
/* background-color: #2d5a4e;
th {
/* background-color: #2d5a4e;
color: white; */
padding: 8px 12px;
text-align: left;
}
padding: 8px 12px;
text-align: left;
}
td {
padding: 6px 12px;
vertical-align: top;
}
td {
padding: 6px 12px;
vertical-align: top;
}
tr.desc td {
/* background-color: #f0f4f0; */
font-size: 0.95em;
}
tr.desc td {
/* background-color: #f0f4f0; */
font-size: 0.95em;
}
tr.main td {
border-top: 1px solid #ccc;
}
tr.main td {
border-top: 1px solid #ccc;
}
.icon {
width: 48px;
text-align: center;
}
.icon {
width: 48px;
text-align: center;
}
.rare {
color: #c0392b;
}
.rare {
color: #c0392b;
}
</style>
<h2>SAMPLE RARE DAGGER WEAPONS</h2>
<table>
<thead>
<tr>
<th colspan="2">WEAPON</th>
<th>COST</th>
<th>ACCURACY</th>
<th>DAMAGE</th>
</tr>
</thead>
<tbody>
<thead>
<tr>
<th colspan="2">WEAPON</th>
<th>COST</th>
<th>ACCURACY</th>
<th>DAMAGE</th>
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Latrodectus</b></td>
<td>250 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 4】poison</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ No Quality.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Latrodectus</b></td>
<td>250 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 4】poison</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ No Quality.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Heart Knife</b></td>
<td>550 z</td>
<td>【DEX + WLP】</td>
<td>【HR + 4】light</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Deals 5 extra damage to <b>demons</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Heart Knife</b></td>
<td>550 z</td>
<td>【DEX + WLP】</td>
<td>【HR + 4】light</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Deals 5 extra damage to <b>demons</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Atom Slicer</b></td>
<td>600 z</td>
<td>【DEX + DEX】+1</td>
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Damage dealt by this weapon ignores Resistances.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Atom Slicer</b></td>
<td>600 z</td>
<td>【DEX + DEX】+1</td>
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Damage dealt by this weapon ignores Resistances.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Silent Edge</b></td>
<td>700 z</td>
<td>【DEX + DEX】</td>
<td>【HR + 4】air</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You are immune to <b>slow</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Silent Edge</b></td>
<td>700 z</td>
<td>【DEX + DEX】</td>
<td>【HR + 4】air</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You are immune to <b>slow</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Spell Sever</b></td>
<td>850 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 4】dark</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ When you hit a creature with this weapon, if the
attack had a <b>single</b> target, you may choose a single spell with a
<b>duration</b> of Scene affecting the target and end its effects on
that creature.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Spell Sever</b></td>
<td>850 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 4】dark</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ When you hit a creature with this weapon, if the attack had a
<b>single</b> target, you may choose a single spell with a <b>duration</b> of Scene affecting the target
and end its effects on that creature.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Assassin's Blade</b></td>
<td>1000 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Deals 5 extra damage to targets who are in
<b>Crisis</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Assassin's Blade</b></td>
<td>1000 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Deals 5 extra damage to targets who are in <b>Crisis</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Gourmet Cutter</b></td>
<td>1350 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ When you hit one or more creatures with this
weapon, you may recover 5 Hit Points.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Gourmet Cutter</b></td>
<td>1350 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ When you hit one or more creatures with this weapon, you may recover 5
Hit Points.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Barbed Knife</b></td>
<td>1650 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Each target hit by this weapon suffers
<b>shaken</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Barbed Knife</b></td>
<td>1650 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>shaken</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Cold Finger</b></td>
<td>1950 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 8】ice</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>weak</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Cold Finger</b></td>
<td>1950 z</td>
<td>【DEX + INS】+1</td>
<td>【HR + 8】ice</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>weak</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Hornet</b></td>
<td>2200 z</td>
<td>【DEX + DEX】+1</td>
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (3)</b>.
</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Hornet</b></td>
<td>2200 z</td>
<td>DEX + DEX】+1</td>
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (3)</b>.</td>
</tr>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Frantic Nail</b></td>
<td>2450 z</td>
<td>【INS + INS】+1</td>
<td>【HR + 8】fire</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>enraged</b>.</td>
</tr>
</tbody>
</table>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Frantic Nail</b></td>
<td>2450 z</td>
<td>INS + INS】+1</td>
<td>【HR + 8】fire</td>
</tr>
<tr class="desc">
<td colspan="4">
One-handed ◆ Melee ◆ Each target hit by this weapon suffers
<b>enraged</b>.
</td>
</tr>
</tbody>
</table>

View File

@@ -47,7 +47,6 @@
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Revolver</b> <span class="rare"></span></td>
@@ -78,7 +77,9 @@
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Attacks with this weapon target Magic Defense.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Attacks with this weapon target Magic Defense.
</td>
</tr>
<tr class="main">
@@ -89,7 +90,9 @@
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Deals 5 extra damage to <b>constructs</b>.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Deals 5 extra damage to <b>constructs</b>.
</td>
</tr>
<tr class="main">
@@ -100,8 +103,10 @@
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Deals 5 extra damage to targets you have a <b>Bond</b> of <b>hatred</b>
towards.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Deals 5 extra damage to targets you have a
<b>Bond</b> of <b>hatred</b> towards.
</td>
</tr>
<tr class="main">
@@ -134,8 +139,10 @@
<td>【HR + 8】poison</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ Potions you create with your Inventory Points deal 5 extra damage and
restore 5 extra Hit Points.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ Potions you create with your Inventory Points deal
5 extra damage and restore 5 extra Hit Points.
</td>
</tr>
<tr class="main">
@@ -146,7 +153,9 @@
<td>【HR + 16】fire</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ Attacks with this weapon have <b>multi (2)</b>.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ Attacks with this weapon have <b>multi (2)</b>.
</td>
</tr>
<tr class="main">
@@ -157,7 +166,10 @@
<td>【HR + 8】ice</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Each target hit by this weapon suffers <b>slow</b>.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Each target hit by this weapon suffers
<b>slow</b>.
</td>
</tr>
<tr class="main">
@@ -168,9 +180,10 @@
<td>【HR + 12】air</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Ranged ◆ Deals extra damage equal to the difference between your current and maximum
<b>Inventory Points</b>.</td>
<td colspan="4">
Two-handed ◆ Ranged ◆ Deals extra damage equal to the difference between
your current and maximum <b>Inventory Points</b>.
</td>
</tr>
</tbody>
</table>
</table>

View File

@@ -47,7 +47,6 @@
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Old Whip</b></td>
@@ -56,7 +55,10 @@
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Deals 5 extra damage to <b>beasts</b> and <b>monsters</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Deals 5 extra damage to <b>beasts</b> and
<b>monsters</b>.
</td>
</tr>
<tr class="main">
@@ -78,9 +80,12 @@
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Damage dealt by this weapon reduces the target's <b>Mind Points</b> instead
of their <b>Hit Points</b>. If the target's Mind Points reach 0, any excess damage is applied to their Hit
Points as normal.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Damage dealt by this weapon reduces the target's
<b>Mind Points</b> instead of their <b>Hit Points</b>. If the target's
Mind Points reach 0, any excess damage is applied to their Hit Points as
normal.
</td>
</tr>
<tr class="main">
@@ -91,7 +96,9 @@
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You have Resistance to <b>fire</b> damage.</td>
<td colspan="4">
One-handed ◆ Melee ◆ You have Resistance to <b>fire</b> damage.
</td>
</tr>
<tr class="main">
@@ -113,8 +120,10 @@
<td>【HR + 8】fire</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You gain a +2 bonus to Accuracy Checks and Magic Checks against
<b>enraged</b> targets.</td>
<td colspan="4">
One-handed ◆ Melee ◆ You gain a +2 bonus to Accuracy Checks and Magic
Checks against <b>enraged</b> targets.
</td>
</tr>
<tr class="main">
@@ -125,7 +134,9 @@
<td>【HR + 12】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (2)</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (2)</b>.
</td>
</tr>
<tr class="main">
@@ -136,7 +147,9 @@
<td>【HR + 12】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ You have Resistance to <b>physical</b> damage.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ You have Resistance to <b>physical</b> damage.
</td>
</tr>
<tr class="main">
@@ -147,7 +160,9 @@
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>slow</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>slow</b>.
</td>
</tr>
<tr class="main">
@@ -158,7 +173,9 @@
<td>【HR + 12】dark</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (3)</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (3)</b>.
</td>
</tr>
<tr class="main">
@@ -169,8 +186,10 @@
<td>【HR + 12】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ You have Immunity to <b>dark</b> and <b>light</b> damage.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ You have Immunity to <b>dark</b> and
<b>light</b> damage.
</td>
</tr>
</tbody>
</table>
</table>

View File

@@ -46,7 +46,6 @@
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Bardiche</b> <span class="rare"></span></td>
@@ -66,7 +65,9 @@
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Deals 5 extra damage to <b>constructs</b>.</td>
<td colspan="4">
One-handed ◆ Melee ◆ Deals 5 extra damage to <b>constructs</b>.
</td>
</tr>
<tr class="main">
@@ -77,7 +78,9 @@
<td>【HR + 10】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Deals 5 extra damage to <b>monsters</b>.</td>
<td colspan="4">
One-handed ◆ Melee ◆ Deals 5 extra damage to <b>monsters</b>.
</td>
</tr>
<tr class="main">
@@ -88,7 +91,9 @@
<td>【HR + 14】poison</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Deals 5 extra damage to <b>humanoids</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Deals 5 extra damage to <b>humanoids</b>.
</td>
</tr>
<tr class="main">
@@ -99,7 +104,10 @@
<td>【HR + 10】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Deals 5 extra damage to <b>beasts</b> and <b>plants</b>.</td>
<td colspan="4">
One-handed ◆ Melee ◆ Deals 5 extra damage to <b>beasts</b> and
<b>plants</b>.
</td>
</tr>
<tr class="main">
@@ -121,7 +129,9 @@
<td>【HR + 14】light</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (2)</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (2)</b>.
</td>
</tr>
<tr class="main">
@@ -132,7 +142,9 @@
<td>【HR + 14】earth</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>slow</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>slow</b>.
</td>
</tr>
<tr class="main">
@@ -143,7 +155,10 @@
<td>【HR + 10】bolt</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>dazed</b>.</td>
<td colspan="4">
One-handed ◆ Melee ◆ Each target hit by this weapon suffers
<b>dazed</b>.
</td>
</tr>
<tr class="main">
@@ -154,7 +169,9 @@
<td>【HR + 18】fire</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ You have Immunity to <b>fire</b> damage.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ You have Immunity to <b>fire</b> damage.
</td>
</tr>
<tr class="main">
@@ -165,7 +182,10 @@
<td>【HR + 18】dark</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>enraged</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Each target hit by this weapon suffers
<b>enraged</b>.
</td>
</tr>
<tr class="main">
@@ -176,8 +196,9 @@
<td>【HR + 18】ice</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ You gain a +1 bonus to Defense and Magic Defense.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ You gain a +1 bonus to Defense and Magic Defense.
</td>
</tr>
</tbody>
</table>
</table>

View File

@@ -34,7 +34,6 @@
.rare {
color: #c0392b;
}
</style>
<table>
@@ -47,7 +46,6 @@
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Dragontongue</b> <span class="rare"></span></td>
@@ -56,7 +54,9 @@
<td>【HR + 12】fire</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Attacks with this weapon target Magic Defense.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Attacks with this weapon target Magic Defense.
</td>
</tr>
<tr class="main">
@@ -67,7 +67,9 @@
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Deals 1 extra damage per status effect you have.</td>
<td colspan="4">
One-handed ◆ Melee ◆ Deals 1 extra damage per status effect you have.
</td>
</tr>
<tr class="main">
@@ -78,7 +80,9 @@
<td>【HR + 16】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Damage dealt by this weapon ignores Resistances.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Damage dealt by this weapon ignores Resistances.
</td>
</tr>
<tr class="main">
@@ -100,7 +104,9 @@
<td>【HR + 12】ice</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You have Resistance to <b>ice</b> damage.</td>
<td colspan="4">
One-handed ◆ Melee ◆ You have Resistance to <b>ice</b> damage.
</td>
</tr>
<tr class="main">
@@ -111,8 +117,10 @@
<td>【HR + 12】earth</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ As long as you have at least three <b>Bonds</b> of <b>loyalty</b> or
<b>affection</b>, you gain a +1 bonus to Defense and Magic Defense.
<td colspan="4">
Two-handed ◆ Melee ◆ As long as you have at least three <b>Bonds</b> of
<b>loyalty</b> or <b>affection</b>, you gain a +1 bonus to Defense and
Magic Defense.
</td>
</tr>
@@ -124,8 +132,10 @@
<td>【HR + 12】dark</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ When you hit one or more creatures with this weapon, you may recover 10
Mind Points.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ When you hit one or more creatures with this
weapon, you may recover 10 Mind Points.
</td>
</tr>
<tr class="main">
@@ -136,8 +146,11 @@
<td>【HR + 12】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ If you roll a <b>critical success</b> on an Accuracy Check with this
weapon, you may spend that <b>opportunity</b> to deal 10 extra damage.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ If you roll a <b>critical success</b> on an
Accuracy Check with this weapon, you may spend that
<b>opportunity</b> to deal 10 extra damage.
</td>
</tr>
<tr class="main">
@@ -148,7 +161,9 @@
<td>【HR + 16】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>weak</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Each target hit by this weapon suffers <b>weak</b>.
</td>
</tr>
<tr class="main">
@@ -170,8 +185,10 @@
<td>【HR + 16】light</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ You have Immunity to <b>fire</b> and <b>ice</b> damage.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ You have Immunity to <b>fire</b> and
<b>ice</b> damage.
</td>
</tr>
</tbody>
</table>
</table>

View File

@@ -47,7 +47,6 @@
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Zweihänder</b> <span class="rare"></span></td>
@@ -67,7 +66,9 @@
<td>【HR + 10】ice</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Attacks with this weapon target Magic Defense.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Attacks with this weapon target Magic Defense.
</td>
</tr>
<tr class="main">
@@ -89,7 +90,9 @@
<td>【HR + 6】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ You are immune to <b>enraged</b>.</td>
<td colspan="4">
One-handed ◆ Melee ◆ You are immune to <b>enraged</b>.
</td>
</tr>
<tr class="main">
@@ -111,7 +114,9 @@
<td>【HR + 6】dark</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Deals 5 extra damage if you are in <b>Crisis</b>.</td>
<td colspan="4">
One-handed ◆ Melee ◆ Deals 5 extra damage if you are in <b>Crisis</b>.
</td>
</tr>
<tr class="main">
@@ -122,7 +127,9 @@
<td>【HR + 10】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ This weapon can target <b>flying</b> creatures.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ This weapon can target <b>flying</b> creatures.
</td>
</tr>
<tr class="main">
@@ -144,7 +151,10 @@
<td>【HR + 10】physical</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Deals 2 extra damage for each <b>Class</b> you have mastered.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Deals 2 extra damage for each <b>Class</b> you have
mastered.
</td>
</tr>
<tr class="main">
@@ -155,7 +165,9 @@
<td>【HR + 10】poison</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Melee ◆ Deals 5 extra damage to <b>weak</b> targets.</td>
<td colspan="4">
One-handed ◆ Melee ◆ Deals 5 extra damage to <b>weak</b> targets.
</td>
</tr>
<tr class="main">
@@ -166,7 +178,9 @@
<td>【HR + 14】air</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (2)</b>.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ Attacks with this weapon have <b>multi (2)</b>.
</td>
</tr>
<tr class="main">
@@ -177,8 +191,9 @@
<td>【HR + 10】light</td>
</tr>
<tr class="desc">
<td colspan="4">Two-handed ◆ Melee ◆ You are immune to all status effects.</td>
<td colspan="4">
Two-handed ◆ Melee ◆ You are immune to all status effects.
</td>
</tr>
</tbody>
</table>
</table>

View File

@@ -43,7 +43,6 @@
</tr>
</thead>
<tbody>
<tr class="main">
<td class="icon" rowspan="2">🗡️</td>
<td><b>Crescent Edge</b></td>
@@ -52,7 +51,9 @@
<td>【HR + 4】light</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Attacks with this weapon target Magic Defense.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Attacks with this weapon target Magic Defense.
</td>
</tr>
<tr class="main">
@@ -85,7 +86,10 @@
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Deals 5 extra damage to <b>beasts</b> and <b>monsters</b>.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Deals 5 extra damage to <b>beasts</b> and
<b>monsters</b>.
</td>
</tr>
<tr class="main">
@@ -96,7 +100,9 @@
<td>【HR + 8】air</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Damage dealt by this weapon ignores Resistances.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Damage dealt by this weapon ignores Resistances.
</td>
</tr>
<tr class="main">
@@ -107,7 +113,9 @@
<td>【HR + 8】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ You are immune to <b>poisoned</b>.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ You are immune to <b>poisoned</b>.
</td>
</tr>
<tr class="main">
@@ -118,7 +126,9 @@
<td>【HR + 4】ice</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ You have Resistance to <b>ice</b> damage.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ You have Resistance to <b>ice</b> damage.
</td>
</tr>
<tr class="main">
@@ -129,7 +139,9 @@
<td>【HR + 4】earth</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ You have Resistance to <b>dark</b> damage.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ You have Resistance to <b>dark</b> damage.
</td>
</tr>
<tr class="main">
@@ -140,7 +152,9 @@
<td>【HR + 4】physical</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Attacks with this weapon have <b>multi (2)</b>.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Attacks with this weapon have <b>multi (2)</b>.
</td>
</tr>
<tr class="main">
@@ -151,7 +165,10 @@
<td>【HR + 8】bolt</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Each target hit by this weapon suffers <b>shaken</b>.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Each target hit by this weapon suffers
<b>shaken</b>.
</td>
</tr>
<tr class="main">
@@ -162,7 +179,9 @@
<td>【HR + 4】dark</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ You gain a +1 bonus to Defense and Magic Defense.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ You gain a +1 bonus to Defense and Magic Defense.
</td>
</tr>
<tr class="main">
@@ -173,8 +192,10 @@
<td>【HR + 4】poison</td>
</tr>
<tr class="desc">
<td colspan="4">One-handed ◆ Ranged ◆ Each target hit by this weapon suffers <b>poisoned</b>.</td>
<td colspan="4">
One-handed ◆ Ranged ◆ Each target hit by this weapon suffers
<b>poisoned</b>.
</td>
</tr>
</tbody>
</table>
</table>

View File

@@ -11,29 +11,45 @@
<li><a href="/books/core/#page-30">Scenes</a> <span>30</span></li>
<li><a href="/books/core/#page-31">Interlude Scenes</a> <span>31</span></li>
<li><a href="/books/core/#page-31">Game Master Scenes</a> <span>31</span></li>
<li><a href="/books/core/#page-32">Sessions and Campaigns</a> <span>32</span></li>
<li>
<a href="/books/core/#page-32">Sessions and Campaigns</a> <span>32</span>
</li>
<li><a href="/books/core/#page-33">Important Rules</a> <span>33</span></li>
<li><a href="/books/core/#page-34">Anatomy of a Character</a> <span>34</span></li>
<li>
<a href="/books/core/#page-34">Anatomy of a Character</a> <span>34</span>
</li>
<li><a href="/books/core/#page-38">Checks</a> <span>38</span></li>
<li><a href="/books/core/#page-40">Critical Success</a> <span>40</span></li>
<li><a href="/books/core/#page-40">Fumble</a> <span>40</span></li>
<li><a href="/books/core/#page-41">Opportunities</a> <span>41</span></li>
<li><a href="/books/core/#page-42">Performing an Attribute Check</a> <span>42</span></li>
<li>
<a href="/books/core/#page-42">Performing an Attribute Check</a>
<span>42</span>
</li>
<li><a href="/books/core/#page-43">Difficulty Levels</a> <span>43</span></li>
<li><a href="/books/core/#page-46">Invoking a Trait</a> <span>46</span></li>
<li><a href="/books/core/#page-47">Invoking a Bond</a> <span>47</span></li>
<li><a href="/books/core/#page-48">Opposed Checks</a> <span>48</span></li>
<li><a href="/books/core/#page-49">Open Checks</a> <span>49</span></li>
<li><a href="/books/core/#page-49">Situational Modifiers</a> <span>49</span></li>
<li>
<a href="/books/core/#page-49">Situational Modifiers</a> <span>49</span>
</li>
<li><a href="/books/core/#page-50">Group Checks</a> <span>50</span></li>
<li><a href="/books/core/#page-51">Frequently Used Checks</a> <span>51</span></li>
<li>
<a href="/books/core/#page-51">Frequently Used Checks</a> <span>51</span>
</li>
<li><a href="/books/core/#page-52">Clocks</a> <span>52</span></li>
<li><a href="/books/core/#page-56">Bonds</a> <span>56</span></li>
<li><a href="/books/core/#page-58">Conflict Scenes</a> <span>58</span></li>
<li><a href="/books/core/#page-58">The Structure of a Conflict</a> <span>58</span></li>
<li>
<a href="/books/core/#page-58">The Structure of a Conflict</a>
<span>58</span>
</li>
<li><a href="/books/core/#page-61">Initiative</a> <span>61</span></li>
<li><a href="/books/core/#page-62">Rounds and Turns</a> <span>62</span></li>
<li><a href="/books/core/#page-64">Space and Distances</a> <span>64</span></li>
<li>
<a href="/books/core/#page-64">Space and Distances</a> <span>64</span>
</li>
<li><a href="/books/core/#page-66">Actions</a> <span>66</span></li>
<li><a href="/books/core/#page-68">Attack</a> <span>68</span></li>
<li><a href="/books/core/#page-70">Equipment</a> <span>70</span></li>
@@ -46,8 +62,13 @@
<li><a href="/books/core/#page-76">Skill</a> <span>76</span></li>
<li><a href="/books/core/#page-76">Teamwork</a> <span>76</span></li>
<li><a href="/books/core/#page-78">Sample Goal Clocks</a> <span>78</span></li>
<li><a href="/books/core/#page-82">Optional Conflict Rules</a> <span>82</span></li>
<li><a href="/books/core/#page-84">Hit Points and Mind Points</a> <span>84</span></li>
<li>
<a href="/books/core/#page-82">Optional Conflict Rules</a> <span>82</span>
</li>
<li>
<a href="/books/core/#page-84">Hit Points and Mind Points</a>
<span>84</span>
</li>
<li><a href="/books/core/#page-86">0 Hit Points</a> <span>86</span></li>
<li><a href="/books/core/#page-88">Sacrifice</a> <span>88</span></li>
<li><a href="/books/core/#page-89">Surrendering</a> <span>89</span></li>
@@ -61,7 +82,9 @@
<li><a href="/books/core/#page-101">Ultima Points</a> <span>101</span></li>
<li><a href="/books/core/#page-102">Escalation</a> <span>102</span></li>
<li><a href="/books/core/#page-104">Inventory Points</a> <span>104</span></li>
<li><a href="/books/core/#page-106">Journeys and Travels</a> <span>106</span></li>
<li>
<a href="/books/core/#page-106">Journeys and Travels</a> <span>106</span>
</li>
<li><a href="/books/core/#page-108">Dangers</a> <span>108</span></li>
<li><a href="/books/core/#page-109">Discoveries</a> <span>109</span></li>
<li><a href="/books/core/#page-110">Dungeons</a> <span>110</span></li>

View File

@@ -1,11 +1,21 @@
<ul>
<li><a href="/books/core/#page-120">Rituals during Conflicts</a> <span>120</span></li>
<li><a href="/books/core/#page-122">Economy and Items</a> <span>122</span></li>
<li><a href="/books/core/#page-124">Services and Transports</a> <span>124</span></li>
<li>
<a href="/books/core/#page-120">Rituals during Conflicts</a>
<span>120</span>
</li>
<li>
<a href="/books/core/#page-122">Economy and Items</a> <span>122</span>
</li>
<li>
<a href="/books/core/#page-124">Services and Transports</a> <span>124</span>
</li>
<li><a href="/books/core/#page-126">Equipment</a> <span>126</span></li>
<li><a href="/books/core/#page-126">Martial Items</a> <span>126</span></li>
<li><a href="/books/core/#page-130">Basic Weapons</a> <span>130</span></li>
<li><a href="/books/core/#page-132">Basic Armors and Shields</a> <span>132</span></li>
<li>
<a href="/books/core/#page-132">Basic Armors and Shields</a>
<span>132</span>
</li>
<li><a href="/books/core/#page-134">Projects</a> <span>134</span></li>
<li><a href="/books/core/#page-140">Group Dynamics</a> <span>140</span></li>
</ul>
@@ -15,8 +25,12 @@
<li><a href="/books/core/#page-146">Before You Start</a> <span>146</span></li>
<li><a href="/books/core/#page-148">World Creation</a> <span>148</span></li>
<li><a href="/books/core/#page-152">Group Creation</a> <span>152</span></li>
<li><a href="/books/core/#page-154">Character Creation</a> <span>154</span></li>
<li><a href="/books/core/#page-172">Classic Characters</a> <span>172</span></li>
<li>
<a href="/books/core/#page-154">Character Creation</a> <span>154</span>
</li>
<li>
<a href="/books/core/#page-172">Classic Characters</a> <span>172</span>
</li>
<li><a href="/books/core/#page-176">Arcanist</a> <span>176</span></li>
<li><a href="/books/core/#page-182">Chimerist</a> <span>182</span></li>
<li><a href="/books/core/#page-184">Darkblade</a> <span>184</span></li>
@@ -33,32 +47,60 @@
<li><a href="/books/core/#page-216">Wayfarer</a> <span>216</span></li>
<li><a href="/books/core/#page-218">Weaponmaster</a> <span>218</span></li>
<li><a href="/books/core/#page-220">Prologues</a> <span>220</span></li>
<li><a href="/books/core/#page-226">Experience and Levels</a> <span>226</span></li>
<li><a href="/books/core/#page-229">Creating High Level PCs</a> <span>229</span></li>
<li><a href="/books/core/#page-230">Optional Advancement Rules</a> <span>230</span></li>
<li>
<a href="/books/core/#page-226">Experience and Levels</a> <span>226</span>
</li>
<li>
<a href="/books/core/#page-229">Creating High Level PCs</a> <span>229</span>
</li>
<li>
<a href="/books/core/#page-230">Optional Advancement Rules</a>
<span>230</span>
</li>
<li><a href="/books/core/#page-232">Heroic Skills</a> <span>232</span></li>
</ul>
<h2>4. GAME MASTER</h2>
<ul>
<li><a href="/books/core/#page-244">The Game Master's Role</a> <span>244</span></li>
<li><a href="/books/core/#page-248">Character Classes</a> <span>248</span></li>
<li><a href="/books/core/#page-254">The Art of Villains</a> <span>254</span></li>
<li>
<a href="/books/core/#page-244">The Game Master's Role</a> <span>244</span>
</li>
<li>
<a href="/books/core/#page-248">Character Classes</a> <span>248</span>
</li>
<li>
<a href="/books/core/#page-254">The Art of Villains</a> <span>254</span>
</li>
<li><a href="/books/core/#page-258">Dungeons</a> <span>258</span></li>
<li><a href="/books/core/#page-264">Rewards</a> <span>264</span></li>
<li><a href="/books/core/#page-266">Designing Rare Items</a> <span>266</span></li>
<li><a href="/books/core/#page-268">Designing Rare Weapons</a> <span>268</span></li>
<li><a href="/books/core/#page-280">Designing Rare Armors and Shields</a> <span>280</span></li>
<li><a href="/books/core/#page-284">Designing Accessories</a> <span>284</span></li>
<li>
<a href="/books/core/#page-266">Designing Rare Items</a> <span>266</span>
</li>
<li>
<a href="/books/core/#page-268">Designing Rare Weapons</a> <span>268</span>
</li>
<li>
<a href="/books/core/#page-280">Designing Rare Armors and Shields</a>
<span>280</span>
</li>
<li>
<a href="/books/core/#page-284">Designing Accessories</a> <span>284</span>
</li>
<li><a href="/books/core/#page-288">Artifacts</a> <span>288</span></li>
<li><a href="/books/core/#page-292">Designing Battles</a> <span>292</span></li>
<li><a href="/books/core/#page-295">Elites and Champions</a> <span>295</span></li>
<li>
<a href="/books/core/#page-292">Designing Battles</a> <span>292</span>
</li>
<li>
<a href="/books/core/#page-295">Elites and Champions</a> <span>295</span>
</li>
<li><a href="/books/core/#page-300">Boss Battles</a> <span>300</span></li>
<li><a href="/books/core/#page-300">Designing NPCs</a> <span>300</span></li>
<li><a href="/books/core/#page-304">NPC Species</a> <span>304</span></li>
<li><a href="/books/core/#page-306">NPC Skills</a> <span>306</span></li>
<li><a href="/books/core/#page-310">NPC Spells</a> <span>310</span></li>
<li><a href="/books/core/#page-312">Sample Boss Designs</a> <span>312</span></li>
<li>
<a href="/books/core/#page-312">Sample Boss Designs</a> <span>312</span>
</li>
</ul>
<h2>5. BESTIARY</h2>
@@ -72,4 +114,4 @@
<li><a href="/books/core/#page-342">Monsters</a> <span>342</span></li>
<li><a href="/books/core/#page-346">Plants</a> <span>346</span></li>
<li><a href="/books/core/#page-350">Undead</a> <span>350</span></li>
</ul>
</ul>

View File

@@ -1,4 +1,4 @@
<h1>Objective</h1>
<h2>Objective</h2>
<p>
You use this action when you want to make progress towards one of your goals
within the conflict — either one you had established at the start of the
@@ -7,54 +7,64 @@
grab a target and try to immobilize them, pull a lever to activate a
mechanism, and so on.
</p>
<p>
<strong>O W</strong> Describe what you want to accomplish and how you intend
to approach it.
</p>
<p>
Needless to say, you can only pursue a goal if you have a reasonable way to
achieve it — the <strong>Game Master</strong> has final say on which actions
can be performed, but should do their best to allow a variety of different
approaches.
</p>
<h3>1</h3>
<p>
If your goal is simple enough—such as leaping past a chasm or climbing a
rope—you will likely accomplish it with a single successful
<strong>Check</strong>.
</p>
<p>
<strong>O W</strong> Since Objective gets you closer to what you want to
achieve within the conflict, it will always require a Check.
</p>
<ul>
<ol>
<li>
If your goal is opposed by another participant in the conflict scene, the
Check will be an Opposed Check against them. Even if multiple participants
can oppose you, only one of them will perform the Check — this should be the
most capable among your adversaries.
<p>
Describe what you want to accomplish and how you intend to approach it.
</p>
<p>
Needless to say, you can only pursue a goal if you have a reasonable way
to achieve it — the <strong>Game Master</strong> has final say on which
actions can be performed, but should do their best to allow a variety of
different approaches.
</p>
</li>
<li>Otherwise, it will be an Attribute Check.</li>
</ul>
<h3>2</h3>
<p>
<strong>O W</strong> If your Check is successful, you alter the
<strong>Clock</strong> in your favor — which, based on the Clock in question,
means filling or erasing a certain number of sections, as previously explained
on <a href="/books/core/#page-53">page 53</a>:
</p>
<ul>
<li>One section for succeeding.</li>
<li>
An additional section for beating the Difficulty Level or the opponent's
roll by three (3) or more, or two additional sections for beating it by six
(6) or more.
<p>
Since Objective gets you closer to what you want to achieve within the
conflict, it will always require a Check.
</p>
<ul>
<li>
If your goal is opposed by another participant in the conflict scene,
the Check will be an Opposed Check against them. Even if multiple
participants can oppose you, only one of them will perform the Check —
this should be the most capable among your adversaries.
</li>
<li>Otherwise, it will be an Attribute Check.</li>
</ul>
</li>
</ul>
<h3>3</h3>
<p>
Most of the time, your goal will be complex enough to involve the use of a
<strong>Clock</strong> — especially if achieving it brings you closer to
winning the conflict.
</p>
<hr />
<li>
<p>
If your goal is simple enough—such as leaping past a chasm or climbing a
rope—you will likely accomplish it with a single successful
<strong>Check</strong>.
</p>
</li>
<li>
<p>
Most of the time, your goal will be complex enough to involve the use of a
<strong>Clock</strong> — especially if achieving it brings you closer to
winning the conflict.
</p>
</li>
<li>
<p>
If your Check is successful, you alter the
<strong>Clock</strong> in your favor — which, based on the Clock in
question, means filling or erasing a certain number of sections, as
previously explained on <a href="/books/core/#page-53">page 53</a>:
</p>
<ul>
<li>One section for succeeding.</li>
<li>
An additional section for beating the Difficulty Level or the opponent's
roll by three (3) or more, or two additional sections for beating it by
six (6) or more.
</li>
</ul>
</li>
</ol>

File diff suppressed because it is too large Load Diff

View File

@@ -205,3 +205,160 @@ li.active .page-num {
padding-bottom: 0.8em;
margin-bottom: 1.4em;
}
/* ── Search modal ── */
.search-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
z-index: 1000;
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 10vh;
}
.search-modal {
width: 580px;
max-width: calc(100vw - 40px);
max-height: 70vh;
background: var(--surface2);
border: 1px solid var(--border-bright);
display: flex;
flex-direction: column;
overflow: hidden;
}
.search-input {
width: 100%;
padding: 14px 16px;
background: var(--surface);
border: none;
border-bottom: 1px solid var(--border-bright);
color: var(--text);
font-family: var(--font-body);
font-size: 1rem;
outline: none;
flex-shrink: 0;
}
.search-input::placeholder { color: var(--text-dim); }
.search-results {
overflow-y: auto;
flex: 1;
list-style: none;
padding: 0;
margin: 0;
}
.search-result {
padding: 10px 16px;
border-bottom: 1px solid var(--border);
cursor: pointer;
display: flex;
flex-direction: column;
gap: 3px;
transition: background 0.1s;
}
.search-result:last-child { border-bottom: none; }
.search-result:hover,
.search-result.active { background: var(--surface3); }
.search-result-meta {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 8px;
}
.search-result-heading {
font-family: var(--font-display);
font-size: 0.7rem;
letter-spacing: 0.08em;
color: var(--teal);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.search-result-page {
font-family: var(--font-mono);
font-size: 0.7rem;
color: var(--text-dim);
white-space: nowrap;
flex-shrink: 0;
}
.search-result-snippet {
font-size: 0.85rem;
color: var(--text-dim);
line-height: 1.5;
}
.search-result-snippet mark {
background: none;
color: var(--gold);
font-weight: 600;
}
.search-empty {
padding: 24px 16px;
text-align: center;
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 0.8rem;
}
.search-footer {
display: flex;
gap: 16px;
padding: 7px 14px;
border-top: 1px solid var(--border);
background: var(--surface3);
flex-shrink: 0;
}
.search-footer span {
font-family: var(--font-mono);
font-size: 0.68rem;
color: var(--text-dim);
}
/* ── Heading permalink buttons ── */
.page-section h1,
.page-section h2,
.page-section h3 {
position: relative;
scroll-margin-top: 60px;
}
.heading-link {
position: absolute;
left: -24px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 17px;
padding: 2px 4px;
line-height: 1;
transition: color 0.15s;
user-select: none;
}
/* h1 has padding-bottom: 0.3em — shift button up so it centers with the text line, not the full box */
.page-section h1 > .heading-link {
top: calc(50% - 5px);
}
.heading-link:hover,
.heading-link--copied {
color: var(--teal);
}

50
data/spells.schema.json Normal file
View File

@@ -0,0 +1,50 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "data/spells.schema.json",
"title": "Spell Sheet",
"description": "A collection of Fabula Ultima spells and arcana entries.",
"type": "object",
"properties": {
"spells": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Name of the spell or arcana."
},
"cost": {
"type": "string",
"minimum": 0,
"description": "MP cost to cast the spell."
},
"targets": {
"type": "string",
"description": "Who or what the spell affects (e.g. \"One creature\", \"All enemies\")."
},
"duration": {
"type": "string",
"description": "How long the spell's effect lasts (e.g. \"Instantaneous\", \"Scene\")."
},
"description": {
"type": "string",
"description": "Full effect description and any special rules for the spell."
},
"class": {
"type": "string",
"enum": ["arcanist", "chimerist", "elementalist", "entropist", "spiritist"],
"description": "The magic-using class this spell belongs to."
},
"offensive": {
"type": "boolean",
"description": "Whether the spell is offensive in nature."
}
},
"required": ["name", "cost", "targets", "duration", "description", "class"],
"additionalProperties": false
}
}
},
"required": ["spells"]
}

287
data/spells.yml Normal file
View File

@@ -0,0 +1,287 @@
spells:
- name: Elemental Shroud
cost: 5 x T
targets: Up to three creatures
duration: scene
description: "You weave magical energy and protect the targets from the fury of the elements. Choose a damage type: air, bolt, earth, fire or ice. Until this spell ends, each target gains Resistance against the chosen damage type."
class: elementalist
- name: Elemental Weapon
cost: "10"
targets: One weapon
duration: Scene
description: "You imbue a weapon with elemental energy. Choose a damage type: air, bolt, earth, fire, or ice. Until this spell ends, all damage dealt by the weapon becomes of the chosen damage type. If you have that weapon equipped while you cast this spell, you may perform a free attack with it as part of the same action. This spell can only be cast on a weapon equipped by a willing creature."
class: elementalist
- name: Flare
cost: "20"
targets: One creature
duration: Instantaneous
description: "You channel a single ray of fire towards your foe, its temperature so high that it will pierce through most defenses. The target suffers【 HR + 25】 fire damage. Damage dealt by this spell ignores Resistances."
class: elementalist
offensive: true
- name: Fulgur
cost: "10 x T"
targets: Up to three creatures
duration: Instantaneous
description: "You weave electricity into a wave of crackling bolts. Each target hit by this spell suffers 【HR + 15】 bolt damage. Opportunity: Each target hit by this spell suffers dazed."
class: elementalist
offensive: true
- name: Glacies
cost: 10 x T
targets: Up to three creatures
duration: Instantaneous
description: "You coat your foes under a thick layer of frost. Each target hit by this spell suffers【 HR + 15】 ice damage. Opportunity: Each target hit by this spell suffers slow."
class: elementalist
offensive: true
- name: Iceberg
cost: "20"
targets: One creature
duration: Instantaneous
description: "A pillar of ice magic envelops your foe, suddenly dropping their body temperature to a critical level. The target suffers【 HR + 25】 ice damage. Damage dealt by this spell ignores Resistances."
class: elementalist
offensive: true
- name: Ignis
cost: 10 x T
targets: Up to three creatures
duration: Instantaneous
description: "You unleash a searing barrage against your foes, conjuring flames out of thin air. Each target hit by this spell suffers【 HR + 15】 fire damage. Opportunity: Each target hit by this spell suffers shaken."
class: elementalist
offensive: true
- name: Soaring Strike
cost: "10"
targets: Self
duration: Instantaneous
description: "The wind carries your strikes across the battlefield. You may immediately perform a free attack with a melee weapon you have equipped. This attack may target creatures that can only be targeted by ranged attacks. If you used a weapon belonging to the brawling or spear Category for this attack, it deals 5 extra damage. If you hit a flying target with this attack, you may force them to land immediately."
class: elementalist
- name: Terra
cost: 10 x T
targets: Up to three creatures
duration: Instantaneous
description: "Spires of jagged rock erupt from the ground beneath your foes, closing around them. Each target hit by this spell suffers【 HR + 15】 earth damage. This spell cannot target creatures who are flying, floating, falling, or otherwise in mid-air. Opportunity: Each target hit by this spell performs one fewer action on their next turn (to a minimum of 0 actions)."
class: elementalist
offensive: true
- name: Thunderbolt
cost: "20"
targets: One creature
duration: Instantaneous
description: "You send lightning striking at your foe. The target suffers【 HR + 25】 bolt damage. Damage dealt by this spell ignores Resistances."
class: elementalist
offensive: true
- name: Ventus
cost: 10 x T
targets: One creature
duration: Instantaneosu
description: "You summon the power of winds against your enemy. Each target hit by this spell suffers【 HR + 15】 air damage. Opportunity: Each flying target hit by this spell is forced to land immediately."
class: elementalist
offensive: true
- name: Acceleration
cost: "20"
targets: One creature
duration: Scene
description: "You bend the fabric of time. Until this spell ends, the target gains the ability to perform a single additional action during each of their turns. Once the target has performed a total of two additional actions granted by this spell, this spell ends."
class: entropist
offensive: false
- name: Anomaly
cost: "20"
targets: One creature
duration: Scene
description: "You alter the very nature of your target. Until this spell ends, if the target would suffer damage of a type they Absorb or are Immune to, they are instead treated as if they were Vulnerable to that damage type. Once that happens, this spell ends."
class: entropist
offensive: true
- name: Dark Weapon
cost: "10"
targets: One equipped weapon
duration: Scene
description: "You imbue a weapon with dark energy. Until this spell ends, all damage dealt by the weapon becomes of the dark type. If you have that weapon equipped while you cast this spell, you may perform a free attack with it as part of the same action. This spell can only be cast on a weapon equipped by a willing creature."
class: entropist
offensive: false
- name: Dispel
cost: "10"
targets: One creature
duration: Instantaneous
description: "You release a wave of negative energy and cleanse all magic from a creature. If the target is affected by one or more spells with a duration of Scene, they are no longer affected by any of those spells instead."
class: entropist
offensive: false
- name: Divination
cost: "10"
targets: Self
duration: Scene
description: "You glimpse briefly into the future. Until this spell ends, after a creature you can see performs a Check, if it was not a fumble nor a critical success, you may force that creature to reroll both dice. Once you have forced two rerolls this way, this spell ends."
class: entropist
offensive: false
- name: Drain Spirit
cost: "5"
targets: One creature
duration: Instantaneous
description: "You consume a creature's psyche. The target loses【 HR + 15】 Mind Points. Then, you recover an amount of Mind Points equal to half the Mind Points loss they suffered (if the loss was reduced to 0 in some way, you recover none)."
class: entropist
offensive: true
- name: Drain Vigor
cost: "10"
targets: One creature
duration: Instantaneous
description: "You steal another creature's life force. The target suffers【 HR + 15】 dark damage. Then, you recover an amount of Hit Points equal to half the Hit Points loss they suffered (if the loss was reduced to 0 in some way, you recover none)."
class: entropist
offensive: true
- name: Gamble
cost: Up to 20
targets: Special
duration: Scene
description: |
You summon a vortex of chaotic energy. Roll your current Willpower die once for every 10 Mind Points spent while casting this spell, then keep the single die you prefer: the number on that die determines the effects of this spell.
1) You lose half of your current Hit Points and half of your current Mind Points.
2-3) Each creature present on the scene, including yourself, suffers poisoned.
4-6) Each creature present on the scene, including yourself, suffers slow.
7-8) Choose up to three creatures you can see: each of them recovers 50 Hit Points and also recovers from all status effects.
9+) Choose any number of creatures you can see: each of them suffers 30 damage. The damage type is determined randomly by rolling a d6: 1. air 2. bolt 3. dark 4. earth 5. fire 6. poison
class: entropist
offensive: false
- name: Mirror
cost: "10"
targets: One creature
duration: Scene
description: "You twist the laws of magic. Until this spell ends, if an offensive (r) spell is cast on the target, the creature who cast that offensive spell will be targeted in their stead (any other targets of the offensive spell will be targeted as normal). Once that happens, this spell ends."
class: entropist
offensive: false
- name: Omega
cost: "20"
targets: One creature
duration: Instantaneous
description: "You invoke doom on your foe, turning strength into frailty. The target loses an amount of Hit Points equal to【 20 + half the target's level】"
class: entropist
offensive: true
- name: Stop
cost: "10"
targets: One creature
duration: Instantaneous
description: "You trap a foe inside a circle of altered time and space. The target will perform one fewer action on their next turn (to a minimum of 0 actions)."
class: entropist
offensive: true
- name: Umbra
cost: 10 x T
targets: Up to three creatures
duration: Scene
description: "A storm of dark energy turns matter into ash. Each target hit by this spell suffers 【HR + 15】 dark damage. Opportunity: Each target hit by this spell suffers weak."
class: entropist
offensive: true
- name: Vortex
cost: "10"
targets: Self
duration: Scene
description: "A roaring gale surrounds you, blowing away arrows and bullets. Until this spell ends, you gain a +2 bonus to your Defense against ranged attacks."
class: elementalist
- name: Aura
cost: 5 x T
targets: Up to three creatures
duration: scene
description: You project your soul outside your body and direct it to surround the targets, shielding them from dangerous magic. Until this spell ends, each target may treat their Magic Defense as being equal to 12 against any effects that target it (they are still free to use their normal Defense score if higher than 12).
class: spiritist
- name: Awaken
cost: "20"
targets: One creature
duration: scene
description: "You allow a creature to focus their vital energy into accomplishing what they previously could not. Choose one Attribute: Dexterity, Insight, Might, or Willpower. Until this spell ends, the target treats the chosen Attribute as if it were one die size higher (up to a maximum of d12)."
class: spiritist
- name: Barrier
cost: 5 x T
targets: Up to three creatures
duration: scene
description: You project your soul outside your body and weave it into a barrier to protect the targets from attacks. Until this spell ends, each target may treat their Defense as being equal to 12 against any effects that target it (they are still free to use their normal Defense score if higher than 12).
class: spiritist
- name: Cleanse
cost: 5 x T
targets: Up to three creatures
duration: Instantaneous
description: You strengthen and purify the soul energy coursing through your companions. Each target recovers from all status effects.
class: spiritist
- name: Enrage
cost: rr 10
targets: One creature
duration: Instantaneous
description: You cause a creature to lose any semblance of temper and act brazenly. The target suffers enraged and cannot perform the Guard or Spell actions during their next turn.
class: spiritist
offensive: true
- name: Hallucination
cost: rr 5 x T
targets: Up to three creatures
duration: Instantaneous
description: "You alter the senses of your enemies, causing them to experience bizarre or frightening hallucinations. Choose dazed or shaken: you inflict the chosen status effect on each target hit by this spell."
class: spiritist
offensive: true
- name: Heal
cost: 10 x T
targets: Up to three creatures
duration: Instantaneous
description: "You invigorate your companions, soothing their pain and healing their fatigue. Each target recovers 40 Hit Points. This amount increases to 50 Hit Points if you are level 20 or higher, or to 60 Hit Points if you are level 40 or higher."
class: spiritist
offensive: false
- name: Lux
cost: 10 x T
targets: Up to three creatures
duration: Instantaneous
description: "You focus your inner energy into a barrage of blinding soul rays. Each target hit by this spell suffers【 HR + 15】 light damage. Opportunity: Each target hit by this spell suffers dazed."
class: spiritist
offensive: true
- name: Mercy
cost: "20"
targets: One creature
duration: Scene
description: "You strengthen the heart of a creature against suffering and despair. Until this spell ends, if the target would be reduced to 0 Hit Points, they are instead left standing with exactly 1 Hit Point. Once that happens, this spell ends."
class: spiritist
offensive: false
- name: Reinforce
cost: 5 x T
targets: Up to three creatures
duration: Scene
description: "You protect the targets from attacks that would corrupt their body and spirit. Choose dazed, enraged, poisoned, shaken, slow, or weak. Until this spell ends, each target becomes immune to the chosen status effect."
class: spiritist
offensive: false
- name: Soul Weapon
cost: "10"
targets: One equipped weapon
duration: Scene
description: "You imbue a weapon with the cleansing energy of your spirit. Until this spell ends, all damage dealt by the weapon becomes of the light type. If you have that weapon equipped while you cast this spell, you may perform a free attack with it as part of the same action. This spell can only be cast on a weapon equipped by a willing creature."
class: spiritist
offensive: false
- name: Torpor
cost: 5 x T
targets: Up to three creatures
duration: Instantaneous
description: "You smother the soul energy coursing through the bodies of your foes, hindering their movements. Choose slow or weak: you inflict the chosen status effect on each target hit by this spell."
class: spiritist
offensive: true

40
package-lock.json generated
View File

@@ -24,7 +24,8 @@
"typescript": "^6.0.3",
"webpack": "^5.107.2",
"webpack-cli": "^7.0.3",
"webpack-dev-server": "^5.2.4"
"webpack-dev-server": "^5.2.4",
"yaml-loader": "^0.9.0"
}
},
"node_modules/@babel/code-frame": {
@@ -4243,6 +4244,13 @@
"node": ">=0.10.0"
}
},
"node_modules/javascript-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
"dev": true,
"license": "MIT"
},
"node_modules/jest-regex-util": {
"version": "30.4.0",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz",
@@ -7233,6 +7241,36 @@
"dev": true,
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/yaml-loader": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/yaml-loader/-/yaml-loader-0.9.0.tgz",
"integrity": "sha512-N0bPfDR42qCruELfmpF6Q8XnDu1elTULGjvVn7u6rsfDa8WhlmJbjXQRh+x5TGmpOqw8R9qfiUgc3aKP+8Hd4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"javascript-stringify": "^2.0.1",
"yaml": "^2.0.0"
},
"engines": {
"node": ">= 20"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@@ -25,6 +25,7 @@
"typescript": "^6.0.3",
"webpack": "^5.107.2",
"webpack-cli": "^7.0.3",
"webpack-dev-server": "^5.2.4"
"webpack-dev-server": "^5.2.4",
"yaml-loader": "^0.9.0"
}
}

69
scripts/anchors.py Normal file
View File

@@ -0,0 +1,69 @@
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "bs4",
# ]
# ///
from bs4 import BeautifulSoup
import re
from pathlib import Path
# Dictionary to track how many times we've seen each header ID
header_seen = {}
def add_anchors_to_headers(html_content):
# Parse the HTML content
soup = BeautifulSoup(html_content, 'html.parser')
# Find all header tags (h1 through h6)
header_tags = soup.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])
# Add a count-based ID to each header tag
for header in header_tags:
# Extract header text (fallback to tag name if empty)
header_text = header.get_text(strip=True) or header.name
# Normalize header
header_text = header_text.lower().replace(" ", "-")
# Create base ID: hN-<text>
base_id = f"{header.name}-{header_text}"
# Check if we've seen this base ID before
if base_id in header_seen:
# Append next count: -1, -2, -3...
count = header_seen[base_id]
header_seen[base_id] = count + 1
header_id = f"{base_id}-{count + 1}"
else:
# First time seeing this text → count starts at 1
header_seen[base_id] = 1
header_id = base_id
# Add the ID to the header tag
header['id'] = header_id
# Wrap the header in an anchor link (clickable permalink)
# The link points to itself via the id attribute
# anchor_tag = soup.new_tag("a", href=f"#{header_id}", class_="anchor-link")
# anchor_tag.string = f"🔗"
# header.wrap(anchor_tag)
# Return the modified HTML content
return str(soup)
for book in ('./books/core', './books/natural-fantasy-atlas'):
for root, dirs, files in Path(book).walk():
for fn in files:
path = root / fn
if path.suffix != ".html":
continue
with path.open('r') as fh:
raw_html = fh.read()
new_html = add_anchors_to_headers(raw_html)
with path.open('w') as fh:
fh.write(new_html)

49
scripts/spells-to-html.js Normal file
View File

@@ -0,0 +1,49 @@
#!/usr/bin/env node
const fs = require('node:fs')
const path = require('node:path')
const { parse } = require('yaml')
const ymlPath = path.join(__dirname, "..", "data", "spells.yml");
const data = parse(fs.readFileSync(ymlPath, "utf8"));
const columns = ["name", "cost", "targets", "duration"];
const myEscape = (s) =>
String(s ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
const generateTable = (spells, class_) => {
const rows = spells
.filter((elem) => elem.class === class_)
.map(
(spell) =>
` <tr>\n${columns.map((col) => ` <td>${myEscape(spell[col])}</td>`).join("\n")}\n </tr><tr><td colspan="4">${spell["description"]}</td></tr>`,
)
.join("\n");
const headers = columns
.map((col) => ` <th>${col.charAt(0).toUpperCase() + col.slice(1)}</th>`)
.join("\n");
return `<table>
<thead>
<tr>
${headers}
</tr>
</thead>
<tbody>
${rows}
</tbody>
</table>
`;
};
["arcanist", "chimerist", "elementalist", "entropist", "spiritist"].forEach(
(class_) => {
process.stdout.write(`<h2>${class_} Spells</h2>\n`)
process.stdout.write(generateTable(data.spells, class_));
},
);

15
shell.nix Normal file
View File

@@ -0,0 +1,15 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShellNoCC {
packages = with pkgs; [
go
gopls
just
nodejs
starship
typescript-language-server
];
shellHook = ''
eval "$(starship init bash)"
'';
}

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import SearchModal, { SearchEntry } from './SearchModal';
interface BookIndexProps {
title: string;
@@ -6,10 +7,63 @@ interface BookIndexProps {
pages: BookPage[];
}
function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\w\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.trim();
}
export default function BookIndex({ title, logoText, pages }: BookIndexProps) {
const pageNums = useMemo(() => pages.map(p => p.n), [pages]);
const total = pageNums.length;
// Single DOMParser pass per page: build search index then inject permalink buttons.
// Both outputs are stable memos so React's dangerouslySetInnerHTML diffing never
// strips the injected buttons on re-render.
const { processedPages, searchIndex } = useMemo(() => {
const parser = new DOMParser();
const slugCount: Record<string, number> = {};
const searchIndex: SearchEntry[] = [];
const processedPages = pages.map(({ n, content }) => {
const doc = parser.parseFromString(content, 'text/html');
// 1. Index text blocks before mutation so heading textContent has no '#' noise
let currentHeading = '';
doc.body.querySelectorAll('h1, h2, h3, p, li, blockquote, td').forEach(el => {
if (/^H[1-3]$/.test(el.tagName)) {
currentHeading = el.textContent?.trim() || '';
} else {
const text = el.textContent?.trim() || '';
if (text.length > 20) {
searchIndex.push({ pageNum: n, headingContext: currentHeading, text });
}
}
});
// 2. Add IDs and permalink buttons to headings
doc.querySelectorAll('h1, h2, h3').forEach(heading => {
const baseSlug = slugify(heading.textContent || '');
if (!baseSlug) return;
slugCount[baseSlug] = (slugCount[baseSlug] || 0) + 1;
const id = slugCount[baseSlug] === 1 ? baseSlug : `${baseSlug}-${slugCount[baseSlug] - 1}`;
heading.id = id;
const btn = doc.createElement('button');
btn.className = 'heading-link';
btn.setAttribute('aria-label', 'Copy permalink');
btn.textContent = '#';
heading.insertBefore(btn, heading.firstChild);
});
return { n, content: doc.body.innerHTML };
});
return { processedPages, searchIndex };
}, [pages]);
const [currentIdx, setCurrentIdx] = useState(() => {
const m = window.location.hash.match(/^#page-(\d+)$/);
if (m) {
@@ -19,6 +73,8 @@ export default function BookIndex({ title, logoText, pages }: BookIndexProps) {
return 0;
});
const [isSearchOpen, setIsSearchOpen] = useState(false);
const contentRef = useRef<HTMLDivElement>(null);
const goTo = useCallback((n: number, smooth: boolean, push: boolean) => {
@@ -66,9 +122,15 @@ export default function BookIndex({ title, logoText, pages }: BookIndexProps) {
return () => container.removeEventListener('scroll', handleScroll);
}, [pageNums]);
// Keyboard navigation
// Keyboard navigation (page-level) and search shortcut
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
// Ctrl+K / Ctrl+F → open search
if ((e.ctrlKey || e.metaKey) && (e.key === 'k' || e.key === 'f')) {
e.preventDefault();
setIsSearchOpen(true);
return;
}
const tag = (e.target as HTMLElement).tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
setCurrentIdx(prev => {
@@ -88,16 +150,61 @@ export default function BookIndex({ title, logoText, pages }: BookIndexProps) {
// Browser back/forward
useEffect(() => {
const handlePopState = () => {
const m = location.hash.match(/^#page-(\d+)$/);
if (m) {
const n = parseInt(m[1], 10);
const hash = location.hash.slice(1);
const pageM = hash.match(/^page-(\d+)$/);
if (pageM) {
const n = parseInt(pageM[1], 10);
if (pageNums.includes(n)) goTo(n, false, false);
} else if (hash) {
const target = document.getElementById(hash);
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
};
window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, [goTo, pageNums]);
// Event delegation for heading permalink buttons
useEffect(() => {
const container = contentRef.current;
if (!container) return;
const handleClick = (e: MouseEvent) => {
const btn = (e.target as Element).closest('.heading-link') as HTMLButtonElement | null;
if (!btn) return;
const id = btn.closest('h1, h2, h3')?.id;
if (!id) return;
e.preventDefault();
const url = `${window.location.origin}${window.location.pathname}#${id}`;
history.pushState(null, '', `#${id}`);
navigator.clipboard.writeText(url).catch(() => {
const ta = document.createElement('textarea');
ta.value = url;
document.body.appendChild(ta);
ta.select();
document.execCommand('copy');
document.body.removeChild(ta);
});
const prev = btn.textContent!;
btn.textContent = '✓';
btn.classList.add('heading-link--copied');
setTimeout(() => {
btn.textContent = prev;
btn.classList.remove('heading-link--copied');
}, 1500);
};
container.addEventListener('click', handleClick);
return () => container.removeEventListener('click', handleClick);
}, []);
// Scroll to heading hash on initial load
useEffect(() => {
const hash = window.location.hash.slice(1);
if (hash && !/^page-\d+$/.test(hash)) {
const target = document.getElementById(hash);
if (target) target.scrollIntoView({ behavior: 'instant', block: 'start' });
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const currentPage = pageNums[currentIdx];
return (
@@ -105,6 +212,9 @@ export default function BookIndex({ title, logoText, pages }: BookIndexProps) {
<header>
<div className="logo">{logoText}</div>
<div className="toolbar">
<button className="tab" onClick={() => setIsSearchOpen(true)} title="Search (Ctrl+K)">
Search
</button>
<span id="page-indicator">{currentIdx + 1} / {total}</span>
</div>
</header>
@@ -153,7 +263,7 @@ export default function BookIndex({ title, logoText, pages }: BookIndexProps) {
</button>
</div>
<div id="content" ref={contentRef}>
{pages.map(({ n, content }) => (
{processedPages.map(({ n, content }) => (
<section
key={n}
id={`page-${n}`}
@@ -164,6 +274,14 @@ export default function BookIndex({ title, logoText, pages }: BookIndexProps) {
</div>
</div>
</div>
{isSearchOpen && (
<SearchModal
searchIndex={searchIndex}
onNavigate={n => goTo(n, false, true)}
onClose={() => setIsSearchOpen(false)}
/>
)}
</>
);
}

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect, useCallback, useRef } from "react";
import "./fabula-ultima-sheet.css";
import spellsFile from "../data/spells.yml";
const STATUSES = ["Slow", "Enraged", "Dazed", "Weak", "Poisoned", "Shaken"];
const FEELINGS = [
@@ -77,8 +78,9 @@ interface ClassEntry {
interface Spell {
name: string;
spellClass: string;
notes: string;
mp: string | number;
mp: string;
targets: string;
duration: string;
}
@@ -192,6 +194,34 @@ function apiURL(path: string) {
return new URL(path, window.location.href).toString();
}
function autoResize(el: HTMLTextAreaElement) {
el.style.height = "auto";
el.style.height = el.scrollHeight + "px";
}
// Copy text to the clipboard. navigator.clipboard is only available in a
// secure context (HTTPS or localhost); over plain HTTP it's undefined, so we
// fall back to the deprecated execCommand("copy"), which still works there.
// Must be called from within a user gesture (e.g. a click handler).
async function copyToClipboard(text: string) {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
return;
}
const ta = document.createElement("textarea");
ta.value = text;
ta.style.position = "fixed";
ta.style.opacity = "0";
document.body.appendChild(ta);
ta.focus();
ta.select();
try {
document.execCommand("copy");
} finally {
document.body.removeChild(ta);
}
}
export default function CharacterSheet() {
const [activeTab, setActiveTab] = useState("main");
const [urlMode, setUrlMode] = useState(false);
@@ -204,6 +234,7 @@ export default function CharacterSheet() {
const [primaryClasses, setPrimaryClasses] = useState<ClassEntry[]>([]);
const [otherClasses, setOtherClasses] = useState<ClassEntry[]>([]);
const [spells, setSpells] = useState<Spell[]>([]);
const [spellPickerOpen, setSpellPickerOpen] = useState(false);
const [statuses, setStatuses] = useState<CheckMap>({});
const [martial, setMartial] = useState<CheckMap>({});
const [disciplines, setDisciplines] = useState<CheckMap>({});
@@ -287,6 +318,7 @@ export default function CharacterSheet() {
oc: otherClasses.map((c) => ({ n: c.name, b: c.benefits, s: c.skills })),
sp: spells.map((s) => ({
n: s.name,
cl: s.spellClass,
nt: s.notes,
mp: s.mp,
tg: s.targets,
@@ -394,6 +426,7 @@ export default function CharacterSheet() {
setSpells(
rawSpells.map((s: SavedData) => ({
name: s.n ?? s.name ?? "",
spellClass: s.cl ?? s.spellClass ?? "",
notes: s.nt ?? s.notes ?? "",
mp: s.mp ?? "",
targets: s.tg ?? s.targets ?? "",
@@ -516,7 +549,7 @@ export default function CharacterSheet() {
// Backend unreachable: fall back to a self-contained inline ?c= link.
shareURL = base + "?c=" + (await compressToBase64(json));
}
await navigator.clipboard.writeText(shareURL);
await copyToClipboard(shareURL);
setCopyStatus(true);
setTimeout(() => setCopyStatus(false), 2000);
}, [collectData]);
@@ -1148,6 +1181,8 @@ export default function CharacterSheet() {
<textarea
placeholder="Skill information…"
value={cls.skills || ""}
ref={(el) => { if (el) autoResize(el); }}
onInput={(e) => autoResize(e.currentTarget)}
onChange={(e) =>
setPrimaryClasses((prev) =>
prev.map((c, i) =>
@@ -1297,6 +1332,7 @@ export default function CharacterSheet() {
<thead>
<tr>
<th className="spell-name-col">Name / Notes</th>
<th className="spell-class-col">Class</th>
<th className="spell-mp-col">MP Cost</th>
<th className="spell-targets-col">Targets</th>
<th className="spell-dur-col">Duration</th>
@@ -1305,101 +1341,165 @@ export default function CharacterSheet() {
</thead>
<tbody>
{spells.map((s, i) => (
<tr key={i}>
<td className="spell-name-col">
<input
type="text"
placeholder="Spell / Arcana name…"
value={s.name || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, name: e.target.value } : sp,
),
)
}
/>
<textarea
placeholder="Notes / effect description…"
value={s.notes || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, notes: e.target.value } : sp,
),
)
}
/>
</td>
<td className="spell-mp-col">
<input
type="number"
placeholder="0"
value={s.mp || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, mp: e.target.value } : sp,
),
)
}
style={{ minHeight: 32 }}
/>
</td>
<td className="spell-targets-col">
<input
type="text"
placeholder="Target(s)…"
value={s.targets || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, targets: e.target.value } : sp,
),
)
}
/>
</td>
<td className="spell-dur-col">
<input
type="text"
placeholder="Duration…"
value={s.duration || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, duration: e.target.value } : sp,
),
)
}
/>
</td>
<td className="spell-del-col">
<button
className="spell-del-btn"
onClick={() =>
setSpells((prev) => prev.filter((_, j) => j !== i))
}
>
</button>
</td>
</tr>
<React.Fragment key={i}>
<tr className="spell-inputs-row">
<td className="spell-name-col">
<input
type="text"
placeholder="Spell / Arcana name…"
value={s.name || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, name: e.target.value } : sp,
),
)
}
/>
</td>
<td className="spell-class-col">
<input
type="text"
placeholder="Class…"
value={s.spellClass || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, spellClass: e.target.value } : sp,
),
)
}
/>
</td>
<td className="spell-mp-col">
<input
type="text"
placeholder="MP cost…"
value={s.mp || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, mp: e.target.value } : sp,
),
)
}
/>
</td>
<td className="spell-targets-col">
<input
type="text"
placeholder="Target(s)…"
value={s.targets || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, targets: e.target.value } : sp,
),
)
}
/>
</td>
<td className="spell-dur-col">
<input
type="text"
placeholder="Duration…"
value={s.duration || ""}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, duration: e.target.value } : sp,
),
)
}
/>
</td>
<td className="spell-del-col">
<button
className="spell-del-btn"
onClick={() =>
setSpells((prev) => prev.filter((_, j) => j !== i))
}
>
</button>
</td>
</tr>
<tr className="spell-notes-row">
<td colSpan={6}>
<textarea
placeholder="Notes / effect description…"
value={s.notes || ""}
ref={(el) => { if (el) autoResize(el); }}
onInput={(e) => autoResize(e.currentTarget)}
onChange={(e) =>
setSpells((prev) =>
prev.map((sp, j) =>
j === i ? { ...sp, notes: e.target.value } : sp,
),
)
}
/>
</td>
</tr>
</React.Fragment>
))}
</tbody>
</table>
<button
className="add-btn"
style={{ marginTop: 10 }}
onClick={() =>
setSpells((prev) => [
...prev,
{ name: "", notes: "", mp: "", targets: "", duration: "" },
])
}
>
+ Add Spell / Arcana
</button>
<div style={{ display: "flex", gap: 8, marginTop: 10 }}>
<button
className="add-btn"
onClick={() =>
setSpells((prev) => [
...prev,
{ name: "", spellClass: "", notes: "", mp: "", targets: "", duration: "" },
])
}
>
+ Add Spell / Arcana
</button>
<button
className="add-btn"
onClick={() => setSpellPickerOpen(true)}
>
+ Add Template Spell
</button>
</div>
{spellPickerOpen && (
<div className="spell-picker-overlay" onClick={() => setSpellPickerOpen(false)}>
<div className="spell-picker-modal" onClick={(e) => e.stopPropagation()}>
<div className="spell-picker-header">
<span>Choose a spell</span>
<button className="spell-picker-close" onClick={() => setSpellPickerOpen(false)}></button>
</div>
<ul className="spell-picker-list">
{(spellsFile as SpellsFile).spells.map((t, i) => (
<li
key={i}
className="spell-picker-item"
onClick={() => {
setSpells((prev) => [
...prev,
{
name: t.name,
spellClass: t.class,
notes: t.description,
mp: t.cost,
targets: t.targets,
duration: t.duration,
},
]);
setSpellPickerOpen(false);
}}
>
<span className="spell-picker-name">{t.name}</span>
<span className="spell-picker-class">{t.class}</span>
</li>
))}
</ul>
</div>
</div>
)}
</div>
<div className="section">

128
src/SearchModal.tsx Normal file
View File

@@ -0,0 +1,128 @@
import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
export interface SearchEntry {
pageNum: number;
headingContext: string;
text: string;
}
interface Props {
searchIndex: SearchEntry[];
onNavigate: (pageNum: number) => void;
onClose: () => void;
}
function escapeHtml(s: string): string {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function buildSnippetHtml(text: string, q: string): string {
const lo = text.toLowerCase();
const idx = lo.indexOf(q);
if (idx === -1) return escapeHtml(text.slice(0, 140)) + (text.length > 140 ? '…' : '');
const r = 65;
const s = Math.max(0, idx - r);
const e = Math.min(text.length, idx + q.length + r);
return (
(s > 0 ? '…' : '') +
escapeHtml(text.slice(s, idx)) +
`<mark>${escapeHtml(text.slice(idx, idx + q.length))}</mark>` +
escapeHtml(text.slice(idx + q.length, e)) +
(e < text.length ? '…' : '')
);
}
export default function SearchModal({ searchIndex, onNavigate, onClose }: Props) {
const [query, setQuery] = useState('');
const [activeIdx, setActiveIdx] = useState(0);
const inputRef = useRef<HTMLInputElement>(null);
const listRef = useRef<HTMLUListElement>(null);
useEffect(() => { inputRef.current?.focus(); }, []);
const results = useMemo(() => {
const q = query.trim().toLowerCase();
if (q.length < 2) return [];
const seen: Record<number, number> = {};
const out: { pageNum: number; headingContext: string; snippetHtml: string }[] = [];
for (const entry of searchIndex) {
if (out.length >= 40) break;
const hitText = entry.text.toLowerCase().includes(q);
const hitHeading = entry.headingContext.toLowerCase().includes(q);
if (!hitText && !hitHeading) continue;
const count = seen[entry.pageNum] ?? 0;
if (count >= 2) continue;
seen[entry.pageNum] = count + 1;
out.push({
pageNum: entry.pageNum,
headingContext: entry.headingContext,
snippetHtml: buildSnippetHtml(hitText ? entry.text : entry.headingContext, q),
});
}
return out;
}, [query, searchIndex]);
useEffect(() => { setActiveIdx(0); }, [results]);
useEffect(() => {
const el = listRef.current?.children[activeIdx] as HTMLElement | undefined;
el?.scrollIntoView({ block: 'nearest' });
}, [activeIdx]);
const go = useCallback((pageNum: number) => {
onNavigate(pageNum);
onClose();
}, [onNavigate, onClose]);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') { onClose(); return; }
if (e.key === 'ArrowDown') { e.preventDefault(); setActiveIdx(i => Math.min(i + 1, results.length - 1)); }
if (e.key === 'ArrowUp') { e.preventDefault(); setActiveIdx(i => Math.max(i - 1, 0)); }
if (e.key === 'Enter' && results[activeIdx]) go(results[activeIdx].pageNum);
};
const q = query.trim();
return (
<div className="search-overlay" onClick={onClose}>
<div className="search-modal" onClick={e => e.stopPropagation()}>
<input
ref={inputRef}
className="search-input"
type="text"
placeholder="Search the rulebook…"
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
/>
{q.length < 2 ? (
<div className="search-empty">Type at least 2 characters</div>
) : results.length === 0 ? (
<div className="search-empty">No results for &ldquo;{q}&rdquo;</div>
) : (
<ul className="search-results" ref={listRef}>
{results.map((r, i) => (
<li
key={i}
className={`search-result${i === activeIdx ? ' active' : ''}`}
onClick={() => go(r.pageNum)}
onMouseEnter={() => setActiveIdx(i)}
>
<div className="search-result-meta">
<span className="search-result-heading">{r.headingContext || '—'}</span>
<span className="search-result-page">p.{r.pageNum}</span>
</div>
<div className="search-result-snippet" dangerouslySetInnerHTML={{ __html: r.snippetHtml }} />
</li>
))}
</ul>
)}
<div className="search-footer">
<span> navigate</span>
<span> go to page</span>
<span>esc close</span>
</div>
</div>
</div>
);
}

View File

@@ -765,12 +765,37 @@ input[type="number"] {
min-height: 55px;
}
.spell-inputs-row td {
border-bottom: none;
padding-top: 10px;
}
.spell-notes-row td {
padding-top: 0;
padding-bottom: 10px;
}
.spell-notes-row textarea {
display: block;
width: 100%;
min-height: 0;
box-sizing: border-box;
}
.spell-name-col {
width: 35%;
}
.spell-name-col input {
font-weight: bold;
}
.spell-class-col {
width: 14%;
}
.spell-mp-col {
width: 12%;
width: 10%;
}
.spell-targets-col {
@@ -782,7 +807,8 @@ input[type="number"] {
}
.spell-del-col {
width: 40px;
width: 1px;
white-space: nowrap;
}
.spell-del-btn {
@@ -821,6 +847,82 @@ input[type="number"] {
color: var(--text-bright);
}
.spell-picker-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
}
.spell-picker-modal {
background: var(--surface2);
border: 1px solid var(--border-bright);
width: min(480px, 90vw);
max-height: 70vh;
display: flex;
flex-direction: column;
}
.spell-picker-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
border-bottom: 1px solid var(--border-bright);
font-family: var(--font-display);
font-size: 0.6rem;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--teal);
}
.spell-picker-close {
background: none;
border: none;
cursor: pointer;
color: var(--text-dim);
font-size: 0.9rem;
line-height: 1;
padding: 0;
}
.spell-picker-close:hover {
color: var(--red);
}
.spell-picker-list {
list-style: none;
margin: 0;
padding: 0;
overflow-y: auto;
}
.spell-picker-item {
display: flex;
align-items: baseline;
justify-content: space-between;
padding: 8px 14px;
cursor: pointer;
border-bottom: 1px solid var(--border);
}
.spell-picker-item:hover {
background: var(--surface3);
}
.spell-picker-name {
font-size: 0.9rem;
}
.spell-picker-class {
font-size: 0.7rem;
color: var(--text-dim);
text-transform: capitalize;
}
/* ── DISCIPLINES ─────────────────────────────────── */
.disciplines-row {
display: flex;

19
src/globals.d.ts vendored
View File

@@ -1,5 +1,24 @@
declare module "*.css";
declare module "*.yml" {
const data: unknown;
export default data;
}
interface SpellTemplate {
name: string;
cost: string;
targets: string;
duration: string;
description: string;
class: string;
offensive?: boolean;
}
interface SpellsFile {
spells: SpellTemplate[];
}
interface BookPage {
n: number;
content: string;

View File

@@ -65,6 +65,10 @@ module.exports = (env, argv) => {
},
},
},
{
test: /\.ya?ml$/,
use: "yaml-loader",
},
{
test: /\.css$/,
use: [
@@ -81,6 +85,25 @@ module.exports = (env, argv) => {
...(isProd
? [new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })]
: []),
// Register book HTML source files as compilation dependencies so webpack
// watches them in dev mode and triggers a rebuild when they change.
{
apply(compiler) {
compiler.hooks.afterCompile.tap("WatchBookPages", (compilation) => {
const bookDirs = [
path.resolve(__dirname, "books/core"),
path.resolve(__dirname, "books/natural-fantasy-atlas"),
];
for (const dir of bookDirs) {
try {
fs.readdirSync(dir)
.filter(f => f.endsWith(".html") && f !== "index.html")
.forEach(f => compilation.fileDependencies.add(path.join(dir, f)));
} catch (_) {}
}
});
},
},
new HtmlWebpackPlugin({
template: "./src/fabula-ultima-sheet.html",
filename: "index.html",
@@ -88,7 +111,7 @@ module.exports = (env, argv) => {
scriptLoading: "blocking",
}),
new HtmlWebpackPlugin({
templateContent: bookTemplate(
templateContent: () => bookTemplate(
"Fabula Ultima - Core Rulebook",
"Core Rules",
"./books/core"
@@ -98,7 +121,7 @@ module.exports = (env, argv) => {
scriptLoading: "blocking",
}),
new HtmlWebpackPlugin({
templateContent: bookTemplate(
templateContent: () => bookTemplate(
"Fabula Ultima - Natural Fantasy Atlas",
"Natural Fantasy Atlas",
"./books/natural-fantasy-atlas"