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

209 lines
10 KiB
HTML

<!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:#0a0806;font-family:system-ui,sans-serif}
a{color:#8f3a34}a:hover{color:#6f2b26}
</style>
</helmet>
<div style="width:1920px;height:1080px;position:relative;overflow:hidden;background:repeating-linear-gradient(135deg,#201a14 0 18px,#1a150f 18px 36px)">
<!-- dimmed scene -->
<div style="position:absolute;inset:0;background:radial-gradient(110% 90% at 50% 40%,rgba(0,0,0,.35) 40%,rgba(0,0,0,.82) 100%)"></div>
<div style="position:absolute;top:36px;left:50%;transform:translateX(-50%);color:#4a3f30;font:12px ui-monospace,monospace;letter-spacing:.18em;opacity:.6">SCENE · GALLOWSMOOR · THE BACK ALLEY, RAINING</div>
<div style="position:absolute;inset:0;display:flex">
<!-- ===== LEFT: NPC ===== -->
<div style="width:640px;flex:none;position:relative;display:flex;flex-direction:column;justify-content:flex-end;padding:56px 52px">
<!-- portrait -->
<div style="position:absolute;left:52px;top:96px;right:52px;bottom:300px;border:1px dashed #4a4238;border-radius:16px;background:repeating-linear-gradient(135deg,#2c2620 0 16px,#282219 16px 32px);display:flex;align-items:center;justify-content:center;color:#5a5040;font:12px ui-monospace,monospace;letter-spacing:.14em">NPC PORTRAIT</div>
<!-- info -->
<div style="position:relative">
<div style="font:600 46px 'Architects Daughter',cursive;color:#e8d9b6;line-height:1">Grud Ninefingers</div>
<div style="font:12px ui-monospace,monospace;letter-spacing:.14em;color:#a9843f;margin-top:6px">BEASTFOLK · FENCE · KNOWS EVERYONE</div>
<!-- disposition -->
<div style="margin-top:22px;max-width:360px">
<div style="display:flex;justify-content:space-between;font:11px ui-monospace,monospace;letter-spacing:.1em;color:#8a7f6c"><span>DISPOSITION</span><span style="color:{{ dispColor }}">{{ dispLabel }}</span></div>
<div style="height:10px;border-radius:6px;background:#2a251f;border:1px solid #4a4238;overflow:hidden;margin-top:5px"><div style="width:{{ dispPct }};height:100%;background:{{ dispColor }};transition:width .3s,background .3s"></div></div>
</div>
</div>
</div>
<!-- ===== RIGHT: CONVERSATION ===== -->
<div style="flex:1;min-width:0;background:linear-gradient(180deg,#ece2ca,#e4d8bd);border-left:4px solid #1a1512;box-sizing:border-box;padding:56px 64px;display:flex;flex-direction:column;position:relative">
<sc-if value="{{ hasBanner }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute;top:44px;right:56px;transform:rotate(-10deg);border:4px solid #8f3a34;color:#8f3a34;font:800 30px ui-monospace,monospace;letter-spacing:.12em;padding:6px 18px;border-radius:8px;opacity:.72">{{ banner }}</div>
</sc-if>
<!-- speaker -->
<div style="font:12px ui-monospace,monospace;letter-spacing:.18em;color:#8a7748">{{ speaker }}</div>
<!-- narration beat -->
<sc-if value="{{ hasNarration }}" hint-placeholder-val="{{ true }}">
<div style="font:18px/1.55 Georgia,serif;font-style:italic;color:#7a684a;margin-top:16px;max-width:900px">{{ narration }}</div>
</sc-if>
<!-- spoken line -->
<sc-if value="{{ hasLine }}" hint-placeholder-val="{{ true }}">
<div style="font:34px/1.4 Georgia,serif;color:#3a2a1a;margin-top:18px;max-width:960px">&ldquo;{{ line }}&rdquo;</div>
</sc-if>
<div style="height:2px;background:#bfa878;margin:36px 0 26px;max-width:960px"></div>
<!-- responses -->
<div style="font:12px ui-monospace,monospace;letter-spacing:.16em;color:#8a7748;margin-bottom:16px">YOUR RESPONSE</div>
<div style="display:flex;flex-direction:column;gap:10px;max-width:1000px">
<sc-for list="{{ choices }}" as="c" hint-placeholder-count="4">
<div onClick="{{ c.onSelect }}" style="display:flex;align-items:center;gap:16px;background:{{ c.bg }};border:1px solid {{ c.border }};border-radius:11px;padding:15px 18px;cursor:{{ c.cursor }};opacity:{{ c.opacity }}" style-hover="border-color:#8f7a4a">
<span style="font:600 15px ui-monospace,monospace;color:#a9885a;width:16px">{{ c.num }}</span>
<sc-if value="{{ c.hasTag }}" hint-placeholder-val="{{ false }}">
<span style="font:600 10px ui-monospace,monospace;letter-spacing:.08em;color:{{ c.tagFg }};background:{{ c.tagBg }};border:1px solid {{ c.tagFg }};border-radius:5px;padding:4px 9px;white-space:nowrap">{{ c.tag }}</span>
</sc-if>
<span style="flex:1;min-width:0;font:17px system-ui;color:#3a2f1c">{{ c.text }}</span>
<sc-if value="{{ c.hasNote }}" hint-placeholder-val="{{ false }}">
<span style="font:11px ui-monospace,monospace;color:#a08a5a;white-space:nowrap">🔒 {{ c.note }}</span>
</sc-if>
</div>
</sc-for>
</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 {
TAG = {
red: { fg: '#8f3a34', bg: 'rgba(143,58,52,.1)' },
gold: { fg: '#8f6a2a', bg: 'rgba(143,106,42,.12)' },
grey: { fg: '#8a8378', bg: 'rgba(138,131,120,.12)' },
};
nodes() {
return {
start: {
narration: "Grud Ninefingers doesn't get up. Rain runs off the awning between you.",
line: 'You again. And bleeding on my stoop.',
choices: [
{ text: "I've got coin. I need the salve — the real fevergrass.", tag: 'BARTER', to: 'deal' },
{ text: "You'll help me, or I bleed on more than your stoop.", tag: 'INTIMIDATE · STR', tt: 'red', to: 'threat', dd: -8 },
{ text: 'The Bonewright sent me.', tag: 'LIE', tt: 'grey', disabled: true, note: 'Not yet known to the Bonewright' },
{ text: "For the Ninth Saint's sake, Grud.", tag: 'FAITH', tt: 'gold', to: 'faith', dd: 4 },
{ text: '(Leave.)', to: 'leave' },
],
},
deal: {
narration: 'He leans forward at last. The coin-smell interests him more than the blood-smell.',
line: 'Real fevergrass costs. Forty silver, and I forget your face by morning.',
choices: [
{ text: 'Pay the forty.', tag: 'PAY ◈40', tt: 'gold', to: 'paid', dd: 6 },
{ text: 'Twenty. You still owe me for Saltcrol.', tag: 'HAGGLE · DEX', tt: 'red', to: 'haggle' },
{ text: '(Back.)', to: 'start' },
],
},
threat: {
narration: 'His claws spread flat on the counter. Slow. Deliberate.',
line: 'Big words. From a man with rot climbing his arm.',
choices: [
{ text: 'Step closer. Let him smell it.', tag: 'PRESS · STR', tt: 'red', to: 'knife', dd: -10 },
{ text: '...Fine. Coin, then.', to: 'deal', dd: 2 },
],
},
faith: {
narration: 'Something crosses his face. Not kindness. Older than that.',
line: "The Saint didn't save the Saint. But a debt's a debt. Thirty, then.",
choices: [
{ text: 'Pay the thirty.', tag: 'PAY ◈30', tt: 'gold', to: 'paid', dd: 8 },
{ text: '(Back.)', to: 'start' },
],
},
haggle: {
narration: 'A long pause. He remembers Saltcrol. So do you.',
line: '...Thirty. And you never say that name in my alley again.',
choices: [
{ text: 'Done.', tag: 'PAY ◈30', tt: 'gold', to: 'paid', dd: 3 },
{ text: 'Forty it is.', to: 'paid', dd: 1 },
],
},
knife: {
narration: 'Steel scrapes leather. The knife is out before you finish the step.',
line: 'Last chance to walk, sellsword.',
choices: [
{ text: '(Draw your blade.)', tag: 'COMBAT', tt: 'red', to: 'combat' },
{ text: '(Back off, hands up.)', to: 'threat', dd: 2 },
],
},
paid: {
narration: 'He slides a wrapped bundle across. It reeks of pondweed and salvation.',
line: 'Pleasure doing. Try not to die in my alley — bad for trade.',
choices: [{ text: '(Take the salve and go.)', to: 'leave' }],
},
leave: {
end: true,
narration: 'You step back into the rain. The arm still burns, but the coin, or the courage, is spent.',
line: '',
choices: [{ text: '(Start the conversation over.)', to: 'start' }],
},
combat: {
end: true, banner: 'COMBAT',
narration: 'No more talking. The alley narrows to him, the knife, and the space between.',
line: '',
choices: [{ text: '(Start the conversation over.)', to: 'start' }],
},
};
}
state = { node: 'start', disp: 30 };
choose(ch) {
if (ch.disabled) return;
this.setState(s => ({
node: ch.to,
disp: Math.max(0, Math.min(100, s.disp + (ch.dd || 0))),
}));
}
renderVals() {
const n = this.nodes()[this.state.node];
const choices = n.choices.map((c, i) => {
const tag = this.TAG[c.tt] || null;
return {
num: i + 1,
text: c.text,
hasTag: !!c.tag, tag: c.tag || '',
tagFg: tag ? tag.fg : '#8a7748', tagBg: tag ? tag.bg : 'rgba(138,131,120,.12)',
hasNote: !!c.disabled, note: c.note || '',
cursor: c.disabled ? 'default' : 'pointer',
opacity: c.disabled ? '0.5' : '1',
bg: c.disabled ? '#e8ddc2' : '#f1e8d2',
border: c.disabled ? '#d3c39a' : '#bfa878',
onSelect: () => this.choose(c),
};
});
const d = this.state.disp;
let dispLabel, dispColor;
if (d < 20) { dispLabel = 'HOSTILE'; dispColor = '#a5442e'; }
else if (d < 45) { dispLabel = 'WARY'; dispColor = '#a5772a'; }
else if (d < 70) { dispLabel = 'NEUTRAL'; dispColor = '#8a7748'; }
else { dispLabel = 'WARMING'; dispColor = '#5f8a4f'; }
return {
speaker: n.end ? 'THE DM' : 'GRUD NINEFINGERS',
hasNarration: !!n.narration, narration: n.narration,
hasLine: !!n.line, line: n.line,
hasBanner: !!n.banner, banner: n.banner || '',
choices,
dispPct: d + '%', dispLabel, dispColor,
};
}
}
</script>
</body>
</html>