Changes to stackpng.scroll.pub

ffff:76.132.245.71
ffff:76.132.245.71
10 days ago
updated script.js
script.js
Changed around line 18: document.addEventListener("DOMContentLoaded", () => {
+ console.log(`Loaded image: ${file.name}`);
+ console.log(`Dimensions: ${img.naturalWidth} x ${img.naturalHeight}`);
+
+ width: img.naturalWidth,
+ height: img.naturalHeight,
Changed around line 42: document.addEventListener("DOMContentLoaded", () => {
- ${image.name}
+ ${image.name} (${image.width}x${image.height})
Changed around line 63: document.addEventListener("DOMContentLoaded", () => {
- 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;
- 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;
ffff:76.132.245.71
ffff:76.132.245.71
10 days ago
updated script.js
script.js
Changed around line 67: document.addEventListener("DOMContentLoaded", () => {
+ images,
ffff:76.132.245.71
ffff:76.132.245.71
10 days ago
updated script.js
script.js
Changed around line 66: document.addEventListener("DOMContentLoaded", () => {
- console.log(totalHeight, totalWidth);
+ console.log(
+ images.map((image) => image.height),
+ totalHeight,
+ totalWidth,
+ );
ffff:76.132.245.71
ffff:76.132.245.71
10 days ago
updated script.js
script.js
Changed around line 66: document.addEventListener("DOMContentLoaded", () => {
+ console.log(totalHeight, totalWidth);
ffff:76.132.245.71
ffff:76.132.245.71
10 days ago
updated script.js
script.js
Changed around line 1
- 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");
+
- 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) => {
- name: file.name
+ name: file.name,
Changed around line 32: document.addEventListener('DOMContentLoaded', () => {
- layersList.innerHTML = '';
+ layersList.innerHTML = "";
- const li = document.createElement('li');
- li.className = 'layer-item';
+ const li = document.createElement("li");
+ li.className = "layer-item";
- ${index > 0 ? `` : ''}
- ${index < images.length - 1 ? `` : ''}
+ ${index > 0 ? `` : ""}
+ ${index < images.length - 1 ? `` : ""}
-
+
Changed around line 58: document.addEventListener('DOMContentLoaded', () => {
+ let totalHeight = images
+ .map((image) => image.height)
+ .reduce((a, b) => a + b, 0);
+ let totalWidth = Math.max(images.map((img) => img.width));
+
- canvas.width = firstImage.width;
- canvas.height = firstImage.height;
-
+ canvas.width = totalWidth;
+ canvas.height = totalHeight;
+
- 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;
Changed around line 90: document.addEventListener('DOMContentLoaded', () => {
- dropZone.addEventListener('click', () => fileInput.click());
-
- fileInput.addEventListener('change', (e) => {
+ dropZone.addEventListener("click", () => fileInput.click());
+
+ fileInput.addEventListener("change", (e) => {
- fileInput.value = '';
+ fileInput.value = "";
- dropZone.addEventListener('dragover', (e) => {
+ dropZone.addEventListener("dragover", (e) => {
- 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) => {
- dropZone.classList.remove('drag-over');
+ dropZone.classList.remove("drag-over");
- 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");
root
root
10 days ago
Initial commit
body.html
Changed around line 1
+
+

StackPNG

+

Drop PNG images to stack and merge them together

+
+
+
+
+
+
+
+
+

Drop PNG images here or click to select

+
+
+
+
+
+

Image Layers

+
    +
    +
    +
    +

    Preview

    +
    +
    +
    +
    +
    +
    +

    Built with vanilla JavaScript | No server-side processing

    +
    index.scroll
    Changed around line 1
    + buildHtml
    + baseUrl https://stackpng.scroll.pub
    + metaTags
    + editButton /edit.html
    + title StackPNG - Layer & Merge PNG Images
    + style.css
    + body.html
    + script.js
    prompt.json
    Changed around line 1
    + {
    + "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
    \n

    StackPNG

    \n

    Drop PNG images to stack and merge them together

    \n
    \n\n
    \n
    \n
    \n \n \n \n

    Drop PNG images here or click to select

    \n \n
    \n
    \n \n
    \n

    Image Layers

    \n
      \n
      \n \n
      \n

      Preview

      \n \n \n
      \n
      \n\n
      \n

      Built with vanilla JavaScript | No server-side processing

      \n
      \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 ${image.name}\n
      \n ${index > 0 ? `` : ''}\n ${index < images.length - 1 ? `` : ''}\n \n
      \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
      + }
      + }
      readme.scroll
      Changed around line 1
      + # 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.
      script.js
      Changed around line 1
      + 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 = `
      + ${image.name}
      +
      + ${index > 0 ? `` : ''}
      + ${index < images.length - 1 ? `` : ''}
      +
      +
      + `;
      + 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();
      + });
      + });
      style.css
      Changed around line 1
      + :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;
      + }
      + }