775 lines
47 KiB
HTML
775 lines
47 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://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||
|
||
<script src="https://unpkg.com/mithril/mithril.js" defer></script>
|
||
|
||
<script src="colors.js"></script>
|
||
|
||
<link rel="stylesheet" href="style.css" />
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||
<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="p-0 md:p-6 flex flex-col items-center justify-start min-h-screen bg-[conic-gradient(at_top_left,_var(--tw-gradient-stops))] from-indigo-100 via-white to-pink-100 text-slate-800 overflow-hidden">
|
||
<div class="container mx-auto mt-2 p-4 lg:p-6 bg-white/80 lg:backdrop-blur-xl rounded-3xl border border-white/50 shadow-2xl flex flex-col gap-4 max-w-7xl lg:h-[calc(97vh-2rem)] overflow-hidden ring-1 ring-black/5">
|
||
|
||
<header id="app-header" class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-3 px-1 lg:px-0">
|
||
<div class="flex items-center gap-3">
|
||
|
||
<div>
|
||
<div class="text-3xl 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 class="text-xs text-indigo-500 font-bold uppercase tracking-wider">Professional Design Tool</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex flex-wrap lg:flex-nowrap items-center gap-2 lg:gap-4 w-full lg:w-auto">
|
||
<nav id="mode-tabs" class="flex flex-wrap items-center gap-2">
|
||
<button type="button" class="tab-btn tab-active" data-target="#tab-organic" aria-pressed="true">Organic</button>
|
||
<button type="button" class="tab-btn tab-idle" data-target="#tab-classic" aria-pressed="false">Classic</button>
|
||
<button type="button" class="tab-btn tab-idle" data-target="#tab-wall" aria-pressed="false">Wall</button>
|
||
<button type="button" class="tab-btn tab-idle" data-target="#tab-helium" aria-pressed="false">Helium</button>
|
||
</nav>
|
||
<div class="flex items-center gap-2 lg:gap-3 ml-auto">
|
||
<button id="app-fullscreen-toggle" class="btn-dark text-xs px-3 py-2" aria-label="Enter fullscreen">
|
||
<i class="fa-solid fa-expand" aria-hidden="true"></i>
|
||
<span class="sr-only">Enter fullscreen</span>
|
||
</button>
|
||
<button id="clear-canvas-btn-top" class="btn-danger text-xs px-3 py-2">Start Fresh</button>
|
||
</div>
|
||
</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>
|
||
</div>
|
||
<div class="control-stack" data-mobile-tab="controls">
|
||
<div class="panel-heading">Tools</div>
|
||
<div class="panel-card">
|
||
<div class="grid grid-cols-4 gap-2 mb-3">
|
||
<button id="tool-draw" class="tool-btn" aria-pressed="true" title="V">
|
||
<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>
|
||
<span class="hidden sm:inline">Draw</span>
|
||
</button>
|
||
<button id="tool-garland" class="tool-btn" aria-pressed="false" title="G">
|
||
<svg viewBox="0 0 24 24"><path d="M4 17c3-4 6-6 9-6s5 2 7 6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/><circle cx="7" cy="14" r="1.4"/><circle cx="11.5" cy="12.5" r="1.4"/><circle cx="16" cy="13.5" r="1.4"/><circle cx="19" cy="16.5" r="1.4"/></svg>
|
||
<span class="hidden sm:inline">Path</span>
|
||
</button>
|
||
<button id="tool-erase" class="tool-btn" aria-pressed="false" title="E">
|
||
<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>
|
||
<span class="hidden sm:inline">Erase</span>
|
||
</button>
|
||
<button id="tool-select" class="tool-btn" aria-pressed="false" title="S">
|
||
<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>
|
||
<span class="hidden sm:inline">Select</span>
|
||
</button>
|
||
</div>
|
||
<div class="grid grid-cols-3 gap-2 mb-3">
|
||
<button id="tool-undo" class="tool-btn" aria-label="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>
|
||
<span class="hidden sm:inline">Undo</span>
|
||
</button>
|
||
<button id="tool-redo" class="tool-btn" aria-label="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>
|
||
<span class="hidden sm:inline">Redo</span>
|
||
</button>
|
||
<button id="tool-eyedropper" class="tool-btn" title="Pick Color" aria-label="Eyedropper" aria-pressed="false">
|
||
<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>
|
||
<span class="hidden sm:inline">Picker</span>
|
||
</button>
|
||
</div>
|
||
<div class="mb-3 flex items-center gap-2">
|
||
<span class="text-xs font-semibold text-gray-700">Color</span>
|
||
<div id="quick-color-chip" class="current-color-chip cursor-pointer !w-8 !h-8 shrink-0" title="Choose active color"></div>
|
||
<button type="button" id="quick-color-btn" class="btn-blue text-xs px-3 py-2">Change Color</button>
|
||
</div>
|
||
<p class="hint mt-1">Use Path to click-drag a line; balloons will be auto-placed along it.</p>
|
||
<div id="garland-controls" class="mt-2 flex flex-col gap-3 text-sm text-gray-700">
|
||
<div class="flex items-center gap-3">
|
||
<label for="garland-density" class="font-medium w-20">Density</label>
|
||
<input id="garland-density" type="range" min="0.6" max="1.6" step="0.1" value="1" class="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
|
||
<span id="garland-density-label" class="w-10 text-right text-xs text-gray-500">1.0</span>
|
||
</div>
|
||
<div class="flex flex-col gap-2">
|
||
<div class="flex items-center justify-between">
|
||
<span class="font-medium text-sm text-gray-700">Main Colors</span>
|
||
<button type="button" id="garland-add-color" class="btn-blue text-xs px-3 py-1">+ Add</button>
|
||
</div>
|
||
<div id="garland-main-chips" class="flex flex-wrap gap-2"></div>
|
||
<p class="hint text-xs">Tap a chip to change it. You can add up to 10 main colors.</p>
|
||
<div class="flex items-center gap-2">
|
||
<span class="font-medium text-sm text-gray-700">Accent</span>
|
||
<button type="button" id="garland-accent-chip" class="replace-chip" aria-label="Pick accent color"></button>
|
||
<button type="button" id="garland-accent-clear" class="btn-yellow text-xs px-3 py-1">Clear</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<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">Hover to preview. Click-drag to erase.</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">
|
||
<p class="hint">Drag balloons to reposition. Use arrows/touches for fine nudges.</p>
|
||
</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="32" step="0.5" value="11" class="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer" disabled>
|
||
<span id="selected-size-label" class="text-xs w-12 text-right">0\"</span>
|
||
</div>
|
||
<div class="mt-2 grid grid-cols-3 gap-2">
|
||
<button type="button" class="btn-dark text-sm py-2" id="rotate-selected-left" disabled>Rotate -15°</button>
|
||
<button type="button" class="btn-dark text-sm py-2" id="rotate-selected-reset" disabled>Reset</button>
|
||
<button type="button" class="btn-dark text-sm py-2" id="rotate-selected-right" disabled>Rotate +15°</button>
|
||
</div>
|
||
<div class="mt-2 grid grid-cols-3 gap-2">
|
||
<button type="button" class="btn-dark text-sm py-2" id="ribbon-length-down" disabled>Ribbon Shorter</button>
|
||
<button type="button" class="btn-dark text-sm py-2" id="ribbon-attach-weight" disabled>Attach to Weight</button>
|
||
<button type="button" class="btn-dark text-sm py-2" id="ribbon-length-up" disabled>Ribbon Longer</button>
|
||
</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>
|
||
</div>
|
||
|
||
<div class="panel-heading mt-4">Size & Shine</div>
|
||
<div class="panel-card">
|
||
<div id="size-preset-group" class="grid grid-cols-5 gap-2 mb-2"></div>
|
||
<div id="helium-placement-row" class="hidden mt-2 mb-2">
|
||
<div class="text-xs font-semibold text-gray-700 mb-2">Place</div>
|
||
<div class="flex gap-2">
|
||
<button type="button" id="helium-place-balloon" class="tab-btn tab-active text-xs px-3 py-2" aria-pressed="true">Balloon</button>
|
||
<button type="button" id="helium-place-curl" class="tab-btn tab-idle text-xs px-3 py-2" aria-pressed="false">Curl 260</button>
|
||
<button type="button" id="helium-place-ribbon" class="tab-btn tab-idle text-xs px-3 py-2" aria-pressed="false">Ribbon</button>
|
||
<button type="button" id="helium-place-weight" class="tab-btn tab-idle text-xs px-3 py-2" aria-pressed="false">Weight</button>
|
||
</div>
|
||
</div>
|
||
<p class="hint mb-3">Size presets adjust the diameter for new balloons.</p>
|
||
<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>
|
||
<label class="text-sm inline-flex items-center gap-2 font-medium">
|
||
<input id="toggle-border-checkbox" type="checkbox" class="align-middle">
|
||
Outline Balloons
|
||
</label>
|
||
<button type="button" id="fit-view-btn" class="btn-dark text-sm mt-3 w-full">Fit to Design</button>
|
||
</div>
|
||
|
||
<div class="control-stack" data-mobile-tab="colors">
|
||
<div class="panel-heading">Organic Colors</div>
|
||
<div class="panel-card space-y-4">
|
||
<div class="space-y-2">
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-sm font-medium text-gray-700">Active color</span>
|
||
<div id="current-color-chip" class="current-color-chip">
|
||
<span id="current-color-label" class="text-xs font-semibold text-slate-700"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-2">
|
||
<div class="flex items-center justify-between">
|
||
<span class="text-sm font-semibold text-gray-700">Project Palette</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="space-y-2">
|
||
<div class="text-sm font-semibold text-gray-700">Replace Color</div>
|
||
<div class="flex items-center gap-2 replace-row">
|
||
<button type="button" class="replace-chip" id="replace-from-chip" aria-label="Pick color to replace"></button>
|
||
<span class="text-xs font-semibold text-slate-500">→</span>
|
||
<button type="button" class="replace-chip" id="replace-to-chip" aria-label="Pick replacement color"></button>
|
||
<span id="replace-count" class="text-xs text-slate-500 ml-auto"></span>
|
||
</div>
|
||
<div class="grid grid-cols-1 gap-2">
|
||
<select id="replace-from" class="sr-only"></select>
|
||
<select id="replace-to" class="sr-only"></select>
|
||
<button id="replace-btn" class="btn-blue">Replace</button>
|
||
<p id="replace-msg" class="hint"></p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-2">
|
||
<div class="text-sm font-semibold text-gray-700">Color Library</div>
|
||
<div id="color-palette" class="palette-box"></div>
|
||
</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 export keeps vectors in Classic; Organic embeds textures.</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>
|
||
</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="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div class="space-y-2">
|
||
<div class="text-sm font-medium text-gray-700">Shape</div>
|
||
<div class="flex gap-2">
|
||
<button type="button" class="tab-btn tab-active pattern-btn" data-pattern-shape="arch" aria-pressed="true">Arch</button>
|
||
<button type="button" class="tab-btn tab-idle pattern-btn" data-pattern-shape="column" aria-pressed="false">Column</button>
|
||
</div>
|
||
</div>
|
||
<div class="space-y-2">
|
||
<div class="text-sm font-medium text-gray-700">Balloon Count</div>
|
||
<div class="flex gap-2">
|
||
<button type="button" class="tab-btn tab-active pattern-btn" data-pattern-count="4" aria-pressed="true">4 Colors</button>
|
||
<button type="button" class="tab-btn tab-idle pattern-btn" data-pattern-count="5" aria-pressed="false">5 Colors</button>
|
||
</div>
|
||
</div>
|
||
<div class="md:col-span-2 space-y-2">
|
||
<div class="text-sm font-medium text-gray-700">Layout</div>
|
||
<div class="flex gap-2 flex-wrap">
|
||
<button type="button" class="tab-btn tab-active pattern-btn" data-pattern-layout="spiral" aria-pressed="true">Spiral</button>
|
||
<button type="button" class="tab-btn tab-idle pattern-btn" data-pattern-layout="stacked" aria-pressed="false">Stacked</button>
|
||
<button type="button" class="tab-btn tab-idle pattern-btn" data-pattern-layout="corinthian" aria-pressed="false">Corinthian</button>
|
||
<button type="button" class="tab-btn tab-idle" id="classic-manual-btn" aria-pressed="false">Manual paint</button>
|
||
</div>
|
||
<div id="classic-expanded-row" class="flex items-center gap-2 hidden">
|
||
<label class="text-sm inline-flex items-center gap-2 font-medium">
|
||
<input id="classic-expanded-toggle" type="checkbox" class="align-middle" checked>
|
||
Expanded spacing
|
||
</label>
|
||
<p class="hint m-0">Separate clusters for easier taps.</p>
|
||
</div>
|
||
<div id="classic-manual-size-row" class="hidden flex flex-col gap-2">
|
||
<div class="flex items-center justify-between">
|
||
<div class="text-sm font-medium text-gray-700">Manual size mode</div>
|
||
<span id="classic-manual-size-label" class="text-xs text-gray-500">Quad 1</span>
|
||
</div>
|
||
<div class="flex items-center gap-2 flex-wrap">
|
||
<button type="button" id="classic-size-mode-paint" class="tab-btn tab-idle" data-tool="paint">Paint</button>
|
||
<button type="button" id="classic-size-mode-inflate" class="tab-btn tab-idle" data-tool="inflate">Inflate</button>
|
||
<button type="button" id="classic-size-mode-deflate" class="tab-btn tab-idle" data-tool="deflate">Deflate</button>
|
||
<button type="button" id="classic-size-mode-slide" class="tab-btn tab-idle" data-tool="slide">Slide</button>
|
||
<button type="button" id="classic-size-reset" class="btn-yellow text-xs px-2 py-1">Reset</button>
|
||
</div>
|
||
<p class="hint m-0">Pick a mode; click a quad to target. Drag to slide in Slide.</p>
|
||
</div>
|
||
<div id="classic-focus-row" class="flex items-center gap-2 hidden">
|
||
<button type="button" class="btn-dark text-xs px-3 py-2 hidden" id="classic-focus-prev" aria-hidden="true" tabindex="-1">◀ Prev</button>
|
||
<span id="classic-focus-label" class="text-sm text-gray-700">Clusters 1–8</span>
|
||
<button type="button" class="btn-dark text-xs px-3 py-2 hidden" id="classic-focus-next" aria-hidden="true" tabindex="-1">Next ▶</button>
|
||
<button type="button" class="btn-dark text-xs px-3 py-2 hidden" id="classic-focus-zoomout" aria-hidden="true" tabindex="-1">Zoom Out</button>
|
||
<button type="button" class="btn-dark text-xs px-3 py-2 hidden" id="classic-quad-reset" aria-hidden="true" tabindex="-1">Reset Quad</button>
|
||
</div>
|
||
</div>
|
||
<select id="classic-pattern" class="select align-middle hidden" aria-hidden="true" tabindex="-1">
|
||
<option value="Arch 4">Arch 4 (4-color spiral)</option>
|
||
<option value="Column 4">Column 4 (quad wrap)</option>
|
||
<option value="Column 4 Corinthian">Column 4 (corinthian)</option>
|
||
<option value="Arch 5">Arch 5 (5-color spiral)</option>
|
||
<option value="Column 5">Column 5 (5-balloon wrap)</option>
|
||
<option value="Column 5 Corinthian">Column 5 (corinthian)</option>
|
||
<option value="Arch 4 Stacked">Arch 4 (stacked)</option>
|
||
<option value="Column 4 Stacked">Column 4 (stacked)</option>
|
||
<option value="Arch 5 Stacked">Arch 5 (stacked)</option>
|
||
<option value="Column 5 Stacked">Column 5 (stacked)</option>
|
||
</select>
|
||
<label class="text-sm">Length (ft):
|
||
<input id="classic-length-ft" type="number" min="1" max="100" step="0.5" value="5" class="w-full px-2 py-1 border rounded align-middle">
|
||
</label>
|
||
</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
|
||
</label>
|
||
</div>
|
||
|
||
<div id="topper-controls" class="hidden grid grid-cols-1 sm:grid-cols-4 gap-3 items-start pt-2">
|
||
<div class="sm:col-span-2">
|
||
<div class="text-sm font-medium mb-2">Topper Shape</div>
|
||
<div id="topper-type-group" class="topper-type-group">
|
||
<button type="button" class="tab-btn topper-type-btn tab-active" data-type="round" aria-pressed="true"><i class="fa-regular fa-circle-dot"></i><span class="hidden sm:inline">Round</span></button>
|
||
<button type="button" class="tab-btn topper-type-btn tab-idle" data-type="star" aria-pressed="false"><i class="fa-solid fa-star"></i><span class="hidden sm:inline">Star</span></button>
|
||
<button type="button" class="tab-btn topper-type-btn tab-idle" data-type="heart" aria-pressed="false"><i class="fa-solid fa-heart"></i><span class="hidden sm:inline">Heart</span></button>
|
||
</div>
|
||
<div class="mt-2">
|
||
<div class="text-xs font-semibold text-gray-600 mb-1">Numbers</div>
|
||
<div class="topper-number-grid">
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-0" aria-pressed="false">0</button>
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-1" aria-pressed="false">1</button>
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-2" aria-pressed="false">2</button>
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-3" aria-pressed="false">3</button>
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-4" aria-pressed="false">4</button>
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-5" aria-pressed="false">5</button>
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-6" aria-pressed="false">6</button>
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-7" aria-pressed="false">7</button>
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-8" aria-pressed="false">8</button>
|
||
<button type="button" class="tab-btn topper-type-btn topper-number-btn tab-idle" data-type="num-9" aria-pressed="false">9</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="sm:col-span-2 flex flex-wrap gap-2 justify-end">
|
||
<button type="button" id="classic-nudge-open" class="btn-dark text-xs px-3 py-2">Nudge Panel</button>
|
||
</div>
|
||
</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 font-medium">
|
||
<input id="classic-border-enabled" type="checkbox" class="align-middle">
|
||
Outline Balloons
|
||
</label>
|
||
<label class="text-sm inline-flex items-center gap-2">
|
||
<input id="classic-reverse" type="checkbox" class="align-middle">
|
||
Reverse spiral
|
||
</label>
|
||
<p class="hint">Use stacked for “same color per quad” layouts; reverse flips the spiral.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-stack" data-mobile-tab="colors">
|
||
<div class="panel-heading">Classic Colors</div>
|
||
<div class="panel-card">
|
||
<div class="flex items-center justify-between mb-2">
|
||
<div id="classic-slots" class="flex items-center gap-2"></div>
|
||
<button id="classic-remove-slot" class="btn-dark text-sm px-3 py-2 hidden" type="button" title="Remove color slot">-</button>
|
||
<button id="classic-add-slot" class="btn-dark text-sm px-3 py-2 hidden" type="button" title="Add color slot">+</button>
|
||
</div>
|
||
<div class="flex items-center gap-3 mb-2">
|
||
<span class="text-sm text-gray-700 classic-label">Active color</span>
|
||
<div id="classic-active-chip" class="current-color-chip cursor-pointer" title="Tap to scroll to palette">
|
||
<span id="classic-active-label" class="text-xs font-semibold text-slate-700"></span>
|
||
</div>
|
||
</div>
|
||
<div id="classic-topper-color-block" class="mb-3 hidden">
|
||
<div class="text-sm text-gray-700 classic-label">Topper Color</div>
|
||
<div class="flex items-center gap-3 mt-1">
|
||
<button id="classic-topper-color-swatch" class="slot-swatch" title="Click to change topper color">T</button>
|
||
</div>
|
||
</div>
|
||
<div id="classic-project-block" class="space-y-1">
|
||
<div class="text-sm text-gray-700 classic-label">Project Palette</div>
|
||
<div id="classic-project-palette" class="palette-box min-h-[2.4rem]"></div>
|
||
</div>
|
||
|
||
<div id="classic-replace-block" class="mt-2">
|
||
<div class="text-sm text-gray-700 classic-label">Replace Color</div>
|
||
<div class="panel-card space-y-3">
|
||
<div class="flex items-center gap-2 replace-row">
|
||
<button type="button" class="replace-chip" id="classic-replace-from-chip" aria-label="Pick color to replace"></button>
|
||
<span class="text-xs font-semibold text-slate-500">→</span>
|
||
<button type="button" class="replace-chip" id="classic-replace-to-chip" aria-label="Pick replacement color"></button>
|
||
<span id="classic-replace-count" class="text-xs text-slate-500 ml-auto"></span>
|
||
</div>
|
||
<div class="grid grid-cols-1 gap-2">
|
||
<select id="classic-replace-from" class="sr-only"></select>
|
||
<select id="classic-replace-to" class="sr-only"></select>
|
||
<button id="classic-replace-btn" class="btn-blue">Replace</button>
|
||
<p id="classic-replace-msg" class="hint"></p>
|
||
</div>
|
||
</div>
|
||
</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</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 keeps the vector Classic layout; PNG is raster.</p>
|
||
</div>
|
||
<p class="hint text-red-500">Classic JSON save/load coming soon.</p>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<section id="classic-canvas-panel"
|
||
class="order-1 w-full lg:flex-1 grid grid-rows-[1fr] lg:grid-rows-[minmax(0,1fr)] gap-2 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>
|
||
<div id="classic-mobile-bar" class="mobile-action-bar hidden">
|
||
<div class="mobile-action-chip" id="classic-active-chip-floating" title="Active">
|
||
<span class="color-dot" id="classic-active-dot-floating"></span>
|
||
</div>
|
||
<div class="mobile-action-row">
|
||
<button type="button" class="mobile-action-btn" id="classic-undo-manual" aria-label="Undo">
|
||
<i class="fa-solid fa-rotate-left" aria-hidden="true"></i>
|
||
<span>Undo</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn" id="classic-redo-manual" aria-label="Redo">
|
||
<i class="fa-solid fa-rotate-right" aria-hidden="true"></i>
|
||
<span>Redo</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn" id="classic-pick-manual" aria-label="Eyedropper" aria-pressed="false" data-tool="pick">
|
||
<i class="fa-solid fa-eye-dropper" aria-hidden="true"></i>
|
||
<span>Pick</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn" id="classic-erase-manual" aria-label="Toggle Erase" aria-pressed="false" data-tool="erase">
|
||
<i class="fa-solid fa-eraser" aria-hidden="true"></i>
|
||
<span>Erase</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn danger" id="classic-clear-manual" aria-label="Clear">
|
||
<i class="fa-solid fa-trash" aria-hidden="true"></i>
|
||
<span>Clear</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn" id="classic-export-manual" aria-label="Export">
|
||
<i class="fa-solid fa-download" aria-hidden="true"></i>
|
||
<span>Export</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div id="floating-topper-nudge" class="floating-nudge hidden">
|
||
<div class="floating-nudge-header">
|
||
<div class="panel-heading">Nudge & Size Topper</div>
|
||
<button type="button" id="floating-nudge-toggle" class="btn-dark text-xs px-3 py-2" aria-label="Close nudge panel">×</button>
|
||
</div>
|
||
<div class="floating-nudge-body space-y-3">
|
||
<div class="grid grid-cols-3 gap-2">
|
||
<div></div>
|
||
<button type="button" class="btn-dark nudge-topper" data-dx="0" data-dy="0.5" aria-label="Move Topper Up">↑</button>
|
||
<div></div>
|
||
<button type="button" class="btn-dark nudge-topper" data-dx="-0.5" data-dy="0" aria-label="Move Topper Left">←</button>
|
||
<div></div>
|
||
<button type="button" class="btn-dark nudge-topper" data-dx="0.5" data-dy="0" aria-label="Move Topper Right">→</button>
|
||
<div></div>
|
||
<button type="button" class="btn-dark nudge-topper" data-dx="0" data-dy="-0.5" aria-label="Move Topper Down">↓</button>
|
||
<div></div>
|
||
</div>
|
||
<div>
|
||
<div class="text-sm font-medium mb-1">Topper Size</div>
|
||
<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">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
|
||
<section id="tab-wall" class="hidden flex flex-col lg:flex-row gap-4 lg:h-[calc(100vh-10rem)]">
|
||
<aside id="wall-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">Wall Controls</h2>
|
||
</div>
|
||
<div class="control-stack" data-mobile-tab="controls">
|
||
<div class="panel-heading">Grid</div>
|
||
<div class="panel-card grid grid-cols-2 gap-3">
|
||
<label class="text-sm font-medium flex flex-col gap-1">Columns
|
||
<input id="wall-cols" type="number" min="2" max="20" step="1" value="9" class="w-full px-2 py-1 border rounded">
|
||
</label>
|
||
<label class="text-sm font-medium flex flex-col gap-1">Rows
|
||
<input id="wall-rows" type="number" min="2" max="20" step="1" value="7" class="w-full px-2 py-1 border rounded">
|
||
</label>
|
||
<label class="text-sm font-medium flex flex-col gap-1 col-span-2">Pattern
|
||
<select id="wall-pattern" class="select">
|
||
<option value="grid">Square Grid</option>
|
||
<option value="x">X / Diamond</option>
|
||
</select>
|
||
</label>
|
||
<label class="text-sm font-medium inline-flex items-center gap-2 col-span-2">
|
||
<input id="wall-show-wire" type="checkbox" class="align-middle" checked>
|
||
Show wireframe for empty spots
|
||
</label>
|
||
<label class="text-sm font-medium inline-flex items-center gap-2 col-span-2">
|
||
<input id="wall-outline" type="checkbox" class="align-middle" checked>
|
||
Outline balloons
|
||
</label>
|
||
</div>
|
||
|
||
<div class="panel-heading mt-4">Tools</div>
|
||
<div class="panel-card">
|
||
<div class="wall-toolbar">
|
||
<button type="button" id="wall-tool-paint" class="tool-btn" aria-pressed="true">
|
||
<i class="fa-solid fa-brush"></i>
|
||
<span>Paint</span>
|
||
</button>
|
||
<button type="button" id="wall-tool-erase" class="tool-btn" aria-pressed="false">
|
||
<i class="fa-solid fa-eraser"></i>
|
||
<span>Erase</span>
|
||
</button>
|
||
</div>
|
||
<p class="hint mt-2 text-xs">Paint applies the active color; Erase clears. Hold modifier on desktop to erase temporarily.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-stack" data-mobile-tab="colors">
|
||
<div class="panel-heading">Wall Colors</div>
|
||
<div class="panel-card space-y-4">
|
||
<div class="space-y-2">
|
||
<div class="flex items-center gap-3">
|
||
<span class="text-sm font-medium text-gray-700">Active color</span>
|
||
<div id="wall-active-color-chip" class="current-color-chip">
|
||
<span id="wall-active-color-label" class="text-xs font-semibold text-slate-700"></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-2">
|
||
<div class="flex items-center justify-between">
|
||
<span class="text-sm font-semibold text-gray-700">Project Palette</span>
|
||
</div>
|
||
<div id="wall-used-palette" class="palette-box min-h-[2.4rem]"></div>
|
||
</div>
|
||
|
||
<div class="space-y-2">
|
||
<div class="flex items-center gap-2 replace-row">
|
||
<button type="button" class="replace-chip" id="wall-replace-from-chip" aria-label="Pick wall color to replace"></button>
|
||
<span class="text-xs font-semibold text-slate-500">→</span>
|
||
<button type="button" class="replace-chip" id="wall-replace-to-chip" aria-label="Pick wall replacement color"></button>
|
||
<span id="wall-replace-count" class="text-xs text-slate-500 ml-auto"></span>
|
||
</div>
|
||
<div class="grid grid-cols-1 gap-2">
|
||
<select id="wall-replace-from" class="sr-only"></select>
|
||
<select id="wall-replace-to" class="sr-only"></select>
|
||
<button type="button" id="wall-replace-btn" class="btn-dark text-sm">Replace</button>
|
||
<div id="wall-replace-msg" class="text-xs text-gray-500"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-2">
|
||
<div class="text-sm font-semibold text-gray-700">Color Library</div>
|
||
<div id="wall-palette" class="palette-box min-h-[3rem]"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="control-stack" data-mobile-tab="save">
|
||
<div class="panel-heading">Save & Export</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">Exports the current wall view.</p>
|
||
</div>
|
||
<div>
|
||
<div class="panel-heading text-sm">Quick Paint (uses active color)</div>
|
||
<div class="grid grid-cols-2 gap-2 mt-2">
|
||
<button type="button" id="wall-paint-links" class="btn-blue text-xs px-2 py-2">Paint Links</button>
|
||
<button type="button" id="wall-paint-small" class="btn-blue text-xs px-2 py-2">Paint 5" Nodes</button>
|
||
<button type="button" id="wall-paint-gaps" class="btn-blue text-xs px-2 py-2">Paint 11" Gaps</button>
|
||
</div>
|
||
<p class="hint mt-2">Fills only that group; pattern-aware for Grid vs X.</p>
|
||
</div>
|
||
<div class="flex flex-wrap gap-3">
|
||
<button type="button" id="wall-clear" class="btn-danger text-sm px-3 py-2 flex-1">Clear</button>
|
||
<button type="button" id="wall-fill-all" class="btn-blue text-sm px-3 py-2 flex-1">Fill All</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<section id="wall-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"
|
||
style="height:92%;">
|
||
<div class="flex items-center justify-between px-4 py-3 border-b border-gray-200 bg-white/70">
|
||
<div class="text-base font-semibold text-slate-700">Balloon Wall</div>
|
||
<div class="text-sm text-gray-500">Columns/Rows: <span id="wall-grid-label">9 × 7</span></div>
|
||
</div>
|
||
<div id="wall-display" class="flex-1 bg-white relative overflow-auto">
|
||
<div class="p-6 text-gray-500 text-sm">Wall designer will load here.</div>
|
||
</div>
|
||
</section>
|
||
</section>
|
||
|
||
<section id="tab-helium" class="hidden flex flex-col lg:flex-row gap-4 lg:h-[calc(100vh-10rem)]">
|
||
<aside id="helium-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">Helium Controls</h2>
|
||
</div>
|
||
<div class="control-stack">
|
||
<div class="panel-heading">Coming Soon</div>
|
||
<div class="panel-card space-y-2">
|
||
<p class="text-sm text-gray-700">Helium layouts are in progress.</p>
|
||
<p class="hint">We can add bouquet builders, ceiling clusters, and tied columns here.</p>
|
||
</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<section class="order-1 w-full lg:flex-1 flex items-center justify-center rounded-2xl bg-white/70 ring-1 ring-black/5">
|
||
<div class="text-sm text-gray-500">Helium canvas coming soon.</div>
|
||
</section>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<div id="mobile-tabbar" class="mobile-tabbar">
|
||
<button type="button" class="mobile-tab-btn" data-mobile-tab="controls" aria-pressed="true" aria-label="Tools">
|
||
<i class="mobile-tab-icon fa-solid fa-wand-magic-sparkles" aria-hidden="true"></i>
|
||
<span class="sr-only">Tools</span>
|
||
</button>
|
||
<button type="button" class="mobile-tab-btn" data-mobile-tab="colors" aria-pressed="false" aria-label="Colors">
|
||
<i class="mobile-tab-icon fa-solid fa-palette" aria-hidden="true"></i>
|
||
<span class="sr-only">Colors</span>
|
||
</button>
|
||
<button type="button" class="mobile-tab-btn" data-mobile-tab="save" aria-pressed="false" aria-label="Save and Share">
|
||
<i class="mobile-tab-icon fa-solid fa-cloud-arrow-up" aria-hidden="true"></i>
|
||
<span class="sr-only">Save</span>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Mobile sticky action bar -->
|
||
<div id="mobile-action-bar" class="mobile-action-bar hidden">
|
||
<div class="mobile-action-chip" id="mobile-active-color-chip" title="Active color"></div>
|
||
<div class="mobile-action-row">
|
||
<button type="button" class="mobile-action-btn" id="mobile-act-undo" aria-label="Undo">
|
||
<i class="fa-solid fa-rotate-left" aria-hidden="true"></i>
|
||
<span>Undo</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn" id="mobile-act-redo" aria-label="Redo">
|
||
<i class="fa-solid fa-rotate-right" aria-hidden="true"></i>
|
||
<span>Redo</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn" id="mobile-act-eyedrop" aria-label="Eyedropper">
|
||
<i class="fa-solid fa-eye-dropper" aria-hidden="true"></i>
|
||
<span>Pick</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn" id="mobile-act-erase" aria-label="Toggle Erase">
|
||
<i class="fa-solid fa-eraser" aria-hidden="true"></i>
|
||
<span>Erase</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn danger" id="mobile-act-clear" aria-label="Clear canvas">
|
||
<i class="fa-solid fa-trash" aria-hidden="true"></i>
|
||
<span>Clear</span>
|
||
</button>
|
||
<button type="button" class="mobile-action-btn" id="mobile-act-export" aria-label="Export PNG">
|
||
<i class="fa-solid fa-download" aria-hidden="true"></i>
|
||
<span>Export</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Color picker modal -->
|
||
<div id="color-picker-modal" class="color-modal hidden" role="dialog" aria-modal="true" aria-labelledby="color-picker-title">
|
||
<div class="color-modal-backdrop"></div>
|
||
<div class="color-modal-card">
|
||
<div class="color-modal-header">
|
||
<div>
|
||
<div id="color-picker-title" class="color-modal-title">Choose a color</div>
|
||
<div id="color-picker-subtitle" class="color-modal-subtitle"></div>
|
||
</div>
|
||
<button type="button" id="color-picker-close" class="color-modal-close" aria-label="Close">×</button>
|
||
</div>
|
||
<div id="color-picker-grid" class="color-modal-grid"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Export modal -->
|
||
<div id="export-modal" class="color-modal hidden" role="dialog" aria-modal="true" aria-labelledby="export-modal-title">
|
||
<div class="color-modal-backdrop"></div>
|
||
<div class="color-modal-card">
|
||
<div class="color-modal-header">
|
||
<div>
|
||
<div id="export-modal-title" class="color-modal-title">Export design</div>
|
||
<div class="color-modal-subtitle">Choose a format to download</div>
|
||
</div>
|
||
<button type="button" id="export-modal-close" class="color-modal-close" aria-label="Close">×</button>
|
||
</div>
|
||
<div class="flex flex-col sm:flex-row gap-3">
|
||
<button type="button" class="btn-blue flex-1" data-export-choice="png">Export PNG</button>
|
||
<button type="button" class="btn-dark flex-1" data-export-choice="svg">Export SVG</button>
|
||
</div>
|
||
<p class="hint mt-2 text-xs text-slate-500">SVG keeps vector shapes where possible (textures/images stay raster). PNG renders a high-res snapshot.</p>
|
||
</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>
|
||
|
||
<!-- Palette must load before shared.js; it is already included in the <head>. -->
|
||
<script src="shared.js" defer></script>
|
||
<script src="script.js" defer></script>
|
||
<script src="organic.js" defer></script>
|
||
<script src="wall.js" defer></script>
|
||
<script src="classic.js" defer></script>
|
||
|
||
<div id="classic-quad-modal" class="quad-modal hidden" aria-hidden="true">
|
||
<div class="quad-modal-backdrop"></div>
|
||
<div class="quad-modal-panel" role="dialog" aria-modal="true" aria-label="Quad detail">
|
||
<div class="quad-modal-header">
|
||
<div class="quad-modal-title">Quad Detail</div>
|
||
<button type="button" id="classic-quad-modal-close" class="btn-dark text-xs px-3 py-2">Close</button>
|
||
</div>
|
||
<div class="quad-modal-body">
|
||
<div id="classic-quad-modal-display" class="quad-modal-display"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</body>
|
||
</html>
|