Files
code_of_conquest_dnd/mockups/Shop.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

248 lines
15 KiB
HTML
Raw Permalink 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: WARES ============ -->
<div style="width:1180px;height:1080px;background:linear-gradient(180deg,#ece2ca,#e4d8bd);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 Crooked Coin</div>
<div style="font:12px ui-monospace,monospace;color:#8a7748;letter-spacing:.08em;margin-top:2px">NO REFUNDS · NO QUESTIONS · NO CITY GUARD</div>
</div>
<div style="display:flex;gap:22px;align-items:flex-end">
<div style="text-align:right"><div style="font:10px ui-monospace,monospace;letter-spacing:.1em;color:#8a7748">YOUR PURSE</div><div style="font:600 24px ui-monospace,monospace;color:#8f6a2a">◈ {{ gold }}</div></div>
<div style="text-align:right"><div style="font:10px ui-monospace,monospace;letter-spacing:.1em;color:#8a7748">GRUD'S COIN</div><div style="font:600 24px ui-monospace,monospace;color:#7a684a">◈ {{ merchantGold }}</div></div>
</div>
</div>
<!-- buy/sell tabs -->
<div style="display:flex;gap:8px">
<sc-for list="{{ tabs }}" as="t" hint-placeholder-count="2">
<div onClick="{{ t.onSelect }}" style="font:13px ui-monospace,monospace;letter-spacing:.1em;padding:9px 22px;border-radius:8px;border:1px solid {{ t.border }};background:{{ t.bg }};color:{{ t.fg }};cursor:pointer">{{ t.label }}</div>
</sc-for>
</div>
<!-- item rows -->
<div style="flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:9px">
<sc-for list="{{ rows }}" as="r" hint-placeholder-count="8">
<div onClick="{{ r.onSelect }}" style="display:flex;align-items:center;gap:16px;background:{{ r.bg }};border:1px solid {{ r.border }};border-radius:11px;padding:12px 16px;cursor:{{ r.cursor }};opacity:{{ r.opacity }}" style-hover="border-color:#8f7a4a">
<div style="width:52px;height:52px;flex:none;border-radius:9px;border:2px solid {{ r.rarityColor }};background:#e6d9b8;position:relative;display:flex;align-items:center;justify-content:center">
<span style="font:600 11px ui-monospace,monospace;color:#5a4326">{{ r.glyph }}</span>
<div style="position:absolute;left:6px;right:6px;bottom:5px;height:3px;border-radius:2px;background:{{ r.rarityColor }}"></div>
</div>
<div style="flex:1;min-width:0">
<div style="font:600 18px system-ui;color:#3a2f1c">{{ r.name }}</div>
<div style="font:11px ui-monospace,monospace;color:#8a7748;letter-spacing:.04em;margin-top:2px">{{ r.typeLabel }}</div>
</div>
<div style="font:12px ui-monospace,monospace;color:#a9885a;width:120px;text-align:right">{{ r.qtyText }}</div>
<div style="font:600 19px ui-monospace,monospace;color:#8f6a2a;width:90px;text-align:right">◈ {{ r.price }}</div>
</div>
</sc-for>
</div>
</div>
<!-- ============ RIGHT: MERCHANT + DEAL ============ -->
<div style="width:740px;flex:none;height:1080px;background:repeating-linear-gradient(135deg,#26221c 0 16px,#221e18 16px 32px);border-left:4px solid #1a1512;box-sizing:border-box;padding:34px 38px;display:flex;flex-direction:column;gap:20px;position:relative">
<div style="position:absolute;inset:0;background:radial-gradient(120% 60% at 50% 15%,rgba(0,0,0,0) 40%,rgba(0,0,0,.5) 100%);pointer-events:none"></div>
<!-- merchant -->
<div style="position:relative;display:flex;gap:18px;align-items:center">
<div style="width:92px;height:92px;flex:none;border-radius:12px;border:1px dashed #5a5348;background:repeating-linear-gradient(135deg,#2c2620 0 12px,#282219 12px 24px);display:flex;align-items:center;justify-content:center;font:10px ui-monospace,monospace;color:#6b6152;text-align:center">MUG<br>SHOT</div>
<div>
<div style="font:600 30px 'Architects Daughter',cursive;color:#e8d9b6;line-height:1">Grud Ninefingers</div>
<div style="font:12px ui-monospace,monospace;letter-spacing:.1em;color:#a9843f;margin-top:3px">BEASTFOLK FENCE · GALLOWSMOOR</div>
</div>
</div>
<div style="position:relative;font:16px/1.55 Georgia,serif;font-style:italic;color:#c9bda3;background:rgba(18,15,11,.5);border-left:3px solid #a9843f;border-radius:8px;padding:13px 16px;min-height:58px">{{ merchantMsg }}</div>
<div style="position:relative;height:1px;background:#3a352e"></div>
<!-- deal detail -->
<div style="position:relative;flex:1;display:flex;flex-direction:column;gap:18px">
<div style="display:flex;gap:18px">
<div style="width:96px;height:96px;flex:none;border-radius:12px;border:2px solid {{ selRarityColor }};background:rgba(40,34,28,.7);position:relative;display:flex;align-items:center;justify-content:center">
<span style="font:600 15px ui-monospace,monospace;color:#c9bda3">{{ selGlyph }}</span>
<div style="position:absolute;left:8px;right:8px;bottom:7px;height:4px;border-radius:2px;background:{{ selRarityColor }}"></div>
</div>
<div style="flex:1;min-width:0">
<div style="font:600 26px 'Architects Daughter',cursive;color:{{ selRarityColor }};line-height:1.05">{{ selName }}</div>
<div style="font:11px ui-monospace,monospace;letter-spacing:.1em;color:#8a7f6c;margin-top:4px">{{ selType }}</div>
<div style="margin-top:12px;display:flex;flex-direction:column;gap:5px">
<sc-for list="{{ selStats }}" as="line" hint-placeholder-count="2">
<div style="font:14px system-ui;color:#d8ccb2">▸ {{ line }}</div>
</sc-for>
</div>
</div>
</div>
<div style="font:15px/1.55 Georgia,serif;font-style:italic;color:#8a7f6c">"{{ selFlavor }}"</div>
</div>
<!-- price + action -->
<div style="position:relative;border-top:1px solid #3a352e;padding-top:20px">
<div style="display:flex;align-items:baseline;justify-content:space-between;margin-bottom:14px">
<span style="font:12px ui-monospace,monospace;letter-spacing:.12em;color:#8a7f6c">{{ priceLabel }}</span>
<span style="font:600 30px ui-monospace,monospace;color:#c8963f">◈ {{ dealPrice }}</span>
</div>
<div onClick="{{ onAction }}" style="text-align:center;padding:18px 0;border:1.5px solid {{ actionBorder }};border-radius:11px;background:{{ actionBg }};color:{{ actionFg }};font:600 19px system-ui;letter-spacing:.06em;cursor:{{ actionCursor }}">{{ actionLabel }}</div>
<div style="text-align:center;margin-top:9px;font:11px 'Architects Daughter',cursive;color:#7a7060">{{ actionNote }}</div>
</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' };
items() {
return {
salve: { name: 'Fevergrass Salve', cat: 'consumable', rarity: 'common', glyph: 'SLV', value: 20, stats: ['Clears Infection (1)', 'Heals 2d4 over time'], flavor: 'Tastes of pond. Works anyway.' },
antitox: { name: 'Antitoxin', cat: 'consumable', rarity: 'common', glyph: 'ATX', value: 35, stats: ['Advantage vs. poison for 1 hour'], flavor: 'Grud swears it is not just vinegar.' },
caltrops: { name: 'Iron Caltrops', cat: 'consumable', rarity: 'common', glyph: 'CAL', value: 15, stats: ['Scatter: 1d4 and stops a charge'], flavor: 'Rusty. The tetanus is a feature.' },
oil: { name: 'Oil of Sharpness', cat: 'consumable', rarity: 'uncommon', glyph: 'OIL', value: 60, stats: ['+1 weapon damage for 10 min'], flavor: 'Smells of the forge and something worse.' },
ash: { name: 'Grave-Ash', cat: 'misc', rarity: 'common', glyph: 'ASH', value: 8, stats: ['Spell reagent'], flavor: 'Someone was cremated for this. Cheaply.' },
falchion: { name: 'Notched Falchion', cat: 'weapon', slot: 'mainhand', rarity: 'uncommon', glyph: 'FAL', value: 85, stats: ['1217 slashing', '+1 vs. unarmored'], flavor: 'The nicks are from bone, not practice.' },
chain: { name: 'Chain Shirt', cat: 'armor', slot: 'chest', rarity: 'uncommon', glyph: 'CHN', value: 140, stats: ['+5 armor', 'Heavy on the shoulders'], flavor: 'Rings missing over the heart. Ask the last owner.' },
map: { name: "Smuggler's Map", cat: 'misc', rarity: 'rare', glyph: 'MAP', value: 220, stats: ['Reveals a hidden route out'], flavor: 'The ink runs when it rains. Memorize it fast.' },
shiv: { name: 'Rusted Shiv', cat: 'weapon', slot: 'mainhand', rarity: 'common', glyph: 'SHV', value: 5, stats: ['47 slashing'], flavor: 'A door hinge with ambitions.' },
bones: { name: 'Human Finger-bones', cat: 'misc', rarity: 'common', glyph: 'BNE', value: 0, stats: ["Someone's tally"], flavor: 'Grud will not touch these. Nobody will.' },
signet: { name: 'Cracked Signet', cat: 'misc', rarity: 'uncommon', glyph: 'SIG', value: 120, stats: ['A fallen noble house'], flavor: 'Worth more to the wrong people.' },
ration: { name: 'Stale Ration', cat: 'consumable', rarity: 'common', glyph: 'RTN', value: 2, stats: ['Removes Hungry'], flavor: 'Hard enough to break a tooth.' },
pelt: { name: 'Wolf Pelt', cat: 'misc', rarity: 'common', glyph: 'PLT', value: 12, stats: ['Trade good'], flavor: 'Still smells of wet dog and worse.' },
tooth: { name: 'Silver Tooth', cat: 'misc', rarity: 'uncommon', glyph: 'TTH', value: 40, stats: ['Trade good'], flavor: 'Pried loose. Do not ask from whom.' },
};
}
state = {
mode: 'buy',
sel: 'salve',
gold: 1240,
merchantGold: 340,
stock: { salve: 5, antitox: 3, caltrops: 4, oil: 2, ash: 8, falchion: 1, chain: 1, map: 1 },
owned: { shiv: 1, bones: 9, signet: 1, ration: 4, pelt: 3, tooth: 2 },
msg: 'Grud Ninefingers eyes your coat for bulges. "Show me silver, or show yourself out."',
};
rc(r) { return this.RARITY[r] || '#8a8378'; }
buyPrice(id) { return this.items()[id].value; }
sellPrice(id) { return Math.round(this.items()[id].value * 0.4); }
typeOf(it) {
const cap = s => s.charAt(0).toUpperCase() + s.slice(1);
const c = it.cat === 'misc' ? 'Trinket' : cap(it.cat);
return it.slot ? c + ' · ' + cap(it.slot) : c;
}
buy() {
const id = this.state.sel;
const p = this.buyPrice(id);
if ((this.state.stock[id] || 0) <= 0) return;
if (this.state.gold < p) { this.setState({ msg: 'Grud laughs, wet and low. "Come back with coin, not lint."' }); return; }
this.setState(s => ({
gold: s.gold - p, merchantGold: s.merchantGold + p,
stock: { ...s.stock, [id]: s.stock[id] - 1 },
owned: { ...s.owned, [id]: (s.owned[id] || 0) + 1 },
msg: '"Mm." Grud bites the coin, then pockets it. The goods are yours.',
}));
}
sell() {
const id = this.state.sel;
const p = this.sellPrice(id);
if ((this.state.owned[id] || 0) <= 0) return;
if (p <= 0) { this.setState({ msg: 'Grud shoves it back across the counter. "I fence goods, not grief."' }); return; }
if (this.state.merchantGold < p) { this.setState({ msg: '"Grud\u2019s tapped out. Come back when I\u2019ve robbed someone."' }); return; }
this.setState(s => ({
gold: s.gold + p, merchantGold: s.merchantGold - p,
owned: { ...s.owned, [id]: s.owned[id] - 1 },
stock: { ...s.stock, [id]: (s.stock[id] || 0) + 1 },
msg: 'Grud sniffs it, names a number lower than you hoped, and pays it anyway.',
}));
}
renderVals() {
const ITEMS = this.items();
const mode = this.state.mode;
const buying = mode === 'buy';
const tabs = [
{ id: 'buy', label: 'BUY' },
{ id: 'sell', label: 'SELL' },
].map(t => ({
label: t.label,
onSelect: () => {
const src = t.id === 'buy' ? this.state.stock : this.state.owned;
const first = Object.keys(src).find(k => src[k] > 0) || this.state.sel;
this.setState({ mode: t.id, sel: first });
},
bg: t.id === mode ? '#8f3a34' : '#efe6cf',
fg: t.id === mode ? '#f1e8d2' : '#6a5a3a',
border: t.id === mode ? '#8f3a34' : '#cbb684',
}));
const src = buying ? this.state.stock : this.state.owned;
const ids = Object.keys(src);
const rows = ids.map(id => {
const it = ITEMS[id];
const qty = src[id] || 0;
const soldout = qty <= 0;
return {
glyph: it.glyph, name: it.name, typeLabel: this.typeOf(it),
rarityColor: this.rc(it.rarity),
qtyText: buying ? (soldout ? 'sold out' : '×' + qty + ' in stock') : '×' + qty + ' yours',
price: buying ? this.buyPrice(id) : this.sellPrice(id),
onSelect: () => { if (!soldout) this.setState({ sel: id }); },
cursor: soldout ? 'default' : 'pointer',
opacity: soldout ? '0.45' : '1',
bg: id === this.state.sel ? 'rgba(143,58,52,.12)' : '#f1e8d2',
border: id === this.state.sel ? '#8f3a34' : '#bfa878',
};
});
const id = this.state.sel;
const it = ITEMS[id];
const price = buying ? this.buyPrice(id) : this.sellPrice(id);
const stockQty = this.state.stock[id] || 0;
const ownQty = this.state.owned[id] || 0;
let enabled, note;
if (buying) {
enabled = stockQty > 0 && this.state.gold >= price;
note = stockQty <= 0 ? 'Grud is out of these.' : (this.state.gold < price ? 'Not enough silver in your purse.' : 'Grud will take your coin.');
} else {
enabled = ownQty > 0 && price > 0 && this.state.merchantGold >= price;
note = ownQty <= 0 ? "You've none left to sell." : (price <= 0 ? "Grud won't pay a copper for it." : (this.state.merchantGold < price ? "Grud can't cover that right now." : 'Grud will buy it off you.'));
}
return {
gold: this.state.gold.toLocaleString(),
merchantGold: this.state.merchantGold.toLocaleString(),
tabs, rows,
merchantMsg: this.state.msg,
selGlyph: it.glyph, selName: it.name, selType: this.typeOf(it),
selRarityColor: this.rc(it.rarity), selStats: it.stats, selFlavor: it.flavor,
priceLabel: buying ? 'GRUD ASKS' : 'GRUD OFFERS',
dealPrice: price,
actionLabel: buying ? 'BUY' : 'SELL',
onAction: buying ? () => this.buy() : () => this.sell(),
actionBg: enabled ? 'linear-gradient(180deg,#a5423a,#7a2f29)' : '#2a251f',
actionFg: enabled ? '#f4e6cf' : '#6b6152',
actionBorder: enabled ? '#8f3a34' : '#3a352e',
actionCursor: enabled ? 'pointer' : 'default',
actionNote: note,
};
}
}
</script>
</body>
</html>