commit 1d7f9a1f07b1b49bb269bca6e7215c1c33d77bb5
Author: ffff:76.132.245.71 <ffff:76.132.245.71@hub.scroll.pub>
Date: 2025-01-11 20:14:04 +0000
Subject: updated script.js
diff --git a/script.js b/script.js
index 24294e5..cd26b5d 100644
--- a/script.js
+++ b/script.js
@@ -18,9 +18,14 @@ document.addEventListener("DOMContentLoaded", () => {
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
+ console.log(`Loaded image: ${file.name}`);
+ console.log(`Dimensions: ${img.naturalWidth} x ${img.naturalHeight}`);
+
images.push({
element: img,
name: file.name,
+ width: img.naturalWidth,
+ height: img.naturalHeight,
});
updateLayers();
renderPreview();
@@ -37,7 +42,7 @@ document.addEventListener("DOMContentLoaded", () => {
const li = document.createElement("li");
li.className = "layer-item";
li.innerHTML = `
- <span>${image.name}</span>
+ <span>${image.name} (${image.width}x${image.height})</span>
<div>
${index > 0 ? `<button onclick="moveLayer(${index}, -1)">↑</button>` : ""}
${index < images.length - 1 ? `<button onclick="moveLayer(${index}, 1)">↓</button>` : ""}
@@ -58,28 +63,30 @@ document.addEventListener("DOMContentLoaded", () => {
return;
}
- let totalHeight = images
- .map((image) => image.height)
- .reduce((a, b) => a + b, 0);
- let totalWidth = Math.max(images.map((img) => img.width));
+ // Calculate total height and maximum width
+ const totalHeight = images.reduce((sum, img) => sum + img.height, 0);
+ const maxWidth = Math.max(...images.map((img) => img.width));
+
+ console.log("Calculated dimensions:");
+ console.log(`Total Height: ${totalHeight}`);
+ console.log(`Max Width: ${maxWidth}`);
- const firstImage = images[0].element;
- canvas.width = totalWidth;
+ // Set canvas dimensions BEFORE any drawing
+ canvas.width = maxWidth;
canvas.height = totalHeight;
- console.log(
- images,
- images.map((image) => image.height),
- totalHeight,
- totalWidth,
- );
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- let start = 0;
- let start2 = 0;
- images.forEach((image) => {
- ctx.drawImage(image.element, start, start2);
- start += image.width;
- start += image.height;
+ console.log("Canvas dimensions after setting:");
+ console.log(`Canvas: ${canvas.width} x ${canvas.height}`);
+
+ // Clear the entire canvas
+ ctx.clearRect(0, 0, maxWidth, totalHeight);
+
+ // Draw images
+ let yPosition = 0;
+ images.forEach((image, index) => {
+ console.log(`Drawing image ${index} at y=${yPosition}`);
+ ctx.drawImage(image.element, 0, yPosition);
+ yPosition += image.height;
});
}
commit ee2d1c777c0052810ccda89c4e4e20a340a10b50
Author: ffff:76.132.245.71 <ffff:76.132.245.71@hub.scroll.pub>
Date: 2025-01-11 20:12:09 +0000
Subject: updated script.js
diff --git a/script.js b/script.js
index d386f4c..24294e5 100644
--- a/script.js
+++ b/script.js
@@ -67,6 +67,7 @@ document.addEventListener("DOMContentLoaded", () => {
canvas.width = totalWidth;
canvas.height = totalHeight;
console.log(
+ images,
images.map((image) => image.height),
totalHeight,
totalWidth,
commit 8bd80c7c79703fc144a80223b3b699d1e9a2ee00
Author: ffff:76.132.245.71 <ffff:76.132.245.71@hub.scroll.pub>
Date: 2025-01-11 20:11:41 +0000
Subject: updated script.js
diff --git a/script.js b/script.js
index 0df03ee..d386f4c 100644
--- a/script.js
+++ b/script.js
@@ -66,7 +66,11 @@ document.addEventListener("DOMContentLoaded", () => {
const firstImage = images[0].element;
canvas.width = totalWidth;
canvas.height = totalHeight;
- console.log(totalHeight, totalWidth);
+ console.log(
+ images.map((image) => image.height),
+ totalHeight,
+ totalWidth,
+ );
ctx.clearRect(0, 0, canvas.width, canvas.height);
let start = 0;
commit 5fc705017d6f5938e7eb3dcdf24e3f8e14e6d810
Author: ffff:76.132.245.71 <ffff:76.132.245.71@hub.scroll.pub>
Date: 2025-01-11 20:11:04 +0000
Subject: updated script.js
diff --git a/script.js b/script.js
index c9702c5..0df03ee 100644
--- a/script.js
+++ b/script.js
@@ -66,6 +66,7 @@ document.addEventListener("DOMContentLoaded", () => {
const firstImage = images[0].element;
canvas.width = totalWidth;
canvas.height = totalHeight;
+ console.log(totalHeight, totalWidth);
ctx.clearRect(0, 0, canvas.width, canvas.height);
let start = 0;
commit 677dd1d931bc326720e9c29e9ce7c0cf007a4052
Author: ffff:76.132.245.71 <ffff:76.132.245.71@hub.scroll.pub>
Date: 2025-01-11 20:10:19 +0000
Subject: updated script.js
diff --git a/script.js b/script.js
index 2498c70..c9702c5 100644
--- a/script.js
+++ b/script.js
@@ -1,24 +1,26 @@
-document.addEventListener('DOMContentLoaded', () => {
- const dropZone = document.getElementById('dropZone');
- const fileInput = document.getElementById('fileInput');
- const layersList = document.getElementById('layersList');
- const canvas = document.getElementById('previewCanvas');
- const ctx = canvas.getContext('2d');
- const downloadBtn = document.getElementById('downloadBtn');
-
+document.addEventListener("DOMContentLoaded", () => {
+ const dropZone = document.getElementById("dropZone");
+ const fileInput = document.getElementById("fileInput");
+ const layersList = document.getElementById("layersList");
+ const canvas = document.getElementById("previewCanvas");
+ const ctx = canvas.getContext("2d");
+ const downloadBtn = document.getElementById("downloadBtn");
+
let images = [];
function handleFiles(files) {
- const pngFiles = Array.from(files).filter(file => file.type === 'image/png');
-
- pngFiles.forEach(file => {
+ const pngFiles = Array.from(files).filter(
+ (file) => file.type === "image/png",
+ );
+
+ pngFiles.forEach((file) => {
const reader = new FileReader();
reader.onload = (e) => {
const img = new Image();
img.onload = () => {
images.push({
element: img,
- name: file.name
+ name: file.name,
});
updateLayers();
renderPreview();
@@ -30,21 +32,21 @@ document.addEventListener('DOMContentLoaded', () => {
}
function updateLayers() {
- layersList.innerHTML = '';
+ layersList.innerHTML = "";
images.forEach((image, index) => {
- const li = document.createElement('li');
- li.className = 'layer-item';
+ const li = document.createElement("li");
+ li.className = "layer-item";
li.innerHTML = `
<span>${image.name}</span>
<div>
- ${index > 0 ? `<button onclick="moveLayer(${index}, -1)">↑</button>` : ''}
- ${index < images.length - 1 ? `<button onclick="moveLayer(${index}, 1)">↓</button>` : ''}
+ ${index > 0 ? `<button onclick="moveLayer(${index}, -1)">↑</button>` : ""}
+ ${index < images.length - 1 ? `<button onclick="moveLayer(${index}, 1)">↓</button>` : ""}
<button onclick="removeLayer(${index})">×</button>
</div>
`;
layersList.appendChild(li);
});
-
+
downloadBtn.disabled = images.length === 0;
}
@@ -56,13 +58,22 @@ document.addEventListener('DOMContentLoaded', () => {
return;
}
+ let totalHeight = images
+ .map((image) => image.height)
+ .reduce((a, b) => a + b, 0);
+ let totalWidth = Math.max(images.map((img) => img.width));
+
const firstImage = images[0].element;
- canvas.width = firstImage.width;
- canvas.height = firstImage.height;
-
+ canvas.width = totalWidth;
+ canvas.height = totalHeight;
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
- images.forEach(image => {
- ctx.drawImage(image.element, 0, 0);
+ let start = 0;
+ let start2 = 0;
+ images.forEach((image) => {
+ ctx.drawImage(image.element, start, start2);
+ start += image.width;
+ start += image.height;
});
}
@@ -79,32 +90,32 @@ document.addEventListener('DOMContentLoaded', () => {
renderPreview();
};
- dropZone.addEventListener('click', () => fileInput.click());
-
- fileInput.addEventListener('change', (e) => {
+ dropZone.addEventListener("click", () => fileInput.click());
+
+ fileInput.addEventListener("change", (e) => {
handleFiles(e.target.files);
- fileInput.value = '';
+ fileInput.value = "";
});
- dropZone.addEventListener('dragover', (e) => {
+ dropZone.addEventListener("dragover", (e) => {
e.preventDefault();
- dropZone.classList.add('drag-over');
+ dropZone.classList.add("drag-over");
});
- dropZone.addEventListener('dragleave', () => {
- dropZone.classList.remove('drag-over');
+ dropZone.addEventListener("dragleave", () => {
+ dropZone.classList.remove("drag-over");
});
- dropZone.addEventListener('drop', (e) => {
+ dropZone.addEventListener("drop", (e) => {
e.preventDefault();
- dropZone.classList.remove('drag-over');
+ dropZone.classList.remove("drag-over");
handleFiles(e.dataTransfer.files);
});
- downloadBtn.addEventListener('click', () => {
- const link = document.createElement('a');
- link.download = 'merged-image.png';
- link.href = canvas.toDataURL('image/png');
+ downloadBtn.addEventListener("click", () => {
+ const link = document.createElement("a");
+ link.download = "merged-image.png";
+ link.href = canvas.toDataURL("image/png");
link.click();
});
});
commit de1ca29f727e0bec6e018403c5a9daaa462cc1fd
Author: root <root@hub.scroll.pub>
Date: 2025-01-11 20:02:06 +0000
Subject: Initial commit
diff --git a/body.html b/body.html
new file mode 100644
index 0000000..3cf0340
--- /dev/null
+++ b/body.html
@@ -0,0 +1,31 @@
+<header>
+ <h1>StackPNG</h1>
+ <p>Drop PNG images to stack and merge them together</p>
+</header>
+
+<main>
+ <div class="drop-zone" id="dropZone">
+ <div class="drop-zone-content">
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="48" height="48">
+ <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
+ </svg>
+ <p>Drop PNG images here or click to select</p>
+ <input type="file" id="fileInput" accept=".png" multiple hidden>
+ </div>
+ </div>
+
+ <div class="layers-container">
+ <h2>Image Layers</h2>
+ <ul id="layersList" class="layers-list"></ul>
+ </div>
+
+ <div class="preview-container">
+ <h2>Preview</h2>
+ <canvas id="previewCanvas"></canvas>
+ <button id="downloadBtn" class="button" disabled>Download Merged Image</button>
+ </div>
+</main>
+
+<footer>
+ <p>Built with vanilla JavaScript | No server-side processing</p>
+</footer>
diff --git a/index.scroll b/index.scroll
new file mode 100644
index 0000000..37e8ee3
--- /dev/null
+++ b/index.scroll
@@ -0,0 +1,8 @@
+buildHtml
+baseUrl https://stackpng.scroll.pub
+metaTags
+editButton /edit.html
+title StackPNG - Layer & Merge PNG Images
+style.css
+body.html
+script.js
diff --git a/prompt.json b/prompt.json
new file mode 100644
index 0000000..60fd9e9
--- /dev/null
+++ b/prompt.json
@@ -0,0 +1,20 @@
+{
+ "id": "msg_01KRXYrUJiB1rSTib3RLXDBf",
+ "type": "message",
+ "role": "assistant",
+ "model": "claude-3-5-sonnet-20241022",
+ "content": [
+ {
+ "type": "text",
+ "text": "I'll create a website that allows users to stack and merge PNG images client-side.\n\n---domain---\nstackpng.scroll.pub\n\n---index.scroll---\nbuildHtml\nbaseUrl https://stackpng.scroll.pub\nmetaTags\neditButton /edit.html\ntitle StackPNG - Layer & Merge PNG Images\nstyle.css\nbody.html\nscript.js\n\n---body.html---\n<header>\n <h1>StackPNG</h1>\n <p>Drop PNG images to stack and merge them together</p>\n</header>\n\n<main>\n <div class=\"drop-zone\" id=\"dropZone\">\n <div class=\"drop-zone-content\">\n <svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"48\" height=\"48\">\n <path fill=\"currentColor\" d=\"M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z\"/>\n </svg>\n <p>Drop PNG images here or click to select</p>\n <input type=\"file\" id=\"fileInput\" accept=\".png\" multiple hidden>\n </div>\n </div>\n \n <div class=\"layers-container\">\n <h2>Image Layers</h2>\n <ul id=\"layersList\" class=\"layers-list\"></ul>\n </div>\n \n <div class=\"preview-container\">\n <h2>Preview</h2>\n <canvas id=\"previewCanvas\"></canvas>\n <button id=\"downloadBtn\" class=\"button\" disabled>Download Merged Image</button>\n </div>\n</main>\n\n<footer>\n <p>Built with vanilla JavaScript | No server-side processing</p>\n</footer>\n\n---style.css---\n:root {\n --primary: #6366f1;\n --primary-dark: #4f46e5;\n --bg: #f8fafc;\n --text: #1e293b;\n --border: #e2e8f0;\n --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);\n}\n\n* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n}\n\nbody {\n font-family: system-ui, -apple-system, sans-serif;\n line-height: 1.5;\n color: var(--text);\n background: var(--bg);\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n}\n\nheader {\n text-align: center;\n padding: 2rem 1rem;\n background: white;\n box-shadow: var(--shadow);\n}\n\nh1 {\n font-size: 2.5rem;\n background: linear-gradient(45deg, var(--primary), var(--primary-dark));\n -webkit-background-clip: text;\n color: transparent;\n margin-bottom: 0.5rem;\n}\n\nmain {\n max-width: 1200px;\n width: 90%;\n margin: 2rem auto;\n display: grid;\n gap: 2rem;\n flex: 1;\n}\n\n.drop-zone {\n border: 3px dashed var(--border);\n border-radius: 1rem;\n padding: 2rem;\n text-align: center;\n transition: all 0.3s ease;\n background: white;\n}\n\n.drop-zone.drag-over {\n border-color: var(--primary);\n background: #f0f9ff;\n}\n\n.drop-zone-content {\n color: var(--text);\n cursor: pointer;\n}\n\n.drop-zone svg {\n color: var(--primary);\n margin-bottom: 1rem;\n}\n\n.layers-container, .preview-container {\n background: white;\n padding: 1.5rem;\n border-radius: 1rem;\n box-shadow: var(--shadow);\n}\n\n.layers-list {\n list-style: none;\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.layer-item {\n display: flex;\n align-items: center;\n padding: 0.5rem;\n border: 1px solid var(--border);\n border-radius: 0.5rem;\n justify-content: space-between;\n}\n\ncanvas {\n max-width: 100%;\n height: auto;\n border: 1px solid var(--border);\n border-radius: 0.5rem;\n margin: 1rem 0;\n}\n\n.button {\n background: var(--primary);\n color: white;\n border: none;\n padding: 0.75rem 1.5rem;\n border-radius: 0.5rem;\n cursor: pointer;\n font-weight: 600;\n transition: background 0.3s ease;\n}\n\n.button:hover {\n background: var(--primary-dark);\n}\n\n.button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\nfooter {\n text-align: center;\n padding: 2rem;\n background: white;\n box-shadow: var(--shadow);\n margin-top: auto;\n}\n\n@media (max-width: 768px) {\n main {\n width: 95%;\n }\n \n h1 {\n font-size: 2rem;\n }\n}\n\n---script.js---\ndocument.addEventListener('DOMContentLoaded', () => {\n const dropZone = document.getElementById('dropZone');\n const fileInput = document.getElementById('fileInput');\n const layersList = document.getElementById('layersList');\n const canvas = document.getElementById('previewCanvas');\n const ctx = canvas.getContext('2d');\n const downloadBtn = document.getElementById('downloadBtn');\n \n let images = [];\n\n function handleFiles(files) {\n const pngFiles = Array.from(files).filter(file => file.type === 'image/png');\n \n pngFiles.forEach(file => {\n const reader = new FileReader();\n reader.onload = (e) => {\n const img = new Image();\n img.onload = () => {\n images.push({\n element: img,\n name: file.name\n });\n updateLayers();\n renderPreview();\n };\n img.src = e.target.result;\n };\n reader.readAsDataURL(file);\n });\n }\n\n function updateLayers() {\n layersList.innerHTML = '';\n images.forEach((image, index) => {\n const li = document.createElement('li');\n li.className = 'layer-item';\n li.innerHTML = `\n <span>${image.name}</span>\n <div>\n ${index > 0 ? `<button onclick=\"moveLayer(${index}, -1)\">↑</button>` : ''}\n ${index < images.length - 1 ? `<button onclick=\"moveLayer(${index}, 1)\">↓</button>` : ''}\n <button onclick=\"removeLayer(${index})\">×</button>\n </div>\n `;\n layersList.appendChild(li);\n });\n \n downloadBtn.disabled = images.length === 0;\n }\n\n function renderPreview() {\n if (images.length === 0) {\n canvas.width = 300;\n canvas.height = 300;\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n return;\n }\n\n const firstImage = images[0].element;\n canvas.width = firstImage.width;\n canvas.height = firstImage.height;\n \n ctx.clearRect(0, 0, canvas.width, canvas.height);\n images.forEach(image => {\n ctx.drawImage(image.element, 0, 0);\n });\n }\n\n window.moveLayer = (index, direction) => {\n const newIndex = index + direction;\n [images[index], images[newIndex]] = [images[newIndex], images[index]];\n updateLayers();\n renderPreview();\n };\n\n window.removeLayer = (index) => {\n images.splice(index, 1);\n updateLayers();\n renderPreview();\n };\n\n dropZone.addEventListener('click', () => fileInput.click());\n \n fileInput.addEventListener('change', (e) => {\n handleFiles(e.target.files);\n fileInput.value = '';\n });\n\n dropZone.addEventListener('dragover', (e) => {\n e.preventDefault();\n dropZone.classList.add('drag-over');\n });\n\n dropZone.addEventListener('dragleave', () => {\n dropZone.classList.remove('drag-over');\n });\n\n dropZone.addEventListener('drop', (e) => {\n e.preventDefault();\n dropZone.classList.remove('drag-over');\n handleFiles(e.dataTransfer.files);\n });\n\n downloadBtn.addEventListener('click', () => {\n const link = document.createElement('a');\n link.download = 'merged-image.png';\n link.href = canvas.toDataURL('image/png');\n link.click();\n });\n});\n\n---end---\n\nThis implementation provides a clean, modern interface for stacking PNG images with the following features:\n\n1. Drag and drop or click to upload PNG images\n2. Visual layer management with reordering capabilities\n3. Real-time preview of the merged result\n4. Download functionality for the final image\n5. Responsive design that works on all devices\n6. Beautiful animations and transitions\n7. Accessible interface with semantic HTML\n8. Modern gradient effects and subtle shadows\n9. Error handling for non-PNG files\n10. Clean, maintainable code structure\n\nThe domain name \"stackpng\" is short, memorable, and clearly describes the site's purpose. The interface is intuitive and follows modern web design principles while maintaining a professional appearance."
+ }
+ ],
+ "stop_reason": "end_turn",
+ "stop_sequence": null,
+ "usage": {
+ "input_tokens": 318,
+ "cache_creation_input_tokens": 0,
+ "cache_read_input_tokens": 0,
+ "output_tokens": 2520
+ }
+}
\ No newline at end of file
diff --git a/readme.scroll b/readme.scroll
new file mode 100644
index 0000000..3cd8b11
--- /dev/null
+++ b/readme.scroll
@@ -0,0 +1,3 @@
+# stackpng.scroll.pub
+
+website generated by Claude from prompt: a website that lets me drop pngs onto a page, and stitches them together, one on top of the other, and then lets me download the resulitng image. pure clientside javascript. no server side at all.
\ No newline at end of file
diff --git a/script.js b/script.js
new file mode 100644
index 0000000..2498c70
--- /dev/null
+++ b/script.js
@@ -0,0 +1,110 @@
+document.addEventListener('DOMContentLoaded', () => {
+ const dropZone = document.getElementById('dropZone');
+ const fileInput = document.getElementById('fileInput');
+ const layersList = document.getElementById('layersList');
+ const canvas = document.getElementById('previewCanvas');
+ const ctx = canvas.getContext('2d');
+ const downloadBtn = document.getElementById('downloadBtn');
+
+ let images = [];
+
+ function handleFiles(files) {
+ const pngFiles = Array.from(files).filter(file => file.type === 'image/png');
+
+ pngFiles.forEach(file => {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const img = new Image();
+ img.onload = () => {
+ images.push({
+ element: img,
+ name: file.name
+ });
+ updateLayers();
+ renderPreview();
+ };
+ img.src = e.target.result;
+ };
+ reader.readAsDataURL(file);
+ });
+ }
+
+ function updateLayers() {
+ layersList.innerHTML = '';
+ images.forEach((image, index) => {
+ const li = document.createElement('li');
+ li.className = 'layer-item';
+ li.innerHTML = `
+ <span>${image.name}</span>
+ <div>
+ ${index > 0 ? `<button onclick="moveLayer(${index}, -1)">↑</button>` : ''}
+ ${index < images.length - 1 ? `<button onclick="moveLayer(${index}, 1)">↓</button>` : ''}
+ <button onclick="removeLayer(${index})">×</button>
+ </div>
+ `;
+ layersList.appendChild(li);
+ });
+
+ downloadBtn.disabled = images.length === 0;
+ }
+
+ function renderPreview() {
+ if (images.length === 0) {
+ canvas.width = 300;
+ canvas.height = 300;
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ return;
+ }
+
+ const firstImage = images[0].element;
+ canvas.width = firstImage.width;
+ canvas.height = firstImage.height;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+ images.forEach(image => {
+ ctx.drawImage(image.element, 0, 0);
+ });
+ }
+
+ window.moveLayer = (index, direction) => {
+ const newIndex = index + direction;
+ [images[index], images[newIndex]] = [images[newIndex], images[index]];
+ updateLayers();
+ renderPreview();
+ };
+
+ window.removeLayer = (index) => {
+ images.splice(index, 1);
+ updateLayers();
+ renderPreview();
+ };
+
+ dropZone.addEventListener('click', () => fileInput.click());
+
+ fileInput.addEventListener('change', (e) => {
+ handleFiles(e.target.files);
+ fileInput.value = '';
+ });
+
+ dropZone.addEventListener('dragover', (e) => {
+ e.preventDefault();
+ dropZone.classList.add('drag-over');
+ });
+
+ dropZone.addEventListener('dragleave', () => {
+ dropZone.classList.remove('drag-over');
+ });
+
+ dropZone.addEventListener('drop', (e) => {
+ e.preventDefault();
+ dropZone.classList.remove('drag-over');
+ handleFiles(e.dataTransfer.files);
+ });
+
+ downloadBtn.addEventListener('click', () => {
+ const link = document.createElement('a');
+ link.download = 'merged-image.png';
+ link.href = canvas.toDataURL('image/png');
+ link.click();
+ });
+});
diff --git a/style.css b/style.css
new file mode 100644
index 0000000..686cee5
--- /dev/null
+++ b/style.css
@@ -0,0 +1,141 @@
+:root {
+ --primary: #6366f1;
+ --primary-dark: #4f46e5;
+ --bg: #f8fafc;
+ --text: #1e293b;
+ --border: #e2e8f0;
+ --shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
+}
+
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: system-ui, -apple-system, sans-serif;
+ line-height: 1.5;
+ color: var(--text);
+ background: var(--bg);
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+}
+
+header {
+ text-align: center;
+ padding: 2rem 1rem;
+ background: white;
+ box-shadow: var(--shadow);
+}
+
+h1 {
+ font-size: 2.5rem;
+ background: linear-gradient(45deg, var(--primary), var(--primary-dark));
+ -webkit-background-clip: text;
+ color: transparent;
+ margin-bottom: 0.5rem;
+}
+
+main {
+ max-width: 1200px;
+ width: 90%;
+ margin: 2rem auto;
+ display: grid;
+ gap: 2rem;
+ flex: 1;
+}
+
+.drop-zone {
+ border: 3px dashed var(--border);
+ border-radius: 1rem;
+ padding: 2rem;
+ text-align: center;
+ transition: all 0.3s ease;
+ background: white;
+}
+
+.drop-zone.drag-over {
+ border-color: var(--primary);
+ background: #f0f9ff;
+}
+
+.drop-zone-content {
+ color: var(--text);
+ cursor: pointer;
+}
+
+.drop-zone svg {
+ color: var(--primary);
+ margin-bottom: 1rem;
+}
+
+.layers-container, .preview-container {
+ background: white;
+ padding: 1.5rem;
+ border-radius: 1rem;
+ box-shadow: var(--shadow);
+}
+
+.layers-list {
+ list-style: none;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.layer-item {
+ display: flex;
+ align-items: center;
+ padding: 0.5rem;
+ border: 1px solid var(--border);
+ border-radius: 0.5rem;
+ justify-content: space-between;
+}
+
+canvas {
+ max-width: 100%;
+ height: auto;
+ border: 1px solid var(--border);
+ border-radius: 0.5rem;
+ margin: 1rem 0;
+}
+
+.button {
+ background: var(--primary);
+ color: white;
+ border: none;
+ padding: 0.75rem 1.5rem;
+ border-radius: 0.5rem;
+ cursor: pointer;
+ font-weight: 600;
+ transition: background 0.3s ease;
+}
+
+.button:hover {
+ background: var(--primary-dark);
+}
+
+.button:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+footer {
+ text-align: center;
+ padding: 2rem;
+ background: white;
+ box-shadow: var(--shadow);
+ margin-top: auto;
+}
+
+@media (max-width: 768px) {
+ main {
+ width: 95%;
+ }
+
+ h1 {
+ font-size: 2rem;
+ }
+}