diff --git a/.gitignore b/.gitignore
index a011b10..7b8cdea 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,6 @@
!.dev.vars.example
.env*
!.env.example
+
+# npm
+node_modules/
diff --git a/css/input.css b/css/input.css
new file mode 100644
index 0000000..3cfa7f1
--- /dev/null
+++ b/css/input.css
@@ -0,0 +1,451 @@
+@import "tailwindcss";
+@plugin "daisyui" {
+ themes: false;
+}
+
+/* Scan project files for Tailwind class usage */
+@source "../index.html";
+@source "../js";
+
+/* ── Color tokens (also used in JS inline styles) ── */
+:root {
+ --stone-darkest: #0a0908;
+ --stone-dark: #131110;
+ --stone-mid: #1c1917;
+ --stone-lift: #252220;
+ --stone-edge: #302c28;
+ --stone-face: #3d3830;
+ --stone-pale: #5a5248;
+ --torch-amber: #c87820;
+ --torch-gold: #e0a030;
+ --torch-bright: #f0c060;
+ --torch-dim: #6a3c08;
+ --torch-glow: rgba(200,120,32,0.15);
+ --text-primary: #c8bfa8;
+ --text-dim: #7a7060;
+ --text-faint: #4a4438;
+}
+
+/* ── daisyUI tokenforge theme ── */
+[data-theme="tokenforge"] {
+ color-scheme: dark;
+ --color-base-100: #0a0908;
+ --color-base-200: #131110;
+ --color-base-300: #1e1c18;
+ --color-base-content: #c8bfa8;
+ --color-primary: #c87820;
+ --color-primary-content: #0a0600;
+ --color-secondary: #e0a030;
+ --color-secondary-content: #0a0600;
+ --color-accent: #f0c060;
+ --color-accent-content: #0a0600;
+ --color-neutral: #2a2820;
+ --color-neutral-content: #7a7060;
+ --color-info: #38bdf8;
+ --color-success: #4ade80;
+ --color-warning: #fb923c;
+ --color-error: #f87171;
+ font-family: 'Inter', sans-serif;
+}
+
+*, *::before, *::after { box-sizing: border-box; }
+html, body { height: 100%; overflow-x: hidden; }
+
+body {
+ background: var(--stone-darkest);
+ color: var(--text-primary);
+ font-family: 'Inter', sans-serif;
+ position: relative;
+}
+
+/* ── Background canvas & overlay ── */
+#bg-canvas {
+ position: fixed;
+ inset: 0;
+ width: 100%; height: 100%;
+ pointer-events: none;
+ z-index: 0;
+}
+
+body::after {
+ content: '';
+ position: fixed;
+ inset: 0;
+ background: radial-gradient(ellipse 80% 70% at 50% 40%, transparent 20%, rgba(5,4,3,0.88) 100%);
+ pointer-events: none;
+ z-index: 1;
+}
+
+/* ── Header ── */
+.tf-header {
+ position: relative;
+ z-index: 10;
+ width: 100%;
+ background: linear-gradient(180deg, rgba(8,7,6,0.98) 0%, rgba(10,9,8,0.92) 100%);
+ backdrop-filter: blur(8px);
+ border-bottom: 1px solid var(--stone-edge);
+ box-shadow: 0 4px 24px rgba(0,0,0,0.7), 0 1px 0 rgba(200,120,32,0.12);
+}
+
+.tf-header-inner {
+ width: 100%;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 20px 32px 18px;
+ display: flex;
+ align-items: center;
+ gap: 14px;
+ justify-content: center;
+}
+
+@keyframes flicker {
+ 0%,100% { filter: drop-shadow(0 0 6px #c87820) brightness(1); }
+ 15% { filter: drop-shadow(0 0 12px #e0a030) brightness(1.15); }
+ 30% { filter: drop-shadow(0 0 4px #c87820) brightness(0.92); }
+ 50% { filter: drop-shadow(0 0 10px #e0a030) brightness(1.08); }
+ 70% { filter: drop-shadow(0 0 5px #c87820) brightness(0.95); }
+}
+
+.header-torch {
+ font-size: 26px;
+ animation: flicker 3s ease-in-out infinite;
+ flex-shrink: 0;
+}
+
+.logo-title {
+ font-family: 'Cinzel', serif;
+ font-size: 22px;
+ font-weight: 700;
+ letter-spacing: 0.1em;
+ color: var(--torch-gold);
+ text-shadow: 0 0 20px rgba(200,120,32,0.5), 0 1px 0 rgba(0,0,0,0.8);
+ line-height: 1;
+}
+
+.logo-sub {
+ font-size: 10px;
+ letter-spacing: 0.26em;
+ color: var(--text-dim);
+ text-transform: uppercase;
+ margin-top: 3px;
+}
+
+.header-rule {
+ width: 100%;
+ height: 3px;
+ background: repeating-linear-gradient(
+ 90deg,
+ transparent 0px, transparent 2px,
+ rgba(200,120,32,0.08) 2px, rgba(200,120,32,0.08) 4px
+ );
+}
+
+/* ── Panels ── */
+.tf-panel {
+ background:
+ radial-gradient(ellipse 60% 40% at 0% 0%, rgba(40,55,20,0.18) 0%, transparent 60%),
+ linear-gradient(170deg, #1e1b17 0%, #161310 50%, #191613 100%);
+ border: 1px solid var(--stone-edge);
+ border-radius: 2px;
+ padding: 18px 20px 20px;
+ margin-bottom: 14px;
+ position: relative;
+ box-shadow:
+ inset 0 1px 0 rgba(255,255,255,0.04),
+ inset 0 -1px 0 rgba(0,0,0,0.5),
+ inset 2px 0 0 rgba(0,0,0,0.3),
+ 0 6px 24px rgba(0,0,0,0.6);
+}
+
+.tf-panel::before {
+ content: '';
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ height: 2px;
+ background: linear-gradient(90deg,
+ transparent 0%, var(--torch-dim) 15%,
+ var(--torch-amber) 40%, var(--torch-dim) 70%, transparent 100%);
+ opacity: 0.5;
+ border-radius: 2px 2px 0 0;
+}
+
+.tf-section-head {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 14px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid var(--stone-edge);
+}
+
+.section-title {
+ font-family: 'Cinzel', serif;
+ font-size: 11px;
+ font-weight: 600;
+ letter-spacing: 0.18em;
+ color: var(--torch-gold);
+ text-transform: uppercase;
+ flex: 1;
+}
+
+.section-icon {
+ font-size: 13px;
+ opacity: 0.55;
+ animation: flicker 3s ease-in-out infinite;
+ animation-delay: var(--flicker-delay, 0s);
+}
+
+/* ── Drop zone ── */
+#drop-zone {
+ border: 1px dashed var(--stone-face);
+ border-radius: 2px;
+ padding: 28px 20px;
+ text-align: center;
+ cursor: pointer;
+ background:
+ repeating-linear-gradient(45deg,
+ rgba(0,0,0,0.15) 0px, rgba(0,0,0,0.15) 1px,
+ transparent 1px, transparent 8px),
+ rgba(8,7,6,0.5);
+ transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;
+}
+
+#drop-zone:hover, #drop-zone.over {
+ border-color: var(--torch-amber);
+ background: rgba(14,10,6,0.7);
+ box-shadow: 0 0 18px rgba(200,120,32,0.1), inset 0 0 16px rgba(200,120,32,0.04);
+}
+
+/* ── Border grid (elements created by JS — keep these class names) ── */
+.border-grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.border-opt {
+ cursor: pointer;
+ border-radius: 2px;
+ border: 1px solid var(--stone-edge);
+ background: rgba(40,35,28,0.9);
+ padding: 8px 6px 6px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 5px;
+ transition: border-color 0.15s, background 0.15s, box-shadow 0.15s, transform 0.1s;
+}
+
+.border-opt:hover {
+ border-color: var(--torch-dim);
+ background: rgba(55,45,32,0.95);
+ transform: translateY(-1px);
+ box-shadow: 0 3px 10px rgba(0,0,0,0.5);
+}
+
+.border-opt.sel {
+ border-color: var(--torch-amber);
+ background: rgba(50,32,10,0.95);
+ box-shadow: 0 0 10px var(--torch-glow), inset 0 0 6px rgba(200,120,32,0.08);
+}
+
+.border-opt.loading img { opacity: 0.2; }
+
+.border-opt img {
+ width: 60px; height: 60px;
+ object-fit: contain;
+ border-radius: 50%;
+ background: rgba(70,60,45,0.6);
+ transition: opacity 0.2s;
+}
+
+.border-opt .bname {
+ font-family: 'Cinzel', serif;
+ font-size: 9px;
+ letter-spacing: 0.03em;
+ color: var(--text-dim);
+ text-align: center;
+ line-height: 1.3;
+ width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.border-opt.sel .bname { color: var(--torch-gold); }
+
+/* ── Checkbox + toggle rows ── */
+.tf-link-row {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 12px;
+ padding: 7px 10px;
+ background: rgba(6,5,4,0.5);
+ border-radius: 2px;
+ border: 1px solid var(--stone-edge);
+}
+
+/* ── Preview frame ── */
+.preview-frame {
+ width: 100%;
+ position: relative;
+ padding: 10px;
+ background:
+ radial-gradient(ellipse at 50% 0%, rgba(200,120,32,0.06) 0%, transparent 60%),
+ linear-gradient(160deg, #1a1714 0%, #0e0c0a 100%);
+ border: 1px solid var(--stone-edge);
+ border-radius: 2px;
+ box-shadow:
+ inset 0 2px 0 rgba(0,0,0,0.5),
+ inset 0 0 30px rgba(0,0,0,0.4),
+ 0 0 0 1px rgba(200,120,32,0.06),
+ 0 8px 32px rgba(0,0,0,0.6);
+}
+
+.preview-frame::before {
+ content: '';
+ position: absolute;
+ top: 0; left: 10%; right: 10%;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, var(--torch-amber), transparent);
+ opacity: 0.4;
+}
+
+.bolt {
+ position: absolute;
+ width: 8px; height: 8px;
+ border-radius: 50%;
+ background: radial-gradient(circle at 35% 35%, var(--stone-face), var(--stone-dark));
+ border: 1px solid var(--stone-edge);
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.08);
+ z-index: 1;
+}
+.bolt-tl { top: 5px; left: 5px; }
+.bolt-tr { top: 5px; right: 5px; }
+.bolt-bl { bottom: 5px; left: 5px; }
+.bolt-br { bottom: 5px; right: 5px; }
+
+.checker {
+ width: 100%;
+ aspect-ratio: 1;
+ background: repeating-conic-gradient(#100e0c 0% 25%, #0c0a08 0% 50%) 0 0 / 14px 14px;
+ border-radius: 1px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ position: relative;
+ box-shadow: inset 0 0 40px rgba(0,0,0,0.7);
+}
+
+.checker::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: radial-gradient(ellipse at 50% 100%, rgba(200,120,32,0.08) 0%, transparent 65%);
+ pointer-events: none;
+ z-index: 2;
+}
+
+#preview-canvas {
+ position: absolute;
+ top: 0; left: 0;
+ width: 100%; height: 100%;
+ cursor: grab;
+ z-index: 1;
+}
+#preview-canvas.dragging { cursor: grabbing; }
+
+.no-img {
+ color: var(--text-faint);
+ font-size: 12px;
+ text-align: center;
+ padding: 24px;
+ line-height: 1.8;
+ pointer-events: none;
+ z-index: 1;
+}
+
+/* ── Safe area ring ── */
+#safe-ring {
+ position: absolute;
+ top: 50%; left: 50%;
+ transform: translate(-50%, -50%);
+ border-radius: 50%;
+ border: 2px dashed rgba(200,120,32,0.6);
+ pointer-events: none;
+ z-index: 3;
+ transition: width 0.08s, height 0.08s;
+ box-shadow: 0 0 8px rgba(200,120,32,0.3);
+ display: none;
+}
+
+/* ── Export button (custom gold gradient, not a daisyUI btn) ── */
+#export-btn {
+ font-family: 'Cinzel', serif;
+ font-size: 14px;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+ color: #0a0600;
+ background: linear-gradient(160deg, var(--torch-dim) 0%, var(--torch-amber) 35%, var(--torch-gold) 55%, var(--torch-bright) 75%, var(--torch-amber) 100%);
+ border: none;
+ border-radius: 2px;
+ padding: 14px 24px;
+ cursor: pointer;
+ width: 100%;
+ max-width: 420px;
+ text-transform: uppercase;
+ position: relative;
+ overflow: hidden;
+ transition: transform 0.1s, box-shadow 0.2s;
+ box-shadow:
+ 0 1px 0 rgba(0,0,0,0.6),
+ 0 4px 18px rgba(200,120,32,0.35),
+ inset 0 1px 0 rgba(255,220,100,0.3);
+ text-shadow: 0 1px 0 rgba(0,0,0,0.4);
+}
+
+#export-btn::before {
+ content: '';
+ position: absolute;
+ top: 0; left: -80%;
+ width: 50%; height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
+ transform: skewX(-15deg);
+ transition: left 0.5s ease;
+}
+#export-btn:hover:not(:disabled)::before { left: 150%; }
+#export-btn:hover:not(:disabled) {
+ transform: translateY(-1px);
+ box-shadow: 0 4px 20px rgba(200,120,32,0.4), inset 0 1px 0 rgba(255,220,100,0.3);
+}
+#export-btn:active:not(:disabled) { transform: translateY(0); }
+#export-btn:disabled { opacity: 0.25; cursor: not-allowed; }
+
+/* ── Footer ── */
+.tf-footer {
+ position: relative;
+ z-index: 10;
+ width: 100%;
+ border-top: 1px solid var(--stone-edge);
+ background: linear-gradient(180deg, rgba(8,7,6,0.95) 0%, rgba(5,4,3,0.98) 100%);
+ backdrop-filter: blur(8px);
+}
+
+.tf-footer-heading {
+ font-family: 'Cinzel', serif;
+ font-size: 10px;
+ font-weight: 600;
+ letter-spacing: 0.18em;
+ color: var(--torch-gold);
+ text-transform: uppercase;
+ margin-bottom: 10px;
+}
+
+/* ── Scrollbar ── */
+::-webkit-scrollbar { width: 6px; }
+::-webkit-scrollbar-track { background: var(--stone-darkest); }
+::-webkit-scrollbar-thumb { background: var(--stone-edge); border-radius: 1px; }
+::-webkit-scrollbar-thumb:hover { background: var(--torch-dim); }
diff --git a/index.html b/index.html
index c12c454..f027206 100644
--- a/index.html
+++ b/index.html
@@ -1,5 +1,5 @@
-
+
@@ -8,15 +8,14 @@
-
+
-