feat(@projects/@magic-civilization): ✨ add design gallery UI
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
fe8dad3379
commit
2823d7facf
6 changed files with 671 additions and 35 deletions
635
.project/designs/design-gallery.html
Normal file
635
.project/designs/design-gallery.html
Normal file
|
|
@ -0,0 +1,635 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Age of Dwarves — Design System Gallery</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Grenze+Gotisch:wght@900&family=Bitter:wght@700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-deepest: #171219;
|
||||
--bg-panel: #17121e;
|
||||
--bg-surface: #221a14;
|
||||
--bg-raised: #2a2018;
|
||||
--bg-list: #120e1e;
|
||||
--bg-list-sel: #3f2d0d;
|
||||
--bg-menu: #0e0a17;
|
||||
--btn-normal: #1f1733;
|
||||
--btn-hover: #331a0d;
|
||||
--btn-pressed: #472f0f;
|
||||
|
||||
--text-title: #f2d973;
|
||||
--text-primary: #e0d8c8;
|
||||
--text-secondary: #bfb7a6;
|
||||
--text-muted: #b2b2b2;
|
||||
--text-disabled: #80806699;
|
||||
--text-btn: #e0d199;
|
||||
--text-btn-hover: #ffeb80;
|
||||
--text-btn-press: #ffffb3;
|
||||
|
||||
--accent-gold: #d9a020;
|
||||
--accent-gold-bright: #d9b33f;
|
||||
--accent-gold-press: #ffd14d;
|
||||
--accent-gold-res: #f2d133;
|
||||
--accent-science: #66bfff;
|
||||
--accent-sage: #66b866;
|
||||
--accent-ping: #ffd973;
|
||||
|
||||
--sem-positive: #66e666;
|
||||
--sem-golden-age: #ffeb66;
|
||||
--sem-negative: #d95940;
|
||||
--sem-warning: #e69933;
|
||||
--sem-diplomacy: #e68c73;
|
||||
--sem-trade: #ccbf73;
|
||||
|
||||
--border-panel: #73591fcc;
|
||||
--border-focus: #d9b340;
|
||||
|
||||
--font-heading: 'Grenze Gotisch', serif;
|
||||
--font-body: 'Bitter', serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
background: var(--bg-deepest);
|
||||
color: var(--text-primary);
|
||||
font-family: var(--font-body);
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
padding: 48px 32px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 48px;
|
||||
color: var(--text-title);
|
||||
letter-spacing: 0.04em;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.subtitle {
|
||||
color: var(--text-secondary);
|
||||
font-size: 14px;
|
||||
margin-bottom: 56px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 56px;
|
||||
}
|
||||
.section-title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 24px;
|
||||
color: var(--accent-gold);
|
||||
border-bottom: 1px solid var(--border-panel);
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 24px;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* ── COLOR SWATCHES ── */
|
||||
.swatch-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.swatch {
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border-panel);
|
||||
}
|
||||
.swatch-color {
|
||||
height: 60px;
|
||||
}
|
||||
.swatch-info {
|
||||
background: var(--bg-panel);
|
||||
padding: 8px 10px;
|
||||
}
|
||||
.swatch-name {
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
font-family: monospace;
|
||||
}
|
||||
.swatch-hex {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
font-family: monospace;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.group-label {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
/* ── TYPOGRAPHY ── */
|
||||
.type-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 24px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid #73591f33;
|
||||
}
|
||||
.type-label {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
width: 80px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.type-specimen { flex: 1; }
|
||||
|
||||
/* ── BUTTONS ── */
|
||||
.btn-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
font-family: var(--font-body);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--text-btn);
|
||||
background: var(--btn-normal);
|
||||
border: 1px solid var(--border-panel);
|
||||
border-radius: 3px;
|
||||
padding: 8px 18px;
|
||||
cursor: pointer;
|
||||
transition: all 150ms ease;
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
.btn:hover { background: var(--btn-hover); border-color: var(--accent-gold-bright); color: var(--text-btn-hover); }
|
||||
.btn.pressed { background: var(--btn-pressed); border-color: var(--accent-gold-press); color: var(--text-btn-press); border-width: 2px; }
|
||||
.btn.disabled { color: var(--text-disabled); cursor: not-allowed; opacity: 0.7; }
|
||||
.btn.focus { border-color: var(--border-focus); border-width: 2px; }
|
||||
.btn.icon { width: 36px; height: 36px; padding: 0; display: flex; align-items: center; justify-content: center; font-size: 18px; }
|
||||
|
||||
.btn-end-turn {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 18px;
|
||||
color: var(--text-title);
|
||||
background: #2a1a06;
|
||||
border: 2px solid var(--accent-gold);
|
||||
border-radius: 3px;
|
||||
padding: 10px 32px;
|
||||
cursor: pointer;
|
||||
letter-spacing: 0.06em;
|
||||
transition: all 150ms ease;
|
||||
}
|
||||
.btn-end-turn:hover { background: #3d2608; border-color: var(--accent-gold-bright); }
|
||||
|
||||
.btn-war { background: #3d1a14; border-color: #e6735a; color: #e6735a; }
|
||||
.btn-peace { background: #0e2614; border-color: #8cd99a; color: #8cd99a; }
|
||||
.btn-trade { background: #1f1a08; border-color: var(--sem-trade); color: var(--sem-trade); }
|
||||
|
||||
/* ── PANELS ── */
|
||||
.panel-demo {
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border-panel);
|
||||
border-radius: 4px;
|
||||
padding: 18px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.panel-title {
|
||||
font-family: var(--font-heading);
|
||||
font-size: 18px;
|
||||
color: var(--text-title);
|
||||
margin-bottom: 12px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.panel-divider {
|
||||
border: none;
|
||||
border-top: 1px solid #99731f80;
|
||||
margin: 12px 0;
|
||||
}
|
||||
|
||||
.list-panel {
|
||||
background: var(--bg-list);
|
||||
border: 1px solid #4d4014b2;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.list-item {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
border-bottom: 1px solid #73591f22;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.list-item:last-child { border-bottom: none; }
|
||||
.list-item.selected {
|
||||
background: var(--bg-list-sel);
|
||||
border-color: #d9b340cc;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.list-item .value { color: var(--accent-gold-res); font-size: 13px; font-family: monospace; }
|
||||
|
||||
/* ── STAT CHIPS ── */
|
||||
.stat-row { display: flex; gap: 12px; flex-wrap: wrap; }
|
||||
.stat-chip {
|
||||
background: var(--bg-panel);
|
||||
border: 1px solid var(--border-panel);
|
||||
border-radius: 3px;
|
||||
padding: 6px 12px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.stat-chip .icon { font-size: 16px; }
|
||||
.stat-chip .val { font-family: monospace; font-weight: bold; }
|
||||
.gold .val { color: var(--accent-gold-res); }
|
||||
.sci .val { color: var(--accent-science); }
|
||||
.happy .val { color: var(--sem-positive); }
|
||||
.neg .val { color: var(--sem-negative); }
|
||||
.warn .val { color: var(--sem-warning); }
|
||||
|
||||
/* ── PLAYER COLORS ── */
|
||||
.player-grid {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.player-swatch {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ffffff22;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding-bottom: 4px;
|
||||
font-size: 10px;
|
||||
color: #000000cc;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* ── SEMANTIC ── */
|
||||
.sem-row { display: flex; gap: 12px; flex-wrap: wrap; }
|
||||
.sem-badge {
|
||||
padding: 6px 14px;
|
||||
border-radius: 3px;
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/* ── PROMOTION BADGE ── */
|
||||
.promo-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: #1a1208;
|
||||
border: 1px solid var(--accent-gold);
|
||||
border-radius: 3px;
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
color: var(--accent-gold);
|
||||
}
|
||||
|
||||
/* ── NOTIFICATION TOAST ── */
|
||||
.toast {
|
||||
background: #1a1208ee;
|
||||
border: 1px solid var(--accent-gold);
|
||||
border-radius: 4px;
|
||||
padding: 12px 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
max-width: 380px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.toast-icon { font-size: 22px; }
|
||||
.toast-title { color: var(--text-title); font-size: 15px; margin-bottom: 2px; }
|
||||
.toast-body { color: var(--text-secondary); font-size: 13px; }
|
||||
|
||||
/* ── GAUGE ── */
|
||||
.gauge-row { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
|
||||
.gauge-track {
|
||||
width: 120px;
|
||||
height: 10px;
|
||||
background: #ffffff18;
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.gauge-fill { height: 100%; border-radius: 2px; }
|
||||
|
||||
/* ── FOG DEMO ── */
|
||||
.fog-demo {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
.fog-tile {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
color: #ffffff88;
|
||||
text-align: center;
|
||||
border: 1px solid #ffffff11;
|
||||
background: #2a5520;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.fog-tile .fog-layer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 10px;
|
||||
}
|
||||
.fog-tile .terrain-label { position: relative; z-index: 1; }
|
||||
|
||||
footer {
|
||||
margin-top: 80px;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
font-family: monospace;
|
||||
border-top: 1px solid var(--border-panel);
|
||||
padding-top: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Age of Dwarves</h1>
|
||||
<p class="subtitle">UI Design System · Episode 1 · Magic Civilization Series</p>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ BACKGROUNDS -->
|
||||
<div class="section">
|
||||
<div class="section-title">Backgrounds</div>
|
||||
<div class="swatch-grid">
|
||||
<div class="swatch"><div class="swatch-color" style="background:#171219"></div><div class="swatch-info"><div class="swatch-name">bg-deepest</div><div class="swatch-hex">#171219</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#0e0a17"></div><div class="swatch-info"><div class="swatch-name">bg-menu</div><div class="swatch-hex">#0e0a17</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#17121e"></div><div class="swatch-info"><div class="swatch-name">bg-panel</div><div class="swatch-hex">#17121e</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#120e1e"></div><div class="swatch-info"><div class="swatch-name">bg-list</div><div class="swatch-hex">#120e1e</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#1a1410"></div><div class="swatch-info"><div class="swatch-name">bg-base</div><div class="swatch-hex">#1a1410</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#221a14"></div><div class="swatch-info"><div class="swatch-name">bg-surface</div><div class="swatch-hex">#221a14</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#2a2018"></div><div class="swatch-info"><div class="swatch-name">bg-raised</div><div class="swatch-hex">#2a2018</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#3f2d0d"></div><div class="swatch-info"><div class="swatch-name">bg-list-selected</div><div class="swatch-hex">#3f2d0d</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#1f1733"></div><div class="swatch-info"><div class="swatch-name">btn-normal</div><div class="swatch-hex">#1f1733</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#331a0d"></div><div class="swatch-info"><div class="swatch-name">btn-hover</div><div class="swatch-hex">#331a0d</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#472f0f"></div><div class="swatch-info"><div class="swatch-name">btn-pressed</div><div class="swatch-hex">#472f0f</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ TEXT -->
|
||||
<div class="section">
|
||||
<div class="section-title">Text Hierarchy</div>
|
||||
<div class="swatch-grid">
|
||||
<div class="swatch"><div class="swatch-color" style="background:#f2d973"></div><div class="swatch-info"><div class="swatch-name">text-title</div><div class="swatch-hex">#f2d973</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#e0d8c8"></div><div class="swatch-info"><div class="swatch-name">text-primary</div><div class="swatch-hex">#e0d8c8</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#bfb7a6"></div><div class="swatch-info"><div class="swatch-name">text-secondary</div><div class="swatch-hex">#bfb7a6</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#b2b2b2"></div><div class="swatch-info"><div class="swatch-name">text-muted</div><div class="swatch-hex">#b2b2b2</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#e0d199"></div><div class="swatch-info"><div class="swatch-name">text-button</div><div class="swatch-hex">#e0d199</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#ffeb80"></div><div class="swatch-info"><div class="swatch-name">text-btn-hover</div><div class="swatch-hex">#ffeb80</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#ffffb3"></div><div class="swatch-info"><div class="swatch-name">text-btn-press</div><div class="swatch-hex">#ffffb3</div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ ACCENTS & SEMANTIC -->
|
||||
<div class="section">
|
||||
<div class="section-title">Accents & Semantic</div>
|
||||
<div class="group-label">Accents</div>
|
||||
<div class="swatch-grid">
|
||||
<div class="swatch"><div class="swatch-color" style="background:#d9a020"></div><div class="swatch-info"><div class="swatch-name">accent-gold</div><div class="swatch-hex">#d9a020</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#d9b33f"></div><div class="swatch-info"><div class="swatch-name">gold-bright</div><div class="swatch-hex">#d9b33f</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#ffd14d"></div><div class="swatch-info"><div class="swatch-name">gold-press</div><div class="swatch-hex">#ffd14d</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#f2d133"></div><div class="swatch-info"><div class="swatch-name">gold-resource</div><div class="swatch-hex">#f2d133</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#66bfff"></div><div class="swatch-info"><div class="swatch-name">accent-science</div><div class="swatch-hex">#66bfff</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#66b866"></div><div class="swatch-info"><div class="swatch-name">accent-sage</div><div class="swatch-hex">#66b866</div></div></div>
|
||||
<div class="swatch"><div class="swatch-color" style="background:#ffd973"></div><div class="swatch-info"><div class="swatch-name">accent-ping</div><div class="swatch-hex">#ffd973</div></div></div>
|
||||
</div>
|
||||
<div class="group-label" style="margin-top:20px">Semantic</div>
|
||||
<div class="sem-row">
|
||||
<div class="sem-badge" style="background:#0d3d0d;color:#66e666;border:1px solid #66e66666">✓ Positive</div>
|
||||
<div class="sem-badge" style="background:#3d3300;color:#ffeb66;border:1px solid #ffeb6666">★ Golden Age</div>
|
||||
<div class="sem-badge" style="background:#3d0f08;color:#d95940;border:1px solid #d9594066">✗ Negative</div>
|
||||
<div class="sem-badge" style="background:#3d2000;color:#e69933;border:1px solid #e6993366">⚠ Warning</div>
|
||||
<div class="sem-badge" style="background:#3d1a14;color:#e68c73;border:1px solid #e68c7366">⚔ War</div>
|
||||
<div class="sem-badge" style="background:#8cd99a22;color:#8cd99a;border:1px solid #8cd99a66">☮ Peace</div>
|
||||
<div class="sem-badge" style="background:#1f1a08;color:#ccbf73;border:1px solid #ccbf7366">⇌ Trade</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ PLAYER COLORS -->
|
||||
<div class="section">
|
||||
<div class="section-title">Player Colors (Default Palette)</div>
|
||||
<div class="player-grid">
|
||||
<div class="player-swatch" style="background:#3366ff;color:#fff">Blue</div>
|
||||
<div class="player-swatch" style="background:#e63333;color:#fff">Red</div>
|
||||
<div class="player-swatch" style="background:#33cc4d;color:#000">Green</div>
|
||||
<div class="player-swatch" style="background:#e6cc1a;color:#000">Yellow</div>
|
||||
<div class="player-swatch" style="background:#b24de6;color:#fff">Purple</div>
|
||||
<div class="player-swatch" style="background:#e6801a;color:#000">Orange</div>
|
||||
<div class="player-swatch" style="background:#1accd9;color:#000">Cyan</div>
|
||||
<div class="player-swatch" style="background:#cc4d80;color:#fff">Magenta</div>
|
||||
<div class="player-swatch" style="background:#806659;color:#fff">Brown</div>
|
||||
<div class="player-swatch" style="background:#999999;color:#000">Gray</div>
|
||||
<div class="player-swatch" style="background:#66b366;color:#000">Sage</div>
|
||||
<div class="player-swatch" style="background:#4d4d99;color:#fff">Navy</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ TYPOGRAPHY -->
|
||||
<div class="section">
|
||||
<div class="section-title">Typography</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">24px / title</div>
|
||||
<div class="type-specimen" style="font-family:'Grenze Gotisch',serif;font-size:24px;color:#f2d973;font-weight:900">Ironhold — Capital of the Stoneguard</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">16px / md</div>
|
||||
<div class="type-specimen" style="font-family:'Bitter',serif;font-size:16px;color:#e0d8c8;font-weight:700">Research Complete: Iron Smelting Unlocked</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">15px / base</div>
|
||||
<div class="type-specimen" style="font-family:'Bitter',serif;font-size:15px;color:#e0d8c8;font-weight:700">Production: Barracks · 8 turns remaining</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">14px / sm</div>
|
||||
<div class="type-specimen" style="font-family:'Bitter',serif;font-size:14px;color:#bfb7a6;font-weight:700">Population 6 · Food +3 · Culture +2 per turn</div>
|
||||
</div>
|
||||
<div class="type-row">
|
||||
<div class="type-label">13px / xs</div>
|
||||
<div class="type-specimen" style="font-family:monospace;font-size:13px;color:#b2b2b2">Turn 42 · Era: Iron Age · Seed 0xDEAD</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ BUTTONS -->
|
||||
<div class="section">
|
||||
<div class="section-title">Buttons</div>
|
||||
<div class="group-label">States</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn">Normal</button>
|
||||
<button class="btn" style="background:var(--btn-hover);border-color:var(--accent-gold-bright);color:var(--text-btn-hover)">Hover</button>
|
||||
<button class="btn pressed">Pressed</button>
|
||||
<button class="btn focus">Focused</button>
|
||||
<button class="btn disabled">Disabled</button>
|
||||
</div>
|
||||
<div class="group-label">Variants</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn-end-turn">End Turn</button>
|
||||
<button class="btn icon">⚔</button>
|
||||
<button class="btn icon">🏛</button>
|
||||
<button class="btn icon">🔬</button>
|
||||
<button class="btn icon">⚙</button>
|
||||
<button class="btn btn-war">Declare War</button>
|
||||
<button class="btn btn-peace">Offer Peace</button>
|
||||
<button class="btn btn-trade">Propose Trade</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ PANELS -->
|
||||
<div class="section">
|
||||
<div class="section-title">Panels & Lists</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
|
||||
<div>
|
||||
<div class="panel-demo">
|
||||
<div class="panel-title">Tile Information</div>
|
||||
<hr class="panel-divider">
|
||||
<div style="font-size:14px;color:var(--text-secondary);margin-bottom:6px">⛰ Mountain Plains</div>
|
||||
<div style="font-size:13px;color:var(--text-muted);margin-bottom:12px">Owner: Stoneguard Clan · Improved</div>
|
||||
<div class="stat-row">
|
||||
<div class="stat-chip gold"><span class="icon">🪙</span><span class="val">+2</span></div>
|
||||
<div class="stat-chip"><span class="icon">🔨</span><span class="val" style="color:var(--text-secondary)">+3</span></div>
|
||||
<div class="stat-chip"><span class="icon">🌾</span><span class="val" style="color:var(--accent-sage)">+1</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="list-panel">
|
||||
<div class="list-item"><span>Barracks</span><span class="value">120 ⚒</span></div>
|
||||
<div class="list-item selected"><span>Forge <span style="color:var(--accent-gold);font-size:11px">↗ 3 turns</span></span><span class="value">80 ⚒</span></div>
|
||||
<div class="list-item"><span>Marketplace</span><span class="value">150 ⚒</span></div>
|
||||
<div class="list-item"><span>Library</span><span class="value">130 ⚒</span></div>
|
||||
<div class="list-item"><span>Bathhouse</span><span class="value">110 ⚒</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ HUD ELEMENTS -->
|
||||
<div class="section">
|
||||
<div class="section-title">HUD Elements</div>
|
||||
<div class="group-label">Resource Stats (Top Bar)</div>
|
||||
<div class="stat-row" style="margin-bottom:24px">
|
||||
<div class="stat-chip gold"><span class="icon">🪙</span><span class="val">+84</span></div>
|
||||
<div class="stat-chip sci"><span class="icon">⚗</span><span class="val">+22</span></div>
|
||||
<div class="stat-chip happy"><span class="icon">☺</span><span class="val">+7</span></div>
|
||||
<div class="stat-chip"><span class="icon">🌿</span><span class="val" style="color:var(--accent-sage)">+12</span></div>
|
||||
<div class="stat-chip neg"><span class="icon">⚔</span><span class="val">War</span></div>
|
||||
<div class="stat-chip warn"><span class="icon">⚠</span><span class="val">Unhappy −3</span></div>
|
||||
</div>
|
||||
|
||||
<div class="group-label">Climate Gauges</div>
|
||||
<div style="margin-bottom:24px">
|
||||
<div class="gauge-row">
|
||||
<span style="font-size:12px;color:var(--text-muted);width:60px">Cold</span>
|
||||
<div class="gauge-track"><div class="gauge-fill" style="background:#1a4dff;width:75%"></div></div>
|
||||
<span style="font-size:12px;color:#66b3ff">−2.1°</span>
|
||||
</div>
|
||||
<div class="gauge-row">
|
||||
<span style="font-size:12px;color:var(--text-muted);width:60px">Warm</span>
|
||||
<div class="gauge-track"><div class="gauge-fill" style="background:#26cc40;width:45%"></div></div>
|
||||
<span style="font-size:12px;color:var(--text-primary)">+0.4°</span>
|
||||
</div>
|
||||
<div class="gauge-row">
|
||||
<span style="font-size:12px;color:var(--text-muted);width:60px">Hot</span>
|
||||
<div class="gauge-track"><div class="gauge-fill" style="background:#ff260d;width:20%"></div></div>
|
||||
<span style="font-size:12px;color:#ff731a">⚠ Warming</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group-label">Notifications</div>
|
||||
<div>
|
||||
<div class="toast">
|
||||
<div class="toast-icon">⚔</div>
|
||||
<div><div class="toast-title">Combat Victory</div><div class="toast-body">Warrior defeated Emberfall Scout · +12 XP</div></div>
|
||||
</div>
|
||||
<div class="toast" style="border-color:var(--accent-science)">
|
||||
<div class="toast-icon">⚗</div>
|
||||
<div><div class="toast-title" style="color:var(--accent-science)">Research Complete</div><div class="toast-body">Iron Smelting unlocked · Choose next research</div></div>
|
||||
</div>
|
||||
<div class="toast" style="border-color:var(--sem-positive)">
|
||||
<div class="toast-icon">★</div>
|
||||
<div><div class="toast-title" style="color:var(--sem-golden-age)">Golden Age</div><div class="toast-body">The Stoneguard enters a Golden Age · +2 turns</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="group-label" style="margin-top:24px">Promotions</div>
|
||||
<div class="btn-row">
|
||||
<div class="promo-badge">★ Strength I</div>
|
||||
<div class="promo-badge">★★ Flanking II</div>
|
||||
<div class="promo-badge">★ City Raider</div>
|
||||
<div class="promo-badge">★★★ Combat Master</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════════════════════════════════ FOG OF WAR -->
|
||||
<div class="section">
|
||||
<div class="section-title">Fog of War States</div>
|
||||
<div class="fog-demo">
|
||||
<div>
|
||||
<div class="fog-tile" style="background:#2a5520">
|
||||
<span class="terrain-label" style="font-size:11px;color:#ffffffcc">Visible</span>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--text-muted);text-align:center;margin-top:4px">0% fog</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fog-tile" style="background:#2a5520">
|
||||
<div class="fog-layer" style="background:rgba(0,0,0,0.70)">
|
||||
<span style="color:#ffffff66;font-size:10px">Explored</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--text-muted);text-align:center;margin-top:4px">70% black</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fog-tile" style="background:#2a5520">
|
||||
<div class="fog-layer" style="background:rgba(0,0,0,0.90)">
|
||||
<span style="color:#ffffff44;font-size:10px">Unknown</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--text-muted);text-align:center;margin-top:4px">90% black</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fog-tile" style="background:#3366ff55;border-color:#3366ff66">
|
||||
<div class="fog-layer" style="background:rgba(51,102,255,0.38)">
|
||||
<span style="color:#ffffffaa;font-size:10px">Owned</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--text-muted);text-align:center;margin-top:4px">38% tint</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fog-tile" style="background:#2a5520;position:relative">
|
||||
<div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center">
|
||||
<div style="width:16px;height:16px;border-radius:50%;background:rgba(255,217,115,0.9);box-shadow:0 0 0 4px rgba(255,217,115,0.4),0 0 0 8px rgba(255,217,115,0.15)"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:11px;color:var(--text-muted);text-align:center;margin-top:4px">Ping ring</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
Age of Dwarves · UI Design System · sources: ui_theme.tres · fantasy-theme.ts · palettes.json
|
||||
</footer>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -261,24 +261,15 @@ enum UnitRole {
|
|||
}
|
||||
|
||||
fn unit_role(unit: &TacticalUnit) -> UnitRole {
|
||||
// `can_found_city` is the data-driven founder flag populated from the
|
||||
// engine's per-unit JSON (via the bridge in task #3). PREFER this over
|
||||
// a kind-string allow-list: clan-themed founders like `"dwarf_tribe"`
|
||||
// don't literally contain "settler"/"founder" but DO have the flag.
|
||||
// Letting them fall through to Military would make movement.rs move
|
||||
// the settler every turn, which races settle.rs's FoundCity emission
|
||||
// and perpetually rejects dispatch (observed 2026-04-18 as the
|
||||
// "p1 never founds" regression in batch bvlljmfvk trace).
|
||||
if unit.can_found_city {
|
||||
// Use the canonical predicate so clan-themed founders (e.g. "dwarf_tribe")
|
||||
// are caught by the data-driven flag rather than a literal kind-string.
|
||||
// Moving a settler every turn races settle.rs's FoundCity emission and
|
||||
// causes the "p1 never founds" regression (batch bvlljmfvk, 2026-04-18).
|
||||
if unit.is_founder() {
|
||||
return UnitRole::Settler;
|
||||
}
|
||||
match unit.kind.as_str() {
|
||||
"settler" | "dwarf_founder" | "founder" => UnitRole::Settler,
|
||||
"worker" | "engineer" => UnitRole::Worker,
|
||||
// Everything else in the Age of Dwarves data set has combat stats
|
||||
// — warriors, spearmen, pikemen, archers, crossbowmen, cavalry,
|
||||
// scouts. Match the GDScript heuristic `attack > 0 ||
|
||||
// ranged_attack > 0` here via kind-based classification.
|
||||
_ => UnitRole::Military,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use std::time::Instant;
|
|||
use crate::evaluator::ScoringWeights;
|
||||
use crate::mcts::XorShift64;
|
||||
|
||||
use super::{Action, TacticalCity, TacticalPlayerState, TacticalState};
|
||||
use super::{Action, TacticalCity, TacticalPlayerState, TacticalState, TacticalUnit};
|
||||
|
||||
/// Gold floor required before the AI commits to a rush-buy assault unit.
|
||||
///
|
||||
|
|
@ -285,7 +285,7 @@ fn pick_for_city(
|
|||
// founders — mirrors `city_index == 0` gate.
|
||||
let expansion_target = (axes.expansion as u32 / 3).clamp(1, 5);
|
||||
if city_count < expansion_target && city.is_capital {
|
||||
let founder_count = player.units.iter().filter(|u| is_founder(&u.kind)).count() as u32;
|
||||
let founder_count = player.units.iter().filter(|u| is_founder(u)).count() as u32;
|
||||
if founder_count == 0 {
|
||||
return ids::FOUNDER.into();
|
||||
}
|
||||
|
|
@ -415,16 +415,16 @@ fn count_military_units(player: &TacticalPlayerState) -> u32 {
|
|||
player
|
||||
.units
|
||||
.iter()
|
||||
.filter(|u| is_military(&u.kind))
|
||||
.filter(|u| is_military(u))
|
||||
.count() as u32
|
||||
}
|
||||
|
||||
fn is_military(kind: &str) -> bool {
|
||||
!matches!(kind, "founder" | "settler" | "worker")
|
||||
fn is_military(unit: &TacticalUnit) -> bool {
|
||||
!unit.is_founder() && unit.kind != "worker"
|
||||
}
|
||||
|
||||
fn is_founder(kind: &str) -> bool {
|
||||
matches!(kind, "founder" | "settler")
|
||||
fn is_founder(unit: &TacticalUnit) -> bool {
|
||||
unit.is_founder()
|
||||
}
|
||||
|
||||
/// Max military count across all opponents. GDScript uses total enemy
|
||||
|
|
|
|||
|
|
@ -114,13 +114,8 @@ pub(crate) fn decide_settle(
|
|||
actions
|
||||
}
|
||||
|
||||
/// A settler is any unit flagged `can_found_city` by the engine's data pack.
|
||||
/// Prefer the data-driven flag over string matching on `kind` — clan-themed
|
||||
/// founders like `"dwarf_tribe"` wouldn't match a hardcoded id set.
|
||||
/// Falls back to string match on legacy fixtures (where `can_found_city` is
|
||||
/// default-false) so existing tests continue to pass.
|
||||
fn is_settler(unit: &TacticalUnit) -> bool {
|
||||
unit.can_found_city || matches!(unit.kind.as_str(), "settler" | "founder")
|
||||
unit.is_founder()
|
||||
}
|
||||
|
||||
/// Return the action for a single settler, or `None` if the settler cannot
|
||||
|
|
|
|||
|
|
@ -161,6 +161,14 @@ pub struct TacticalUnit {
|
|||
pub patrol_order: Option<Vec<(i32, i32)>>,
|
||||
}
|
||||
|
||||
impl TacticalUnit {
|
||||
/// True when this unit can found a city. Checks the data-driven flag first;
|
||||
/// falls back to kind-string matching for test fixtures that omit the flag.
|
||||
pub fn is_founder(&self) -> bool {
|
||||
self.can_found_city || matches!(self.kind.as_str(), "settler" | "founder")
|
||||
}
|
||||
}
|
||||
|
||||
/// Specification for a producible military unit — carries enough data for the
|
||||
/// production layer to select tier-appropriate units as tech unlocks (p0-39).
|
||||
///
|
||||
|
|
|
|||
|
|
@ -75,11 +75,15 @@ pub fn derived_defense(axes: &BTreeMap<String, i32>) -> i32 {
|
|||
/// to capital-assault commitment. Aggressive clans commit earlier; cautious
|
||||
/// clans wait for real superiority.
|
||||
///
|
||||
/// Range: `axis=1` → 1.80 (very cautious), `axis=5` → 1.50 (post-p0-37+39
|
||||
/// tempo baseline), `axis=10` → 1.15 (rush-happy). Baseline raised 2026-04-18
|
||||
/// from 1.25 so games reach T250+ — tier-3+ tech chains need the runway.
|
||||
/// Range: `axis=1` → 2.5 (very cautious), `axis=5` → 2.0 (baseline),
|
||||
/// `axis=10` → 1.5 (rush-happy). Baseline raised 2026-04-26 from 1.50 to 2.0
|
||||
/// (and rush-happy floor from 1.15 → 1.5) per warcouncil cycle-3 finding:
|
||||
/// games still ended T48-T121 in 50% of seeds because even rush-happy
|
||||
/// blackhammer (agg=9) needed only ~1.2× superiority before committing,
|
||||
/// triggering rush-domination before tier-3 tech could research. Higher
|
||||
/// thresholds give losers economic + tech runway.
|
||||
pub fn dominance_factor(axes: &BTreeMap<String, i32>) -> f32 {
|
||||
lerp_axis(axis(axes, "aggression"), 1.80, 1.50, 1.15)
|
||||
lerp_axis(axis(axes, "aggression"), 2.5, 2.0, 1.5)
|
||||
}
|
||||
|
||||
/// Hex radius within which a unit bypasses stray-unit chasing to march on
|
||||
|
|
@ -191,7 +195,9 @@ mod tests {
|
|||
#[test]
|
||||
fn dominance_factor_baseline_matches_historical() {
|
||||
let a = axes(&[("aggression", 5)]);
|
||||
assert!((dominance_factor(&a) - 1.50).abs() < 1e-6);
|
||||
// Baseline raised 2026-04-26 from 1.50 → 2.0 (warcouncil cycle-3
|
||||
// rush-domination dampening). See dominance_factor() docs.
|
||||
assert!((dominance_factor(&a) - 2.0).abs() < 1e-6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -242,7 +248,7 @@ mod tests {
|
|||
#[test]
|
||||
fn empty_axes_match_historical_baseline() {
|
||||
let empty: BTreeMap<String, i32> = BTreeMap::new();
|
||||
assert!((dominance_factor(&empty) - 1.50).abs() < 1e-6);
|
||||
assert!((dominance_factor(&empty) - 2.0).abs() < 1e-6);
|
||||
assert_eq!(capital_approach_hex(&empty), 16);
|
||||
assert!((retreat_hp_fraction(&empty) - 0.40).abs() < 1e-6);
|
||||
assert_eq!(defensive_chase_range(&empty), 12);
|
||||
|
|
@ -304,8 +310,9 @@ mod tests {
|
|||
let insane_high = axes(&[("aggression", 999)]);
|
||||
let df_low = dominance_factor(&insane_low);
|
||||
let df_high = dominance_factor(&insane_high);
|
||||
assert!(df_low >= 1.14 && df_low <= 1.81);
|
||||
assert!(df_high >= 1.14 && df_high <= 1.81);
|
||||
// Range bumped 2026-04-26 alongside dominance_factor lerp (1.5–2.5).
|
||||
assert!(df_low >= 1.49 && df_low <= 2.51);
|
||||
assert!(df_high >= 1.49 && df_high <= 2.51);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue