Files
code_of_conquest_dnd/mockups/Inventory.dc.html
Phillip Tarrant 49bc6b22c0 docs(mockups): add the UI/systems bible (11 screens + README)
High-fidelity HTML/CSS/JS design references for the tactical-CRPG direction —
title, character creation, character sheet, inventory, world map, quest log,
shop, pause/settings, dialogue, combat HUD, main exploration window. README is
the handoff spec (visual system, per-screen state models, Godot translation
notes). Not production code — the source of truth for the game's look + systems.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 16:06:23 -05:00

293 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet data-dc-atomics>
<link href="https://fonts.googleapis.com/css2?family=Architects+Daughter&display=swap" rel="stylesheet">
<style>
body{margin:0;background:#0f0d0b;font-family:system-ui,sans-serif}
a{color:#8f3a34}a:hover{color:#6f2b26}
</style>
</helmet>
<div style="width:1920px;height:1080px;position:relative;display:flex">
<!-- ============ LEFT: PAPER DOLL ============ -->
<div style="width:620px;height:1080px;position:relative;overflow:hidden;background:repeating-linear-gradient(135deg,#26221c 0 16px,#221e18 16px 32px);display:flex;flex-direction:column">
<div style="position:absolute;inset:0;background:radial-gradient(120% 70% at 50% 40%,rgba(0,0,0,0) 35%,rgba(0,0,0,.6) 100%)"></div>
<!-- header -->
<div style="position:relative;padding:28px 34px 0">
<div style="font:13px ui-monospace,monospace;letter-spacing:.18em;color:#8a7f6c">EQUIPMENT</div>
<div style="font:600 30px 'Architects Daughter',cursive;color:#e8d9b6;margin-top:4px">Vexcca the Nameless</div>
<div style="font:13px ui-monospace,monospace;letter-spacing:.12em;color:#a9843f;margin-top:2px">HUMAN · SELLSWORD · LVL 4</div>
</div>
<!-- paper doll -->
<div style="position:relative;flex:1;display:flex;align-items:center;justify-content:center">
<div style="position:relative;width:620px;height:430px">
<!-- model -->
<div style="position:absolute;left:50%;top:60px;transform:translateX(-50%);width:150px;height:310px;border:1px dashed #5a5348;border-radius:14px;background:repeating-linear-gradient(135deg,#2c2620 0 14px,#282219 14px 28px);display:flex;align-items:center;justify-content:center;text-align:center;color:#6b6152;font:11px ui-monospace,monospace;letter-spacing:.1em;padding:0 14px">PAPER<br>DOLL</div>
<!-- equipment slots -->
<sc-for list="{{ equip }}" as="s" hint-placeholder-count="12">
<div style="position:absolute;{{ s.posStyle }}">
<div onClick="{{ s.onSelect }}" style="width:62px;height:62px;border-radius:10px;border:1px solid #4a4238;background:rgba(18,15,11,.72);position:relative;display:flex;align-items:center;justify-content:center;cursor:pointer" style-hover="border-color:#a9843f">
<sc-if value="{{ s.filled }}" hint-placeholder-val="{{ true }}">
<span style="font:600 11px ui-monospace,monospace;color:#c9bda3">{{ s.glyph }}</span>
<div style="position:absolute;left:6px;right:6px;bottom:5px;height:3px;border-radius:2px;background:{{ s.rarityColor }}"></div>
</sc-if>
<sc-if value="{{ s.empty }}" hint-placeholder-val="{{ false }}">
<span style="font:8px ui-monospace,monospace;letter-spacing:.08em;color:#5a5348">{{ s.label }}</span>
</sc-if>
<sc-if value="{{ s.sel }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute;inset:-2px;border:2.5px solid #a9843f;border-radius:11px;pointer-events:none"></div>
</sc-if>
</div>
</div>
</sc-for>
</div>
</div>
<!-- derived stats -->
<div style="position:relative;display:flex;gap:10px;padding:0 34px 34px">
<div style="flex:1;background:rgba(18,15,11,.7);border:1px solid #3a352e;border-radius:10px;padding:11px 12px"><div style="font:10px ui-monospace,monospace;letter-spacing:.1em;color:#8a7f6c">ARMOR</div><div style="font:600 22px 'Architects Daughter',cursive;color:#e8d9b6">14</div></div>
<div style="flex:1;background:rgba(18,15,11,.7);border:1px solid #3a352e;border-radius:10px;padding:11px 12px"><div style="font:10px ui-monospace,monospace;letter-spacing:.1em;color:#8a7f6c">DAMAGE</div><div style="font:600 22px 'Architects Daughter',cursive;color:#e8d9b6">1217</div></div>
<div style="flex:1;background:rgba(18,15,11,.7);border:1px solid #3a352e;border-radius:10px;padding:11px 12px"><div style="font:10px ui-monospace,monospace;letter-spacing:.1em;color:#caa39c">HP</div><div style="font:600 22px 'Architects Daughter',cursive;color:#e8d9b6">42/60</div></div>
<div style="flex:1;background:rgba(18,15,11,.7);border:1px solid #3a352e;border-radius:10px;padding:11px 12px"><div style="font:10px ui-monospace,monospace;letter-spacing:.1em;color:#9caccb">MP</div><div style="font:600 22px 'Architects Daughter',cursive;color:#e8d9b6">18/30</div></div>
</div>
</div>
<!-- ============ RIGHT: SATCHEL ============ -->
<div style="width:1300px;height:1080px;background:linear-gradient(180deg,#ece2ca,#e4d8bd);border-left:4px solid #1a1512;box-sizing:border-box;padding:30px 40px;display:flex;flex-direction:column;gap:18px">
<!-- header -->
<div style="display:flex;align-items:flex-end;justify-content:space-between;border-bottom:2px solid #bfa878;padding-bottom:14px">
<div>
<div style="font:600 30px 'Architects Daughter',cursive;letter-spacing:.04em;color:#5a4326">The Satchel</div>
<div style="font:12px ui-monospace,monospace;color:#8a7748;letter-spacing:.08em;margin-top:2px">EVERYTHING YOU'VE STOLEN, LOOTED OR REFUSED TO DROP</div>
</div>
<div style="display:flex;align-items:flex-end;gap:22px">
<div style="text-align:right"><div style="font:10px ui-monospace,monospace;letter-spacing:.1em;color:#8a7748">GOLD</div><div style="font:600 22px ui-monospace,monospace;color:#8f6a2a">◈ 1,240</div></div>
<div style="text-align:right;width:200px">
<div style="display:flex;justify-content:space-between;font:10px ui-monospace,monospace;letter-spacing:.08em;color:#8a7748"><span>ENCUMBRANCE</span><span style="color:{{ weightColor }}">{{ weightText }}</span></div>
<div style="height:9px;border-radius:5px;background:#d3c39a;border:1px solid #bfa878;overflow:hidden;margin-top:4px"><div style="width:{{ weightPct }};height:100%;background:{{ weightColor }}"></div></div>
</div>
<div style="font:11px ui-monospace,monospace;color:#a99464;border:1px solid #cbb684;border-radius:6px;padding:6px 10px;cursor:pointer" style-hover="background:#e6d9b8">ESC ✕</div>
</div>
</div>
<!-- tabs -->
<div style="display:flex;gap:8px">
<sc-for list="{{ tabs }}" as="t" hint-placeholder-count="5">
<div onClick="{{ t.onSelect }}" style="font:12px ui-monospace,monospace;letter-spacing:.08em;padding:7px 14px;border-radius:7px;border:1px solid {{ t.border }};background:{{ t.bg }};color:{{ t.fg }};cursor:pointer">{{ t.label }}</div>
</sc-for>
</div>
<!-- grid -->
<div style="display:grid;grid-template-columns:repeat(12,1fr);gap:8px">
<sc-for list="{{ cells }}" as="c" hint-placeholder-count="60">
<div onClick="{{ c.onSelect }}" style="aspect-ratio:1;border-radius:8px;border:1px solid #cbb684;background:{{ c.bg }};position:relative;cursor:pointer;display:flex;align-items:center;justify-content:center" style-hover="border-color:#8f7a4a">
<sc-if value="{{ c.filled }}" hint-placeholder-val="{{ false }}">
<span style="font:600 12px ui-monospace,monospace;color:#5a4326">{{ c.glyph }}</span>
<div style="position:absolute;left:5px;right:5px;bottom:4px;height:3px;border-radius:2px;background:{{ c.rarityColor }}"></div>
<span style="position:absolute;top:3px;right:5px;font:9px ui-monospace,monospace;color:#8a7748">{{ c.qtyLabel }}</span>
</sc-if>
<sc-if value="{{ c.sel }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute;inset:-2px;border:2.5px solid #8f3a34;border-radius:9px;pointer-events:none"></div>
</sc-if>
</div>
</sc-for>
</div>
<!-- detail -->
<div style="margin-top:auto;min-height:196px;background:#f1e8d2;border:1px solid #bfa878;border-radius:12px;padding:20px 24px">
<sc-if value="{{ hasSel }}" hint-placeholder-val="{{ true }}">
<div style="display:flex;gap:22px;height:100%">
<!-- icon -->
<div style="width:96px;height:96px;border-radius:12px;border:2px solid {{ selRarityColor }};background:#e6d9b8;position:relative;display:flex;align-items:center;justify-content:center;flex:none">
<span style="font:600 16px ui-monospace,monospace;color:#5a4326">{{ selGlyph }}</span>
<div style="position:absolute;left:8px;right:8px;bottom:7px;height:4px;border-radius:2px;background:{{ selRarityColor }}"></div>
</div>
<!-- body -->
<div style="flex:1;min-width:0">
<div style="font:600 24px 'Architects Daughter',cursive;color:{{ selRarityColor }};line-height:1.1">{{ selName }}</div>
<div style="font:11px ui-monospace,monospace;letter-spacing:.1em;color:#8a7748;margin-top:3px">{{ selType }}</div>
<div style="margin-top:12px;display:flex;flex-wrap:wrap;gap:5px 18px">
<sc-for list="{{ selStats }}" as="line" hint-placeholder-count="2">
<div style="font:14px system-ui;color:#3a2f1c">▸ {{ line }}</div>
</sc-for>
</div>
<div style="margin-top:12px;font:14px/1.5 Georgia,serif;font-style:italic;color:#7a684a">"{{ selFlavor }}"</div>
</div>
<!-- side: value + actions -->
<div style="width:180px;flex:none;display:flex;flex-direction:column;gap:8px;border-left:1px solid #cbb684;padding-left:20px">
<div style="display:flex;justify-content:space-between;font:12px ui-monospace,monospace;color:#8a7748"><span>WEIGHT</span><span style="color:#5a4326">{{ selWeight }}</span></div>
<div style="display:flex;justify-content:space-between;font:12px ui-monospace,monospace;color:#8a7748"><span>VALUE</span><span style="color:#8f6a2a">◈ {{ selValue }}</span></div>
<div style="margin-top:auto;display:flex;flex-direction:column;gap:7px">
<sc-if value="{{ selCanEquip }}" hint-placeholder-val="{{ false }}">
<div style="text-align:center;padding:9px 0;border:1.5px solid #8f3a34;border-radius:8px;background:linear-gradient(180deg,#a5423a,#7a2f29);color:#f4e6cf;font:600 13px system-ui;letter-spacing:.04em;cursor:pointer" style-hover="background:#b8524a">EQUIP</div>
</sc-if>
<sc-if value="{{ selCanUse }}" hint-placeholder-val="{{ false }}">
<div style="text-align:center;padding:9px 0;border:1.5px solid #8f3a34;border-radius:8px;background:linear-gradient(180deg,#a5423a,#7a2f29);color:#f4e6cf;font:600 13px system-ui;letter-spacing:.04em;cursor:pointer" style-hover="background:#b8524a">USE</div>
</sc-if>
<div style="text-align:center;padding:9px 0;border:1px solid #a99464;border-radius:8px;background:#e6d9b8;color:#5a4326;font:600 13px system-ui;letter-spacing:.04em;cursor:pointer" style-hover="background:#d9c9a2">DROP</div>
</div>
</div>
</div>
</sc-if>
<sc-if value="{{ noSel }}" hint-placeholder-val="{{ false }}">
<div style="height:100%;display:flex;align-items:center;justify-content:center;font:15px 'Architects Daughter',cursive;color:#a99464">Select something. Turn it over in your hands.</div>
</sc-if>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{&quot;$preview&quot;:{&quot;width&quot;:1920,&quot;height&quot;:1080}}">
class Component extends DCLogic {
RARITY = { common: '#8a8378', uncommon: '#5f8a4f', rare: '#4f7ab8', epic: '#8a5fb8', legendary: '#c8963f' };
COLS = 12; ROWS = 5;
MAXW = 60;
items() {
return {
// equipped
falchion: { name: 'Notched Falchion', cat: 'weapon', slot: 'mainhand', rarity: 'uncommon', glyph: 'FAL', weight: 4.5, value: 85, qty: 1, stats: ['1217 slashing', '+1 to hit vs. unarmored'], flavor: 'The nicks in the edge are from bone, not practice.' },
buckler: { name: 'Dwarf-iron Buckler', cat: 'armor', slot: 'offhand', rarity: 'uncommon', glyph: 'BKL', weight: 5, value: 70, qty: 1, stats: ['+4 block', 'Shield bash: 1d4'], flavor: 'Dented inward. Whatever hit it came off worse.' },
hood: { name: "Cutpurse's Hood", cat: 'armor', slot: 'head', rarity: 'uncommon', glyph: 'HD', weight: 1, value: 55, qty: 1, stats: ['+2 armor', '+1 DEX'], flavor: 'Keeps the rain and the witnesses off.' },
jerkin: { name: 'Boiled Leather Jerkin', cat: 'armor', slot: 'chest', rarity: 'common', glyph: 'JRK', weight: 8, value: 40, qty: 1, stats: ['+3 armor', '1 to sneak'], flavor: 'Stinks of the tanner who died in it.' },
amulet: { name: 'Amulet of the Ninth Saint', cat: 'misc', slot: 'amulet', rarity: 'rare', glyph: 'AMU', weight: 0.2, value: 320, qty: 1, stats: ['+2 FTH', 'Once a day: reroll a death save'], flavor: 'The saint was hanged. The amulet remembers.' },
ringdrowned: { name: 'Ring of the Drowned', cat: 'misc', slot: 'ring1', rarity: 'rare', glyph: 'RNG', weight: 0.1, value: 280, qty: 1, stats: ['+1 MAG', 'Breathe water, briefly'], flavor: 'Cold, always. It has not dried in years.' },
boots: { name: 'Split Marching Boots', cat: 'armor', slot: 'boots', rarity: 'common', glyph: 'BT', weight: 2, value: 15, qty: 1, stats: ['+1 armor'], flavor: 'One sole flaps. You walk quiet anyway.' },
// backpack
shiv: { name: 'Rusted Shiv', cat: 'weapon', slot: 'mainhand', rarity: 'common', glyph: 'SHV', weight: 2, value: 5, qty: 1, stats: ['47 slashing', 'Snaps on a natural 1'], flavor: 'A door hinge with ambitions.' },
cap: { name: 'Padded Cap', cat: 'armor', slot: 'head', rarity: 'common', glyph: 'CAP', weight: 1, value: 8, qty: 1, stats: ['+1 armor'], flavor: 'Sweat-stiff. Previous owner not consulted.' },
salve: { name: 'Fevergrass Salve', cat: 'consumable', rarity: 'common', glyph: 'SLV', weight: 0.3, value: 20, qty: 3, stats: ['Clears Infection (1)', 'Heals 2d4 over time'], flavor: 'Tastes of pond. Works anyway.' },
bile: { name: 'Vial of Black Bile', cat: 'consumable', rarity: 'uncommon', glyph: 'BILE', weight: 0.4, value: 45, qty: 2, stats: ['Throw: 2d6 acid', 'Fouls the ground it hits'], flavor: 'Do not uncork it near your face.' },
ration: { name: 'Stale Ration', cat: 'consumable', rarity: 'common', glyph: 'RTN', weight: 0.5, value: 2, qty: 4, stats: ['Removes Hungry', 'Small chance of the runs'], flavor: 'Hard enough to break a tooth. Some have.' },
picks: { name: "Tinker's Lockpicks", cat: 'tool', rarity: 'uncommon', glyph: 'PICK', weight: 0.2, value: 60, qty: 1, stats: ['+2 to pick locks', 'Breaks on a fumble'], flavor: 'Six left. There were twelve.' },
bones: { name: 'Human Finger-bones', cat: 'misc', rarity: 'common', glyph: 'BNE', weight: 0.1, value: 0, qty: 9, stats: ["Someone's tally"], flavor: 'Not yours. You keep counting them anyway.' },
signet: { name: 'Cracked Signet', cat: 'misc', rarity: 'uncommon', glyph: 'SIG', weight: 0.1, value: 120, qty: 1, stats: ['Quest item'], flavor: 'A noble house that no longer pays its debts.' },
};
}
slotList() {
return [
{ key: 'head', label: 'HEAD', pos: 'left:30px;top:12px' },
{ key: 'amulet', label: 'NECK', pos: 'left:30px;top:80px' },
{ key: 'chest', label: 'CHEST', pos: 'left:30px;top:148px' },
{ key: 'cloak', label: 'CLOAK', pos: 'left:30px;top:216px' },
{ key: 'gloves', label: 'HANDS', pos: 'left:30px;top:284px' },
{ key: 'belt', label: 'BELT', pos: 'left:30px;top:352px' },
{ key: 'mainhand', label: 'MAIN', pos: 'left:528px;top:12px' },
{ key: 'offhand', label: 'OFF', pos: 'left:528px;top:80px' },
{ key: 'ring1', label: 'RING', pos: 'left:528px;top:148px' },
{ key: 'ring2', label: 'RING', pos: 'left:528px;top:216px' },
{ key: 'legs', label: 'LEGS', pos: 'left:528px;top:284px' },
{ key: 'boots', label: 'FEET', pos: 'left:528px;top:352px' },
];
}
equipped() {
return { head: 'hood', amulet: 'amulet', chest: 'jerkin', cloak: null, gloves: null, belt: null, mainhand: 'falchion', offhand: 'buckler', ring1: 'ringdrowned', ring2: null, legs: null, boots: 'boots' };
}
backpack() { return ['shiv', 'cap', 'salve', 'bile', 'ration', 'picks', 'bones', 'signet']; }
state = { sel: 'falchion', tab: 'all' };
tabDefs() {
return [
{ id: 'all', label: 'ALL', test: () => true },
{ id: 'weapon', label: 'WEAPONS', test: (it) => it.cat === 'weapon' },
{ id: 'armor', label: 'ARMOR', test: (it) => it.cat === 'armor' },
{ id: 'consumable', label: 'CONSUMABLES', test: (it) => it.cat === 'consumable' },
{ id: 'misc', label: 'MISC', test: (it) => it.cat === 'misc' || it.cat === 'tool' },
];
}
rc(r) { return this.RARITY[r] || '#8a8378'; }
renderVals() {
const ITEMS = this.items();
const eq = this.equipped();
const sel = this.state.sel;
// equipment slots
const equip = this.slotList().map(s => {
const id = eq[s.key];
const it = id ? ITEMS[id] : null;
return {
key: s.key, label: s.label, posStyle: s.pos,
filled: !!it, empty: !it,
glyph: it ? it.glyph : '',
rarityColor: it ? this.rc(it.rarity) : '#4a4238',
sel: !!id && id === sel,
onSelect: () => { if (id) this.setState({ sel: id }); },
};
});
// tabs
const tab = this.state.tab;
const tabs = this.tabDefs().map(t => ({
label: t.label,
onSelect: () => this.setState({ tab: t.id }),
bg: t.id === tab ? '#8f3a34' : '#efe6cf',
fg: t.id === tab ? '#f1e8d2' : '#6a5a3a',
border: t.id === tab ? '#8f3a34' : '#cbb684',
}));
const activeTest = this.tabDefs().find(t => t.id === tab).test;
// grid cells
const visible = this.backpack().filter(id => activeTest(ITEMS[id]));
const total = this.COLS * this.ROWS;
const cells = [];
for (let i = 0; i < total; i++) {
const id = visible[i];
const it = id ? ITEMS[id] : null;
cells.push({
filled: !!it,
glyph: it ? it.glyph : '',
qtyLabel: it && it.qty > 1 ? ('×' + it.qty) : '',
rarityColor: it ? this.rc(it.rarity) : '#cbb684',
bg: it ? '#f4ecd6' : '#e4d6b6',
sel: !!id && id === sel,
onSelect: () => { if (id) this.setState({ sel: id }); },
});
}
// encumbrance
let w = 0;
Object.values(eq).forEach(id => { if (id) w += ITEMS[id].weight; });
this.backpack().forEach(id => { const it = ITEMS[id]; w += it.weight * it.qty; });
w = Math.round(w * 10) / 10;
const pct = Math.min(100, Math.round((w / this.MAXW) * 100));
// detail
const it = ITEMS[sel];
const typeLabel = it ? this.typeOf(it) : '';
return {
equip, tabs, cells,
weightText: w + ' / ' + this.MAXW,
weightPct: pct + '%',
weightColor: pct > 90 ? '#8f3a34' : (pct > 70 ? '#a5772a' : '#5f8a4f'),
hasSel: !!it, noSel: !it,
selName: it ? it.name : '',
selType: typeLabel,
selGlyph: it ? it.glyph : '',
selRarityColor: it ? this.rc(it.rarity) : '#5a4326',
selStats: it ? it.stats : [],
selFlavor: it ? it.flavor : '',
selWeight: it ? it.weight : '',
selValue: it ? it.value : '',
selCanEquip: !!(it && it.slot),
selCanUse: !!(it && it.cat === 'consumable'),
};
}
typeOf(it) {
const capt = (s) => s.charAt(0).toUpperCase() + s.slice(1);
const cat = it.cat === 'tool' ? 'Tool' : (it.cat === 'misc' ? 'Trinket' : capt(it.cat));
if (it.slot) return cat + ' · ' + capt(it.slot);
return cat;
}
}
</script>
</body>
</html>