balloonDesign/index.html

392 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Balloon Studio — Organic & Classic</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
<script src="https://unpkg.com/mithril/mithril.js" defer></script>
<script src="colors.js"></script>
<link rel="stylesheet" href="style.css" />
<style>
.tab-btn{padding:.5rem .75rem;border-radius:.5rem;font-size:.875rem;font-weight:600;transition:background-color .2s,color .2s,box-shadow .2s}
.tab-active{background:#2563eb;color:#fff;box-shadow:0 2px 6px rgba(37,99,235,.35)}
.tab-idle{background:#e5e7eb;color:#1f2937}.tab-idle:hover{background:#d1d5db}
#classic-display{width:100%;max-width:1200px;height:70vh;border:1px solid #e5e7eb;background:#fff;overflow:auto;border-radius:.75rem}
.copy-message{opacity:0;pointer-events:none;transition:opacity .2s}.copy-message.show{opacity:1}
</style>
</head>
<body class="flex flex-col min-h-screen p-0 md:p-6 items-center justify-start bg-[conic-gradient(at_top_left,_var(--tw-gradient-stops))] from-indigo-100 via-white to-pink-100 text-slate-800">
<header class="flex items-center justify-between gap-3 px-2 lg:px-0 py-2">
<div class="flex items-center gap-3">
<div>
<div class="text-xl font-black text-transparent bg-clip-text bg-gradient-to-r from-indigo-600 to-pink-600 tracking-tight filter drop-shadow-sm">Balloon Studio</div>
</div>
</div>
<!-- Mode Switcher Restored -->
<nav id="mode-tabs" class="flex gap-1 bg-slate-100 p-1 rounded-lg">
<button type="button" class="tab-btn tab-active text-xs !py-1 !px-2" data-target="#tab-organic" aria-pressed="true">Organic</button>
<button type="button" class="tab-btn tab-idle text-xs !py-1 !px-2" data-target="#tab-classic" aria-pressed="false">Classic</button>
</nav>
<div class="flex items-center gap-2">
<button id="header-undo" class="tool-btn !p-2" title="Undo">
<svg viewBox="0 0 24 24"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/></svg>
</button>
<button id="header-redo" class="tool-btn !p-2" title="Redo">
<svg viewBox="0 0 24 24"><path d="M18.4 10.6C16.55 9 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"/></svg>
</button>
<button id="header-export" class="btn-blue text-xs font-bold !py-2 !px-3" data-export="png">Export</button>
</div>
</header>
<section id="tab-organic" class="flex flex-col lg:flex-row gap-4 lg:h-[calc(100vh-10rem)]">
<aside id="controls-panel" class="control-sheet lg:static lg:w-[360px] lg:max-h-none lg:overflow-y-auto">
<div class="panel-header-row">
<h2 class="panel-title">Organic Controls</h2>
<button type="button" class="sheet-close-btn" data-sheet-toggle="controls-panel">Hide</button>
</div>
<div class="control-stack" data-mobile-tab="controls">
<div class="panel-heading">Selection Options</div>
<div class="panel-card">
<div id="eraser-controls" class="hidden flex flex-col gap-2">
<label class="text-sm font-medium text-gray-700">Eraser Size: <span id="eraser-size-label">30</span>px</label>
<input type="range" id="eraser-size" min="10" max="120" value="30" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<p class="hint">Click-drag to erase. Preview circle shows the area.</p>
</div>
<div id="select-controls" class="hidden flex flex-col gap-2">
<div class="flex gap-2">
<button id="delete-selected" class="btn-danger" disabled>Delete</button>
<button id="duplicate-selected" class="btn-dark" disabled>Duplicate</button>
</div>
<div class="mt-2">
<div class="flex items-center gap-2 text-xs text-gray-600 mb-1">
<span class="font-semibold">Move</span>
<span class="hint">↑↓←→</span>
</div>
<div class="grid grid-cols-4 gap-1 max-w-xs">
<button type="button" class="btn-dark nudge-selected text-sm py-2" data-dx="0" data-dy="-5" aria-label="Move Selection Up"></button>
<button type="button" class="btn-dark nudge-selected text-sm py-2" data-dx="5" data-dy="0" aria-label="Move Selection Right"></button>
<button type="button" class="btn-dark nudge-selected text-sm py-2" data-dx="0" data-dy="5" aria-label="Move Selection Down"></button>
<button type="button" class="btn-dark nudge-selected text-sm py-2" data-dx="-5" data-dy="0" aria-label="Move Selection Left"></button>
</div>
</div>
<div class="mt-2 flex items-center gap-2 text-xs text-gray-600">
<span class="font-semibold">Resize</span>
<input type="range" id="selected-size" min="5" max="200" value="40" class="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" disabled>
<span id="selected-size-label" class="text-xs w-10 text-right">0</span>
</div>
<div class="mt-2 grid grid-cols-2 gap-2">
<button type="button" class="btn-dark text-sm py-2" id="bring-forward" disabled>Bring Forward</button>
<button type="button" class="btn-dark text-sm py-2" id="send-backward" disabled>Send Backward</button>
</div>
<div class="mt-2">
<button type="button" class="btn-blue w-full" id="apply-selected-color" disabled>Apply Current Color</button>
<p class="hint">Uses the color/texture currently picked in the palette.</p>
</div>
<p class="hint">Click a balloon to select. Del/Backspace removes. Esc clears.</p>
</div>
<!-- Fallback hint if nothing selected/erasing -->
<p id="controls-empty-hint" class="hint mt-2 hidden">Select a balloon or choose the Eraser tool to see options.</p>
</div>
</div>
<div class="control-stack" data-mobile-tab="colors">
<div class="panel-heading">Balloon Size</div>
<div class="panel-card">
<div id="size-preset-group" class="grid grid-cols-5 gap-2 mb-2"></div>
<label class="text-sm inline-flex items-center gap-2 font-medium">
<input id="toggle-shine-checkbox" type="checkbox" class="align-middle" checked>
Enable Shine
</label>
</div>
<div class="panel-heading mt-4">Palette in Use</div>
<div class="panel-card">
<div class="flex items-center justify-between mb-2">
<span class="text-sm text-gray-600">Colors currently on your canvas.</span>
<button id="sort-used-toggle" class="text-sm underline">Sort: Most → Least</button>
</div>
<div id="used-palette" class="palette-box min-h-[3rem]"></div>
</div>
<div class="panel-heading mt-4">Color Library</div>
<div class="panel-card">
<p class="hint mb-2">Pick a color to draw with.</p>
<div id="color-palette" class="palette-box"></div>
</div>
<div class="panel-heading mt-4">Swap Colors</div>
<div class="panel-card">
<div class="grid grid-cols-1 gap-2">
<label class="text-sm font-medium">Change this color:</label>
<select id="replace-from" class="select"></select>
<label class="text-sm font-medium">To this new color:</label>
<select id="replace-to" class="select"></select>
<button id="replace-btn" class="btn-blue">Swap All</button>
<p id="replace-msg" class="hint"></p>
</div>
</div>
</div>
<div class="control-stack" data-mobile-tab="save">
<div class="panel-heading">Share</div>
<div class="panel-card">
<div class="relative mb-3">
<input type="text" id="share-link-output" class="w-full p-3 pr-10 border border-gray-300 rounded-lg text-sm text-gray-800 focus:outline-none focus:ring-2 focus:ring-blue-500" readonly placeholder="Click 'Generate Link' to create a shareable URL">
<div id="copy-message" class="copy-message absolute right-2 top-1/2 -translate-y-1/2 bg-blue-500 text-white px-2 py-1 text-xs rounded-full">Copied!</div>
</div>
<button id="generate-link-btn" class="btn-indigo w-full">Generate Shareable Link</button>
</div>
<div class="panel-heading mt-4">Save & Load</div>
<div class="panel-card">
<div class="flex flex-wrap gap-3 mb-3">
<button id="clear-canvas-btn" class="btn-danger">Clear Canvas</button>
<button id="save-json-btn" class="btn-green">Save Design</button>
<label for="load-json-input" class="btn-yellow text-center cursor-pointer">Load JSON</label>
<input type="file" id="load-json-input" class="hidden" accept=".json">
</div>
<div class="flex flex-wrap gap-3 mt-2">
<button class="btn-dark bg-blue-600" data-export="png">Export PNG</button>
<button class="btn-dark bg-blue-700" data-export="svg">Export SVG</button>
<p class="hint w-full">SVG currently Classic only.</p>
</div>
</div>
</div>
</aside>
<section id="canvas-panel" class="order-1 lg:order-2 w-full lg:flex-1 flex flex-col items-stretch rounded-2xl overflow-hidden bg-white/50 shadow-inner ring-1 ring-black/5">
<canvas id="balloon-canvas" class="balloon-canvas w-full min-h-[65vh]"></canvas>
</section>
</section>
<section id="tab-classic" class="hidden flex flex-col lg:flex-row gap-4 lg:h-[calc(100vh-10rem)]">
<aside id="classic-controls-panel" class="control-sheet lg:static lg:w-[360px] lg:max-h-none lg:overflow-y-auto">
<div class="panel-header-row">
<h2 class="panel-title">Classic Controls</h2>
<button type="button" class="sheet-close-btn" data-sheet-toggle="classic-controls-panel">Hide</button>
</div>
<div class="control-stack" data-mobile-tab="controls">
<div class="panel-heading">Pattern & Layout</div>
<div class="panel-card space-y-4">
<div class="flex flex-wrap items-center gap-3">
<span class="classic-quick-label">Colors</span>
<div class="dock-pill-group">
<button type="button" class="dock-pill classic-variant-btn" data-pattern-variant="4">4 colors</button>
<button type="button" class="dock-pill classic-variant-btn" data-pattern-variant="5">5 colors</button>
</div>
</div>
<select id="classic-pattern" class="select hidden">
<option value="Arch 4">Arch 4 (4-color spiral)</option>
<option value="Column 4">Column 4 (quad wrap)</option>
<option value="Arch 5">Arch 5 (5-color spiral)</option>
<option value="Column 5">Column 5 (5-balloon wrap)</option>
</select>
<div class="space-y-2">
<div class="flex items-center gap-2">
<span class="classic-quick-label">Length</span>
<span class="text-xs text-gray-500" id="classic-length-label">ft</span>
</div>
<div id="classic-length-presets" class="length-dial"></div>
<input id="classic-length-ft" type="number" min="1" max="100" step="0.5" value="5" class="hidden">
</div>
<div id="classic-topper-toggle-row" class="flex items-center gap-3 pt-2 border-t border-gray-200 hidden">
<label class="text-sm inline-flex items-center gap-2 font-medium">
<input id="classic-topper-enabled" type="checkbox" class="align-middle">
Add Topper (24")
</label>
</div>
<div id="topper-controls" class="hidden grid grid-cols-1 sm:grid-cols-4 gap-3 items-end pt-2">
<label class="text-sm sm:col-span-2">Topper Type:
<select id="classic-topper-type" class="select align-middle" disabled>
<option value="round">24" Round</option>
<option value="star">24" Star</option>
<option value="heart">24" Heart</option>
</select>
</label>
<label class="text-sm">Topper Color:
<div id="classic-topper-color-swatch" class="slot-swatch mx-auto" title="Click to change topper color">T</div>
</label>
<div class="col-span-full">
<div class="panel-heading mt-2 mb-2">Topper Nudge</div>
<div class="grid grid-cols-3 gap-2 max-w-xs justify-items-center">
<button type="button" class="btn-nudge nudge-topper" data-dx="0" data-dy="0.5" aria-label="Move Topper Up"></button>
<button type="button" class="btn-nudge nudge-topper" data-dx="0.5" data-dy="0" aria-label="Move Topper Right"></button>
<button type="button" class="btn-nudge nudge-topper" data-dx="0" data-dy="-0.5" aria-label="Move Topper Down"></button>
<button type="button" class="btn-nudge nudge-topper col-span-3" data-dx="-0.5" data-dy="0" aria-label="Move Topper Left"></button>
</div>
</div>
<label class="text-sm">Topper Size:
<input id="classic-topper-size" type="range" min="0.5" max="2" step="0.05" value="1" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
</label>
</div>
<div class="text-xs text-gray-500">
<span id="classic-cluster-hint">≈ 10 clusters (rule: 2 clusters/ft)</span>
</div>
<div class="flex flex-wrap items-center gap-x-6 gap-y-3 pt-2 border-t border-gray-200">
<label class="text-sm inline-flex items-center gap-2 font-medium">
<input id="classic-shine-enabled" type="checkbox" class="align-middle" checked>
Enable Shine
</label>
<label class="text-sm inline-flex items-center gap-2">
<input id="classic-reverse" type="checkbox" class="align-middle">
Reverse spiral
</label>
</div>
</div>
</div>
<div class="control-stack" data-mobile-tab="colors">
<div class="panel-heading">Classic Colors</div>
<div class="panel-card">
<div id="classic-slots" class="flex items-center gap-2 mb-3">
<button type="button" class="slot-btn tab-btn" data-slot="1">#1</button>
<button type="button" class="slot-btn tab-btn" data-slot="2">#2</button>
<button type="button" class="slot-btn tab-btn" data-slot="3">#3</button>
<button type="button" class="slot-btn tab-btn" data-slot="4">#4</button>
<button type="button" class="slot-btn tab-btn" data-slot="5">#5</button>
</div>
<div class="text-sm text-gray-600 mb-1">Pick a color for <span id="classic-active-label" class="font-bold">Slot #1</span> (from colors.js):</div>
<div id="classic-swatch-grid" class="palette-box min-h-[3rem]"></div>
<div class="flex flex-wrap gap-2 mt-3">
<button id="classic-randomize-colors" class="btn-dark">Randomize 5</button>
</div>
</div>
</div>
<div class="control-stack" data-mobile-tab="save">
<div class="panel-heading">Save & Share</div>
<div class="panel-card space-y-3">
<div class="flex flex-wrap gap-3">
<button class="btn-dark bg-blue-600" data-export="png">Export PNG</button>
<button class="btn-dark bg-blue-700" data-export="svg">Export SVG</button>
<p class="hint w-full">SVG recommended for Classic.</p>
</div>
<p class="hint text-red-500">Classic JSON save/load not available yet.</p>
</div>
</div>
</aside>
<section id="classic-canvas-panel"
class="order-1 w-full lg:flex-1 flex flex-col items-stretch shadow-x3 rounded-2xl overflow-hidden bg-white">
<div id="classic-display"
class="rounded-xl"
style="width:100%;height:72vh;border:1px solid #e5e7eb;background:#fff;overflow:auto;"></div>
</section>
</section>
<div id="mobile-tabbar" class="mobile-tabbar justify-center gap-6 !py-4 !bg-slate-900/95 backdrop-blur-xl border-t border-white/10">
<div id="dock-organic" class="flex items-center gap-3">
<button id="dock-draw" class="mobile-tool-btn active" data-dock="organic" title="Draw">
<svg viewBox="0 0 24 24"><path d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/></svg>
</button>
<button id="dock-erase" class="mobile-tool-btn" data-dock="organic" title="Erase">
<svg viewBox="0 0 24 24"><path d="M16.24 3.56l4.95 4.94c.78.79.78 2.05 0 2.84L12 20.53a4.008 4.008 0 0 1-5.66 0L2.81 17c-.78-.79-.78-2.05 0-2.84l10.6-10.6c.79-.78 2.05-.78 2.83 0zM4.22 15.58l3.54 3.53c.78.79 2.04.79 2.83 0l8.48-8.48-3.54-3.54-8.48 8.48c-.79.79-.79 2.05 0 2.84z"/></svg>
</button>
<button id="dock-color-trigger" class="dock-color-btn" style="background-color: #2563eb;" aria-label="Open Colors"></button>
<button id="dock-select" class="mobile-tool-btn" data-dock="organic" title="Select">
<svg viewBox="0 0 24 24"><path d="M7 2l12 11.2-5.8.5 3.3 7.3-2.2.9-3.2-7.4-4.4 4V2z"/></svg>
</button>
<button id="dock-picker" class="mobile-tool-btn" data-dock="organic" title="Picker">
<svg viewBox="0 0 24 24"><path d="M20.71 5.63l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-3.12 3.12-1.93-1.91-1.41 1.41 1.42 1.42L3 16.25V21h4.75l8.92-8.92 1.42 1.42 1.41-1.41-1.92-1.92 3.12-3.12c.4-.4.4-1.03.01-1.42zM6.92 19L5 17.08l8.06-8.06 1.92 1.92L6.92 19z"/></svg>
</button>
</div>
<div id="dock-classic" class="hidden flex items-center justify-center gap-3">
<button id="dock-arch" class="mobile-tool-btn classic-pattern-btn" data-pattern-base="Arch" title="Arch Pattern">
<svg viewBox="0 0 24 24"><path d="M4 18a8 8 0 0 1 16 0" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
<button id="dock-classic-color" class="dock-color-btn" style="background-color:#2563eb;" aria-label="Open Colors"></button>
<button id="dock-column" class="mobile-tool-btn classic-pattern-btn" data-pattern-base="Column" title="Column Pattern">
<svg viewBox="0 0 24 24"><path d="M8 4v16M16 4v16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</button>
</div>
</div>
<!-- Classic drawers for mobile -->
<div id="classic-drawer-pattern" class="classic-drawer hidden">
<div class="drawer-row">
<div class="drawer-pill-group">
<button type="button" class="dock-pill classic-pattern-btn" data-pattern-base="Arch">Arch</button>
<button type="button" class="dock-pill classic-pattern-btn" data-pattern-base="Column">Column</button>
</div>
<div class="drawer-pill-group">
<button type="button" class="dock-pill classic-variant-btn" data-pattern-variant="4">4 colors</button>
<button type="button" class="dock-pill classic-variant-btn" data-pattern-variant="5">5 colors</button>
</div>
</div>
<div class="drawer-row dial-row">
<span class="drawer-label">Length</span>
<div id="classic-length-drawer" class="length-dial"></div>
</div>
<div class="drawer-row topper-row">
<button type="button" class="dock-pill classic-topper-btn" data-topper-toggle>Topper</button>
<div class="topper-inline hidden" id="topper-inline">
<select id="classic-topper-type-inline" class="select select-xs">
<option value="round">Round</option>
<option value="star">Star</option>
<option value="heart">Heart</option>
</select>
<div id="classic-topper-color-swatch-inline" class="slot-swatch" title="Topper color">T</div>
<div class="nudge-pad">
<button type="button" class="btn-nudge nudge-topper" data-dx="0" data-dy="0.5"></button>
<div class="flex gap-2">
<button type="button" class="btn-nudge nudge-topper" data-dx="-0.5" data-dy="0"></button>
<button type="button" class="btn-nudge nudge-topper" data-dx="0.5" data-dy="0"></button>
</div>
<button type="button" class="btn-nudge nudge-topper" data-dx="0" data-dy="-0.5"></button>
</div>
<div class="topper-size-wrap">
<label class="text-xs text-slate-600">Size</label>
<input id="classic-topper-size-inline" type="range" min="0.5" max="2" step="0.05" value="1">
</div>
</div>
</div>
</div>
<div id="classic-drawer-colors" class="classic-drawer hidden">
<div class="drawer-row">
<div id="classic-slots-drawer" class="flex items-center gap-2"></div>
<div class="flex-1 text-right">
<button id="classic-randomize-colors-inline" class="dock-pill">Shuffle 5</button>
</div>
</div>
<div id="classic-swatch-drawer" class="palette-box"></div>
</div>
<div id="message-modal" class="hidden fixed inset-0 z-50 flex items-center justify-center bg-gray-900 bg-opacity-50">
<div class="bg-white p-6 rounded-lg shadow-lg max-w-sm text-center">
<p id="modal-text" class="text-gray-800 text-lg"></p>
<button id="modal-close-btn" class="mt-4 btn-blue">OK</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/lz-string@1.5.0/libs/lz-string.min.js" defer></script>
<script src="script.js" defer></script>
<script src="classic.js" defer></script>
</body>
</html>