hbf0421 commited on
Commit
4a5eddd
·
verified ·
1 Parent(s): fb39000

یک سایت بساز که بعد از اپلود عکس اون رو به تکه های A4 بخش کنه به طوری که با چسباندن تکه ها به همدیگر یک پوستر بزرگ بسازیم

Browse files
Files changed (6) hide show
  1. README.md +8 -5
  2. components/footer.js +101 -0
  3. components/navbar.js +71 -0
  4. index.html +118 -19
  5. script.js +274 -0
  6. style.css +55 -19
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Posterpuzzle Maker
3
- emoji: 🏃
4
- colorFrom: blue
5
- colorTo: blue
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
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
+ &copy; ${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
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
 
 
 
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
 
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
+ }