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>
209 lines
10 KiB
HTML
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">“{{ line }}”</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="{"$preview":{"width":1920,"height":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>
|