Spaces:
Running
Running
یک سایت بساز که بعد از اپلود عکس اون رو به تکه های A4 بخش کنه به طوری که با چسباندن تکه ها به همدیگر یک پوستر بزرگ بسازیم
Browse files- README.md +8 -5
- components/footer.js +101 -0
- components/navbar.js +71 -0
- index.html +118 -19
- script.js +274 -0
- style.css +55 -19
README.md
CHANGED
|
@@ -1,10 +1,13 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
|
|
|
|
|
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: PosterPuzzle Maker 🧩
|
| 3 |
+
colorFrom: red
|
| 4 |
+
colorTo: yellow
|
| 5 |
+
emoji: 🐳
|
| 6 |
sdk: static
|
| 7 |
pinned: false
|
| 8 |
+
tags:
|
| 9 |
+
- deepsite-v3
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Welcome to your new DeepSite project!
|
| 13 |
+
This project was created with [DeepSite](https://huggingface.co/deepsite).
|
components/footer.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomFooter extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
display: block;
|
| 8 |
+
width: 100%;
|
| 9 |
+
background-color: #1e293b;
|
| 10 |
+
color: white;
|
| 11 |
+
}
|
| 12 |
+
.container {
|
| 13 |
+
max-width: 1200px;
|
| 14 |
+
margin: 0 auto;
|
| 15 |
+
padding: 3rem 1.5rem;
|
| 16 |
+
}
|
| 17 |
+
.footer-content {
|
| 18 |
+
display: flex;
|
| 19 |
+
flex-direction: column;
|
| 20 |
+
gap: 2rem;
|
| 21 |
+
}
|
| 22 |
+
.footer-top {
|
| 23 |
+
display: grid;
|
| 24 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 25 |
+
gap: 2rem;
|
| 26 |
+
}
|
| 27 |
+
.footer-logo {
|
| 28 |
+
font-size: 1.5rem;
|
| 29 |
+
font-weight: 700;
|
| 30 |
+
color: white;
|
| 31 |
+
text-decoration: none;
|
| 32 |
+
display: flex;
|
| 33 |
+
align-items: center;
|
| 34 |
+
gap: 0.5rem;
|
| 35 |
+
margin-bottom: 1rem;
|
| 36 |
+
}
|
| 37 |
+
.footer-links {
|
| 38 |
+
display: flex;
|
| 39 |
+
flex-direction: column;
|
| 40 |
+
gap: 0.75rem;
|
| 41 |
+
}
|
| 42 |
+
.footer-link {
|
| 43 |
+
color: #cbd5e1;
|
| 44 |
+
text-decoration: none;
|
| 45 |
+
transition: color 0.2s;
|
| 46 |
+
}
|
| 47 |
+
.footer-link:hover {
|
| 48 |
+
color: white;
|
| 49 |
+
}
|
| 50 |
+
.footer-bottom {
|
| 51 |
+
border-top: 1px solid #334155;
|
| 52 |
+
padding-top: 2rem;
|
| 53 |
+
text-align: center;
|
| 54 |
+
color: #94a3b8;
|
| 55 |
+
font-size: 0.875rem;
|
| 56 |
+
}
|
| 57 |
+
@media (min-width: 768px) {
|
| 58 |
+
.footer-content {
|
| 59 |
+
gap: 4rem;
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
</style>
|
| 63 |
+
<footer>
|
| 64 |
+
<div class="container">
|
| 65 |
+
<div class="footer-content">
|
| 66 |
+
<div class="footer-top">
|
| 67 |
+
<div>
|
| 68 |
+
<a href="/" class="footer-logo">
|
| 69 |
+
<i data-feather="box"></i>
|
| 70 |
+
PosterPuzzle
|
| 71 |
+
</a>
|
| 72 |
+
<p class="text-slate-300">Create stunning large posters from your favorite images.</p>
|
| 73 |
+
</div>
|
| 74 |
+
<div>
|
| 75 |
+
<h4 class="font-medium text-lg mb-3">Quick Links</h4>
|
| 76 |
+
<div class="footer-links">
|
| 77 |
+
<a href="/" class="footer-link">Home</a>
|
| 78 |
+
<a href="#" class="footer-link">How it works</a>
|
| 79 |
+
<a href="#" class="footer-link">Tutorial</a>
|
| 80 |
+
</div>
|
| 81 |
+
</div>
|
| 82 |
+
<div>
|
| 83 |
+
<h4 class="font-medium text-lg mb-3">Resources</h4>
|
| 84 |
+
<div class="footer-links">
|
| 85 |
+
<a href="#" class="footer-link">Help Center</a>
|
| 86 |
+
<a href="#" class="footer-link">Privacy Policy</a>
|
| 87 |
+
<a href="#" class="footer-link">Terms of Service</a>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
<div class="footer-bottom">
|
| 92 |
+
© ${new Date().getFullYear()} PosterPuzzle Maker. All rights reserved.
|
| 93 |
+
</div>
|
| 94 |
+
</div>
|
| 95 |
+
</div>
|
| 96 |
+
</footer>
|
| 97 |
+
`;
|
| 98 |
+
}
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
customElements.define('custom-footer', CustomFooter);
|
components/navbar.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class CustomNavbar extends HTMLElement {
|
| 2 |
+
connectedCallback() {
|
| 3 |
+
this.attachShadow({ mode: 'open' });
|
| 4 |
+
this.shadowRoot.innerHTML = `
|
| 5 |
+
<style>
|
| 6 |
+
:host {
|
| 7 |
+
display: block;
|
| 8 |
+
width: 100%;
|
| 9 |
+
}
|
| 10 |
+
nav {
|
| 11 |
+
background-color: white;
|
| 12 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
| 13 |
+
}
|
| 14 |
+
.container {
|
| 15 |
+
max-width: 1200px;
|
| 16 |
+
margin: 0 auto;
|
| 17 |
+
padding: 1rem 1.5rem;
|
| 18 |
+
}
|
| 19 |
+
.navbar-content {
|
| 20 |
+
display: flex;
|
| 21 |
+
justify-content: space-between;
|
| 22 |
+
align-items: center;
|
| 23 |
+
}
|
| 24 |
+
.logo {
|
| 25 |
+
font-size: 1.5rem;
|
| 26 |
+
font-weight: 700;
|
| 27 |
+
color: #4f46e5;
|
| 28 |
+
text-decoration: none;
|
| 29 |
+
display: flex;
|
| 30 |
+
align-items: center;
|
| 31 |
+
gap: 0.5rem;
|
| 32 |
+
}
|
| 33 |
+
.nav-links {
|
| 34 |
+
display: flex;
|
| 35 |
+
gap: 1.5rem;
|
| 36 |
+
}
|
| 37 |
+
.nav-link {
|
| 38 |
+
color: #4b5563;
|
| 39 |
+
text-decoration: none;
|
| 40 |
+
font-weight: 500;
|
| 41 |
+
transition: color 0.2s;
|
| 42 |
+
}
|
| 43 |
+
.nav-link:hover {
|
| 44 |
+
color: #4f46e5;
|
| 45 |
+
}
|
| 46 |
+
@media (max-width: 768px) {
|
| 47 |
+
.nav-links {
|
| 48 |
+
display: none;
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
</style>
|
| 52 |
+
<nav>
|
| 53 |
+
<div class="container">
|
| 54 |
+
<div class="navbar-content">
|
| 55 |
+
<a href="/" class="logo">
|
| 56 |
+
<i data-feather="box"></i>
|
| 57 |
+
PosterPuzzle
|
| 58 |
+
</a>
|
| 59 |
+
<div class="nav-links">
|
| 60 |
+
<a href="/" class="nav-link">Home</a>
|
| 61 |
+
<a href="#" class="nav-link">How it works</a>
|
| 62 |
+
<a href="#" class="nav-link">About</a>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
</div>
|
| 66 |
+
</nav>
|
| 67 |
+
`;
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
customElements.define('custom-navbar', CustomNavbar);
|
index.html
CHANGED
|
@@ -1,19 +1,118 @@
|
|
| 1 |
-
<!
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en" class="h-full">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>PosterPuzzle Maker - Create Giant Posters</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<script src="https://cdn.tailwindcss.com"></script>
|
| 9 |
+
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
| 10 |
+
<script src="https://unpkg.com/feather-icons"></script>
|
| 11 |
+
<script src="components/navbar.js"></script>
|
| 12 |
+
<script src="components/footer.js"></script>
|
| 13 |
+
</head>
|
| 14 |
+
<body class="min-h-screen flex flex-col bg-gray-50">
|
| 15 |
+
<custom-navbar></custom-navbar>
|
| 16 |
+
|
| 17 |
+
<main class="flex-grow container mx-auto px-4 py-8">
|
| 18 |
+
<div class="max-w-4xl mx-auto">
|
| 19 |
+
<div class="text-center mb-12">
|
| 20 |
+
<h1 class="text-4xl font-bold text-gray-800 mb-3">Create Your Giant Poster</h1>
|
| 21 |
+
<p class="text-lg text-gray-600">Upload an image and we'll split it into A4 pages for you to print and assemble</p>
|
| 22 |
+
</div>
|
| 23 |
+
|
| 24 |
+
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
|
| 25 |
+
<div class="flex flex-col md:flex-row gap-6">
|
| 26 |
+
<div class="w-full md:w-1/2">
|
| 27 |
+
<div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center transition hover:border-indigo-400">
|
| 28 |
+
<div id="drop-zone" class="cursor-pointer">
|
| 29 |
+
<i data-feather="upload" class="w-12 h-12 text-indigo-500 mx-auto mb-3"></i>
|
| 30 |
+
<h3 class="text-lg font-medium text-gray-700 mb-2">Upload Your Image</h3>
|
| 31 |
+
<p class="text-gray-500 mb-3">Drag & drop or click to select</p>
|
| 32 |
+
<input type="file" id="file-input" accept="image/*" class="hidden">
|
| 33 |
+
<button id="upload-btn" class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition">
|
| 34 |
+
Select Image
|
| 35 |
+
</button>
|
| 36 |
+
</div>
|
| 37 |
+
</div>
|
| 38 |
+
|
| 39 |
+
<div class="mt-6">
|
| 40 |
+
<label for="page-count" class="block text-sm font-medium text-gray-700 mb-2">Number of A4 Pages</label>
|
| 41 |
+
<div class="flex items-center gap-4">
|
| 42 |
+
<input type="range" id="page-count" min="1" max="16" value="4" class="w-full">
|
| 43 |
+
<span id="page-count-value" class="font-medium text-gray-800">4</span>
|
| 44 |
+
</div>
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<div class="mt-6">
|
| 48 |
+
<label for="orientation" class="block text-sm font-medium text-gray-700 mb-2">Orientation</label>
|
| 49 |
+
<div class="flex gap-4">
|
| 50 |
+
<button id="portrait" class="orientation-btn active bg-indigo-100 text-indigo-700 px-4 py-2 rounded-lg">
|
| 51 |
+
Portrait
|
| 52 |
+
</button>
|
| 53 |
+
<button id="landscape" class="orientation-btn px-4 py-2 rounded-lg border border-gray-300">
|
| 54 |
+
Landscape
|
| 55 |
+
</button>
|
| 56 |
+
</div>
|
| 57 |
+
</div>
|
| 58 |
+
|
| 59 |
+
<div class="mt-6 hidden" id="preview-controls">
|
| 60 |
+
<button id="generate-btn" class="w-full bg-indigo-600 text-white px-6 py-3 rounded-lg hover:bg-indigo-700 transition font-medium">
|
| 61 |
+
Generate Poster Pages
|
| 62 |
+
</button>
|
| 63 |
+
</div>
|
| 64 |
+
</div>
|
| 65 |
+
|
| 66 |
+
<div class="w-full md:w-1/2">
|
| 67 |
+
<div class="border border-gray-200 rounded-lg overflow-hidden bg-gray-100 flex items-center justify-center" style="min-height: 300px;">
|
| 68 |
+
<div id="image-preview-container" class="hidden w-full h-full flex items-center justify-center">
|
| 69 |
+
<img id="image-preview" class="max-w-full max-h-full object-contain">
|
| 70 |
+
</div>
|
| 71 |
+
<div id="empty-state" class="text-center p-6">
|
| 72 |
+
<i data-feather="image" class="w-12 h-12 text-gray-400 mx-auto mb-3"></i>
|
| 73 |
+
<p class="text-gray-500">Your image will appear here</p>
|
| 74 |
+
</div>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
</div>
|
| 79 |
+
|
| 80 |
+
<div id="result-section" class="hidden">
|
| 81 |
+
<div class="flex justify-between items-center mb-4">
|
| 82 |
+
<h2 class="text-2xl font-bold text-gray-800">Your Poster Pages</h2>
|
| 83 |
+
<button id="download-all" class="flex items-center gap-2 bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700 transition">
|
| 84 |
+
<i data-feather="download"></i>
|
| 85 |
+
Download All
|
| 86 |
+
</button>
|
| 87 |
+
</div>
|
| 88 |
+
|
| 89 |
+
<div id="poster-pages" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
| 90 |
+
<!-- Generated pages will appear here -->
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
<div class="mt-8 bg-yellow-50 border-l-4 border-yellow-400 p-4">
|
| 94 |
+
<div class="flex">
|
| 95 |
+
<div class="flex-shrink-0">
|
| 96 |
+
<i data-feather="alert-triangle" class="h-5 w-5 text-yellow-400"></i>
|
| 97 |
+
</div>
|
| 98 |
+
<div class="ml-3">
|
| 99 |
+
<p class="text-sm text-yellow-700">
|
| 100 |
+
<strong>Printing instructions:</strong> Print all pages at 100% scale (no scaling).
|
| 101 |
+
Trim the edges as indicated and assemble them together to create your giant poster.
|
| 102 |
+
</p>
|
| 103 |
+
</div>
|
| 104 |
+
</div>
|
| 105 |
+
</div>
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
</main>
|
| 109 |
+
|
| 110 |
+
<custom-footer></custom-footer>
|
| 111 |
+
|
| 112 |
+
<script src="script.js"></script>
|
| 113 |
+
<script>
|
| 114 |
+
feather.replace();
|
| 115 |
+
</script>
|
| 116 |
+
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
| 117 |
+
</body>
|
| 118 |
+
</html>
|
script.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
document.addEventListener('DOMContentLoaded', function() {
|
| 2 |
+
// DOM elements
|
| 3 |
+
const fileInput = document.getElementById('file-input');
|
| 4 |
+
const uploadBtn = document.getElementById('upload-btn');
|
| 5 |
+
const dropZone = document.getElementById('drop-zone');
|
| 6 |
+
const imagePreview = document.getElementById('image-preview');
|
| 7 |
+
const imagePreviewContainer = document.getElementById('image-preview-container');
|
| 8 |
+
const emptyState = document.getElementById('empty-state');
|
| 9 |
+
const pageCountInput = document.getElementById('page-count');
|
| 10 |
+
const pageCountValue = document.getElementById('page-count-value');
|
| 11 |
+
const portraitBtn = document.getElementById('portrait');
|
| 12 |
+
const landscapeBtn = document.getElementById('landscape');
|
| 13 |
+
const previewControls = document.getElementById('preview-controls');
|
| 14 |
+
const generateBtn = document.getElementById('generate-btn');
|
| 15 |
+
const resultSection = document.getElementById('result-section');
|
| 16 |
+
const posterPages = document.getElementById('poster-pages');
|
| 17 |
+
const downloadAllBtn = document.getElementById('download-all');
|
| 18 |
+
|
| 19 |
+
// Current image and settings
|
| 20 |
+
let currentImage = null;
|
| 21 |
+
let currentOrientation = 'portrait';
|
| 22 |
+
let currentPageCount = 4;
|
| 23 |
+
|
| 24 |
+
// Event listeners
|
| 25 |
+
uploadBtn.addEventListener('click', () => fileInput.click());
|
| 26 |
+
fileInput.addEventListener('change', handleFileSelect);
|
| 27 |
+
dropZone.addEventListener('dragover', handleDragOver);
|
| 28 |
+
dropZone.addEventListener('dragleave', handleDragLeave);
|
| 29 |
+
dropZone.addEventListener('drop', handleDrop);
|
| 30 |
+
pageCountInput.addEventListener('input', updatePageCount);
|
| 31 |
+
portraitBtn.addEventListener('click', () => setOrientation('portrait'));
|
| 32 |
+
landscapeBtn.addEventListener('click', () => setOrientation('landscape'));
|
| 33 |
+
generateBtn.addEventListener('click', generatePosterPages);
|
| 34 |
+
downloadAllBtn.addEventListener('click', downloadAllPages);
|
| 35 |
+
|
| 36 |
+
// Update page count display
|
| 37 |
+
function updatePageCount() {
|
| 38 |
+
currentPageCount = parseInt(pageCountInput.value);
|
| 39 |
+
pageCountValue.textContent = currentPageCount;
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// Set orientation
|
| 43 |
+
function setOrientation(orientation) {
|
| 44 |
+
currentOrientation = orientation;
|
| 45 |
+
if (orientation === 'portrait') {
|
| 46 |
+
portraitBtn.classList.add('active', 'bg-indigo-100', 'text-indigo-700');
|
| 47 |
+
portraitBtn.classList.remove('border', 'border-gray-300');
|
| 48 |
+
landscapeBtn.classList.remove('active', 'bg-indigo-100', 'text-indigo-700');
|
| 49 |
+
landscapeBtn.classList.add('border', 'border-gray-300');
|
| 50 |
+
} else {
|
| 51 |
+
landscapeBtn.classList.add('active', 'bg-indigo-100', 'text-indigo-700');
|
| 52 |
+
landscapeBtn.classList.remove('border', 'border-gray-300');
|
| 53 |
+
portraitBtn.classList.remove('active', 'bg-indigo-100', 'text-indigo-700');
|
| 54 |
+
portraitBtn.classList.add('border', 'border-gray-300');
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Handle file selection
|
| 59 |
+
function handleFileSelect(e) {
|
| 60 |
+
const file = e.target.files[0];
|
| 61 |
+
if (file && file.type.match('image.*')) {
|
| 62 |
+
processImageFile(file);
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
// Handle drag over
|
| 67 |
+
function handleDragOver(e) {
|
| 68 |
+
e.preventDefault();
|
| 69 |
+
e.stopPropagation();
|
| 70 |
+
dropZone.classList.add('highlight');
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// Handle drag leave
|
| 74 |
+
function handleDragLeave(e) {
|
| 75 |
+
e.preventDefault();
|
| 76 |
+
e.stopPropagation();
|
| 77 |
+
dropZone.classList.remove('highlight');
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// Handle drop
|
| 81 |
+
function handleDrop(e) {
|
| 82 |
+
e.preventDefault();
|
| 83 |
+
e.stopPropagation();
|
| 84 |
+
dropZone.classList.remove('highlight');
|
| 85 |
+
|
| 86 |
+
const file = e.dataTransfer.files[0];
|
| 87 |
+
if (file && file.type.match('image.*')) {
|
| 88 |
+
processImageFile(file);
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
// Process image file
|
| 93 |
+
function processImageFile(file) {
|
| 94 |
+
const reader = new FileReader();
|
| 95 |
+
reader.onload = function(e) {
|
| 96 |
+
currentImage = new Image();
|
| 97 |
+
currentImage.onload = function() {
|
| 98 |
+
showPreview();
|
| 99 |
+
};
|
| 100 |
+
currentImage.src = e.target.result;
|
| 101 |
+
};
|
| 102 |
+
reader.readAsDataURL(file);
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// Show preview
|
| 106 |
+
function showPreview() {
|
| 107 |
+
imagePreview.src = currentImage.src;
|
| 108 |
+
imagePreviewContainer.classList.remove('hidden');
|
| 109 |
+
emptyState.classList.add('hidden');
|
| 110 |
+
previewControls.classList.remove('hidden');
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
// Generate poster pages
|
| 114 |
+
function generatePosterPages() {
|
| 115 |
+
if (!currentImage) return;
|
| 116 |
+
|
| 117 |
+
// Clear previous results
|
| 118 |
+
posterPages.innerHTML = '';
|
| 119 |
+
resultSection.classList.remove('hidden');
|
| 120 |
+
|
| 121 |
+
// Calculate dimensions for each page
|
| 122 |
+
const aspectRatio = currentImage.width / currentImage.height;
|
| 123 |
+
let cols, rows;
|
| 124 |
+
|
| 125 |
+
if (currentOrientation === 'portrait') {
|
| 126 |
+
// For portrait orientation, we'll split vertically first
|
| 127 |
+
cols = Math.ceil(Math.sqrt(currentPageCount * (210 / 297))); // A4 ratio
|
| 128 |
+
rows = Math.ceil(currentPageCount / cols);
|
| 129 |
+
} else {
|
| 130 |
+
// For landscape orientation, we'll split horizontally first
|
| 131 |
+
rows = Math.ceil(Math.sqrt(currentPageCount * (297 / 210))); // A4 ratio
|
| 132 |
+
cols = Math.ceil(currentPageCount / rows);
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
// Create canvas for the entire image
|
| 136 |
+
const canvas = document.createElement('canvas');
|
| 137 |
+
const ctx = canvas.getContext('2d');
|
| 138 |
+
|
| 139 |
+
// Size canvas to maintain aspect ratio
|
| 140 |
+
if (currentOrientation === 'portrait') {
|
| 141 |
+
canvas.width = cols * 210; // A4 width in mm (approximate)
|
| 142 |
+
canvas.height = (canvas.width / aspectRatio);
|
| 143 |
+
|
| 144 |
+
// If height is less than needed for rows, adjust
|
| 145 |
+
const neededHeight = rows * 297;
|
| 146 |
+
if (canvas.height < neededHeight) {
|
| 147 |
+
canvas.height = neededHeight;
|
| 148 |
+
canvas.width = canvas.height * aspectRatio;
|
| 149 |
+
}
|
| 150 |
+
} else {
|
| 151 |
+
canvas.height = rows * 210; // A4 height in mm (approximate)
|
| 152 |
+
canvas.width = canvas.height * aspectRatio;
|
| 153 |
+
|
| 154 |
+
// If width is less than needed for cols, adjust
|
| 155 |
+
const neededWidth = cols * 297;
|
| 156 |
+
if (canvas.width < neededWidth) {
|
| 157 |
+
canvas.width = neededWidth;
|
| 158 |
+
canvas.height = canvas.width / aspectRatio;
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
// Draw the image on canvas
|
| 163 |
+
ctx.drawImage(currentImage, 0, 0, canvas.width, canvas.height);
|
| 164 |
+
|
| 165 |
+
// Calculate each page's dimensions
|
| 166 |
+
const pageWidth = canvas.width / cols;
|
| 167 |
+
const pageHeight = canvas.height / rows;
|
| 168 |
+
|
| 169 |
+
// Create individual pages
|
| 170 |
+
for (let row = 0; row < rows; row++) {
|
| 171 |
+
for (let col = 0; col < cols; col++) {
|
| 172 |
+
const pageIndex = row * cols + col;
|
| 173 |
+
if (pageIndex >= currentPageCount) break;
|
| 174 |
+
|
| 175 |
+
// Create page canvas
|
| 176 |
+
const pageCanvas = document.createElement('canvas');
|
| 177 |
+
pageCanvas.width = 2480; // A4 at 300dpi
|
| 178 |
+
pageCanvas.height = 3508;
|
| 179 |
+
const pageCtx = pageCanvas.getContext('2d');
|
| 180 |
+
|
| 181 |
+
// Calculate source dimensions
|
| 182 |
+
const sx = col * pageWidth;
|
| 183 |
+
const sy = row * pageHeight;
|
| 184 |
+
const sw = pageWidth;
|
| 185 |
+
const sh = pageHeight;
|
| 186 |
+
|
| 187 |
+
// Draw the portion of the image on the page
|
| 188 |
+
pageCtx.drawImage(
|
| 189 |
+
canvas,
|
| 190 |
+
sx, sy, sw, sh,
|
| 191 |
+
0, 0, pageCanvas.width, pageCanvas.height
|
| 192 |
+
);
|
| 193 |
+
|
| 194 |
+
// Add cut lines and page number
|
| 195 |
+
addPageMarkings(pageCtx, pageCanvas.width, pageCanvas.height, pageIndex + 1);
|
| 196 |
+
|
| 197 |
+
// Create page element
|
| 198 |
+
const pageElement = document.createElement('div');
|
| 199 |
+
pageElement.className = 'poster-page relative';
|
| 200 |
+
|
| 201 |
+
// Create download button
|
| 202 |
+
const downloadBtn = document.createElement('button');
|
| 203 |
+
downloadBtn.className = 'absolute top-2 left-2 bg-white bg-opacity-90 p-2 rounded-full shadow hover:bg-opacity-100 transition';
|
| 204 |
+
downloadBtn.innerHTML = '<i data-feather="download" class="w-4 h-4"></i>';
|
| 205 |
+
downloadBtn.onclick = () => downloadPage(pageCanvas, pageIndex + 1);
|
| 206 |
+
|
| 207 |
+
// Add image and button to page
|
| 208 |
+
const img = document.createElement('img');
|
| 209 |
+
img.src = pageCanvas.toDataURL('image/jpeg', 0.9);
|
| 210 |
+
img.className = 'w-full h-auto';
|
| 211 |
+
|
| 212 |
+
pageElement.appendChild(img);
|
| 213 |
+
pageElement.appendChild(downloadBtn);
|
| 214 |
+
posterPages.appendChild(pageElement);
|
| 215 |
+
}
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
feather.replace();
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
// Add page markings (cut lines and page numbers)
|
| 222 |
+
function addPageMarkings(ctx, width, height, pageNumber) {
|
| 223 |
+
// Cut lines (5mm from edges)
|
| 224 |
+
const cutMargin = 59; // 5mm at 300dpi (5/25.4*300)
|
| 225 |
+
|
| 226 |
+
ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
|
| 227 |
+
ctx.lineWidth = 2;
|
| 228 |
+
ctx.setLineDash([5, 5]);
|
| 229 |
+
|
| 230 |
+
// Top and bottom lines
|
| 231 |
+
ctx.beginPath();
|
| 232 |
+
ctx.moveTo(cutMargin, cutMargin);
|
| 233 |
+
ctx.lineTo(width - cutMargin, cutMargin);
|
| 234 |
+
ctx.moveTo(cutMargin, height - cutMargin);
|
| 235 |
+
ctx.lineTo(width - cutMargin, height - cutMargin);
|
| 236 |
+
ctx.stroke();
|
| 237 |
+
|
| 238 |
+
// Left and right lines
|
| 239 |
+
ctx.beginPath();
|
| 240 |
+
ctx.moveTo(cutMargin, cutMargin);
|
| 241 |
+
ctx.lineTo(cutMargin, height - cutMargin);
|
| 242 |
+
ctx.moveTo(width - cutMargin, cutMargin);
|
| 243 |
+
ctx.lineTo(width - cutMargin, height - cutMargin);
|
| 244 |
+
ctx.stroke();
|
| 245 |
+
|
| 246 |
+
// Page number
|
| 247 |
+
ctx.font = 'bold 40px Arial';
|
| 248 |
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
| 249 |
+
ctx.textAlign = 'right';
|
| 250 |
+
ctx.textBaseline = 'bottom';
|
| 251 |
+
ctx.fillText(pageNumber.toString(), width - 20, height - 20);
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
// Download a single page
|
| 255 |
+
function downloadPage(canvas, pageNumber) {
|
| 256 |
+
const link = document.createElement('a');
|
| 257 |
+
link.download = `poster-page-${pageNumber}.jpg`;
|
| 258 |
+
link.href = canvas.toDataURL('image/jpeg', 0.9);
|
| 259 |
+
link.click();
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
// Download all pages
|
| 263 |
+
function downloadAllPages() {
|
| 264 |
+
const pages = posterPages.querySelectorAll('.poster-page img');
|
| 265 |
+
pages.forEach((img, index) => {
|
| 266 |
+
const link = document.createElement('a');
|
| 267 |
+
link.download = `poster-page-${index + 1}.jpg`;
|
| 268 |
+
link.href = img.src;
|
| 269 |
+
document.body.appendChild(link);
|
| 270 |
+
link.click();
|
| 271 |
+
document.body.removeChild(link);
|
| 272 |
+
});
|
| 273 |
+
}
|
| 274 |
+
});
|
style.css
CHANGED
|
@@ -1,28 +1,64 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
| 4 |
}
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
margin-top: 0;
|
| 9 |
}
|
| 10 |
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
}
|
| 17 |
|
| 18 |
-
.
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
|
| 26 |
-
.
|
| 27 |
-
|
|
|
|
|
|
|
| 28 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Custom styles to enhance the Tailwind base */
|
| 2 |
+
#drop-zone.highlight {
|
| 3 |
+
border-color: #6366f1;
|
| 4 |
+
background-color: #f0f5ff;
|
| 5 |
}
|
| 6 |
|
| 7 |
+
.orientation-btn.active {
|
| 8 |
+
border: 2px solid #6366f1;
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
+
.poster-page {
|
| 12 |
+
position: relative;
|
| 13 |
+
background-color: white;
|
| 14 |
+
overflow: hidden;
|
| 15 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
| 16 |
}
|
| 17 |
|
| 18 |
+
.poster-page::before {
|
| 19 |
+
content: "";
|
| 20 |
+
position: absolute;
|
| 21 |
+
top: 0;
|
| 22 |
+
left: 0;
|
| 23 |
+
right: 0;
|
| 24 |
+
bottom: 0;
|
| 25 |
+
border: 1px dashed rgba(0, 0, 0, 0.2);
|
| 26 |
+
pointer-events: none;
|
| 27 |
}
|
| 28 |
|
| 29 |
+
.cut-line {
|
| 30 |
+
position: absolute;
|
| 31 |
+
background-color: rgba(255, 0, 0, 0.5);
|
| 32 |
+
z-index: 10;
|
| 33 |
}
|
| 34 |
+
|
| 35 |
+
.page-number {
|
| 36 |
+
position: absolute;
|
| 37 |
+
bottom: 4px;
|
| 38 |
+
right: 4px;
|
| 39 |
+
background-color: rgba(255, 255, 255, 0.8);
|
| 40 |
+
padding: 2px 6px;
|
| 41 |
+
border-radius: 4px;
|
| 42 |
+
font-size: 12px;
|
| 43 |
+
font-weight: bold;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
@media print {
|
| 47 |
+
body * {
|
| 48 |
+
visibility: hidden;
|
| 49 |
+
}
|
| 50 |
+
.poster-page, .poster-page * {
|
| 51 |
+
visibility: visible;
|
| 52 |
+
}
|
| 53 |
+
.poster-page {
|
| 54 |
+
position: absolute;
|
| 55 |
+
left: 0;
|
| 56 |
+
top: 0;
|
| 57 |
+
width: 100%;
|
| 58 |
+
height: 100%;
|
| 59 |
+
margin: 0;
|
| 60 |
+
padding: 0;
|
| 61 |
+
box-shadow: none;
|
| 62 |
+
page-break-after: always;
|
| 63 |
+
}
|
| 64 |
+
}
|