feat: Phase 4 port Oracle, Meaning, UNE, and Dice

Ports the four core Mythic GME tools from reference/index.html into
the modular structure. Fate Check and Random Event Check preserve
the exact probability matrix and roll logic, now reading chaos
factor live from the active campaign instead of local state. Meaning
and UNE tables are served from data/tables/ via a new tables route.
UNE motivation uses the published verb+noun tables instead of the
reference's flattened phrase list. Dice (pool builder, custom roll,
percentile, ability score) is ported verbatim as pure frontend logic.
This commit is contained in:
claudecode
2026-07-01 00:13:00 -04:00
parent 3a7340975f
commit ec28933623
15 changed files with 1211 additions and 8 deletions
+493
View File
@@ -23,6 +23,10 @@
--accent: #c9a35c;
--accent-hover: #ddb876;
--accent-text: #121110;
--success: #83a374;
--danger: #c4705a;
--info: #7a9fb5;
}
[data-theme="light"] {
@@ -38,6 +42,10 @@
--accent: #a3822f;
--accent-hover: #8c6f27;
--accent-text: #f5f1ea;
--success: #5c7a4e;
--danger: #a14a36;
--info: #4d7189;
}
* ,
@@ -333,3 +341,488 @@ h1, h2, h3, h4, h5, h6 {
.view.active {
display: block;
}
/* --- Cards (Oracle, Meaning, UNE, Dice) --- */
.card {
max-width: 700px;
margin: 0 auto 16px;
padding: 20px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 4px;
}
.card-title {
margin-bottom: 14px;
font-family: var(--font-heading);
font-size: 0.9rem;
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--text-secondary);
}
.card-note {
margin-bottom: 12px;
font-style: italic;
font-size: 0.85rem;
color: var(--text-secondary);
}
.placeholder {
font-style: italic;
color: var(--text-muted);
font-size: 0.9rem;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate {
animation: fadeIn 0.25s ease forwards;
}
/* --- Buttons shared by Oracle/Meaning/UNE/Dice --- */
.roll-btn {
width: 100%;
margin-top: 8px;
padding: 12px;
background: var(--bg);
border: 1px solid var(--accent);
border-radius: 3px;
color: var(--accent);
font-family: var(--font-heading);
font-size: 0.8rem;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
cursor: pointer;
transition: background var(--transition-speed) ease, border-color var(--transition-speed) ease;
}
.roll-btn:hover {
background: var(--bg-hover);
border-color: var(--accent-hover);
}
.roll-btn:active {
transform: scale(0.98);
}
.roll-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.clear-btn {
padding: 12px 16px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 3px;
color: var(--text-secondary);
font-family: var(--font-heading);
font-size: 0.75rem;
letter-spacing: 0.12em;
text-transform: uppercase;
cursor: pointer;
white-space: nowrap;
transition: border-color var(--transition-speed) ease, color var(--transition-speed) ease;
}
.clear-btn:hover {
border-color: var(--danger);
color: var(--danger);
}
.clear-btn:active {
transform: scale(0.98);
}
/* --- Fate Check --- */
.prob-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
margin-bottom: 12px;
}
.prob-btn {
padding: 8px 4px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--bg);
color: var(--text-secondary);
font-family: var(--font-body);
font-size: 0.82rem;
text-align: center;
cursor: pointer;
transition: border-color var(--transition-speed) ease, color var(--transition-speed) ease, background var(--transition-speed) ease;
}
.prob-btn:hover {
border-color: var(--accent-hover);
color: var(--text-primary);
}
.prob-btn.selected {
border-color: var(--accent);
color: var(--accent);
background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.result-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 64px;
margin-top: 12px;
padding: 18px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--bg);
text-align: center;
}
.result-box.yes {
border-color: var(--success);
}
.result-box.no {
border-color: var(--danger);
}
.result-box.exceptional-yes {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 8%, var(--bg));
}
.result-box.exceptional-no {
border-color: var(--danger);
background: color-mix(in srgb, var(--danger) 8%, var(--bg));
}
.result-box.random-event {
border-color: var(--info);
background: color-mix(in srgb, var(--info) 8%, var(--bg));
}
.result-main {
font-family: var(--font-heading);
font-size: 1.4rem;
font-weight: 600;
letter-spacing: 0.08em;
}
.result-main.yes-color {
color: var(--success);
}
.result-main.no-color {
color: var(--danger);
}
.result-main.blue-color {
color: var(--info);
}
.result-sub {
margin-top: 4px;
font-style: italic;
font-size: 0.85rem;
color: var(--text-secondary);
}
.result-roll {
margin-top: 6px;
font-family: var(--font-mono);
font-size: 0.78rem;
color: var(--text-muted);
}
/* --- Random Event Check / NPC Relationship --- */
.event-result {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 48px;
margin-top: 12px;
padding: 16px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--bg);
text-align: center;
}
.relationship-text {
font-style: italic;
font-size: 1rem;
color: var(--accent);
}
/* --- Meaning Tables --- */
.meaning-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-top: 12px;
}
.meaning-individual-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.meaning-individual-grid .roll-btn {
margin-top: 0;
}
.meaning-result {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 56px;
padding: 14px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--bg);
text-align: center;
}
.meaning-label {
margin-bottom: 6px;
font-family: var(--font-heading);
font-size: 0.68rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--text-muted);
}
.meaning-word {
font-size: 1.15rem;
font-style: italic;
color: var(--info);
}
/* --- UNE --- */
.une-result {
margin-top: 12px;
padding: 18px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--bg);
}
.une-row {
display: flex;
align-items: baseline;
justify-content: space-between;
padding: 6px 0;
border-bottom: 1px solid var(--bg-elevated);
}
.une-row:last-child {
border-bottom: none;
}
.une-key {
min-width: 6rem;
font-family: var(--font-heading);
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-secondary);
}
.une-val {
font-style: italic;
font-size: 1rem;
color: var(--accent);
text-align: right;
}
/* --- Dice --- */
.dice-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-bottom: 12px;
}
.die-btn {
padding: 12px 4px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--bg);
color: var(--text-secondary);
font-family: var(--font-heading);
font-size: 0.8rem;
letter-spacing: 0.08em;
text-align: center;
cursor: pointer;
transition: border-color var(--transition-speed) ease, color var(--transition-speed) ease, background var(--transition-speed) ease;
}
.die-btn:hover {
border-color: var(--accent-hover);
color: var(--text-primary);
}
.die-btn.selected {
border-color: var(--accent);
color: var(--accent);
background: color-mix(in srgb, var(--accent) 10%, transparent);
}
.die-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.pool-display {
display: flex;
align-items: center;
justify-content: center;
min-height: 38px;
margin: 12px 0 6px;
padding: 10px 16px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--bg);
font-family: var(--font-heading);
font-size: 0.95rem;
letter-spacing: 0.04em;
color: var(--accent);
text-align: center;
}
.pool-cap-msg {
margin-bottom: 6px;
font-style: italic;
font-size: 0.8rem;
color: var(--danger);
text-align: center;
}
.dice-actions-row {
display: flex;
gap: 10px;
margin-top: 8px;
}
.dice-actions-row .roll-btn {
flex: 1;
margin-top: 0;
}
.pool-result-groups {
width: 100%;
font-style: italic;
font-size: 0.85rem;
color: var(--text-secondary);
text-align: center;
}
.pool-result-group {
padding: 2px 0;
}
.pool-group-type {
font-family: var(--font-heading);
font-size: 0.68rem;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--text-secondary);
}
.dice-divider {
width: 70%;
height: 1px;
margin: 9px auto;
background: var(--border);
}
.custom-roll-row {
display: flex;
align-items: stretch;
gap: 10px;
margin-bottom: 12px;
}
.custom-roll-input {
flex: 1;
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--bg);
color: var(--text-primary);
font-family: var(--font-body);
font-size: 0.95rem;
line-height: 1.2;
text-align: center;
}
.custom-roll-input:focus {
outline: none;
border-color: var(--accent-hover);
}
.custom-die-input {
display: flex;
align-items: center;
flex: 1;
gap: 6px;
}
.custom-die-prefix {
font-family: var(--font-heading);
font-size: 0.9rem;
color: var(--text-secondary);
}
.dice-result-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 64px;
padding: 18px;
border: 1px solid var(--border);
border-radius: 3px;
background: var(--bg);
text-align: center;
}
.dice-total {
font-family: var(--font-heading);
font-size: 2.2rem;
font-weight: 600;
color: var(--accent);
line-height: 1;
}
.dice-breakdown {
margin-top: 6px;
font-style: italic;
font-size: 0.8rem;
color: var(--text-secondary);
}