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>
This commit is contained in:
2026-07-10 16:06:23 -05:00
parent b639cab0c2
commit 49bc6b22c0
13 changed files with 4671 additions and 0 deletions

264
mockups/Quest Log.dc.html Normal file
View File

@@ -0,0 +1,264 @@
<!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: QUEST LIST ============ -->
<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% 60% at 50% 10%,rgba(0,0,0,0) 45%,rgba(0,0,0,.45) 100%);pointer-events:none"></div>
<!-- header -->
<div style="position:relative;padding:28px 32px 0">
<div style="font:13px ui-monospace,monospace;letter-spacing:.2em;color:#8a7f6c">JOURNAL</div>
<div style="font:600 34px 'Architects Daughter',cursive;color:#e8d9b6;margin-top:4px;line-height:1">Deeds &amp; Debts</div>
</div>
<!-- tabs -->
<div style="position:relative;display:flex;gap:8px;padding:20px 32px 16px">
<sc-for list="{{ tabs }}" as="t" hint-placeholder-count="3">
<div onClick="{{ t.onSelect }}" style="font:12px ui-monospace,monospace;letter-spacing:.06em;padding:8px 14px;border-radius:8px;border:1px solid {{ t.border }};background:{{ t.bg }};color:{{ t.fg }};cursor:pointer">{{ t.label }}</div>
</sc-for>
</div>
<!-- list -->
<div style="position:relative;flex:1;overflow-y:auto;padding:0 24px 24px;display:flex;flex-direction:column;gap:10px">
<sc-for list="{{ list }}" as="q" hint-placeholder-count="4">
<div onClick="{{ q.onSelect }}" style="position:relative;background:{{ q.bg }};border:1px solid {{ q.border }};border-left:4px solid {{ q.typeColor }};border-radius:10px;padding:14px 16px;cursor:pointer;display:flex;flex-direction:column;gap:6px" style-hover="border-color:#a9843f">
<div style="display:flex;align-items:center;gap:9px">
<span style="font:600 17px system-ui;color:#e8d9b6;flex:1;min-width:0">{{ q.title }}</span>
<sc-if value="{{ q.tracked }}" hint-placeholder-val="{{ false }}"><span style="color:#c8963f;font-size:15px"></span></sc-if>
</div>
<div style="display:flex;align-items:center;gap:10px;font:11px ui-monospace,monospace;color:#9a8f78">
<span style="color:{{ q.typeColor }};letter-spacing:.06em">{{ q.typeLabel }}</span>
<span>·</span>
<span>{{ q.loc }}</span>
<span style="margin-left:auto;color:#c9bda3">{{ q.progressText }}</span>
</div>
</div>
</sc-for>
</div>
</div>
<!-- ============ RIGHT: QUEST DETAIL (open page) ============ -->
<div style="width:1300px;height:1080px;background:linear-gradient(180deg,#ece2ca,#e4d8bd);border-left:4px solid #1a1512;box-sizing:border-box;padding:44px 56px;display:flex;flex-direction:column;gap:24px;position:relative;overflow:hidden">
<!-- status stamp -->
<sc-if value="{{ isCompleted }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute;top:70px;right:60px;transform:rotate(-11deg);border:4px solid #5f8a4f;color:#5f8a4f;font:800 30px ui-monospace,monospace;letter-spacing:.1em;padding:8px 20px;border-radius:8px;opacity:.72">COMPLETED</div>
</sc-if>
<sc-if value="{{ isFailed }}" hint-placeholder-val="{{ false }}">
<div style="position:absolute;top:70px;right:60px;transform:rotate(-11deg);border:4px solid #8f3a34;color:#8f3a34;font:800 34px ui-monospace,monospace;letter-spacing:.12em;padding:8px 22px;border-radius:8px;opacity:.72">FAILED</div>
</sc-if>
<!-- title block -->
<div style="border-bottom:2px solid #bfa878;padding-bottom:20px">
<div style="font:12px ui-monospace,monospace;letter-spacing:.16em;color:{{ selTypeColor }}">{{ selTypeLabel }}</div>
<div style="font:600 46px 'Architects Daughter',cursive;color:#4a3520;line-height:1.05;margin-top:6px">{{ selTitle }}</div>
<div style="display:flex;gap:22px;margin-top:14px;font:13px ui-monospace,monospace;color:#8a7748">
<span>GIVEN BY · <span style="color:#5a4326">{{ selGiver }}</span></span>
<span>WHERE · <span style="color:#5a4326">{{ selLoc }}</span></span>
</div>
</div>
<!-- description -->
<div style="font:19px/1.65 Georgia,serif;color:#4a3f2c;max-width:820px">{{ selDesc }}</div>
<!-- objectives -->
<div>
<div style="font:13px ui-monospace,monospace;letter-spacing:.16em;color:#8a7748;margin-bottom:14px">OBJECTIVES</div>
<div style="display:flex;flex-direction:column;gap:11px">
<sc-for list="{{ objectives }}" as="o" hint-placeholder-count="4">
<div style="display:flex;align-items:center;gap:13px">
<span style="font:19px;color:{{ o.boxColor }};width:22px">{{ o.box }}</span>
<span style="font:17px system-ui;color:{{ o.textColor }};{{ o.strike }}">{{ o.text }}</span>
<sc-if value="{{ o.current }}" hint-placeholder-val="{{ false }}"><span style="font:10px ui-monospace,monospace;letter-spacing:.1em;color:#8f3a34;border:1px solid #8f3a34;border-radius:5px;padding:2px 7px">CURRENT</span></sc-if>
</div>
</sc-for>
</div>
</div>
<!-- rewards + actions -->
<div style="margin-top:auto;display:flex;align-items:flex-end;justify-content:space-between;border-top:2px solid #bfa878;padding-top:22px">
<div>
<div style="font:13px ui-monospace,monospace;letter-spacing:.16em;color:#8a7748;margin-bottom:12px">ON COMPLETION</div>
<div style="display:flex;gap:12px;align-items:center">
<div style="background:#f1e8d2;border:1px solid #bfa878;border-radius:9px;padding:10px 16px;text-align:center"><div style="font:9px ui-monospace,monospace;color:#8a7748">GOLD</div><div style="font:600 20px ui-monospace,monospace;color:#8f6a2a">◈ {{ rewardGold }}</div></div>
<div style="background:#f1e8d2;border:1px solid #bfa878;border-radius:9px;padding:10px 16px;text-align:center"><div style="font:9px ui-monospace,monospace;color:#8a7748">XP</div><div style="font:600 20px ui-monospace,monospace;color:#5a4326">{{ rewardXp }}</div></div>
<sc-for list="{{ rewardItems }}" as="ri" hint-placeholder-count="1">
<div style="background:#f1e8d2;border:1px solid #c8963f;border-radius:9px;padding:10px 16px;text-align:center"><div style="font:9px ui-monospace,monospace;color:#8a7748">ITEM</div><div style="font:600 15px 'Architects Daughter',cursive;color:#8f6a2a">{{ ri }}</div></div>
</sc-for>
</div>
</div>
<sc-if value="{{ isActive }}" hint-placeholder-val="{{ true }}">
<div style="display:flex;gap:10px">
<div onClick="{{ onTrack }}" style="padding:14px 22px;border:1.5px solid {{ trackBorder }};border-radius:10px;background:{{ trackBg }};color:{{ trackFg }};font:600 15px system-ui;letter-spacing:.04em;cursor:pointer">{{ trackLabel }}</div>
<div style="padding:14px 22px;border:1px solid #a99464;border-radius:10px;background:#e6d9b8;color:#7a5a3a;font:600 15px system-ui;letter-spacing:.04em;cursor:pointer" style-hover="background:#d9c9a2">Abandon</div>
</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 {
TYPE = {
main: { label: 'MAIN QUEST', color: '#c8963f' },
side: { label: 'SIDE QUEST', color: '#7a9a5a' },
bounty: { label: 'BOUNTY', color: '#a5442e' },
};
questData() {
return [
{
id: 'femur', title: "The Saint's Femur", type: 'main', status: 'active',
giver: 'The Bonewright', loc: 'Barrow of the Ninth',
desc: 'The Bonewright wants the hanged saint\u2019s left femur, and does not care to say why. The grave-wards are failing, and something in the dark is glad of it.',
objectives: [
{ text: 'Reach the Barrow of the Ninth', done: true },
{ text: 'Break 3 of the 5 grave-wards', done: true },
{ text: "Recover the hanged saint's femur", done: false },
{ text: 'Return to the Bonewright — alive', done: false },
],
gold: 250, xp: 400, items: ['Ward-Charm'],
},
{
id: 'crying', title: "Nobody's Crying", type: 'side', status: 'active',
giver: 'A nailed-up notice', loc: 'The Weeping Mire',
desc: 'The town\u2019s tax-collector went into the Mire and never came out. The reward is small. The relief in town is not.',
objectives: [
{ text: 'Find the missing tax-collector', done: false },
{ text: 'Learn what dragged him under', done: false },
],
gold: 80, xp: 150, items: [],
},
{
id: 'bell', title: 'Make the Bell Stop', type: 'side', status: 'active',
giver: 'Overheard in Saltcrol', loc: 'Ashfell',
desc: 'A bell still rings in the burned city, though no one living should be there to ring it. It rings at dusk. People have started to count on it, which is worse.',
objectives: [
{ text: 'Reach the bell tower in Ashfell', done: false },
{ text: 'Silence whatever rings it', done: false },
],
gold: 120, xp: 200, items: [],
},
{
id: 'spider', title: 'The Red Spider', type: 'bounty', status: 'active',
giver: 'Harbor-master, Saltcrol', loc: 'Redwidow Camp',
desc: 'The Redwidows brand a red spider on everyone they rob. The harbor-master wants their branding iron brought back. He did not ask for it clean.',
objectives: [
{ text: 'Identify the Redwidow leader', done: true },
{ text: 'Bring back their branding iron', done: false },
],
gold: 300, xp: 250, items: ['Harbor Writ'],
},
{
id: 'debt', title: 'A Debt in Saltcrol', type: 'main', status: 'completed',
giver: 'Mother Cael', loc: 'Saltcrol Harbor',
desc: 'You paid off the debt that followed you out of the last city. Mostly in coin. Partly in blood.',
objectives: [
{ text: 'Deliver the strongbox to Mother Cael', done: true },
{ text: 'Survive the double-cross', done: true },
],
gold: 200, xp: 350, items: [],
},
{
id: 'rats', title: 'Rats, Obviously', type: 'side', status: 'completed',
giver: 'The Gutted Cod tavern', loc: 'Gallowsmoor',
desc: 'It was rats. It is always rats. These ones were the size of dogs, but still.',
objectives: [{ text: 'Clear the cellar', done: true }],
gold: 25, xp: 60, items: [],
},
{
id: 'pilgrim', title: 'Escort the Pilgrim', type: 'side', status: 'failed',
giver: 'The Ninth Order', loc: 'The Gibbet Road',
desc: 'The pilgrim died on the Gibbet Road. You lived. The Order remembers which of you it paid for.',
objectives: [
{ text: 'Escort the pilgrim to the barrow', done: false },
{ text: 'Keep the pilgrim alive', done: false },
],
gold: 0, xp: 0, items: [],
},
];
}
state = { sel: 'femur', tab: 'active', tracked: 'femur' };
renderVals() {
const quests = this.questData();
const byId = {}; quests.forEach(q => byId[q.id] = q);
const tabDefs = [
{ id: 'active', label: 'ACTIVE' },
{ id: 'completed', label: 'COMPLETED' },
{ id: 'failed', label: 'FAILED' },
];
const tab = this.state.tab;
const tabs = tabDefs.map(t => {
const n = quests.filter(q => q.status === t.id).length;
return {
label: t.label + ' (' + n + ')',
onSelect: () => { const first = quests.find(q => q.status === t.id); this.setState({ tab: t.id, sel: first ? first.id : this.state.sel }); },
bg: t.id === tab ? '#8f3a34' : 'rgba(18,15,11,.6)',
fg: t.id === tab ? '#f4e6cf' : '#9a8f78',
border: t.id === tab ? '#8f3a34' : '#3a352e',
};
});
const list = quests.filter(q => q.status === tab).map(q => {
const doneN = q.objectives.filter(o => o.done).length;
const ty = this.TYPE[q.type];
return {
title: q.title, typeLabel: ty.label, typeColor: ty.color, loc: q.loc,
progressText: q.status === 'active' ? (doneN + '/' + q.objectives.length) : (q.status === 'completed' ? '✓' : '✗'),
tracked: q.id === this.state.tracked,
onSelect: () => this.setState({ sel: q.id }),
bg: q.id === this.state.sel ? 'rgba(143,58,52,.18)' : 'rgba(18,15,11,.55)',
border: q.id === this.state.sel ? '#a9843f' : '#3a352e',
};
});
const s = byId[this.state.sel];
const ty = this.TYPE[s.type];
let currentSet = false;
const objectives = s.objectives.map(o => {
const isCurrent = s.status === 'active' && !o.done && !currentSet;
if (isCurrent) currentSet = true;
return {
text: o.text,
box: o.done ? '☑' : '☐',
boxColor: o.done ? '#5f8a4f' : (isCurrent ? '#8f3a34' : '#a99464'),
textColor: o.done ? '#8a7c5a' : '#3a2f1c',
strike: o.done ? 'text-decoration:line-through' : '',
current: isCurrent,
};
});
const tracked = s.id === this.state.tracked;
return {
tabs, list,
selTitle: s.title, selTypeLabel: ty.label, selTypeColor: ty.color,
selGiver: s.giver, selLoc: s.loc, selDesc: s.desc,
objectives,
rewardGold: s.gold, rewardXp: s.xp, rewardItems: s.items,
isActive: s.status === 'active', isCompleted: s.status === 'completed', isFailed: s.status === 'failed',
onTrack: () => this.setState({ tracked: tracked ? null : s.id }),
trackLabel: tracked ? '★ Tracked' : '☆ Track',
trackBg: tracked ? 'linear-gradient(180deg,#c8963f,#a5772a)' : '#e6d9b8',
trackFg: tracked ? '#2a2015' : '#7a5a3a',
trackBorder: tracked ? '#a5772a' : '#a99464',
};
}
}
</script>
</body>
</html>