This is a submission for Weekend Challenge: Dog Days Edition
What I Built ðķ
Bark Gallery: Speed Sketch Challenge is an arcade-style, browser-based community drawing and voting game built to celebrate International Dog Day!
Players receive a randomized, humorous canine scenario generated using Google AI Studio's Gemini 3.7 Flash model, such as:
- ð A dog caught stealing a slice of pizza
- ðĐš A dog doctor checking a heartbeat with a stethoscope
- ð A dog chef cooking spaghetti in a chaotic kitchen
- ð A dog detective inspecting clues with a magnifying glass
- ðïļ A dog and a duck playing on the beach (Duckmon crossover!)
ðŪ The Core Loop
90-Second Sprint: You get 90 seconds to sketch your masterpiece using our MS Paint-style spray tool, custom color palette, undo stack, and canvas controls.
Arcade Dog Tag: When the timer ends, sign your artwork with a custom arcade dog tag.
-
Community Voting: Your sketch uploads instantly to the live Community Bark Gallery, where visitors can vote in real time:
- âĪïļ Like â
+1 point - ðĶī Treat / Super Like â
+3 points
- âĪïļ Like â
Top 10 Leaderboard: Scores recalculate on the fly and feed into the dynamic leaderboard!
Demo ðŪ
- ð Live Playable Game: https://quackworkstudios.com/doggame.html
- ðŧ Studio Site: https://quackworkstudios.com
Code ðŧ
The complete game is architected as a lightweight, single-file client application featuring an HTML5 <canvas> engine, custom particle spray physics, real-time client-side JPEG compression, voting state persistence, and dynamic leaderboard sorting.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Bark Gallery: Community Dog Doodles â Quackwork Studios</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="robots" content="index,follow">
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Inter:wght@400;600;700;900&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css">
<style>
.game-section {
padding: 30px 16px 80px;
max-width: 1120px;
margin: 0 auto;
}
.pixel-title {
font-family: 'Press Start 2P', monospace;
color: #f59e0b;
font-size: 1.45rem;
letter-spacing: -1px;
margin-bottom: 8px;
}
.rules-card {
background: #0d121c;
border: 1px solid #232d3f;
border-left: 4px solid #f59e0b;
border-radius: 12px;
padding: 16px 20px;
margin-bottom: 24px;
}
.rules-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 14px;
margin-top: 10px;
}
.rule-item {
display: flex;
align-items: flex-start;
gap: 10px;
font-size: 0.8rem;
color: #cbd5e1;
}
.rule-num {
background: #1a2233;
color: #f59e0b;
font-family: 'Press Start 2P', monospace;
font-size: 0.7rem;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
flex-shrink: 0;
}
.game-layout {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
}
@media (min-width: 880px) {
.game-layout {
grid-template-columns: 1.35fr 0.65fr;
}
}
.game-box, .leaderboard-box, .gallery-section {
background: #121824;
border: 2px solid #232d3f;
border-radius: 16px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
}
.status-bar {
background: #1a2233;
border: 1px solid #2e3a50;
border-radius: 12px;
padding: 12px 16px;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 16px;
}
.prompt-container {
flex: 1;
min-width: 220px;
}
.target-title {
font-size: 0.75rem;
text-transform: uppercase;
color: #8f9ba8;
font-weight: 700;
}
.target-breed {
font-family: 'Press Start 2P', monospace;
font-size: 0.8rem;
color: #fcd34d;
margin-top: 4px;
line-height: 1.4;
}
.timer-badge {
font-family: 'Press Start 2P', monospace;
font-size: 1rem;
color: #38bdf8;
background: #0f172a;
padding: 6px 12px;
border-radius: 8px;
border: 1px solid #1e293b;
}
.timer-badge.warning {
color: #ef4444;
border-color: #ef4444;
animation: pulse 1s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.canvas-container {
position: relative;
width: 100%;
max-width: 480px;
aspect-ratio: 1 / 1;
margin: 0 auto 16px;
background: #ffffff;
border-radius: 12px;
border: 3px solid #f59e0b;
overflow: hidden;
box-shadow: 0 0 20px rgba(245, 158, 11, 0.2);
}
#drawCanvas {
width: 100%;
height: 100%;
touch-action: none;
cursor: crosshair;
}
.overlay {
position: absolute;
inset: 0;
background: rgba(10, 14, 23, 0.95);
backdrop-filter: blur(4px);
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px;
text-align: center;
z-index: 10;
}
.tools-bar {
display: flex;
flex-direction: column;
gap: 12px;
background: #1a2233;
padding: 12px 14px;
border-radius: 12px;
border: 1px solid #2e3a50;
}
.tools-subrow {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.tool-selector, .size-selector {
display: flex;
align-items: center;
gap: 4px;
background: #0d121c;
padding: 4px;
border-radius: 8px;
border: 1px solid #232d3f;
}
.tool-btn, .size-btn {
background: none;
border: none;
color: #94a3b8;
padding: 4px 8px;
border-radius: 6px;
font-size: 0.75rem;
cursor: pointer;
font-weight: bold;
transition: all 0.15s ease;
}
.tool-btn.active, .size-btn.active {
background: #f59e0b;
color: #0f172a;
}
.color-swatches {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
}
.color-btn {
width: 24px;
height: 24px;
border-radius: 6px;
border: 2px solid transparent;
cursor: pointer;
transition: transform 0.15s ease;
}
.color-btn:hover {
transform: scale(1.2);
}
.color-btn.active {
border-color: #ffffff;
box-shadow: 0 0 8px rgba(255, 255, 255, 0.9);
transform: scale(1.25);
}
.btn-pixel {
font-family: 'Press Start 2P', monospace;
font-size: 0.6rem;
padding: 8px 10px;
border-radius: 8px;
border: none;
cursor: pointer;
transition: all 0.15s ease;
}
.btn-action {
background: #f59e0b;
color: #0f172a;
box-shadow: 0 4px 0 #b45309;
}
.btn-action:hover {
background: #fbbf24;
transform: translateY(-2px);
box-shadow: 0 6px 0 #b45309;
}
.btn-action:active {
transform: translateY(2px);
box-shadow: 0 2px 0 #b45309;
}
.btn-secondary {
background: #334155;
color: #e2e8f0;
}
.btn-secondary:hover {
background: #475569;
}
/* Leaderboard Styling */
.leaderboard-table {
width: 100%;
border-collapse: collapse;
margin-top: 14px;
font-size: 0.85rem;
}
.leaderboard-table th {
text-align: left;
font-family: 'Press Start 2P', monospace;
font-size: 0.6rem;
color: #8f9ba8;
padding-bottom: 10px;
border-bottom: 1px solid #232d3f;
}
.leaderboard-table td {
padding: 8px 4px;
border-bottom: 1px solid #1a2233;
color: #cbd5e1;
}
.tag-highlight {
font-family: 'Press Start 2P', monospace;
font-size: 0.65rem;
color: #fcd34d;
}
.score-highlight {
font-family: 'Press Start 2P', monospace;
font-size: 0.7rem;
color: #10b981;
text-align: right;
}
/* Gallery Styling */
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
margin-top: 20px;
max-height: 650px;
overflow-y: auto;
padding-right: 6px;
}
.gallery-card {
background: #0d121c;
border: 1px solid #232d3f;
border-radius: 12px;
overflow: hidden;
display: flex;
flex-direction: column;
transition: transform 0.2s ease, border-color 0.2s ease;
}
.gallery-card:hover {
transform: translateY(-4px);
border-color: #f59e0b;
}
.gallery-img-wrap {
width: 100%;
aspect-ratio: 1 / 1;
background: #ffffff;
}
.gallery-img-wrap img {
width: 100%;
height: 100%;
object-fit: cover;
}
.gallery-info {
padding: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.gallery-user-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.gallery-user {
font-family: 'Press Start 2P', monospace;
font-size: 0.65rem;
color: #fcd34d;
}
.gallery-prompt {
font-size: 0.75rem;
color: #94a3b8;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.vote-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 6px;
padding-top: 8px;
border-top: 1px solid #1a2233;
}
.vote-btn {
background: #1a2233;
border: 1px solid #2e3a50;
color: #fff;
padding: 4px 8px;
border-radius: 6px;
cursor: pointer;
font-size: 0.75rem;
display: flex;
align-items: center;
gap: 4px;
transition: all 0.15s ease;
}
.vote-btn:hover {
background: #28364f;
transform: scale(1.05);
}
.vote-btn:active {
transform: scale(0.95);
}
/* Modal */
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.85);
backdrop-filter: blur(5px);
display: none;
align-items: center;
justify-content: center;
z-index: 999;
padding: 16px;
}
.modal-card {
background: #121824;
border: 2px solid #f59e0b;
border-radius: 16px;
max-width: 440px;
width: 100%;
padding: 24px;
color: #e2e8f0;
box-shadow: 0 0 30px rgba(245, 158, 11, 0.3);
text-align: center;
}
.modal-input {
width: 100%;
background: #090d14;
border: 1px solid #2e3a50;
border-radius: 8px;
padding: 10px 12px;
color: #fff;
font-size: 1rem;
margin: 12px 0 20px 0;
text-align: center;
font-family: 'Press Start 2P', monospace;
box-sizing: border-box;
}
.modal-input:focus {
outline: none;
border-color: #f59e0b;
}
</style>
</head>
<body>
<header class="header-wrapper">
<div class="top-notice-bar">
<span class="notice-text">ðū DEV Weekend Challenge: Community Bark Gallery is Live!</span>
</div>
<nav class="navbar">
<div class="navbar-container">
<a href="/" class="brand-link">
<div class="brand-pixel-badge">
<span class="pixel-cell"></span>
<span class="pixel-cell"></span>
<span class="pixel-cell"></span>
<span class="pixel-cell"></span>
</div>
<span class="brand-name">Quackwork Studios</span>
</a>
<ul class="navbar-links">
<li><a href="/" class="navbar-link">Home</a></li>
<li><a href="/#games" class="navbar-link">Games</a></li>
<li><a href="/doggame.html" class="navbar-link" style="color: #f59e0b;">ðū Bark Gallery</a></li>
</ul>
</div>
</nav>
</header>
<main>
<section class="game-section">
<!-- Title & Header -->
<div class="section-header" style="margin-bottom: 16px; text-align: center;">
<h1 class="pixel-title">Bark Gallery: Speed Sketch Challenge ðĻðķ</h1>
<p class="section-subtitle">A community-driven dog doodle showcase built for International Dog Day!</p>
</div>
<!-- Rules Bar -->
<div class="rules-card">
<div style="font-family: 'Press Start 2P', monospace; font-size: 0.75rem; color: #f59e0b; margin-bottom: 8px;">
ð HOW TO PLAY & COMPETE:
</div>
<div class="rules-grid">
<div class="rule-item">
<span class="rule-num">1</span>
<span>Get a random canine scenario prompt.</span>
</div>
<div class="rule-item">
<span class="rule-num">2</span>
<span>You have <strong>90s</strong> to sketch using the pen & spray tools.</span>
</div>
<div class="rule-item">
<span class="rule-num">3</span>
<span>Sign with a <strong>Dog Tag</strong> & upload to the gallery.</span>
</div>
<div class="rule-item">
<span class="rule-num">4</span>
<span>Vote below: <strong>âĪïļ Like (+1 pt)</strong> or <strong>ðĶī Treat (+3 pts)</strong> to top the leaderboard!</span>
</div>
</div>
</div>
<div class="game-layout">
<!-- Drawing Area -->
<div class="game-box">
<div class="status-bar">
<div class="prompt-container">
<div class="target-title">Your 90s Prompt:</div>
<div id="targetBreed" class="target-breed">Loading prompt...</div>
</div>
<div>
<div class="target-title">Time</div>
<div id="timerDisplay" class="timer-badge">90s</div>
</div>
</div>
<!-- Canvas Frame -->
<div class="canvas-container">
<canvas id="drawCanvas" width="480" height="480"></canvas>
<!-- Times Up Overlay -->
<div id="overlay" class="overlay">
<div style="font-size: 3rem; margin-bottom: 10px;">ðĻðū</div>
<h2 style="font-family: 'Press Start 2P', monospace; font-size: 1rem; color: #fcd34d; margin-bottom: 8px;">TIME IS UP!</h2>
<p style="font-size: 0.85rem; color: #cbd5e1; margin-bottom: 16px;">Ready to publish your artwork to the Bark Gallery?</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap; justify-content: center;">
<button id="overlayRetryBtn" class="btn-pixel btn-secondary">ð Redraw</button>
<button id="overlayUploadBtn" class="btn-pixel btn-action">ðĪ Upload Artwork</button>
</div>
</div>
</div>
<!-- Controls Bar -->
<div class="tools-bar">
<!-- Tool Type & Brush Size Subrow -->
<div class="tools-subrow">
<!-- Tool Type -->
<div class="tool-selector">
<button class="tool-btn active" id="penToolBtn">âïļ Pen</button>
<button class="tool-btn" id="sprayToolBtn">ðĻ Spray</button>
<button class="tool-btn" id="eraserToolBtn">ð§― Eraser</button>
</div>
<!-- Brush Sizes -->
<div class="size-selector">
<button class="size-btn" data-size="3">Thin</button>
<button class="size-btn active" data-size="7">Med</button>
<button class="size-btn" data-size="16">Thick</button>
</div>
</div>
<!-- Expanded 18 Color Swatches -->
<div class="color-swatches">
<button class="color-btn active" style="background: #000000;" data-color="#000000" title="Black"></button>
<button class="color-btn" style="background: #4b5563;" data-color="#4b5563" title="Dark Gray"></button>
<button class="color-btn" style="background: #9ca3af;" data-color="#9ca3af" title="Light Gray"></button>
<button class="color-btn" style="background: #ffffff; border: 1px solid #94a3b8;" data-color="#ffffff" title="White"></button>
<button class="color-btn" style="background: #451a03;" data-color="#451a03" title="Dark Brown"></button>
<button class="color-btn" style="background: #78350f;" data-color="#78350f" title="Brown"></button>
<button class="color-btn" style="background: #b45309;" data-color="#b45309" title="Caramel / Tan"></button>
<button class="color-btn" style="background: #f59e0b;" data-color="#f59e0b" title="Golden"></button>
<button class="color-btn" style="background: #fef08a;" data-color="#fef08a" title="Cream / Yellow"></button>
<button class="color-btn" style="background: #b91c1c;" data-color="#b91c1c" title="Dark Red"></button>
<button class="color-btn" style="background: #ef4444;" data-color="#ef4444" title="Bright Red"></button>
<button class="color-btn" style="background: #f97316;" data-color="#f97316" title="Orange"></button>
<button class="color-btn" style="background: #84cc16;" data-color="#84cc16" title="Lime Green"></button>
<button class="color-btn" style="background: #22c55e;" data-color="#22c55e" title="Green"></button>
<button class="color-btn" style="background: #06b6d4;" data-color="#06b6d4" title="Cyan"></button>
<button class="color-btn" style="background: #38bdf8;" data-color="#38bdf8" title="Sky Blue"></button>
<button class="color-btn" style="background: #2563eb;" data-color="#2563eb" title="Royal Blue"></button>
<button class="color-btn" style="background: #a855f7;" data-color="#a855f7" title="Purple"></button>
<button class="color-btn" style="background: #ec4899;" data-color="#ec4899" title="Pink"></button>
</div>
<!-- Action Buttons Row -->
<div class="tools-subrow">
<div style="display: flex; gap: 6px; flex-wrap: wrap;">
<button id="undoBtn" class="btn-pixel btn-secondary" title="Undo last stroke">âĐïļ Undo</button>
<button id="clearBtn" class="btn-pixel btn-secondary" title="Clear canvas">ðïļ Clear</button>
<button id="resetTimerBtn" class="btn-pixel btn-secondary" title="Reset timer and clear canvas for this prompt">ð Reset</button>
<button id="skipBtn" class="btn-pixel btn-secondary" title="Get a new prompt and reset timer">ðē Skip</button>
</div>
<button id="publishBtn" class="btn-pixel btn-action" style="padding: 10px 14px;">Upload Art ðĪ</button>
</div>
</div>
</div>
<!-- Top 10 Leaderboard -->
<div class="leaderboard-box">
<div style="border-bottom: 1px solid #232d3f; padding-bottom: 10px;">
<h3 style="font-family: 'Press Start 2P', monospace; font-size: 0.85rem; color: #f59e0b; margin: 0;">ð Top 10 Dogs</h3>
<p style="font-size: 0.75rem; color: #8f9ba8; margin: 4px 0 0 0;">âĪïļ Like (+1 pt) âĒ ðĶī Treat (+3 pts)</p>
</div>
<table class="leaderboard-table">
<thead>
<tr>
<th>#</th>
<th>Dog Tag</th>
<th style="text-align: right;">Points</th>
</tr>
</thead>
<tbody id="leaderboardBody">
<!-- Dynamically populated 10 items -->
</tbody>
</table>
</div>
</div>
<!-- Live Community Gallery -->
<div class="gallery-section" style="margin-top: 24px;">
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #232d3f; padding-bottom: 12px;">
<div>
<h2 style="font-family: 'Press Start 2P', monospace; font-size: 1.1rem; color: #fcd34d; margin: 0;">ðžïļ Community Bark Gallery</h2>
<p style="font-size: 0.8rem; color: #94a3b8; margin: 4px 0 0 0;">Browse drawings and vote for your favorites!</p>
</div>
<span id="galleryCount" style="font-family: 'Press Start 2P', monospace; font-size: 0.7rem; color: #38bdf8;">0 ARTWORKS</span>
</div>
<div id="galleryGrid" class="gallery-grid">
<!-- Dynamically populated cards -->
</div>
</div>
</section>
</main>
<!-- Upload Modal -->
<div id="uploadModal" class="modal-backdrop">
<div class="modal-card">
<div style="font-size: 2.5rem; margin-bottom: 8px;">ð·ïļ</div>
<h2 style="font-family: 'Press Start 2P', monospace; font-size: 0.95rem; color: #f59e0b; margin-top: 0;">Sign Your Art!</h2>
<p style="font-size: 0.85rem; color: #cbd5e1; margin: 0;">Enter your Dog Tag / Username (3â8 letters):</p>
<input type="text" id="usernameInput" class="modal-input" maxlength="8" placeholder="BARKUS" />
<div style="display: flex; justify-content: center; gap: 10px;">
<button id="cancelModalBtn" class="btn-pixel btn-secondary">Cancel</button>
<button id="confirmUploadBtn" class="btn-pixel btn-action">Post to Gallery ð</button>
</div>
</div>
</div>
<footer class="footer-wrapper">
<div class="footer-container">
<div class="footer-bottom">
<p class="footer-copyright">
ÂĐ 2026 Quackwork Studios âĒ DEV Weekend Challenge: Dog Days Edition
</p>
</div>
</div>
</footer>
<script>
const PROMPTS = [
"A dog and a duck playing on the beach ðïļðĶ",
"A dog doctor checking a heartbeat with a stethoscope ðĐšð",
"A dog driving a fast sports car with sunglasses ðïļ",
"A dog camping by a campfire roasting marshmallows ðïļðĨ",
"A dog playing cards with a poker face around a table ðâ ïļ",
"A dog skydiving with a parachute in mid-air ðŠ",
"A dog chef cooking spaghetti in a chaotic kitchen ððĻâðģ",
"A dog detective inspecting clues with a magnifying glass ððĩïļ",
"A dog DJ rocking big headphones at a dance party ð§ðķ",
"An astronaut dog floating in outer space ð",
"A dog wearing a Santa hat at Christmas ð
",
"A dog celebrating its birthday with a giant cake ð",
"A dog dressed as a spooky Halloween ghost ðŧ",
"A dog surfing a big ocean wave ðââïļ",
"A dog caught red-handed stealing a slice of pizza ð",
"A wrinkly Pug wearing retro sunglasses ð",
"A fluffy Corgi with stubby legs chasing a tennis ball ðū",
"A long Dachshund (Wiener Dog) wearing a sweater ð",
"A majestic Husky howling at the moon ð",
"A fancy Poodle with a stylish fluffy haircut ðĐ",
"A dog dramatically refusing to take a bath in the tub ð",
"A puppy having intense mud zoomies in the rain ð§ïļ"
];
let currentPrompt = "";
let isDrawing = false;
let currentColor = "#000000";
let brushSize = 7;
let currentTool = "pen"; // "pen", "spray", "eraser"
let timeLeft = 90;
let timerInterval = null;
let isRoundActive = false;
// Undo Stack (keeps last 15 actions)
let undoStack = [];
// 10 Detailed Starter Dog Doodles (all 0 points)
const DEFAULT_GALLERY = [
{
id: "doodle-1",
name: "PUGGY",
prompt: "A wrinkly Pug wearing retro sunglasses ð",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><circle cx='240' cy='240' r='105' fill='%23b45309' stroke='%23000' stroke-width='6'/><ellipse cx='240' cy='285' rx='48' ry='32' fill='%23451a03' stroke='%23000' stroke-width='4'/><rect x='165' y='200' width='65' height='38' rx='8' fill='%23000'/><rect x='250' y='200' width='65' height='38' rx='8' fill='%23000'/><line x1='230' y1='216' x2='250' y2='216' stroke='%23000' stroke-width='6'/><polygon points='140,175 125,115 180,155' fill='%2378350f' stroke='%23000' stroke-width='4'/><polygon points='340,175 355,115 300,155' fill='%2378350f' stroke='%23000' stroke-width='4'/></svg>"
},
{
id: "doodle-2",
name: "CHOPPER",
prompt: "A dog caught stealing a slice of pizza ð",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><ellipse cx='190' cy='260' rx='85' ry='55' fill='%23f59e0b' stroke='%23000' stroke-width='6'/><circle cx='130' cy='210' r='45' fill='%23f59e0b' stroke='%23000' stroke-width='6'/><circle cx='120' cy='200' r='6' fill='%23000'/><polygon points='145,225 300,265 290,170' fill='%23fef08a' stroke='%23b45309' stroke-width='6'/><circle cx='215' cy='225' r='10' fill='%23ef4444'/><circle cx='255' cy='235' r='10' fill='%23ef4444'/></svg>"
},
{
id: "doodle-3",
name: "BARKY",
prompt: "A dog wearing a Santa hat at Christmas ð
",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><ellipse cx='240' cy='275' rx='85' ry='75' fill='%23fef08a' stroke='%23000' stroke-width='6'/><circle cx='210' cy='255' r='8' fill='%23000'/><circle cx='270' cy='255' r='8' fill='%23000'/><ellipse cx='240' cy='295' rx='14' ry='10' fill='%23000'/><polygon points='165,210 240,85 315,210' fill='%23ef4444' stroke='%23000' stroke-width='5'/><rect x='155' y='200' width='170' height='26' rx='13' fill='%23ffffff' stroke='%23000' stroke-width='4'/><circle cx='240' cy='75' r='20' fill='%23ffffff' stroke='%23000' stroke-width='4'/></svg>"
},
{
id: "doodle-4",
name: "CAKEDOG",
prompt: "A dog celebrating its birthday with a giant cake ð",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><ellipse cx='160' cy='260' rx='65' ry='55' fill='%239ca3af' stroke='%23000' stroke-width='6'/><circle cx='140' cy='245' r='6' fill='%23000'/><rect x='250' y='245' width='110' height='65' fill='%23ec4899' stroke='%23000' stroke-width='6'/><rect x='270' y='205' width='70' height='40' fill='%2338bdf8' stroke='%23000' stroke-width='6'/><line x1='305' y1='205' x2='305' y2='175' stroke='%23f59e0b' stroke-width='6'/><circle cx='305' cy='170' r='7' fill='%23ef4444'/></svg>"
},
{
id: "doodle-5",
name: "WAVERIDER",
prompt: "A dog surfing a big ocean wave ðââïļ",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><path d='M20,380 Q150,220 280,330 T460,370 L460,480 L20,480 Z' fill='%2338bdf8' stroke='%232563eb' stroke-width='6'/><ellipse cx='230' cy='310' rx='95' ry='16' fill='%23f59e0b' stroke='%23000' stroke-width='5'/><ellipse cx='230' cy='240' rx='45' ry='35' fill='%2378350f' stroke='%23000' stroke-width='5'/><circle cx='215' cy='230' r='5' fill='%23fff'/><circle cx='245' cy='230' r='5' fill='%23fff'/></svg>"
},
{
id: "doodle-6",
name: "MOONDOG",
prompt: "A majestic Husky howling at the moon ð",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><circle cx='360' cy='120' r='52' fill='%23fef08a' stroke='%23f59e0b' stroke-width='6'/><path d='M150,370 L195,240 L265,195 L245,260 L265,370 Z' fill='%234b5563' stroke='%23000' stroke-width='6'/><polygon points='235,215 255,165 275,215' fill='%23b91c1c' stroke='%23000' stroke-width='4'/><circle cx='215' cy='225' r='4' fill='%23000'/></svg>"
},
{
id: "doodle-7",
name: "DUCKPUP",
prompt: "A dog and a duck playing on the beach ðïļðĶ",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><ellipse cx='160' cy='280' rx='60' ry='45' fill='%23f59e0b' stroke='%23000' stroke-width='5'/><circle cx='120' cy='230' r='30' fill='%23f59e0b' stroke='%23000' stroke-width='5'/><circle cx='310' cy='275' r='35' fill='%23fef08a' stroke='%23000' stroke-width='5'/><polygon points='345,270 380,278 345,286' fill='%23f97316' stroke='%23000' stroke-width='3'/><circle cx='240' cy='310' r='22' fill='%23ef4444' stroke='%23000' stroke-width='4'/></svg>"
},
{
id: "doodle-8",
name: "DR_BARK",
prompt: "A dog doctor checking a heartbeat with a stethoscope ðĐšð",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><circle cx='240' cy='240' r='80' fill='%23b45309' stroke='%23000' stroke-width='5'/><rect x='170' y='140' width='140' height='35' rx='8' fill='%2306b6d4' stroke='%23000' stroke-width='4'/><path d='M190,260 Q240,350 290,260' fill='none' stroke='%234b5563' stroke-width='8'/><circle cx='240' cy='320' r='14' fill='%239ca3af' stroke='%23000' stroke-width='3'/></svg>"
},
{
id: "doodle-9",
name: "CAMP_DOG",
prompt: "A dog camping by a campfire roasting marshmallows ðïļðĨ",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><polygon points='280,330 310,240 340,330' fill='%23ef4444' stroke='%23000' stroke-width='4'/><polygon points='295,330 310,270 325,330' fill='%23f59e0b'/><circle cx='170' cy='270' r='55' fill='%2378350f' stroke='%23000' stroke-width='5'/><line x1='170' y1='270' x2='290' y2='270' stroke='%23b45309' stroke-width='4'/><rect x='280' y='260' width='22' height='18' rx='4' fill='%23ffffff' stroke='%23000' stroke-width='2'/></svg>"
},
{
id: "doodle-10",
name: "SPEEDY",
prompt: "A dog driving a fast sports car with sunglasses ðïļ",
likes: 0,
superLikes: 0,
img: "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='480' height='480' viewBox='0 0 480 480'><rect width='480' height='480' fill='%23ffffff'/><circle cx='220' cy='210' r='45' fill='%23f59e0b' stroke='%23000' stroke-width='4'/><rect x='195' y='195' width='50' height='18' rx='4' fill='%23000'/><rect x='100' y='250' width='280' height='80' rx='20' fill='%23ef4444' stroke='%23000' stroke-width='6'/><circle cx='160' cy='330' r='30' fill='%23000'/><circle cx='320' cy='330' r='30' fill='%23000'/></svg>"
}
];
let gallery = JSON.parse(localStorage.getItem("doggame_community_gallery")) || DEFAULT_GALLERY;
const canvas = document.getElementById("drawCanvas");
const ctx = canvas.getContext("2d");
const targetBreedEl = document.getElementById("targetBreed");
const timerDisplay = document.getElementById("timerDisplay");
const overlay = document.getElementById("overlay");
// Tools & Action Buttons
const penToolBtn = document.getElementById("penToolBtn");
const sprayToolBtn = document.getElementById("sprayToolBtn");
const eraserToolBtn = document.getElementById("eraserToolBtn");
const undoBtn = document.getElementById("undoBtn");
const clearBtn = document.getElementById("clearBtn");
const resetTimerBtn = document.getElementById("resetTimerBtn");
const skipBtn = document.getElementById("skipBtn");
const publishBtn = document.getElementById("publishBtn");
const overlayRetryBtn = document.getElementById("overlayRetryBtn");
const overlayUploadBtn = document.getElementById("overlayUploadBtn");
const galleryGrid = document.getElementById("galleryGrid");
const galleryCount = document.getElementById("galleryCount");
const leaderboardBody = document.getElementById("leaderboardBody");
const uploadModal = document.getElementById("uploadModal");
const usernameInput = document.getElementById("usernameInput");
const cancelModalBtn = document.getElementById("cancelModalBtn");
const confirmUploadBtn = document.getElementById("confirmUploadBtn");
function saveState() {
undoStack.push(ctx.getImageData(0, 0, canvas.width, canvas.height));
if (undoStack.length > 15) undoStack.shift();
}
function clearCanvas() {
saveState();
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
clearCanvas();
function startTimer() {
clearInterval(timerInterval);
timeLeft = 90;
timerDisplay.textContent = `${timeLeft}s`;
timerDisplay.classList.remove("warning");
isRoundActive = true;
timerInterval = setInterval(() => {
timeLeft--;
timerDisplay.textContent = `${timeLeft}s`;
if (timeLeft <= 15) {
timerDisplay.classList.add("warning");
}
if (timeLeft <= 0) {
clearInterval(timerInterval);
isRoundActive = false;
overlay.style.display = "flex";
}
}, 1000);
}
function stopTimer() {
clearInterval(timerInterval);
isRoundActive = false;
}
function getCoords(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
const clientX = e.touches ? e.touches[0].clientX : e.clientX;
const clientY = e.touches ? e.touches[0].clientY : e.clientY;
return {
x: (clientX - rect.left) * scaleX,
y: (clientY - rect.top) * scaleY
};
}
// MS Paint Style Spray Function
function drawSpray(x, y) {
const density = brushSize * 3;
const radius = brushSize * 2.2;
ctx.fillStyle = currentTool === "eraser" ? "#ffffff" : currentColor;
for (let i = 0; i < density; i++) {
const offsetX = (Math.random() - 0.5) * 2 * radius;
const offsetY = (Math.random() - 0.5) * 2 * radius;
if (offsetX * offsetX + offsetY * offsetY <= radius * radius) {
ctx.fillRect(x + offsetX, y + offsetY, 1.5, 1.5);
}
}
}
function startDraw(e) {
if (!isRoundActive) return;
e.preventDefault();
saveState();
isDrawing = true;
const { x, y } = getCoords(e);
if (currentTool === "spray") {
drawSpray(x, y);
} else {
ctx.beginPath();
ctx.moveTo(x, y);
}
}
function draw(e) {
if (!isDrawing || !isRoundActive) return;
e.preventDefault();
const { x, y } = getCoords(e);
if (currentTool === "spray") {
drawSpray(x, y);
} else {
ctx.lineTo(x, y);
ctx.strokeStyle = currentTool === "eraser" ? "#ffffff" : currentColor;
ctx.lineWidth = currentTool === "eraser" ? brushSize * 3 : brushSize;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.stroke();
}
}
function stopDraw() {
isDrawing = false;
}
canvas.addEventListener("mousedown", startDraw);
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mouseup", stopDraw);
canvas.addEventListener("mouseleave", stopDraw);
canvas.addEventListener("touchstart", startDraw, { passive: false });
canvas.addEventListener("touchmove", draw, { passive: false });
canvas.addEventListener("touchend", stopDraw);
// Color Swatches
document.querySelectorAll(".color-btn").forEach(btn => {
btn.addEventListener("click", () => {
document.querySelectorAll(".color-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
currentColor = btn.dataset.color;
if (currentTool === "eraser") {
currentTool = "pen";
penToolBtn.classList.add("active");
eraserToolBtn.classList.remove("active");
}
});
});
// Tool Selector
penToolBtn.addEventListener("click", () => {
currentTool = "pen";
penToolBtn.classList.add("active");
sprayToolBtn.classList.remove("active");
eraserToolBtn.classList.remove("active");
});
sprayToolBtn.addEventListener("click", () => {
currentTool = "spray";
sprayToolBtn.classList.add("active");
penToolBtn.classList.remove("active");
eraserToolBtn.classList.remove("active");
});
eraserToolBtn.addEventListener("click", () => {
currentTool = "eraser";
eraserToolBtn.classList.add("active");
penToolBtn.classList.remove("active");
sprayToolBtn.classList.remove("active");
});
// Brush Sizes
document.querySelectorAll(".size-btn").forEach(btn => {
btn.addEventListener("click", () => {
document.querySelectorAll(".size-btn").forEach(b => b.classList.remove("active"));
btn.classList.add("active");
brushSize = parseInt(btn.dataset.size);
});
});
// Undo Action
undoBtn.addEventListener("click", () => {
if (undoStack.length > 0) {
const lastState = undoStack.pop();
ctx.putImageData(lastState, 0, 0);
}
});
clearBtn.addEventListener("click", clearCanvas);
// Reset Timer (Keeps same prompt)
resetTimerBtn.addEventListener("click", () => {
overlay.style.display = "none";
clearCanvas();
undoStack = [];
startTimer();
});
// Skip (New Prompt + Reset Timer)
function nextPrompt() {
stopTimer();
overlay.style.display = "none";
clearCanvas();
undoStack = [];
currentPrompt = PROMPTS[Math.floor(Math.random() * PROMPTS.length)];
targetBreedEl.textContent = currentPrompt;
startTimer();
}
skipBtn.addEventListener("click", nextPrompt);
overlayRetryBtn.addEventListener("click", nextPrompt);
// Upload Modal Flow
function openUploadModal() {
stopTimer();
usernameInput.value = localStorage.getItem("doggame_username") || "BARKUS";
uploadModal.style.display = "flex";
}
publishBtn.addEventListener("click", openUploadModal);
overlayUploadBtn.addEventListener("click", () => {
overlay.style.display = "none";
openUploadModal();
});
cancelModalBtn.addEventListener("click", () => uploadModal.style.display = "none");
confirmUploadBtn.addEventListener("click", () => {
let cleanName = usernameInput.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, "");
if (cleanName.length < 3) cleanName = "DOGE";
cleanName = cleanName.slice(0, 8);
localStorage.setItem("doggame_username", cleanName);
const dataUrl = canvas.toDataURL("image/jpeg", 0.7);
const newArtwork = {
id: "art-" + Date.now(),
name: cleanName,
prompt: currentPrompt,
likes: 0,
superLikes: 0,
img: dataUrl
};
gallery.unshift(newArtwork);
localStorage.setItem("doggame_community_gallery", JSON.stringify(gallery));
uploadModal.style.display = "none";
renderGalleryAndLeaderboard();
nextPrompt();
document.querySelector(".gallery-section").scrollIntoView({ behavior: "smooth" });
});
// Voting System
window.vote = function(id, type) {
const item = gallery.find(g => g.id === id);
if (!item) return;
if (type === "like") {
item.likes += 1;
} else if (type === "super") {
item.superLikes += 1;
}
localStorage.setItem("doggame_community_gallery", JSON.stringify(gallery));
renderGalleryAndLeaderboard();
};
function calculateScore(item) {
return (item.likes * 1) + (item.superLikes * 3);
}
function renderGalleryAndLeaderboard() {
galleryCount.textContent = `${gallery.length} ARTWORKS`;
galleryGrid.innerHTML = gallery.map(item => `
<div class="gallery-card">
<div class="gallery-img-wrap">
<img src="${item.img}" alt="${item.prompt}" />
</div>
<div class="gallery-info">
<div class="gallery-user-row">
<span class="gallery-user">${item.name}</span>
<span style="font-size: 0.75rem; color: #10b981; font-weight: bold;">${calculateScore(item)} PTS</span>
</div>
<div class="gallery-prompt" title="${item.prompt}">${item.prompt}</div>
<div class="vote-bar">
<button class="vote-btn" onclick="vote('${item.id}', 'like')" title="Give a Like (+1 pt)">
âĪïļ <span>${item.likes}</span>
</button>
<button class="vote-btn" style="border-color: #f59e0b;" onclick="vote('${item.id}', 'super')" title="Super Like with a Bone! (+3 pts)">
ðĶī <span>${item.superLikes}</span>
</button>
</div>
</div>
</div>
`).join("");
const sorted = [...gallery].sort((a, b) => calculateScore(b) - calculateScore(a));
leaderboardBody.innerHTML = sorted.slice(0, 10).map((item, idx) => `
<tr>
<td style="color: ${idx === 0 ? '#fcd34d' : idx === 1 ? '#cbd5e1' : idx === 2 ? '#b45309' : '#8f9ba8'}; font-weight: bold;">
${idx + 1}.
</td>
<td class="tag-highlight">${item.name}</td>
<td class="score-highlight">${calculateScore(item)} PTS</td>
</tr>
`).join("");
}
renderGalleryAndLeaderboard();
nextPrompt();
</script>
</body>
</html>
- ð GitHub Repository: https://github.com/ASXGTR/Quackworkstudios
How I Built It ð ïļ
ðïļ Architecture & Hosting Transparency
I chose to host this project on my existing studio domain (Quackwork Studios) and custom backend infrastructure because it was the fastest, most reliable way to deliver a friction-free demo.
Pre-Existing Infrastructure: My website already had a working deployment pipeline, SSL certificates, edge caching, and a clean base template ready to go.
Built 100% from Scratch for this Challenge: The Bark Gallery game itself (
doggame.html), its canvas engine, voting logic, and Gemini-generated assets did not exist before this hackathon.Zero Friction for Judges: Deploying on my own infrastructure let me skip third-party sandbox restrictions and ensure judges and players could jump right in with instant load times and zero configuration.
ðĪ Solo Dev Pairing Workflow with Google AI Studio
All development, prototyping, prompt generation, debugging, and code creation was done inside Google AI Studio using the Gemini 3.7 Flash model from the Featured tab. Gemini acted as a real-time technical co-pilot throughout the build:
Ideation & Prototyping: We brainstormed dog-themed concepts tailored to the 48-hour hackathon timeframe, taking the idea from initial concept to a playable browser prototype in under an hour.
Full-Stack Frontend Generation: Gemini generated the single-file architecture (
doggame.html), including the touch/mouse event listeners, undo state history, and particle-based MS Paint spray effects.Asset Generation in Code: Gemini generated 10 hand-drawn SVG dog illustrations directly in code to pre-populate the community gallery with starter doodles.
Live Debugging & Refactoring: Diagnosed server 404s, resolved edge caching issues, and refactored the canvas engine during development.
ðĻ Creative Features & Toolkit
-
ðïļ Drawing Suite:
- ðĻ MS Paint-Style Spray Can: Realistic particle dispersion physics.
- âïļ Pen & Eraser: 3 stroke thicknesses (Thin, Med, Thick).
- ðĻ 18-Color Swatch Palette: From vibrant neon accents to natural fur tones.
- âĐïļ 15-Step Undo Stack: Fast state restoration to undo misclicks.
-
ðđïļ Canvas Controls:
- ðē Skip: Pulls a fresh prompt and resets the 90s countdown.
- ð Reset: Clears canvas and restarts the timer for the same prompt.
- ðïļ Clear: Wipes the current sketch immediately.
ð Roadblocks, Errors & Solutions
-
The Linux Case Sensitivity 404:
-
Bug: Deploying to GitHub Pages gave a
404 Not Found. -
Cause: The file was committed as
Doggame.html(capital D) while the link targeted/doggame.html. -
Fix: Renamed to all-lowercase
doggame.htmland redeployed.
-
Bug: Deploying to GitHub Pages gave a
-
Aggressive Edge Caching:
- Bug: Fresh commits weren't showing up immediately for players.
-
Fix: Added cache-busting version query parameters (
?v=2,?v=3) and purged edge caches.
-
Client-Side API Deprecation & Key Friction:
- Bug: Querying external models client-side required exposing or asking users for API keys.
- Fix: Pivoted to a 100% frictionless Community Speed-Sketch Showcase: client-side JPEG compression (~15KB) paired with instantaneous real-time voting and leaderboard ranking. Zero backend latency, zero friction, and 100% uptime.
Prize Categories ð
I am submitting this project for:
ð Best Use of Google AI
Bark Gallery was developed end-to-end with Google AI Studio (Gemini 3.7 Flash):
- Generating all humorous, chaotic prompt scenarios
- Generating the core HTML5 canvas engine and particle spray physics
- Generating the embedded starter SVG illustrations
- Debugging deployment, caching, and state management logic
ðū About the Developer
I'm John, a self-taught, solo indie game developer building games at Quackwork Studios (currently developing the multiverse card battler Duckmon).
Grab your digital spray can, post your dog sketch, don't forget to vote and enter, and let's celebrate International Dog Day! ððĻ
Edit - Adjusted Title
Top comments (0)