Skip to content

Instantly share code, notes, and snippets.

@rndmcnlly
Created January 6, 2026 00:05
Show Gist options
  • Select an option

  • Save rndmcnlly/34aafb0cd2a75ae377d10d1058e03189 to your computer and use it in GitHub Desktop.

Select an option

Save rndmcnlly/34aafb0cd2a75ae377d10d1058e03189 to your computer and use it in GitHub Desktop.
Executable concept art for TALL TALE, a game about hauling away America's stuff while a sad giant tells you what it meant.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TALL TALE - Prototype Concepts</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: Georgia, serif;
background: #F5F0E6;
color: #3D3528;
min-height: 100vh;
padding: 20px;
}
h1 {
text-align: center;
font-weight: normal;
font-size: 1.8rem;
margin-bottom: 8px;
color: #5C4033;
}
.subtitle {
text-align: center;
font-style: italic;
font-size: 0.95rem;
color: #7D6B5D;
margin-bottom: 24px;
}
/* Tab Navigation */
.tab-nav {
display: flex;
justify-content: center;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 24px;
}
.tab-btn {
background: #E8E0D0;
border: 2px solid #C9B99A;
border-radius: 8px 8px 0 0;
padding: 12px 20px;
font-family: Georgia, serif;
font-size: 0.95rem;
color: #5C4033;
cursor: pointer;
transition: all 0.2s ease;
}
.tab-btn:hover {
background: #DDD5C3;
}
.tab-btn.active {
background: #FFF9F0;
border-bottom-color: #FFF9F0;
font-weight: bold;
}
/* Tab Content */
.tab-content {
display: none;
background: #FFF9F0;
border: 2px solid #C9B99A;
border-radius: 12px;
padding: 24px;
min-height: 500px;
}
.tab-content.active {
display: block;
}
.tab-title {
font-size: 1.3rem;
margin-bottom: 8px;
color: #5C4033;
}
.tab-desc {
font-size: 0.9rem;
color: #7D6B5D;
margin-bottom: 20px;
font-style: italic;
}
/* Wireframe Area */
.wireframe {
background: #F5F0E6;
border: 2px dashed #C9B99A;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
min-height: 350px;
position: relative;
}
/* Expander */
.expander {
border-top: 1px solid #C9B99A;
margin-top: 16px;
}
.expander-header {
padding: 12px 0;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
color: #7D6B5D;
font-size: 0.9rem;
}
.expander-header:hover {
color: #5C4033;
}
.expander-arrow {
transition: transform 0.2s ease;
}
.expander.open .expander-arrow {
transform: rotate(90deg);
}
.expander-content {
display: none;
padding: 12px 0;
font-size: 0.85rem;
line-height: 1.6;
color: #5C4033;
}
.expander.open .expander-content {
display: block;
}
.expander-content ul {
margin-left: 20px;
}
.expander-content li {
margin-bottom: 8px;
}
/* ============ TAB A: Vignette Composer ============ */
.vignette-area {
display: flex;
flex-direction: column;
height: 340px;
}
.constellation {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.drop-zone {
width: 70px;
height: 70px;
border: 2px dashed #A89880;
border-radius: 50%;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
color: #A89880;
background: #FFFDF8;
transition: all 0.2s ease;
}
.drop-zone.drag-over {
border-color: #5C4033;
background: #F0EBE0;
}
.drop-zone.filled {
border-style: solid;
border-color: #8B7355;
background: #E8E0D0;
font-size: 0.65rem;
color: #5C4033;
font-weight: bold;
}
.drop-zone:nth-child(1) { top: 10px; left: 50%; transform: translateX(-50%); }
.drop-zone:nth-child(2) { top: 60px; right: 60px; }
.drop-zone:nth-child(3) { bottom: 30px; right: 80px; }
.drop-zone:nth-child(4) { bottom: 30px; left: 80px; }
.drop-zone:nth-child(5) { top: 60px; left: 60px; }
.flatbed {
background: #8B7355;
border-radius: 8px;
padding: 12px;
display: flex;
flex-wrap: wrap;
gap: 8px;
min-height: 80px;
}
.object-tile {
padding: 8px 12px;
border-radius: 6px;
font-size: 0.75rem;
cursor: grab;
color: white;
font-weight: bold;
user-select: none;
transition: transform 0.1s ease;
}
.object-tile:hover {
transform: scale(1.05);
}
.object-tile.dragging {
opacity: 0.5;
}
.object-tile[data-object="freezer"] { background: #6B8E8E; }
.object-tile[data-object="dress"] { background: #C9A87C; }
.object-tile[data-object="trombone"] { background: #9E7B5B; }
.object-tile[data-object="sign"] { background: #7B6B5B; }
.object-tile[data-object="mower"] { background: #6B8B6B; }
.object-tile[data-object="tv"] { background: #5B6B7B; }
.paul-output {
margin-top: 16px;
padding: 16px;
background: #FFFDF8;
border-left: 4px solid #C9B99A;
font-style: italic;
font-size: 0.9rem;
line-height: 1.5;
color: #5C4033;
min-height: 60px;
opacity: 0;
transition: opacity 0.5s ease;
}
.paul-output.visible {
opacity: 1;
}
.listen-btn {
margin-top: 12px;
padding: 10px 24px;
background: #5C4033;
color: #FFF9F0;
border: none;
border-radius: 6px;
font-family: Georgia, serif;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s ease;
opacity: 0.3;
pointer-events: none;
}
.listen-btn.enabled {
opacity: 1;
pointer-events: auto;
}
.listen-btn.enabled:hover {
background: #7D6B5D;
}
/* ============ TAB B: Collection Conversation ============ */
.conversation-area {
display: flex;
gap: 24px;
height: 280px;
}
.person-panel {
flex: 1;
background: #FFFDF8;
border: 2px solid #C9B99A;
border-radius: 8px;
padding: 20px;
display: flex;
flex-direction: column;
}
.person-avatar {
width: 80px;
height: 80px;
border-radius: 8px;
margin: 0 auto 12px;
}
.person-name {
text-align: center;
font-weight: bold;
font-size: 1.1rem;
color: #5C4033;
margin-bottom: 8px;
}
.person-desc {
font-size: 0.85rem;
color: #7D6B5D;
line-height: 1.4;
text-align: center;
}
.choices-panel {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
}
.choice-btn {
flex: 1;
padding: 16px;
background: #E8E0D0;
border: 2px solid #C9B99A;
border-radius: 8px;
font-family: Georgia, serif;
font-size: 0.95rem;
color: #5C4033;
cursor: pointer;
transition: all 0.2s ease;
}
.choice-btn:hover {
background: #DDD5C3;
border-color: #A89880;
}
.response-area {
margin-top: 16px;
padding: 16px;
background: #FFFDF8;
border-left: 4px solid #C9B99A;
font-size: 0.9rem;
line-height: 1.5;
color: #5C4033;
min-height: 60px;
}
.truck-counter {
margin-top: 12px;
text-align: right;
font-size: 0.85rem;
color: #7D6B5D;
}
/* ============ TAB C: The Stack ============ */
.stack-area {
height: 320px;
position: relative;
overflow: hidden;
}
.stack-flatbed {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 200px;
height: 8px;
background: #8B7355;
border-radius: 4px;
}
.stack-zone {
position: absolute;
bottom: 8px;
left: 50%;
transform: translateX(-50%);
width: 200px;
height: 300px;
display: flex;
flex-direction: column-reverse;
align-items: center;
}
.stacked-object {
border-radius: 4px;
transition: all 0.3s ease;
position: relative;
}
.drop-btn {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
padding: 12px 28px;
background: #5C4033;
color: #FFF9F0;
border: none;
border-radius: 6px;
font-family: Georgia, serif;
font-size: 0.95rem;
cursor: pointer;
transition: all 0.2s ease;
}
.drop-btn:hover {
background: #7D6B5D;
}
.stack-counter {
position: absolute;
top: 10px;
right: 20px;
font-size: 0.9rem;
color: #7D6B5D;
}
.reset-stack-btn {
position: absolute;
top: 10px;
left: 20px;
padding: 8px 16px;
background: #E8E0D0;
border: 2px solid #C9B99A;
border-radius: 6px;
font-family: Georgia, serif;
font-size: 0.8rem;
color: #5C4033;
cursor: pointer;
}
@keyframes fall {
0% { transform: translateY(-200px); opacity: 0; }
20% { opacity: 1; }
100% { transform: translateY(0); }
}
.stacked-object.falling {
animation: fall 0.4s ease-out forwards;
}
/* ============ TAB D: Paul's Voice ============ */
.voice-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 320px;
text-align: center;
}
.paul-monologue {
max-width: 500px;
font-size: 1.05rem;
line-height: 1.7;
color: #5C4033;
font-style: italic;
min-height: 150px;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.paul-monologue .typing {
color: #A89880;
}
.paul-monologue .text {
opacity: 0;
transition: opacity 0.8s ease;
}
.paul-monologue .text.visible {
opacity: 1;
}
.ask-paul-btn {
padding: 14px 32px;
background: #5C4033;
color: #FFF9F0;
border: none;
border-radius: 8px;
font-family: Georgia, serif;
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
}
.ask-paul-btn:hover {
background: #7D6B5D;
}
.voice-counter {
margin-top: 16px;
font-size: 0.8rem;
color: #A89880;
}
</style>
</head>
<body>
<h1>TALL TALE</h1>
<p class="subtitle">Prototype Concepts — Executable Sketches</p>
<nav class="tab-nav">
<button class="tab-btn active" data-tab="vignette">Vignette Composer</button>
<button class="tab-btn" data-tab="conversation">Collection Conversation</button>
<button class="tab-btn" data-tab="stack">The Stack</button>
<button class="tab-btn" data-tab="voice">Paul's Voice</button>
</nav>
<!-- ============ TAB A: Vignette Composer ============ -->
<div class="tab-content active" id="tab-vignette">
<h2 class="tab-title">Vignette Composer</h2>
<p class="tab-desc">Drag objects into the constellation. Paul finds the story.</p>
<div class="wireframe">
<div class="vignette-area">
<div class="constellation">
<div class="drop-zone" data-slot="0">empty</div>
<div class="drop-zone" data-slot="1">empty</div>
<div class="drop-zone" data-slot="2">empty</div>
<div class="drop-zone" data-slot="3">empty</div>
<div class="drop-zone" data-slot="4">empty</div>
</div>
<div class="flatbed" id="flatbed-a">
<div class="object-tile" draggable="true" data-object="freezer">Freezer</div>
<div class="object-tile" draggable="true" data-object="dress">Wedding Dress</div>
<div class="object-tile" draggable="true" data-object="trombone">Trombone</div>
<div class="object-tile" draggable="true" data-object="sign">Campaign Sign</div>
<div class="object-tile" draggable="true" data-object="mower">Push Mower</div>
<div class="object-tile" draggable="true" data-object="tv">Old TV</div>
</div>
</div>
<button class="listen-btn" id="listen-btn">Listen to Paul</button>
<div class="paul-output" id="paul-output"></div>
</div>
<div class="expander" id="expander-a">
<div class="expander-header">
<span>What the real prototype would test →</span>
<span class="expander-arrow">▶</span>
</div>
<div class="expander-content">
<ul>
<li><strong>Authorship feel:</strong> Does arranging objects feel like composing, or just sorting?</li>
<li><strong>Generative coherence:</strong> Can LLM-assembled narration feel discovered rather than random?</li>
<li><strong>Slot count:</strong> Is 5 slots too many? Too few? Does constraint breed meaning?</li>
<li><strong>Replay value:</strong> Do players try different combinations to hear different stories?</li>
<li><strong>Success signal:</strong> Players saying "I made him say that" rather than "it said something."</li>
</ul>
</div>
</div>
</div>
<!-- ============ TAB B: Collection Conversation ============ -->
<div class="tab-content" id="tab-conversation">
<h2 class="tab-title">Collection Conversation</h2>
<p class="tab-desc">Meet the people. Decide what to carry.</p>
<div class="wireframe">
<div class="conversation-area">
<div class="person-panel">
<div class="person-avatar" id="person-avatar" style="background: #6B8E8E;"></div>
<div class="person-name" id="person-name">Darlene</div>
<div class="person-desc" id="person-desc">She's standing next to a chest freezer. It's been unplugged for six months.</div>
</div>
<div class="choices-panel">
<button class="choice-btn" data-choice="take">Take it</button>
<button class="choice-btn" data-choice="ask">Ask about it</button>
<button class="choice-btn" data-choice="leave">Leave it</button>
</div>
</div>
<div class="response-area" id="response-area">
<em>She's waiting for you to decide.</em>
</div>
<div class="truck-counter" id="truck-counter">Truck: 0 items</div>
</div>
<div class="expander" id="expander-b">
<div class="expander-header">
<span>What the real prototype would test →</span>
<span class="expander-arrow">▶</span>
</div>
<div class="expander-content">
<ul>
<li><strong>Pacing:</strong> How long should each encounter last? When does curiosity become impatience?</li>
<li><strong>Story appetite:</strong> Do players click "Ask" or rush to "Take"? What ratio emerges?</li>
<li><strong>Leave it weight:</strong> Does leaving something behind feel meaningful or wasteful?</li>
<li><strong>Character voice:</strong> How much personality can we convey in 2-3 lines?</li>
<li><strong>Success signal:</strong> Players pausing before choosing. Players mentioning characters by name later.</li>
</ul>
</div>
</div>
</div>
<!-- ============ TAB C: The Stack ============ -->
<div class="tab-content" id="tab-stack">
<h2 class="tab-title">The Stack</h2>
<p class="tab-desc">No story. Just the satisfaction of accumulation.</p>
<div class="wireframe">
<div class="stack-area">
<button class="reset-stack-btn" id="reset-stack-btn">Reset</button>
<button class="drop-btn" id="drop-btn">Drop Next Object</button>
<div class="stack-counter" id="stack-counter">Objects: 0</div>
<div class="stack-zone" id="stack-zone"></div>
<div class="stack-flatbed"></div>
</div>
</div>
<div class="expander" id="expander-c">
<div class="expander-header">
<span>What the real prototype would test →</span>
<span class="expander-arrow">▶</span>
</div>
<div class="expander-content">
<ul>
<li><strong>Core satisfaction:</strong> Is watching the pile grow rewarding without any narrative wrapper?</li>
<li><strong>Physics feel:</strong> How much wobble and precarity is fun vs. stressful?</li>
<li><strong>Failure state:</strong> Should the stack ever collapse? Or is permanence the point?</li>
<li><strong>Visual identity:</strong> Do random shapes and colors read as "stuff" or just "blocks"?</li>
<li><strong>Success signal:</strong> Players clicking more than 10 times. Players showing someone else their stack.</li>
</ul>
</div>
</div>
</div>
<!-- ============ TAB D: Paul's Voice ============ -->
<div class="tab-content" id="tab-voice">
<h2 class="tab-title">Paul's Voice</h2>
<p class="tab-desc">No mechanics. Just the tone of a giant who remembers.</p>
<div class="wireframe">
<div class="voice-area">
<div class="paul-monologue" id="paul-monologue">
<span class="typing" id="typing-indicator" style="display:none;">. . .</span>
<span class="text" id="monologue-text"></span>
</div>
<button class="ask-paul-btn" id="ask-paul-btn">What do you see, Paul?</button>
<div class="voice-counter" id="voice-counter"></div>
</div>
</div>
<div class="expander" id="expander-d">
<div class="expander-header">
<span>What the real prototype would test →</span>
<span class="expander-arrow">▶</span>
</div>
<div class="expander-content">
<ul>
<li><strong>Length tolerance:</strong> How long can Paul talk before players skip? 20 words? 50? 100?</li>
<li><strong>Tone balance:</strong> What ratio of melancholy, humor, and strangeness feels right?</li>
<li><strong>Voice consistency:</strong> Can we define Paul clearly enough that LLM output stays in character?</li>
<li><strong>Silence value:</strong> Do pauses and short lines feel meaningful or broken?</li>
<li><strong>Success signal:</strong> Players clicking through all monologues. Players quoting Paul back.</li>
</ul>
</div>
</div>
</div>
<script>
// ============ TAB SWITCHING ============
const tabBtns = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
tabBtns.forEach(btn => {
btn.addEventListener('click', () => {
tabBtns.forEach(b => b.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
btn.classList.add('active');
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
});
});
// ============ EXPANDERS ============
document.querySelectorAll('.expander-header').forEach(header => {
header.addEventListener('click', () => {
header.parentElement.classList.toggle('open');
});
});
// ============ TAB A: Vignette Composer ============
const vignetteSlots = {};
const objectPhrases = {
freezer: [
"The freezer held everything she couldn't let thaw.",
"It hummed for years after they unplugged it. Nobody knows why.",
"Someone told me freezers remember the shape of what's gone."
],
dress: [
"The dress was never worn. That was the point.",
"She kept it sealed. As if time was a kind of moisture.",
"Some things are too finished to use."
],
trombone: [
"He played it once at a funeral. Couldn't stop after that.",
"Brass holds heat longer than you'd think.",
"The slide still moves. That's the sad part."
],
sign: [
"The campaign ended. The sign stayed up. Weather made it abstract.",
"Nobody remembers what he ran for. Just that he lost.",
"A promise is just a shape if you wait long enough."
],
mower: [
"The lawn grew over it one summer. Felt intentional.",
"He said he'd fix it. That was the last thing he fixed.",
"Green machine, green grass, eventually no difference."
],
tv: [
"It still turns on. Shows the same static it always did.",
"They watched the moon landing on it. And everything after.",
"The glass is curved like an eye that won't close."
]
};
const connectives = [
"And somehow, that reminds me of",
"Which sits next to",
"Nobody asked, but nearby there's",
"And of course,",
"Then there's"
];
let draggedObject = null;
document.querySelectorAll('#flatbed-a .object-tile').forEach(tile => {
tile.addEventListener('dragstart', (e) => {
draggedObject = e.target.dataset.object;
e.target.classList.add('dragging');
});
tile.addEventListener('dragend', (e) => {
e.target.classList.remove('dragging');
});
});
document.querySelectorAll('.drop-zone').forEach(zone => {
zone.addEventListener('dragover', (e) => {
e.preventDefault();
zone.classList.add('drag-over');
});
zone.addEventListener('dragleave', () => {
zone.classList.remove('drag-over');
});
zone.addEventListener('drop', (e) => {
e.preventDefault();
zone.classList.remove('drag-over');
if (draggedObject && !zone.classList.contains('filled')) {
zone.classList.add('filled');
zone.textContent = draggedObject.charAt(0).toUpperCase() + draggedObject.slice(1);
vignetteSlots[zone.dataset.slot] = draggedObject;
updateListenButton();
}
});
zone.addEventListener('click', () => {
if (zone.classList.contains('filled')) {
zone.classList.remove('filled');
zone.textContent = 'empty';
delete vignetteSlots[zone.dataset.slot];
updateListenButton();
}
});
});
function updateListenButton() {
const count = Object.keys(vignetteSlots).length;
const btn = document.getElementById('listen-btn');
if (count >= 3) {
btn.classList.add('enabled');
} else {
btn.classList.remove('enabled');
}
}
document.getElementById('listen-btn').addEventListener('click', () => {
const objects = Object.values(vignetteSlots);
if (objects.length < 3) return;
let story = '';
objects.forEach((obj, i) => {
const phrases = objectPhrases[obj];
const phrase = phrases[Math.floor(Math.random() * phrases.length)];
if (i === 0) {
story += phrase;
} else {
const conn = connectives[Math.floor(Math.random() * connectives.length)];
story += ' ' + conn + ' ' + phrase.charAt(0).toLowerCase() + phrase.slice(1);
}
});
const output = document.getElementById('paul-output');
output.textContent = '"' + story + '"';
output.classList.add('visible');
});
// ============ TAB B: Collection Conversation ============
const people = [
{
name: 'Darlene',
color: '#6B8E8E',
desc: "She's standing next to a chest freezer. It's been unplugged for six months.",
askResponse: '"It was my mother\'s. She kept everything labeled by year. I couldn\'t bring myself to open it after. You understand."',
takeResponse: 'She nods once, slow. "Good. Thank you." She doesn\'t watch you load it.'
},
{
name: 'Earl',
color: '#9E7B5B',
desc: "He's holding a trombone case like it might run off.",
askResponse: '"Played it in the marching band. \'71. We were terrible but loud. My son doesn\'t want it. Nobody wants it."',
takeResponse: 'He hands it over fast, then looks at his hands like he doesn\'t recognize them.'
},
{
name: 'Marcy',
color: '#C9A87C',
desc: "There's a wedding dress on a hanger behind her. Still in plastic.",
askResponse: '"It fit perfect. That was thirty years ago. I think perfect was the problem."',
takeResponse: 'She laughs—surprised by herself. "Lighter already. Isn\'t that something."'
},
{
name: 'Tom',
color: '#7B6B5B',
desc: "He's standing in front of a campaign sign, faded to almost nothing.",
askResponse: '"I ran for water commissioner. Lost by eleven votes. Kept the sign to remember what trying felt like."',
takeResponse: '"Take the whole post if you want. Ground\'s soft. It\'ll come right up."'
}
];
let personIndex = 0;
let truckCount = 0;
function loadPerson(index) {
if (index >= people.length) {
document.getElementById('person-name').textContent = 'End of the road';
document.getElementById('person-desc').textContent = 'No one else is waiting. Time to move on.';
document.getElementById('person-avatar').style.background = '#C9B99A';
document.getElementById('response-area').innerHTML = '<em>The town is quiet now.</em>';
document.querySelectorAll('.choice-btn').forEach(btn => btn.style.display = 'none');
return;
}
const p = people[index];
document.getElementById('person-name').textContent = p.name;
document.getElementById('person-desc').textContent = p.desc;
document.getElementById('person-avatar').style.background = p.color;
document.getElementById('response-area').innerHTML = '<em>Waiting for you to decide.</em>';
}
document.querySelectorAll('.choice-btn').forEach(btn => {
btn.addEventListener('click', () => {
const choice = btn.dataset.choice;
const p = people[personIndex];
const response = document.getElementById('response-area');
if (choice === 'ask') {
response.innerHTML = p.askResponse;
} else if (choice === 'take') {
truckCount++;
document.getElementById('truck-counter').textContent = 'Truck: ' + truckCount + ' items';
response.innerHTML = '<em>' + p.takeResponse + '</em>';
setTimeout(() => {
personIndex++;
loadPerson(personIndex);
}, 1500);
} else if (choice === 'leave') {
response.innerHTML = '<em>You nod and move on. Some things stay where they are.</em>';
setTimeout(() => {
personIndex++;
loadPerson(personIndex);
}, 1200);
}
});
});
// ============ TAB C: The Stack ============
const stackColors = ['#6B8E8E', '#C9A87C', '#9E7B5B', '#7B6B5B', '#6B8B6B', '#5B6B7B', '#8B7B6B', '#6B7B8B'];
let stackCount = 0;
let stackHeight = 0;
document.getElementById('drop-btn').addEventListener('click', () => {
const zone = document.getElementById('stack-zone');
const width = 40 + Math.random() * 80;
const height = 15 + Math.random() * 30;
const color = stackColors[Math.floor(Math.random() * stackColors.length)];
const rotation = -5 + Math.random() * 10;
const offsetX = -15 + Math.random() * 30;
const obj = document.createElement('div');
obj.className = 'stacked-object falling';
obj.style.width = width + 'px';
obj.style.height = height + 'px';
obj.style.background = color;
obj.style.transform = `translateX(${offsetX}px) rotate(${rotation}deg)`;
obj.style.marginTop = '-2px';
zone.appendChild(obj);
stackCount++;
stackHeight += height;
document.getElementById('stack-counter').textContent = 'Objects: ' + stackCount;
if (stackHeight > 280) {
document.getElementById('drop-btn').textContent = 'Stack full!';
document.getElementById('drop-btn').disabled = true;
}
});
document.getElementById('reset-stack-btn').addEventListener('click', () => {
document.getElementById('stack-zone').innerHTML = '';
stackCount = 0;
stackHeight = 0;
document.getElementById('stack-counter').textContent = 'Objects: 0';
document.getElementById('drop-btn').textContent = 'Drop Next Object';
document.getElementById('drop-btn').disabled = false;
});
// ============ TAB D: Paul's Voice ============
const monologues = [
"I've been walking for a long time. Longer than the roads. Sometimes I forget which towns I've already passed through. They all have a water tower. They all have someone who stayed too long.",
"You're doing good work. I mean it. I can't carry the small things anymore. My hands are too big. I pick up a barn and a life falls out.",
"Do you ever think about what enough would feel like? I used to. Now I think the thinking was the problem.",
"There was a town once—I won't say which—where everyone gave me something. I carried it all west. Set it down somewhere in Nevada. It's probably still there. A little mountain of having.",
"Thank you. For the truck. For the asking. Some days I forget anyone's still listening. The wind up here is very loud."
];
let monologueIndex = 0;
document.getElementById('ask-paul-btn').addEventListener('click', () => {
const typing = document.getElementById('typing-indicator');
const text = document.getElementById('monologue-text');
const counter = document.getElementById('voice-counter');
text.classList.remove('visible');
typing.style.display = 'inline';
setTimeout(() => {
typing.style.display = 'none';
text.textContent = monologues[monologueIndex];
text.classList.add('visible');
counter.textContent = (monologueIndex + 1) + ' / ' + monologues.length;
monologueIndex = (monologueIndex + 1) % monologues.length;
}, 800);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment