Sbui503 commited on
Commit
7774eb1
·
verified ·
1 Parent(s): 8926d54

https://www.uprinting.com With admin panel to change pricing quote

Browse files
Files changed (5) hide show
  1. README.md +9 -6
  2. admin.html +243 -0
  3. admin.js +772 -0
  4. index.html +480 -19
  5. script.js +564 -0
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Printpro Custom Printing Quote Calculator G4z3q
3
- emoji: 📚
4
- colorFrom: pink
5
- colorTo: pink
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: PrintPro - Custom Printing & Quote Calculator 🖨️
3
+ colorFrom: yellow
4
+ colorTo: blue
 
5
  sdk: static
6
+ emoji: 🌈
7
+ tags:
8
+ - deepsite-v4
9
  ---
10
 
11
+ # PrintPro - Custom Printing & Quote Calculator 🖨️
12
+
13
+ This project has been created with [DeepSite](https://deepsite.hf.co) AI Vibe Coding.
admin.html ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>PrintPro Admin - Pricing Management</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
11
+ <script>
12
+ tailwind.config = {
13
+ theme: {
14
+ extend: {
15
+ fontFamily: {
16
+ 'sans': ['Inter', 'system-ui', 'sans-serif'],
17
+ },
18
+ colors: {
19
+ brand: {
20
+ 50: '#eff6ff',
21
+ 100: '#dbeafe',
22
+ 200: '#bfdbfe',
23
+ 300: '#93c5fd',
24
+ 400: '#60a5fa',
25
+ 500: '#3b82f6',
26
+ 600: '#2563eb',
27
+ 700: '#1d4ed8',
28
+ 800: '#1e40af',
29
+ 900: '#1e3a8a',
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ </script>
36
+ <style>
37
+ * { scroll-behavior: smooth; }
38
+ .toast { animation: slideIn 0.4s ease, fadeOut 0.4s ease 2.6s forwards; }
39
+ @keyframes slideIn {
40
+ from { transform: translateX(120%); opacity: 0; }
41
+ to { transform: translateX(0); opacity: 1; }
42
+ }
43
+ @keyframes fadeOut {
44
+ from { opacity: 1; }
45
+ to { opacity: 0; }
46
+ }
47
+ .modal-overlay { animation: fadeInOverlay 0.25s ease; }
48
+ @keyframes fadeInOverlay {
49
+ from { opacity: 0; }
50
+ to { opacity: 1; }
51
+ }
52
+ .modal-content { animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
53
+ @keyframes slideUp {
54
+ from { transform: translateY(40px); opacity: 0; }
55
+ to { transform: translateY(0); opacity: 1; }
56
+ }
57
+ input[type="number"]::-webkit-inner-spin-button,
58
+ input[type="number"]::-webkit-outer-spin-button {
59
+ opacity: 1;
60
+ }
61
+ .tier-row { transition: all 0.2s ease; }
62
+ .tier-row:hover { background-color: #f8fafc; }
63
+ </style>
64
+ </head>
65
+ <body class="font-sans bg-gray-100 text-gray-800 antialiased min-h-screen">
66
+
67
+ <!-- ============ LOGIN SCREEN ============ -->
68
+ <div id="loginScreen" class="min-h-screen flex items-center justify-center bg-gradient-to-br from-gray-900 via-brand-900 to-gray-800 p-4">
69
+ <div class="w-full max-w-md">
70
+ <div class="bg-white rounded-2xl shadow-2xl p-8">
71
+ <div class="text-center mb-8">
72
+ <div class="w-16 h-16 bg-brand-600 rounded-2xl flex items-center justify-center mx-auto mb-4">
73
+ <i data-lucide="shield" class="text-white w-8 h-8"></i>
74
+ </div>
75
+ <h1 class="text-2xl font-extrabold text-gray-900">Admin Panel</h1>
76
+ <p class="text-gray-500 text-sm mt-1">Manage your printing pricing</p>
77
+ </div>
78
+ <form id="loginForm" class="space-y-5">
79
+ <div>
80
+ <label class="block text-sm font-semibold text-gray-700 mb-1.5">Username</label>
81
+ <div class="relative">
82
+ <i data-lucide="user" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i>
83
+ <input type="text" id="username" value="admin" required
84
+ class="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-brand-500 focus:border-brand-500 outline-none transition-all text-gray-900">
85
+ </div>
86
+ </div>
87
+ <div>
88
+ <label class="block text-sm font-semibold text-gray-700 mb-1.5">Password</label>
89
+ <div class="relative">
90
+ <i data-lucide="lock" class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400"></i>
91
+ <input type="password" id="password" value="admin123" required
92
+ class="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-brand-500 focus:border-brand-500 outline-none transition-all text-gray-900">
93
+ </div>
94
+ </div>
95
+ <p id="loginError" class="text-red-500 text-sm hidden text-center"></p>
96
+ <button type="submit"
97
+ class="w-full bg-brand-600 text-white font-bold py-3 rounded-xl hover:bg-brand-700 transition-all shadow-lg hover:shadow-xl flex items-center justify-center gap-2">
98
+ <i data-lucide="log-in" class="w-5 h-5"></i> Sign In
99
+ </button>
100
+ </form>
101
+ <p class="text-xs text-gray-400 text-center mt-6">Default: admin / admin123</p>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <!-- ============ DASHBOARD ============ -->
107
+ <div id="dashboard" class="hidden min-h-screen">
108
+ <!-- Top Nav -->
109
+ <nav class="bg-white shadow-sm border-b border-gray-200 sticky top-0 z-40">
110
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
111
+ <div class="flex items-center justify-between h-16">
112
+ <div class="flex items-center gap-3">
113
+ <div class="w-9 h-9 bg-brand-600 rounded-lg flex items-center justify-center">
114
+ <i data-lucide="printer" class="text-white w-5 h-5"></i>
115
+ </div>
116
+ <span class="text-xl font-extrabold text-gray-900 tracking-tight">Print<span class="text-brand-600">Pro</span> <span class="text-xs bg-amber-100 text-amber-700 px-2 py-0.5 rounded-full font-medium ml-1">Admin</span></span>
117
+ </div>
118
+ <div class="flex items-center gap-4">
119
+ <a href="index.html" class="text-sm text-gray-600 hover:text-brand-600 transition-colors flex items-center gap-1.5">
120
+ <i data-lucide="external-link" class="w-4 h-4"></i> View Site
121
+ </a>
122
+ <button onclick="logout()" class="text-sm text-red-600 hover:text-red-700 transition-colors flex items-center gap-1.5 font-medium">
123
+ <i data-lucide="log-out" class="w-4 h-4"></i> Logout
124
+ </button>
125
+ </div>
126
+ </div>
127
+ </div>
128
+ </nav>
129
+
130
+ <!-- Content -->
131
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
132
+ <!-- Header -->
133
+ <div class="flex flex-wrap items-center justify-between gap-4 mb-8">
134
+ <div>
135
+ <h1 class="text-2xl font-extrabold text-gray-900">Pricing Management</h1>
136
+ <p class="text-gray-500 text-sm mt-1">Manage base prices, options, quantity tiers, and turnaround pricing for all products.</p>
137
+ </div>
138
+ <div class="flex gap-3">
139
+ <button onclick="resetToDefault()"
140
+ class="px-4 py-2.5 bg-white border border-gray-300 rounded-xl text-sm font-semibold text-gray-700 hover:bg-gray-50 transition-all flex items-center gap-2">
141
+ <i data-lucide="rotate-ccw" class="w-4 h-4"></i> Reset to Defaults
142
+ </button>
143
+ <button onclick="saveAllPricing()"
144
+ class="px-5 py-2.5 bg-brand-600 text-white rounded-xl text-sm font-semibold hover:bg-brand-700 transition-all shadow-lg flex items-center gap-2">
145
+ <i data-lucide="save" class="w-4 h-4"></i> Save All Changes
146
+ </button>
147
+ </div>
148
+ </div>
149
+
150
+ <!-- Stats -->
151
+ <div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8">
152
+ <div class="bg-white rounded-xl p-5 shadow-sm border border-gray-100">
153
+ <div class="flex items-center gap-3">
154
+ <div class="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
155
+ <i data-lucide="package" class="w-5 h-5 text-blue-600"></i>
156
+ </div>
157
+ <div>
158
+ <p class="text-2xl font-extrabold text-gray-900" id="statProducts">8</p>
159
+ <p class="text-xs text-gray-500">Products</p>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ <div class="bg-white rounded-xl p-5 shadow-sm border border-gray-100">
164
+ <div class="flex items-center gap-3">
165
+ <div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center">
166
+ <i data-lucide="dollar-sign" class="w-5 h-5 text-green-600"></i>
167
+ </div>
168
+ <div>
169
+ <p class="text-2xl font-extrabold text-gray-900" id="statAvgPrice">--</p>
170
+ <p class="text-xs text-gray-500">Avg Base Price</p>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ <div class="bg-white rounded-xl p-5 shadow-sm border border-gray-100">
175
+ <div class="flex items-center gap-3">
176
+ <div class="w-10 h-10 bg-purple-100 rounded-lg flex items-center justify-center">
177
+ <i data-lucide="layers" class="w-5 h-5 text-purple-600"></i>
178
+ </div>
179
+ <div>
180
+ <p class="text-2xl font-extrabold text-gray-900" id="statTiers">--</p>
181
+ <p class="text-xs text-gray-500">Total Tiers</p>
182
+ </div>
183
+ </div>
184
+ </div>
185
+ <div class="bg-white rounded-xl p-5 shadow-sm border border-gray-100">
186
+ <div class="flex items-center gap-3">
187
+ <div class="w-10 h-10 bg-amber-100 rounded-lg flex items-center justify-center">
188
+ <i data-lucide="clock" class="w-5 h-5 text-amber-600"></i>
189
+ </div>
190
+ <div>
191
+ <p class="text-2xl font-extrabold text-gray-900" id="statLastSaved">Never</p>
192
+ <p class="text-xs text-gray-500">Last Saved</p>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </div>
197
+
198
+ <!-- Product Cards -->
199
+ <div id="productCards" class="grid gap-6">
200
+ <!-- Rendered by JS -->
201
+ </div>
202
+ </div>
203
+ </div>
204
+
205
+ <!-- ============ EDIT MODAL ============ -->
206
+ <div id="editModal" class="fixed inset-0 z-50 hidden items-center justify-center p-4 modal-overlay" style="background: rgba(0,0,0,0.5); backdrop-filter: blur(4px);">
207
+ <div class="bg-white rounded-2xl shadow-2xl w-full max-w-3xl max-h-[90vh] overflow-y-auto modal-content">
208
+ <div class="sticky top-0 bg-white border-b border-gray-100 px-6 py-4 rounded-t-2xl flex items-center justify-between z-10">
209
+ <h3 class="text-lg font-extrabold text-gray-900 flex items-center gap-2">
210
+ <i data-lucide="edit-3" class="w-5 h-5 text-brand-600"></i>
211
+ Edit: <span id="modalProductName" class="text-brand-600">--</span>
212
+ </h3>
213
+ <button onclick="closeModal()" class="p-2 hover:bg-gray-100 rounded-lg transition-colors">
214
+ <i data-lucide="x" class="w-5 h-5 text-gray-500"></i>
215
+ </button>
216
+ </div>
217
+ <div id="modalContent" class="p-6">
218
+ <!-- Rendered by JS -->
219
+ </div>
220
+ <div class="sticky bottom-0 bg-gray-50 border-t border-gray-100 px-6 py-4 rounded-b-2xl flex justify-end gap-3">
221
+ <button onclick="closeModal()" class="px-5 py-2.5 bg-white border border-gray-300 rounded-xl text-sm font-semibold text-gray-700 hover:bg-gray-50 transition-all">
222
+ Cancel
223
+ </button>
224
+ <button onclick="saveModalChanges()" class="px-5 py-2.5 bg-brand-600 text-white rounded-xl text-sm font-semibold hover:bg-brand-700 transition-all shadow-lg flex items-center gap-2">
225
+ <i data-lucide="check" class="w-4 h-4"></i> Save Changes
226
+ </button>
227
+ </div>
228
+ </div>
229
+ </div>
230
+
231
+ <!-- Toast Notification -->
232
+ <div id="toast" class="fixed top-6 right-6 z-[100] hidden bg-gray-900 text-white px-5 py-3 rounded-xl shadow-2xl flex items-center gap-2 text-sm font-medium toast">
233
+ <i data-lucide="check-circle" class="w-4 h-4 text-green-400"></i>
234
+ <span id="toastMsg">Saved!</span>
235
+ </div>
236
+
237
+ <script src="https://unpkg.com/lucide@latest"></script>
238
+ <script src="admin.js"></script>
239
+ <script>
240
+ lucide.createIcons();
241
+ </script>
242
+ </body>
243
+ </html>
admin.js ADDED
@@ -0,0 +1,772 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // =============================================
2
+ // PrintPro - Admin Panel Script
3
+ // =============================================
4
+
5
+ // --- DEFAULT PRICING (same as frontend) ---
6
+ const DEFAULT_PRICING = {
7
+ products: {
8
+ 'business-cards': {
9
+ name: 'Business Cards',
10
+ icon: 'credit-card',
11
+ image: 'http://static.photos/office/320x240/42',
12
+ basePrice: 19.00,
13
+ sizes: [
14
+ { id: '3.5x2', name: '3.5" x 2"', price: 0 },
15
+ { id: '3.5x2-rounded', name: '3.5" x 2" Rounded', price: 4 },
16
+ { id: '3.5x2-square', name: '3.5" x 3.5" Square', price: 6 }
17
+ ],
18
+ paperTypes: [
19
+ { id: '14pt-gloss', name: '14pt Gloss', price: 0 },
20
+ { id: '16pt-matte', name: '16pt Matte', price: 6 },
21
+ { id: '14pt-uncoated', name: '14pt Uncoated', price: 3 },
22
+ { id: '16pt-silk', name: '16pt Silk', price: 8 }
23
+ ],
24
+ finishing: [
25
+ { id: 'none', name: 'None', price: 0 },
26
+ { id: 'matte', name: 'Matte Coating', price: 5 },
27
+ { id: 'gloss', name: 'Gloss Coating', price: 5 },
28
+ { id: 'uv', name: 'UV Spot Gloss', price: 12 },
29
+ { id: 'foil', name: 'Foil Stamp', price: 18 }
30
+ ],
31
+ quantityTiers: [
32
+ { min: 50, max: 250, multiplier: 1.0, label: '50-250' },
33
+ { min: 251, max: 500, multiplier: 0.75, label: '251-500' },
34
+ { min: 501, max: 1000, multiplier: 0.55, label: '501-1K' },
35
+ { min: 1001, max: 2500, multiplier: 0.40, label: '1K-2.5K' },
36
+ { min: 2501, max: 5000, multiplier: 0.30, label: '2.5K-5K' },
37
+ { min: 5001, max: 10000, multiplier: 0.22, label: '5K-10K' }
38
+ ],
39
+ turnaround: [
40
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
41
+ { id: 'rush', name: 'Rush (3-4 days)', price: 15 },
42
+ { id: 'express', name: 'Express (1-2 days)', price: 30 }
43
+ ],
44
+ defaultQuantity: 500
45
+ },
46
+ 'flyers': {
47
+ name: 'Flyers',
48
+ icon: 'file-text',
49
+ image: 'http://static.photos/office/320x240/55',
50
+ basePrice: 35.00,
51
+ sizes: [
52
+ { id: '8.5x11', name: '8.5" x 11"', price: 0 },
53
+ { id: '5.5x8.5', name: '5.5" x 8.5"', price: -3 },
54
+ { id: '11x17', name: '11" x 17"', price: 8 }
55
+ ],
56
+ paperTypes: [
57
+ { id: '100lb-gloss', name: '100lb Gloss', price: 0 },
58
+ { id: '80lb-matte', name: '80lb Matte', price: -2 },
59
+ { id: '100lb-uncoated', name: '100lb Uncoated', price: 2 }
60
+ ],
61
+ finishing: [
62
+ { id: 'none', name: 'None', price: 0 },
63
+ { id: 'matte', name: 'Matte Coating', price: 8 },
64
+ { id: 'gloss', name: 'Gloss Coating', price: 8 }
65
+ ],
66
+ quantityTiers: [
67
+ { min: 50, max: 250, multiplier: 1.0, label: '50-250' },
68
+ { min: 251, max: 500, multiplier: 0.70, label: '251-500' },
69
+ { min: 501, max: 1000, multiplier: 0.50, label: '501-1K' },
70
+ { min: 1001, max: 5000, multiplier: 0.35, label: '1K-5K' },
71
+ { min: 5001, max: 10000, multiplier: 0.25, label: '5K-10K' }
72
+ ],
73
+ turnaround: [
74
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
75
+ { id: 'rush', name: 'Rush (3-4 days)', price: 20 },
76
+ { id: 'express', name: 'Express (1-2 days)', price: 40 }
77
+ ],
78
+ defaultQuantity: 250
79
+ },
80
+ 'brochures': {
81
+ name: 'Brochures',
82
+ icon: 'book-open',
83
+ image: 'http://static.photos/office/320x240/67',
84
+ basePrice: 55.00,
85
+ sizes: [
86
+ { id: '8.5x11-bi', name: '8.5"x11" Bi-Fold', price: 0 },
87
+ { id: '8.5x11-tri', name: '8.5"x11" Tri-Fold', price: 3 },
88
+ { id: '11x17-bi', name: '11"x17" Bi-Fold', price: 10 }
89
+ ],
90
+ paperTypes: [
91
+ { id: '100lb-gloss', name: '100lb Gloss', price: 0 },
92
+ { id: '80lb-matte', name: '80lb Matte', price: -3 },
93
+ { id: '100lb-silk', name: '100lb Silk', price: 5 }
94
+ ],
95
+ finishing: [
96
+ { id: 'none', name: 'None', price: 0 },
97
+ { id: 'matte', name: 'Matte Coating', price: 10 },
98
+ { id: 'gloss', name: 'Gloss Coating', price: 10 }
99
+ ],
100
+ quantityTiers: [
101
+ { min: 25, max: 100, multiplier: 1.0, label: '25-100' },
102
+ { min: 101, max: 250, multiplier: 0.70, label: '101-250' },
103
+ { min: 251, max: 500, multiplier: 0.52, label: '251-500' },
104
+ { min: 501, max: 1000, multiplier: 0.40, label: '501-1K' },
105
+ { min: 1001, max: 5000, multiplier: 0.30, label: '1K-5K' }
106
+ ],
107
+ turnaround: [
108
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
109
+ { id: 'rush', name: 'Rush (3-4 days)', price: 25 },
110
+ { id: 'express', name: 'Express (1-2 days)', price: 50 }
111
+ ],
112
+ defaultQuantity: 100
113
+ },
114
+ 'banners': {
115
+ name: 'Banners',
116
+ icon: 'flag',
117
+ image: 'http://static.photos/office/320x240/78',
118
+ basePrice: 45.00,
119
+ sizes: [
120
+ { id: '24x36', name: '24" x 36"', price: 0 },
121
+ { id: '36x48', name: '36" x 48"', price: 15 },
122
+ { id: '48x72', name: '48" x 72"', price: 35 }
123
+ ],
124
+ paperTypes: [
125
+ { id: 'vinyl', name: '13oz Vinyl', price: 0 },
126
+ { id: 'mesh', name: 'Mesh Vinyl', price: 8 },
127
+ { id: 'fabric', name: 'Fabric', price: 20 }
128
+ ],
129
+ finishing: [
130
+ { id: 'none', name: 'None', price: 0 },
131
+ { id: 'grommets', name: 'Grommets', price: 5 },
132
+ { id: 'pole-pockets', name: 'Pole Pockets', price: 10 }
133
+ ],
134
+ quantityTiers: [
135
+ { min: 1, max: 5, multiplier: 1.0, label: '1-5' },
136
+ { min: 6, max: 10, multiplier: 0.80, label: '6-10' },
137
+ { min: 11, max: 25, multiplier: 0.65, label: '11-25' },
138
+ { min: 26, max: 50, multiplier: 0.50, label: '26-50' }
139
+ ],
140
+ turnaround: [
141
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
142
+ { id: 'rush', name: 'Rush (3-4 days)', price: 20 },
143
+ { id: 'express', name: 'Express (1-2 days)', price: 45 }
144
+ ],
145
+ defaultQuantity: 1
146
+ },
147
+ 'stickers': {
148
+ name: 'Stickers & Labels',
149
+ icon: 'sticky-note',
150
+ image: 'http://static.photos/office/320x240/91',
151
+ basePrice: 25.00,
152
+ sizes: [
153
+ { id: '2x2', name: '2" x 2"', price: 0 },
154
+ { id: '3x3', name: '3" x 3"', price: 5 },
155
+ { id: '4x4', name: '4" x 4"', price: 8 },
156
+ { id: 'custom-die', name: 'Custom Die-Cut', price: 15 }
157
+ ],
158
+ paperTypes: [
159
+ { id: 'white-vinyl', name: 'White Vinyl', price: 0 },
160
+ { id: 'clear-vinyl', name: 'Clear Vinyl', price: 5 },
161
+ { id: 'metallic', name: 'Metallic', price: 10 }
162
+ ],
163
+ finishing: [
164
+ { id: 'none', name: 'None', price: 0 },
165
+ { id: 'matte-lam', name: 'Matte Laminate', price: 6 },
166
+ { id: 'gloss-lam', name: 'Gloss Laminate', price: 6 }
167
+ ],
168
+ quantityTiers: [
169
+ { min: 50, max: 250, multiplier: 1.0, label: '50-250' },
170
+ { min: 251, max: 500, multiplier: 0.70, label: '251-500' },
171
+ { min: 501, max: 1000, multiplier: 0.48, label: '501-1K' },
172
+ { min: 1001, max: 2500, multiplier: 0.35, label: '1K-2.5K' },
173
+ { min: 2501, max: 5000, multiplier: 0.25, label: '2.5K-5K' }
174
+ ],
175
+ turnaround: [
176
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
177
+ { id: 'rush', name: 'Rush (3-4 days)', price: 12 },
178
+ { id: 'express', name: 'Express (1-2 days)', price: 28 }
179
+ ],
180
+ defaultQuantity: 250
181
+ },
182
+ 'postcards': {
183
+ name: 'Postcards',
184
+ icon: 'mail',
185
+ image: 'http://static.photos/office/320x240/33',
186
+ basePrice: 28.00,
187
+ sizes: [
188
+ { id: '4x6', name: '4" x 6"', price: 0 },
189
+ { id: '5x7', name: '5" x 7"', price: 4 },
190
+ { id: '6x9', name: '6" x 9"', price: 7 },
191
+ { id: '6x11', name: '6" x 11"', price: 10 }
192
+ ],
193
+ paperTypes: [
194
+ { id: '14pt-gloss', name: '14pt Gloss', price: 0 },
195
+ { id: '16pt-matte', name: '16pt Matte', price: 5 },
196
+ { id: '14pt-uncoated', name: '14pt Uncoated', price: 2 }
197
+ ],
198
+ finishing: [
199
+ { id: 'none', name: 'None', price: 0 },
200
+ { id: 'matte', name: 'Matte Coating', price: 5 },
201
+ { id: 'gloss', name: 'Gloss Coating', price: 5 },
202
+ { id: 'uv-front', name: 'UV Front Only', price: 8 }
203
+ ],
204
+ quantityTiers: [
205
+ { min: 100, max: 500, multiplier: 1.0, label: '100-500' },
206
+ { min: 501, max: 1000, multiplier: 0.72, label: '501-1K' },
207
+ { min: 1001, max: 2500, multiplier: 0.52, label: '1K-2.5K' },
208
+ { min: 2501, max: 5000, multiplier: 0.38, label: '2.5K-5K' },
209
+ { min: 5001, max: 10000, multiplier: 0.28, label: '5K-10K' }
210
+ ],
211
+ turnaround: [
212
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
213
+ { id: 'rush', name: 'Rush (3-4 days)', price: 12 },
214
+ { id: 'express', name: 'Express (1-2 days)', price: 28 }
215
+ ],
216
+ defaultQuantity: 500
217
+ },
218
+ 'letterhead': {
219
+ name: 'Letterhead',
220
+ icon: 'file',
221
+ image: 'http://static.photos/office/320x240/44',
222
+ basePrice: 40.00,
223
+ sizes: [
224
+ { id: '8.5x11', name: '8.5" x 11"', price: 0 },
225
+ { id: '8.5x14', name: '8.5" x 14"', price: 5 }
226
+ ],
227
+ paperTypes: [
228
+ { id: '24lb-bond', name: '24lb Bond', price: 0 },
229
+ { id: '28lb-bond', name: '28lb Bond', price: 5 },
230
+ { id: '32lb-bond', name: '32lb Bond', price: 10 },
231
+ { id: 'linen', name: 'Linen Finish', price: 15 }
232
+ ],
233
+ finishing: [
234
+ { id: 'none', name: 'None', price: 0 },
235
+ { id: 'watermark', name: 'Watermark', price: 8 }
236
+ ],
237
+ quantityTiers: [
238
+ { min: 100, max: 500, multiplier: 1.0, label: '100-500' },
239
+ { min: 501, max: 1000, multiplier: 0.70, label: '501-1K' },
240
+ { min: 1001, max: 2500, multiplier: 0.48, label: '1K-2.5K' },
241
+ { min: 2501, max: 5000, multiplier: 0.35, label: '2.5K-5K' }
242
+ ],
243
+ turnaround: [
244
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
245
+ { id: 'rush', name: 'Rush (3-4 days)', price: 18 },
246
+ { id: 'express', name: 'Express (1-2 days)', price: 38 }
247
+ ],
248
+ defaultQuantity: 500
249
+ },
250
+ 'envelopes': {
251
+ name: 'Envelopes',
252
+ icon: 'mail-open',
253
+ image: 'http://static.photos/office/320x240/56',
254
+ basePrice: 32.00,
255
+ sizes: [
256
+ { id: '10-standard', name: '#10 Standard', price: 0 },
257
+ { id: 'a7', name: 'A7 (5.25x7.25)', price: 3 },
258
+ { id: 'a2', name: 'A2 (4.375x5.75)', price: 0 }
259
+ ],
260
+ paperTypes: [
261
+ { id: '24lb-white', name: '24lb White Wove', price: 0 },
262
+ { id: '28lb-white', name: '28lb White Wove', price: 4 },
263
+ { id: 'kraft', name: 'Kraft Brown', price: 3 }
264
+ ],
265
+ finishing: [
266
+ { id: 'none', name: 'None', price: 0 },
267
+ { id: 'window', name: 'Window', price: 5 },
268
+ { id: 'peel-seal', name: 'Peel & Seal', price: 4 }
269
+ ],
270
+ quantityTiers: [
271
+ { min: 100, max: 500, multiplier: 1.0, label: '100-500' },
272
+ { min: 501, max: 1000, multiplier: 0.72, label: '501-1K' },
273
+ { min: 1001, max: 2500, multiplier: 0.52, label: '1K-2.5K' },
274
+ { min: 2501, max: 5000, multiplier: 0.36, label: '2.5K-5K' }
275
+ ],
276
+ turnaround: [
277
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
278
+ { id: 'rush', name: 'Rush (3-4 days)', price: 15 },
279
+ { id: 'express', name: 'Express (1-2 days)', price: 32 }
280
+ ],
281
+ defaultQuantity: 250
282
+ }
283
+ }
284
+ };
285
+
286
+ // --- STATE ---
287
+ let pricing = null;
288
+ let editingProductKey = null;
289
+ let editingData = null;
290
+
291
+ // --- AUTH ---
292
+ const ADMIN_CREDENTIALS = { username: 'admin', password: 'admin123' };
293
+ let isLoggedIn = sessionStorage.getItem('printpro_admin_loggedin') === 'true';
294
+
295
+ // --- DOM ---
296
+ const loginScreen = document.getElementById('loginScreen');
297
+ const dashboard = document.getElementById('dashboard');
298
+ const loginForm = document.getElementById('loginForm');
299
+ const loginError = document.getElementById('loginError');
300
+ const editModal = document.getElementById('editModal');
301
+ const modalProductName = document.getElementById('modalProductName');
302
+ const modalContent = document.getElementById('modalContent');
303
+ const productCards = document.getElementById('productCards');
304
+ const toast = document.getElementById('toast');
305
+ const toastMsg = document.getElementById('toastMsg');
306
+
307
+ // --- INIT ---
308
+ function getPricing() {
309
+ const stored = localStorage.getItem('printpro_pricing');
310
+ if (stored) {
311
+ try { return JSON.parse(stored); } catch (e) { return JSON.parse(JSON.stringify(DEFAULT_PRICING)); }
312
+ }
313
+ const def = JSON.parse(JSON.stringify(DEFAULT_PRICING));
314
+ localStorage.setItem('printpro_pricing', JSON.stringify(def));
315
+ return def;
316
+ }
317
+
318
+ function savePricingToStorage(data) {
319
+ localStorage.setItem('printpro_pricing', JSON.stringify(data));
320
+ // Dispatch events to notify the frontend
321
+ window.dispatchEvent(new Event('pricingUpdated'));
322
+ // Also try to notify other tabs via storage event
323
+ localStorage.setItem('printpro_pricing_updated', Date.now().toString());
324
+ updateLastSaved();
325
+ }
326
+
327
+ function updateLastSaved() {
328
+ const now = new Date();
329
+ const timeStr = now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
330
+ document.getElementById('statLastSaved').textContent = timeStr;
331
+ localStorage.setItem('printpro_last_saved', timeStr);
332
+ }
333
+
334
+ // --- LOGIN ---
335
+ loginForm.addEventListener('submit', (e) => {
336
+ e.preventDefault();
337
+ const username = document.getElementById('username').value.trim();
338
+ const password = document.getElementById('password').value.trim();
339
+
340
+ if (username === ADMIN_CREDENTIALS.username && password === ADMIN_CREDENTIALS.password) {
341
+ isLoggedIn = true;
342
+ sessionStorage.setItem('printpro_admin_loggedin', 'true');
343
+ loginScreen.classList.add('hidden');
344
+ dashboard.classList.remove('hidden');
345
+ initDashboard();
346
+ showToast('Welcome back, Admin! 👋');
347
+ } else {
348
+ loginError.textContent = 'Invalid username or password.';
349
+ loginError.classList.remove('hidden');
350
+ document.getElementById('password').classList.add('border-red-500');
351
+ setTimeout(() => {
352
+ document.getElementById('password').classList.remove('border-red-500');
353
+ loginError.classList.add('hidden');
354
+ }, 2500);
355
+ }
356
+ });
357
+
358
+ function logout() {
359
+ isLoggedIn = false;
360
+ sessionStorage.removeItem('printpro_admin_loggedin');
361
+ dashboard.classList.add('hidden');
362
+ loginScreen.classList.remove('hidden');
363
+ document.getElementById('password').value = '';
364
+ loginError.classList.add('hidden');
365
+ }
366
+
367
+ // --- TOAST ---
368
+ function showToast(message) {
369
+ toastMsg.textContent = message;
370
+ toast.classList.remove('hidden');
371
+ void toast.offsetWidth;
372
+ toast.classList.add('toast');
373
+ setTimeout(() => {
374
+ toast.classList.add('hidden');
375
+ toast.classList.remove('toast');
376
+ }, 3000);
377
+ }
378
+
379
+ // --- DASHBOARD ---
380
+ function initDashboard() {
381
+ pricing = getPricing();
382
+ updateStats();
383
+ renderProductCards();
384
+ lucide.createIcons();
385
+ }
386
+
387
+ function updateStats() {
388
+ const products = pricing.products;
389
+ const keys = Object.keys(products);
390
+ const totalBase = keys.reduce((sum, k) => sum + products[k].basePrice, 0);
391
+ const avgPrice = totalBase / keys.length;
392
+ const totalTiers = keys.reduce((sum, k) => sum + products[k].quantityTiers.length, 0);
393
+
394
+ document.getElementById('statProducts').textContent = keys.length;
395
+ document.getElementById('statAvgPrice').textContent = '$' + avgPrice.toFixed(2);
396
+ document.getElementById('statTiers').textContent = totalTiers;
397
+ document.getElementById('statLastSaved').textContent = localStorage.getItem('printpro_last_saved') || 'Never';
398
+ }
399
+
400
+ function renderProductCards() {
401
+ const products = pricing.products;
402
+ productCards.innerHTML = Object.entries(products).map(([key, product]) => `
403
+ <div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden hover:shadow-md transition-all">
404
+ <div class="flex flex-col sm:flex-row">
405
+ <div class="sm:w-48 h-40 sm:h-auto flex-shrink-0">
406
+ <img src="${product.image}" alt="${product.name}" class="w-full h-full object-cover">
407
+ </div>
408
+ <div class="flex-1 p-5">
409
+ <div class="flex items-start justify-between mb-3">
410
+ <div>
411
+ <h3 class="text-lg font-bold text-gray-900 flex items-center gap-2">
412
+ <i data-lucide="${product.icon}" class="w-5 h-5 text-brand-500"></i>
413
+ ${product.name}
414
+ </h3>
415
+ <p class="text-sm text-gray-500 mt-0.5">Product Key: <code class="bg-gray-100 px-1.5 py-0.5 rounded text-xs">${key}</code></p>
416
+ </div>
417
+ <span class="text-2xl font-extrabold text-brand-600">$${product.basePrice.toFixed(2)}</span>
418
+ </div>
419
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-xs">
420
+ <div class="bg-gray-50 rounded-lg p-2.5">
421
+ <p class="text-gray-400 uppercase tracking-wider font-semibold mb-0.5">Sizes</p>
422
+ <p class="font-bold text-gray-700">${product.sizes.length} options</p>
423
+ </div>
424
+ <div class="bg-gray-50 rounded-lg p-2.5">
425
+ <p class="text-gray-400 uppercase tracking-wider font-semibold mb-0.5">Paper Types</p>
426
+ <p class="font-bold text-gray-700">${product.paperTypes.length} options</p>
427
+ </div>
428
+ <div class="bg-gray-50 rounded-lg p-2.5">
429
+ <p class="text-gray-400 uppercase tracking-wider font-semibold mb-0.5">Finishing</p>
430
+ <p class="font-bold text-gray-700">${product.finishing.length} options</p>
431
+ </div>
432
+ <div class="bg-gray-50 rounded-lg p-2.5">
433
+ <p class="text-gray-400 uppercase tracking-wider font-semibold mb-0.5">Tiers</p>
434
+ <p class="font-bold text-gray-700">${product.quantityTiers.length} tiers</p>
435
+ </div>
436
+ </div>
437
+ <div class="mt-4 flex gap-2">
438
+ <button onclick="openEditModal('${key}')"
439
+ class="flex-1 bg-brand-600 text-white font-semibold py-2.5 rounded-lg hover:bg-brand-700 transition-all text-sm flex items-center justify-center gap-2">
440
+ <i data-lucide="edit-3" class="w-4 h-4"></i> Edit Pricing
441
+ </button>
442
+ </div>
443
+ </div>
444
+ </div>
445
+ </div>
446
+ `).join('');
447
+ lucide.createIcons();
448
+ }
449
+
450
+ // --- EDIT MODAL ---
451
+ function openEditModal(productKey) {
452
+ editingProductKey = productKey;
453
+ // Deep clone the product data for editing
454
+ editingData = JSON.parse(JSON.stringify(pricing.products[productKey]));
455
+ modalProductName.textContent = editingData.name;
456
+ renderModalContent();
457
+ editModal.classList.remove('hidden');
458
+ editModal.style.display = 'flex';
459
+ document.body.style.overflow = 'hidden';
460
+ }
461
+
462
+ function closeModal() {
463
+ editModal.classList.add('hidden');
464
+ editModal.style.display = 'none';
465
+ document.body.style.overflow = '';
466
+ editingProductKey = null;
467
+ editingData = null;
468
+ }
469
+
470
+ function renderModalContent() {
471
+ const d = editingData;
472
+ modalContent.innerHTML = `
473
+ <!-- Base Price -->
474
+ <div class="mb-6">
475
+ <label class="block text-sm font-bold text-gray-700 mb-2 flex items-center gap-2">
476
+ <i data-lucide="dollar-sign" class="w-4 h-4 text-brand-500"></i> Base Price
477
+ </label>
478
+ <div class="relative">
479
+ <span class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-500 font-bold text-lg">$</span>
480
+ <input type="number" id="editBasePrice" value="${d.basePrice}" step="0.01" min="0"
481
+ class="w-full pl-8 pr-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-brand-500 focus:border-brand-500 outline-none text-gray-900 font-semibold text-lg">
482
+ </div>
483
+ <p class="text-xs text-gray-400 mt-1">The starting price before any options or quantity discounts.</p>
484
+ </div>
485
+
486
+ <!-- Sizes -->
487
+ <div class="mb-6">
488
+ <label class="block text-sm font-bold text-gray-700 mb-2 flex items-center gap-2">
489
+ <i data-lucide="ruler" class="w-4 h-4 text-brand-500"></i> Size Options
490
+ </label>
491
+ <div id="editSizes" class="space-y-2">
492
+ ${d.sizes.map((s, i) => `
493
+ <div class="flex items-center gap-2 bg-gray-50 rounded-lg p-2.5 tier-row">
494
+ <input type="text" value="${s.name}" data-size-name="${i}" placeholder="Size name"
495
+ class="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
496
+ <div class="relative">
497
+ <span class="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-sm">$</span>
498
+ <input type="number" value="${s.price}" data-size-price="${i}" step="0.01" placeholder="Price"
499
+ class="w-24 pl-6 pr-2 py-2 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
500
+ </div>
501
+ <button onclick="removeSizeOption(${i})" class="p-1.5 text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors" title="Remove">
502
+ <i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
503
+ </button>
504
+ </div>
505
+ `).join('')}
506
+ </div>
507
+ <button onclick="addSizeOption()" class="mt-2 text-sm text-brand-600 font-semibold hover:text-brand-700 flex items-center gap-1 transition-colors">
508
+ <i data-lucide="plus-circle" class="w-4 h-4"></i> Add Size Option
509
+ </button>
510
+ </div>
511
+
512
+ <!-- Paper Types -->
513
+ <div class="mb-6">
514
+ <label class="block text-sm font-bold text-gray-700 mb-2 flex items-center gap-2">
515
+ <i data-lucide="file-text" class="w-4 h-4 text-brand-500"></i> Paper Types
516
+ </label>
517
+ <div id="editPapers" class="space-y-2">
518
+ ${d.paperTypes.map((p, i) => `
519
+ <div class="flex items-center gap-2 bg-gray-50 rounded-lg p-2.5 tier-row">
520
+ <input type="text" value="${p.name}" data-paper-name="${i}" placeholder="Paper name"
521
+ class="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
522
+ <div class="relative">
523
+ <span class="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-sm">$</span>
524
+ <input type="number" value="${p.price}" data-paper-price="${i}" step="0.01" placeholder="Price"
525
+ class="w-24 pl-6 pr-2 py-2 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
526
+ </div>
527
+ <button onclick="removePaperOption(${i})" class="p-1.5 text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors" title="Remove">
528
+ <i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
529
+ </button>
530
+ </div>
531
+ `).join('')}
532
+ </div>
533
+ <button onclick="addPaperOption()" class="mt-2 text-sm text-brand-600 font-semibold hover:text-brand-700 flex items-center gap-1 transition-colors">
534
+ <i data-lucide="plus-circle" class="w-4 h-4"></i> Add Paper Option
535
+ </button>
536
+ </div>
537
+
538
+ <!-- Finishing -->
539
+ <div class="mb-6">
540
+ <label class="block text-sm font-bold text-gray-700 mb-2 flex items-center gap-2">
541
+ <i data-lucide="sparkles" class="w-4 h-4 text-brand-500"></i> Finishing Options
542
+ </label>
543
+ <div id="editFinishing" class="space-y-2">
544
+ ${d.finishing.map((f, i) => `
545
+ <div class="flex items-center gap-2 bg-gray-50 rounded-lg p-2.5 tier-row">
546
+ <input type="text" value="${f.name}" data-finishing-name="${i}" placeholder="Finishing name"
547
+ class="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
548
+ <div class="relative">
549
+ <span class="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-sm">$</span>
550
+ <input type="number" value="${f.price}" data-finishing-price="${i}" step="0.01" placeholder="Price"
551
+ class="w-24 pl-6 pr-2 py-2 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
552
+ </div>
553
+ <button onclick="removeFinishingOption(${i})" class="p-1.5 text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors" title="Remove">
554
+ <i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
555
+ </button>
556
+ </div>
557
+ `).join('')}
558
+ </div>
559
+ <button onclick="addFinishingOption()" class="mt-2 text-sm text-brand-600 font-semibold hover:text-brand-700 flex items-center gap-1 transition-colors">
560
+ <i data-lucide="plus-circle" class="w-4 h-4"></i> Add Finishing Option
561
+ </button>
562
+ </div>
563
+
564
+ <!-- Quantity Tiers -->
565
+ <div class="mb-6">
566
+ <label class="block text-sm font-bold text-gray-700 mb-2 flex items-center gap-2">
567
+ <i data-lucide="bar-chart-3" class="w-4 h-4 text-brand-500"></i> Quantity Tiers
568
+ </label>
569
+ <div id="editTiers" class="space-y-2">
570
+ ${d.quantityTiers.map((t, i) => `
571
+ <div class="flex items-center gap-2 bg-gray-50 rounded-lg p-2.5 tier-row flex-wrap">
572
+ <span class="text-xs text-gray-400 font-semibold w-6">#${i+1}</span>
573
+ <label class="text-xs text-gray-500">Min:</label>
574
+ <input type="number" value="${t.min}" data-tier-min="${i}" step="1" min="1"
575
+ class="w-20 px-2 py-1.5 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
576
+ <label class="text-xs text-gray-500">Max:</label>
577
+ <input type="number" value="${t.max}" data-tier-max="${i}" step="1" min="1"
578
+ class="w-20 px-2 py-1.5 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
579
+ <label class="text-xs text-gray-500">Multiplier:</label>
580
+ <input type="number" value="${t.multiplier}" data-tier-mult="${i}" step="0.01" min="0.01" max="1"
581
+ class="w-20 px-2 py-1.5 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
582
+ <button onclick="removeTier(${i})" class="p-1.5 text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors ml-auto" title="Remove">
583
+ <i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
584
+ </button>
585
+ </div>
586
+ `).join('')}
587
+ </div>
588
+ <button onclick="addTier()" class="mt-2 text-sm text-brand-600 font-semibold hover:text-brand-700 flex items-center gap-1 transition-colors">
589
+ <i data-lucide="plus-circle" class="w-4 h-4"></i> Add Tier
590
+ </button>
591
+ </div>
592
+
593
+ <!-- Turnaround -->
594
+ <div class="mb-4">
595
+ <label class="block text-sm font-bold text-gray-700 mb-2 flex items-center gap-2">
596
+ <i data-lucide="clock" class="w-4 h-4 text-brand-500"></i> Turnaround Options
597
+ </label>
598
+ <div id="editTurnaround" class="space-y-2">
599
+ ${d.turnaround.map((t, i) => `
600
+ <div class="flex items-center gap-2 bg-gray-50 rounded-lg p-2.5 tier-row">
601
+ <input type="text" value="${t.name}" data-turnaround-name="${i}" placeholder="Turnaround name"
602
+ class="flex-1 px-3 py-2 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
603
+ <div class="relative">
604
+ <span class="absolute left-2 top-1/2 -translate-y-1/2 text-gray-400 text-sm">$</span>
605
+ <input type="number" value="${t.price}" data-turnaround-price="${i}" step="0.01" placeholder="Price"
606
+ class="w-24 pl-6 pr-2 py-2 border border-gray-200 rounded-lg text-sm focus:ring-1 focus:ring-brand-400 outline-none">
607
+ </div>
608
+ <button onclick="removeTurnaroundOption(${i})" class="p-1.5 text-red-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors" title="Remove">
609
+ <i data-lucide="trash-2" class="w-3.5 h-3.5"></i>
610
+ </button>
611
+ </div>
612
+ `).join('')}
613
+ </div>
614
+ <button onclick="addTurnaroundOption()" class="mt-2 text-sm text-brand-600 font-semibold hover:text-brand-700 flex items-center gap-1 transition-colors">
615
+ <i data-lucide="plus-circle" class="w-4 h-4"></i> Add Turnaround Option
616
+ </button>
617
+ </div>
618
+ `;
619
+ lucide.createIcons();
620
+ }
621
+
622
+ // --- MODAL: ADD/REMOVE OPTIONS ---
623
+ function addSizeOption() { editingData.sizes.push({ id: 'new-' + Date.now(), name: 'New Size', price: 0 }); renderModalContent(); }
624
+ function removeSizeOption(i) { if (editingData.sizes.length > 1) { editingData.sizes.splice(i, 1); renderModalContent(); } else { showToast('Must have at least one size option ⚠️'); } }
625
+
626
+ function addPaperOption() { editingData.paperTypes.push({ id: 'new-' + Date.now(), name: 'New Paper', price: 0 }); renderModalContent(); }
627
+ function removePaperOption(i) { if (editingData.paperTypes.length > 1) { editingData.paperTypes.splice(i, 1); renderModalContent(); } else { showToast('Must have at least one paper option ⚠️'); } }
628
+
629
+ function addFinishingOption() { editingData.finishing.push({ id: 'new-' + Date.now(), name: 'New Finish', price: 0 }); renderModalContent(); }
630
+ function removeFinishingOption(i) { if (editingData.finishing.length > 0) { editingData.finishing.splice(i, 1); renderModalContent(); } }
631
+
632
+ function addTier() {
633
+ const tiers = editingData.quantityTiers;
634
+ const lastTier = tiers[tiers.length - 1];
635
+ const newMin = lastTier ? lastTier.max + 1 : 1;
636
+ tiers.push({ min: newMin, max: newMin + 500, multiplier: 0.50, label: `${newMin}-${newMin+500}` });
637
+ renderModalContent();
638
+ }
639
+ function removeTier(i) { if (editingData.quantityTiers.length > 1) { editingData.quantityTiers.splice(i, 1); renderModalContent(); } else { showToast('Must have at least one tier ⚠️'); } }
640
+
641
+ function addTurnaroundOption() { editingData.turnaround.push({ id: 'new-' + Date.now(), name: 'New Turnaround', price: 0 }); renderModalContent(); }
642
+ function removeTurnaroundOption(i) { if (editingData.turnaround.length > 1) { editingData.turnaround.splice(i, 1); renderModalContent(); } else { showToast('Must have at least one turnaround option ⚠️'); } }
643
+
644
+ // --- SAVE MODAL CHANGES ---
645
+ function saveModalChanges() {
646
+ if (!editingProductKey || !editingData) return;
647
+
648
+ // Gather base price
649
+ const basePriceEl = document.getElementById('editBasePrice');
650
+ editingData.basePrice = parseFloat(basePriceEl.value) || 0;
651
+
652
+ // Gather sizes
653
+ const sizeNames = modalContent.querySelectorAll('[data-size-name]');
654
+ const sizePrices = modalContent.querySelectorAll('[data-size-price]');
655
+ editingData.sizes = editingData.sizes.map((s, i) => ({
656
+ ...s,
657
+ name: sizeNames[i]?.value || s.name,
658
+ price: parseFloat(sizePrices[i]?.value) || 0
659
+ }));
660
+
661
+ // Gather paper types
662
+ const paperNames = modalContent.querySelectorAll('[data-paper-name]');
663
+ const paperPrices = modalContent.querySelectorAll('[data-paper-price]');
664
+ editingData.paperTypes = editingData.paperTypes.map((p, i) => ({
665
+ ...p,
666
+ name: paperNames[i]?.value || p.name,
667
+ price: parseFloat(paperPrices[i]?.value) || 0
668
+ }));
669
+
670
+ // Gather finishing
671
+ const finishingNames = modalContent.querySelectorAll('[data-finishing-name]');
672
+ const finishingPrices = modalContent.querySelectorAll('[data-finishing-price]');
673
+ editingData.finishing = editingData.finishing.map((f, i) => ({
674
+ ...f,
675
+ name: finishingNames[i]?.value || f.name,
676
+ price: parseFloat(finishingPrices[i]?.value) || 0
677
+ }));
678
+
679
+ // Gather tiers
680
+ const tierMins = modalContent.querySelectorAll('[data-tier-min]');
681
+ const tierMaxs = modalContent.querySelectorAll('[data-tier-max]');
682
+ const tierMults = modalContent.querySelectorAll('[data-tier-mult]');
683
+ editingData.quantityTiers = editingData.quantityTiers.map((t, i) => ({
684
+ ...t,
685
+ min: parseInt(tierMins[i]?.value) || t.min,
686
+ max: parseInt(tierMaxs[i]?.value) || t.max,
687
+ multiplier: parseFloat(tierMults[i]?.value) || t.multiplier,
688
+ label: `${parseInt(tierMins[i]?.value) || t.min}-${parseInt(tierMaxs[i]?.value) || t.max}`
689
+ }));
690
+
691
+ // Gather turnaround
692
+ const turnaroundNames = modalContent.querySelectorAll('[data-turnaround-name]');
693
+ const turnaroundPrices = modalContent.querySelectorAll('[data-turnaround-price]');
694
+ editingData.turnaround = editingData.turnaround.map((t, i) => ({
695
+ ...t,
696
+ name: turnaroundNames[i]?.value || t.name,
697
+ price: parseFloat(turnaroundPrices[i]?.value) || 0
698
+ }));
699
+
700
+ // Save to pricing
701
+ pricing.products[editingProductKey] = editingData;
702
+ savePricingToStorage(pricing);
703
+ closeModal();
704
+ updateStats();
705
+ renderProductCards();
706
+ showToast('Product pricing updated successfully! ✅');
707
+ }
708
+
709
+ // --- SAVE ALL ---
710
+ function saveAllPricing() {
711
+ savePricingToStorage(pricing);
712
+ showToast('All pricing saved successfully! 💾');
713
+ }
714
+
715
+ // --- RESET ---
716
+ function resetToDefault() {
717
+ if (confirm('Are you sure you want to reset ALL pricing to default values? This cannot be undone.')) {
718
+ pricing = JSON.parse(JSON.stringify(DEFAULT_PRICING));
719
+ savePricingToStorage(pricing);
720
+ updateStats();
721
+ renderProductCards();
722
+ showToast('Pricing reset to defaults! 🔄');
723
+ }
724
+ }
725
+
726
+ // --- CLOSE MODAL ON OVERLAY CLICK ---
727
+ editModal.addEventListener('click', (e) => {
728
+ if (e.target === editModal) {
729
+ closeModal();
730
+ }
731
+ });
732
+
733
+ // --- KEYBOARD SHORTCUTS ---
734
+ document.addEventListener('keydown', (e) => {
735
+ if (e.key === 'Escape' && !editModal.classList.contains('hidden')) {
736
+ closeModal();
737
+ }
738
+ });
739
+
740
+ // --- INIT ON LOAD ---
741
+ function init() {
742
+ if (isLoggedIn) {
743
+ loginScreen.classList.add('hidden');
744
+ dashboard.classList.remove('hidden');
745
+ initDashboard();
746
+ } else {
747
+ loginScreen.classList.remove('hidden');
748
+ dashboard.classList.add('hidden');
749
+ }
750
+ // Update last saved display
751
+ document.getElementById('statLastSaved').textContent = localStorage.getItem('printpro_last_saved') || 'Never';
752
+ }
753
+
754
+ document.addEventListener('DOMContentLoaded', init);
755
+
756
+ // Expose functions to global scope
757
+ window.openEditModal = openEditModal;
758
+ window.closeModal = closeModal;
759
+ window.saveModalChanges = saveModalChanges;
760
+ window.addSizeOption = addSizeOption;
761
+ window.removeSizeOption = removeSizeOption;
762
+ window.addPaperOption = addPaperOption;
763
+ window.removePaperOption = removePaperOption;
764
+ window.addFinishingOption = addFinishingOption;
765
+ window.removeFinishingOption = removeFinishingOption;
766
+ window.addTier = addTier;
767
+ window.removeTier = removeTier;
768
+ window.addTurnaroundOption = addTurnaroundOption;
769
+ window.removeTurnaroundOption = removeTurnaroundOption;
770
+ window.saveAllPricing = saveAllPricing;
771
+ window.resetToDefault = resetToDefault;
772
+ window.logout = logout;
index.html CHANGED
@@ -1,19 +1,480 @@
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">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>PrintPro - Custom Printing Services | Get Instant Quotes</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
11
+ <script>
12
+ tailwind.config = {
13
+ theme: {
14
+ extend: {
15
+ fontFamily: {
16
+ 'sans': ['Inter', 'system-ui', 'sans-serif'],
17
+ },
18
+ colors: {
19
+ brand: {
20
+ 50: '#eff6ff',
21
+ 100: '#dbeafe',
22
+ 200: '#bfdbfe',
23
+ 300: '#93c5fd',
24
+ 400: '#60a5fa',
25
+ 500: '#3b82f6',
26
+ 600: '#2563eb',
27
+ 700: '#1d4ed8',
28
+ 800: '#1e40af',
29
+ 900: '#1e3a8a',
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ </script>
36
+ <style>
37
+ * { scroll-behavior: smooth; }
38
+ .calc-card { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
39
+ .calc-card:hover { transform: translateY(-2px); box-shadow: 0 20px 40px rgba(0,0,0,0.12); }
40
+ .option-btn { transition: all 0.2s ease; }
41
+ .option-btn.active { transform: scale(1.02); }
42
+ .price-pulse { animation: pulse 0.4s ease-in-out; }
43
+ @keyframes pulse {
44
+ 0%, 100% { transform: scale(1); }
45
+ 50% { transform: scale(1.08); }
46
+ }
47
+ .gradient-hero {
48
+ background: linear-gradient(135deg, #1e3a8a 0%, #3b82f6 30%, #6366f1 60%, #8b5cf6 100%);
49
+ background-size: 400% 400%;
50
+ animation: gradientShift 15s ease infinite;
51
+ }
52
+ @keyframes gradientShift {
53
+ 0% { background-position: 0% 50%; }
54
+ 50% { background-position: 100% 50%; }
55
+ 100% { background-position: 0% 50%; }
56
+ }
57
+ .glass-effect {
58
+ background: rgba(255, 255, 255, 0.08);
59
+ backdrop-filter: blur(20px);
60
+ -webkit-backdrop-filter: blur(20px);
61
+ border: 1px solid rgba(255, 255, 255, 0.15);
62
+ }
63
+ .ribbon {
64
+ position: absolute;
65
+ top: 16px;
66
+ right: -34px;
67
+ transform: rotate(45deg);
68
+ width: 140px;
69
+ text-align: center;
70
+ font-size: 0.75rem;
71
+ font-weight: 700;
72
+ padding: 4px 0;
73
+ letter-spacing: 0.05em;
74
+ }
75
+ input[type="range"] {
76
+ -webkit-appearance: none;
77
+ appearance: none;
78
+ height: 8px;
79
+ border-radius: 4px;
80
+ background: #e2e8f0;
81
+ outline: none;
82
+ }
83
+ input[type="range"]::-webkit-slider-thumb {
84
+ -webkit-appearance: none;
85
+ appearance: none;
86
+ width: 24px;
87
+ height: 24px;
88
+ border-radius: 50%;
89
+ background: #2563eb;
90
+ cursor: pointer;
91
+ border: 3px solid white;
92
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
93
+ transition: all 0.15s ease;
94
+ }
95
+ input[type="range"]::-webkit-slider-thumb:hover {
96
+ transform: scale(1.15);
97
+ box-shadow: 0 4px 16px rgba(37,99,235,0.4);
98
+ }
99
+ .product-card { transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1); }
100
+ .product-card:hover { transform: translateY(-8px); box-shadow: 0 25px 50px rgba(0,0,0,0.15); }
101
+ .product-card:hover .product-overlay { opacity: 1; }
102
+ .product-overlay { transition: opacity 0.35s ease; opacity: 0; }
103
+ .toast { animation: slideIn 0.4s ease, fadeOut 0.4s ease 2.6s forwards; }
104
+ @keyframes slideIn {
105
+ from { transform: translateX(120%); opacity: 0; }
106
+ to { transform: translateX(0); opacity: 1; }
107
+ }
108
+ @keyframes fadeOut {
109
+ from { opacity: 1; }
110
+ to { opacity: 0; }
111
+ }
112
+ </style>
113
+ </head>
114
+ <body class="font-sans bg-gray-50 text-gray-800 antialiased">
115
+
116
+ <!-- ============ NAVIGATION ============ -->
117
+ <nav class="sticky top-0 z-50 bg-white/95 backdrop-blur-md shadow-sm border-b border-gray-100">
118
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
119
+ <div class="flex items-center justify-between h-16">
120
+ <div class="flex items-center gap-2">
121
+ <div class="w-9 h-9 bg-brand-600 rounded-lg flex items-center justify-center">
122
+ <i data-lucide="printer" class="text-white w-5 h-5"></i>
123
+ </div>
124
+ <span class="text-xl font-extrabold text-gray-900 tracking-tight">Print<span class="text-brand-600">Pro</span></span>
125
+ </div>
126
+ <div class="hidden md:flex items-center gap-8 text-sm font-medium text-gray-600">
127
+ <a href="#products" class="hover:text-brand-600 transition-colors">Products</a>
128
+ <a href="#calculator" class="hover:text-brand-600 transition-colors">Quote Calculator</a>
129
+ <a href="#how-it-works" class="hover:text-brand-600 transition-colors">How It Works</a>
130
+ <a href="#features" class="hover:text-brand-600 transition-colors">Features</a>
131
+ <a href="admin.html" class="ml-4 px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg text-gray-700 transition-all flex items-center gap-2 text-xs">
132
+ <i data-lucide="shield" class="w-3.5 h-3.5"></i> Admin
133
+ </a>
134
+ </div>
135
+ <button id="mobileMenuBtn" class="md:hidden p-2 rounded-lg hover:bg-gray-100 transition-colors">
136
+ <i data-lucide="menu" class="w-6 h-6 text-gray-700"></i>
137
+ </button>
138
+ </div>
139
+ </div>
140
+ <!-- Mobile Menu -->
141
+ <div id="mobileMenu" class="hidden md:hidden bg-white border-t border-gray-100 shadow-lg">
142
+ <div class="px-4 py-3 space-y-2">
143
+ <a href="#products" class="block py-2 px-3 rounded-lg hover:bg-gray-50 text-gray-700 font-medium">Products</a>
144
+ <a href="#calculator" class="block py-2 px-3 rounded-lg hover:bg-gray-50 text-gray-700 font-medium">Quote Calculator</a>
145
+ <a href="#how-it-works" class="block py-2 px-3 rounded-lg hover:bg-gray-50 text-gray-700 font-medium">How It Works</a>
146
+ <a href="#features" class="block py-2 px-3 rounded-lg hover:bg-gray-50 text-gray-700 font-medium">Features</a>
147
+ <a href="admin.html" class="block py-2 px-3 rounded-lg bg-gray-100 text-gray-700 font-medium flex items-center gap-2">
148
+ <i data-lucide="shield" class="w-4 h-4"></i> Admin Panel
149
+ </a>
150
+ </div>
151
+ </div>
152
+ </nav>
153
+
154
+ <!-- ============ HERO SECTION ============ -->
155
+ <section class="gradient-hero text-white relative overflow-hidden">
156
+ <div class="absolute inset-0 bg-black/10"></div>
157
+ <div class="absolute top-0 right-0 w-96 h-96 bg-white/5 rounded-full -translate-y-1/2 translate-x-1/4 blur-3xl"></div>
158
+ <div class="absolute bottom-0 left-0 w-72 h-72 bg-white/5 rounded-full translate-y-1/3 -translate-x-1/4 blur-3xl"></div>
159
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 md:py-28 relative z-10">
160
+ <div class="grid md:grid-cols-2 gap-12 items-center">
161
+ <div>
162
+ <span class="inline-block bg-white/20 backdrop-blur-sm rounded-full px-4 py-1.5 text-sm font-semibold mb-6 tracking-wide">
163
+ ⚡ Premium Quality Printing
164
+ </span>
165
+ <h1 class="text-4xl md:text-5xl lg:text-6xl font-extrabold leading-tight mb-6 tracking-tight">
166
+ Custom Printing,<br>
167
+ <span class="text-yellow-300">Instant Quotes</span>
168
+ </h1>
169
+ <p class="text-lg md:text-xl text-white/80 mb-8 max-w-lg leading-relaxed">
170
+ Business cards, flyers, banners, and more. Get an instant quote, customize your options, and order with confidence. Fast turnaround, premium quality.
171
+ </p>
172
+ <div class="flex flex-wrap gap-4">
173
+ <a href="#calculator" class="inline-flex items-center gap-2 bg-white text-brand-700 font-bold px-7 py-3.5 rounded-xl hover:bg-gray-100 transition-all shadow-lg hover:shadow-xl text-lg">
174
+ Get Instant Quote <i data-lucide="arrow-right" class="w-5 h-5"></i>
175
+ </a>
176
+ <a href="#products" class="inline-flex items-center gap-2 bg-white/15 backdrop-blur-sm text-white font-semibold px-7 py-3.5 rounded-xl hover:bg-white/25 transition-all border border-white/30 text-lg">
177
+ View Products
178
+ </a>
179
+ </div>
180
+ <div class="flex items-center gap-6 mt-8 text-white/70 text-sm">
181
+ <div class="flex items-center gap-1.5"><i data-lucide="check-circle" class="w-4 h-4 text-green-400"></i> Free Shipping*</div>
182
+ <div class="flex items-center gap-1.5"><i data-lucide="check-circle" class="w-4 h-4 text-green-400"></i> Fast Turnaround</div>
183
+ <div class="flex items-center gap-1.5"><i data-lucide="check-circle" class="w-4 h-4 text-green-400"></i> Premium Quality</div>
184
+ </div>
185
+ </div>
186
+ <div class="hidden md:flex justify-center">
187
+ <div class="glass-effect rounded-2xl p-6 w-full max-w-sm relative">
188
+ <div class="bg-white/10 rounded-xl p-5 mb-4">
189
+ <i data-lucide="credit-card" class="w-10 h-10 text-yellow-300 mb-3"></i>
190
+ <p class="text-3xl font-extrabold text-white">$<span id="heroPrice">29</span><span class="text-lg font-normal text-white/70"> /starting</span></p>
191
+ <p class="text-white/60 text-sm mt-1">500 Business Cards</p>
192
+ </div>
193
+ <div class="space-y-2">
194
+ <div class="flex justify-between text-sm text-white/70"><span>Paper:</span><span class="text-white font-medium">14pt Gloss</span></div>
195
+ <div class="flex justify-between text-sm text-white/70"><span>Finish:</span><span class="text-white font-medium">Matte Coating</span></div>
196
+ <div class="flex justify-between text-sm text-white/70"><span>Turnaround:</span><span class="text-white font-medium">5-7 Business Days</span></div>
197
+ </div>
198
+ <div class="mt-4 pt-4 border-t border-white/20">
199
+ <button onclick="document.getElementById('calculator').scrollIntoView({behavior:'smooth'})" class="w-full bg-yellow-400 text-gray-900 font-bold py-2.5 rounded-lg hover:bg-yellow-300 transition-colors text-sm">
200
+ Customize Your Quote
201
+ </button>
202
+ </div>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+ </section>
208
+
209
+ <!-- ============ TRUST BAR ============ -->
210
+ <div class="bg-white border-b border-gray-100 py-5">
211
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-wrap items-center justify-center gap-8 md:gap-12">
212
+ <div class="flex items-center gap-2 text-gray-500 text-sm font-medium"><i data-lucide="shield-check" class="w-5 h-5 text-green-500"></i> 100% Satisfaction</div>
213
+ <div class="flex items-center gap-2 text-gray-500 text-sm font-medium"><i data-lucide="truck" class="w-5 h-5 text-brand-500"></i> Free Shipping Over $75</div>
214
+ <div class="flex items-center gap-2 text-gray-500 text-sm font-medium"><i data-lucide="clock" class="w-5 h-5 text-brand-500"></i> Rush Delivery Available</div>
215
+ <div class="flex items-center gap-2 text-gray-500 text-sm font-medium"><i data-lucide="star" class="w-5 h-5 text-yellow-500"></i> 4.9/5 Rating</div>
216
+ </div>
217
+ </div>
218
+
219
+ <!-- ============ PRODUCTS SECTION ============ -->
220
+ <section id="products" class="py-16 md:py-20 bg-gray-50">
221
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
222
+ <div class="text-center mb-12">
223
+ <span class="text-brand-600 font-semibold text-sm tracking-widest uppercase">Our Products</span>
224
+ <h2 class="text-3xl md:text-4xl font-extrabold text-gray-900 mt-3 mb-4">Premium Printing Solutions</h2>
225
+ <p class="text-gray-500 max-w-2xl mx-auto text-lg">Choose from our wide range of printing products. Click any product to get an instant customized quote.</p>
226
+ </div>
227
+ <div id="productGrid" class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-5 md:gap-6">
228
+ <!-- Products rendered by JS -->
229
+ </div>
230
+ </div>
231
+ </section>
232
+
233
+ <!-- ============ QUOTE CALCULATOR ============ -->
234
+ <section id="calculator" class="py-16 md:py-20 bg-white relative">
235
+ <div class="absolute inset-0 bg-gradient-to-b from-gray-50/50 to-white pointer-events-none"></div>
236
+ <div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
237
+ <div class="text-center mb-10">
238
+ <span class="text-brand-600 font-semibold text-sm tracking-widest uppercase">Quote Calculator</span>
239
+ <h2 class="text-3xl md:text-4xl font-extrabold text-gray-900 mt-3 mb-4">Get Your Instant Quote</h2>
240
+ <p class="text-gray-500 max-w-2xl mx-auto">Configure your product below and see pricing update in real-time.</p>
241
+ </div>
242
+
243
+ <div class="bg-white rounded-2xl shadow-xl border border-gray-100 overflow-hidden calc-card">
244
+ <!-- Product Selector -->
245
+ <div class="p-6 md:p-8 border-b border-gray-100 bg-gray-50/50">
246
+ <label class="block text-sm font-semibold text-gray-500 uppercase tracking-wider mb-3">Select Product</label>
247
+ <div id="productSelector" class="flex flex-wrap gap-2">
248
+ <!-- Rendered by JS -->
249
+ </div>
250
+ </div>
251
+
252
+ <!-- Options Grid -->
253
+ <div class="p-6 md:p-8 grid md:grid-cols-2 gap-6">
254
+ <!-- Size -->
255
+ <div>
256
+ <label class="block text-sm font-semibold text-gray-600 mb-2">Size</label>
257
+ <div id="sizeOptions" class="flex flex-wrap gap-2">
258
+ <!-- Rendered by JS -->
259
+ </div>
260
+ </div>
261
+ <!-- Paper Type -->
262
+ <div>
263
+ <label class="block text-sm font-semibold text-gray-600 mb-2">Paper Type</label>
264
+ <div id="paperOptions" class="flex flex-wrap gap-2">
265
+ <!-- Rendered by JS -->
266
+ </div>
267
+ </div>
268
+ <!-- Finishing -->
269
+ <div>
270
+ <label class="block text-sm font-semibold text-gray-600 mb-2">Finishing</label>
271
+ <div id="finishingOptions" class="flex flex-wrap gap-2">
272
+ <!-- Rendered by JS -->
273
+ </div>
274
+ </div>
275
+ <!-- Turnaround -->
276
+ <div>
277
+ <label class="block text-sm font-semibold text-gray-600 mb-2">Turnaround Time</label>
278
+ <div id="turnaroundOptions" class="flex flex-wrap gap-2">
279
+ <!-- Rendered by JS -->
280
+ </div>
281
+ </div>
282
+ </div>
283
+
284
+ <!-- Quantity & Price -->
285
+ <div class="p-6 md:p-8 border-t border-gray-100 bg-gray-50/50">
286
+ <div class="grid md:grid-cols-2 gap-8 items-end">
287
+ <div>
288
+ <label class="block text-sm font-semibold text-gray-600 mb-2">
289
+ Quantity: <span id="quantityDisplay" class="text-brand-600 font-bold text-lg">500</span>
290
+ </label>
291
+ <input type="range" id="quantitySlider" min="0" max="100" value="50" class="w-full cursor-pointer">
292
+ <div class="flex justify-between text-xs text-gray-400 mt-1">
293
+ <span id="qtyMin">50</span>
294
+ <span id="qtyMax">10000</span>
295
+ </div>
296
+ </div>
297
+ <div class="text-center md:text-right">
298
+ <p class="text-sm text-gray-500 font-medium">Estimated Total</p>
299
+ <p class="text-5xl md:text-6xl font-extrabold text-gray-900 tracking-tight" id="totalPrice">$29.00</p>
300
+ <p class="text-sm text-gray-400 mt-1" id="pricePerUnit">$0.058 / unit</p>
301
+ <button class="mt-4 bg-brand-600 text-white font-bold px-8 py-3 rounded-xl hover:bg-brand-700 transition-all shadow-lg hover:shadow-xl inline-flex items-center gap-2 text-lg">
302
+ <i data-lucide="shopping-cart" class="w-5 h-5"></i> Order Now
303
+ </button>
304
+ </div>
305
+ </div>
306
+ </div>
307
+ </div>
308
+ </div>
309
+ </section>
310
+
311
+ <!-- ============ HOW IT WORKS ============ -->
312
+ <section id="how-it-works" class="py-16 md:py-20 bg-gray-50">
313
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
314
+ <div class="text-center mb-12">
315
+ <span class="text-brand-600 font-semibold text-sm tracking-widest uppercase">Process</span>
316
+ <h2 class="text-3xl md:text-4xl font-extrabold text-gray-900 mt-3 mb-4">How It Works</h2>
317
+ </div>
318
+ <div class="grid md:grid-cols-4 gap-8">
319
+ <div class="text-center">
320
+ <div class="w-16 h-16 bg-brand-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
321
+ <i data-lucide="mouse-pointer-click" class="w-8 h-8 text-brand-600"></i>
322
+ </div>
323
+ <h3 class="font-bold text-gray-900 mb-2">1. Choose Product</h3>
324
+ <p class="text-gray-500 text-sm">Select from our wide range of printing products</p>
325
+ </div>
326
+ <div class="text-center">
327
+ <div class="w-16 h-16 bg-brand-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
328
+ <i data-lucide="sliders-horizontal" class="w-8 h-8 text-brand-600"></i>
329
+ </div>
330
+ <h3 class="font-bold text-gray-900 mb-2">2. Customize</h3>
331
+ <p class="text-gray-500 text-sm">Pick size, paper, finish & quantity</p>
332
+ </div>
333
+ <div class="text-center">
334
+ <div class="w-16 h-16 bg-brand-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
335
+ <i data-lucide="calculator" class="w-8 h-8 text-brand-600"></i>
336
+ </div>
337
+ <h3 class="font-bold text-gray-900 mb-2">3. Get Quote</h3>
338
+ <p class="text-gray-500 text-sm">See your instant price in real-time</p>
339
+ </div>
340
+ <div class="text-center">
341
+ <div class="w-16 h-16 bg-brand-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
342
+ <i data-lucide="package-check" class="w-8 h-8 text-brand-600"></i>
343
+ </div>
344
+ <h3 class="font-bold text-gray-900 mb-2">4. Receive Order</h3>
345
+ <p class="text-gray-500 text-sm">Fast delivery right to your doorstep</p>
346
+ </div>
347
+ </div>
348
+ </div>
349
+ </section>
350
+
351
+ <!-- ============ FEATURES ============ -->
352
+ <section id="features" class="py-16 md:py-20 bg-white">
353
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
354
+ <div class="grid md:grid-cols-2 gap-12 items-center">
355
+ <div>
356
+ <span class="text-brand-600 font-semibold text-sm tracking-widest uppercase">Why Choose Us</span>
357
+ <h2 class="text-3xl md:text-4xl font-extrabold text-gray-900 mt-3 mb-6">Quality Prints, Every Time</h2>
358
+ <div class="space-y-4">
359
+ <div class="flex gap-4">
360
+ <div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5">
361
+ <i data-lucide="palette" class="w-5 h-5 text-green-600"></i>
362
+ </div>
363
+ <div>
364
+ <h4 class="font-semibold text-gray-900">Vibrant Full-Color Printing</h4>
365
+ <p class="text-gray-500 text-sm">State-of-the-art offset and digital presses for stunning color accuracy.</p>
366
+ </div>
367
+ </div>
368
+ <div class="flex gap-4">
369
+ <div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5">
370
+ <i data-lucide="layers" class="w-5 h-5 text-green-600"></i>
371
+ </div>
372
+ <div>
373
+ <h4 class="font-semibold text-gray-900">Premium Paper Stock</h4>
374
+ <p class="text-gray-500 text-sm">Choose from 14pt, 16pt, uncoated, and specialty papers for the perfect feel.</p>
375
+ </div>
376
+ </div>
377
+ <div class="flex gap-4">
378
+ <div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5">
379
+ <i data-lucide="zap" class="w-5 h-5 text-green-600"></i>
380
+ </div>
381
+ <div>
382
+ <h4 class="font-semibold text-gray-900">Rush Turnaround Options</h4>
383
+ <p class="text-gray-500 text-sm">Need it fast? Get your order in as little as 2-3 business days with rush delivery.</p>
384
+ </div>
385
+ </div>
386
+ </div>
387
+ </div>
388
+ <div class="grid grid-cols-2 gap-4">
389
+ <div class="bg-gray-50 rounded-xl p-6 text-center">
390
+ <p class="text-4xl font-extrabold text-brand-600">15+</p>
391
+ <p class="text-gray-500 text-sm mt-1">Years Experience</p>
392
+ </div>
393
+ <div class="bg-gray-50 rounded-xl p-6 text-center">
394
+ <p class="text-4xl font-extrabold text-brand-600">50k+</p>
395
+ <p class="text-gray-500 text-sm mt-1">Happy Clients</p>
396
+ </div>
397
+ <div class="bg-gray-50 rounded-xl p-6 text-center">
398
+ <p class="text-4xl font-extrabold text-brand-600">200+</p>
399
+ <p class="text-gray-500 text-sm mt-1">Products</p>
400
+ </div>
401
+ <div class="bg-gray-50 rounded-xl p-6 text-center">
402
+ <p class="text-4xl font-extrabold text-brand-600">98%</p>
403
+ <p class="text-gray-500 text-sm mt-1">Satisfaction</p>
404
+ </div>
405
+ </div>
406
+ </div>
407
+ </div>
408
+ </section>
409
+
410
+ <!-- ============ CTA ============ -->
411
+ <section class="py-16 bg-brand-700 text-white">
412
+ <div class="max-w-4xl mx-auto px-4 text-center">
413
+ <h2 class="text-3xl md:text-4xl font-extrabold mb-4">Ready to Print?</h2>
414
+ <p class="text-white/80 text-lg mb-8">Get your instant quote now and experience premium quality printing.</p>
415
+ <a href="#calculator" class="inline-flex items-center gap-2 bg-yellow-400 text-gray-900 font-bold px-8 py-3.5 rounded-xl hover:bg-yellow-300 transition-all text-lg shadow-lg">
416
+ Get Your Quote <i data-lucide="arrow-right" class="w-5 h-5"></i>
417
+ </a>
418
+ </div>
419
+ </section>
420
+
421
+ <!-- ============ FOOTER ============ -->
422
+ <footer class="bg-gray-900 text-gray-400 py-12">
423
+ <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
424
+ <div class="grid md:grid-cols-4 gap-8 mb-8">
425
+ <div>
426
+ <div class="flex items-center gap-2 mb-4">
427
+ <div class="w-8 h-8 bg-brand-500 rounded-lg flex items-center justify-center">
428
+ <i data-lucide="printer" class="text-white w-4 h-4"></i>
429
+ </div>
430
+ <span class="text-lg font-bold text-white">Print<span class="text-brand-400">Pro</span></span>
431
+ </div>
432
+ <p class="text-sm">Premium custom printing services for businesses and individuals.</p>
433
+ </div>
434
+ <div>
435
+ <h4 class="font-semibold text-white mb-3">Products</h4>
436
+ <ul class="space-y-2 text-sm">
437
+ <li><a href="#" class="hover:text-white transition-colors">Business Cards</a></li>
438
+ <li><a href="#" class="hover:text-white transition-colors">Flyers & Brochures</a></li>
439
+ <li><a href="#" class="hover:text-white transition-colors">Banners & Posters</a></li>
440
+ <li><a href="#" class="hover:text-white transition-colors">Stickers & Labels</a></li>
441
+ </ul>
442
+ </div>
443
+ <div>
444
+ <h4 class="font-semibold text-white mb-3">Support</h4>
445
+ <ul class="space-y-2 text-sm">
446
+ <li><a href="#" class="hover:text-white transition-colors">Contact Us</a></li>
447
+ <li><a href="#" class="hover:text-white transition-colors">FAQs</a></li>
448
+ <li><a href="#" class="hover:text-white transition-colors">Shipping Info</a></li>
449
+ <li><a href="#" class="hover:text-white transition-colors">Returns</a></li>
450
+ </ul>
451
+ </div>
452
+ <div>
453
+ <h4 class="font-semibold text-white mb-3">Contact</h4>
454
+ <ul class="space-y-2 text-sm">
455
+ <li class="flex items-center gap-2"><i data-lucide="mail" class="w-4 h-4"></i> hello@printpro.com</li>
456
+ <li class="flex items-center gap-2"><i data-lucide="phone" class="w-4 h-4"></i> (555) 123-4567</li>
457
+ <li class="flex items-center gap-2"><i data-lucide="map-pin" class="w-4 h-4"></i> San Francisco, CA</li>
458
+ </ul>
459
+ </div>
460
+ </div>
461
+ <div class="border-t border-gray-800 pt-6 text-center text-sm">
462
+ <p>&copy; 2025 PrintPro. All rights reserved. | <a href="admin.html" class="hover:text-white transition-colors">Admin Panel</a></p>
463
+ </div>
464
+ </div>
465
+ </footer>
466
+
467
+ <!-- Toast Notification -->
468
+ <div id="toast" class="fixed top-6 right-6 z-[100] hidden bg-gray-900 text-white px-5 py-3 rounded-xl shadow-2xl flex items-center gap-2 text-sm font-medium toast">
469
+ <i data-lucide="check-circle" class="w-4 h-4 text-green-400"></i>
470
+ <span id="toastMsg">Saved!</span>
471
+ </div>
472
+
473
+ <script src="https://unpkg.com/lucide@latest"></script>
474
+ <script src="script.js"></script>
475
+ <script>
476
+ lucide.createIcons();
477
+ </script>
478
+ <script src="https://deepsite.hf.co/deepsite-badge.js"></script>
479
+ </body>
480
+ </html>
script.js ADDED
@@ -0,0 +1,564 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // =============================================
2
+ // PrintPro - Main Frontend Script
3
+ // =============================================
4
+
5
+ // --- DEFAULT PRICING DATA ---
6
+ const DEFAULT_PRICING = {
7
+ products: {
8
+ 'business-cards': {
9
+ name: 'Business Cards',
10
+ icon: 'credit-card',
11
+ image: 'http://static.photos/office/320x240/42',
12
+ basePrice: 19.00,
13
+ sizes: [
14
+ { id: '3.5x2', name: '3.5" x 2"', price: 0 },
15
+ { id: '3.5x2-rounded', name: '3.5" x 2" Rounded', price: 4 },
16
+ { id: '3.5x2-square', name: '3.5" x 3.5" Square', price: 6 }
17
+ ],
18
+ paperTypes: [
19
+ { id: '14pt-gloss', name: '14pt Gloss', price: 0 },
20
+ { id: '16pt-matte', name: '16pt Matte', price: 6 },
21
+ { id: '14pt-uncoated', name: '14pt Uncoated', price: 3 },
22
+ { id: '16pt-silk', name: '16pt Silk', price: 8 }
23
+ ],
24
+ finishing: [
25
+ { id: 'none', name: 'None', price: 0 },
26
+ { id: 'matte', name: 'Matte Coating', price: 5 },
27
+ { id: 'gloss', name: 'Gloss Coating', price: 5 },
28
+ { id: 'uv', name: 'UV Spot Gloss', price: 12 },
29
+ { id: 'foil', name: 'Foil Stamp', price: 18 }
30
+ ],
31
+ quantityTiers: [
32
+ { min: 50, max: 250, multiplier: 1.0, label: '50-250' },
33
+ { min: 251, max: 500, multiplier: 0.75, label: '251-500' },
34
+ { min: 501, max: 1000, multiplier: 0.55, label: '501-1K' },
35
+ { min: 1001, max: 2500, multiplier: 0.40, label: '1K-2.5K' },
36
+ { min: 2501, max: 5000, multiplier: 0.30, label: '2.5K-5K' },
37
+ { min: 5001, max: 10000, multiplier: 0.22, label: '5K-10K' }
38
+ ],
39
+ turnaround: [
40
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
41
+ { id: 'rush', name: 'Rush (3-4 days)', price: 15 },
42
+ { id: 'express', name: 'Express (1-2 days)', price: 30 }
43
+ ],
44
+ defaultQuantity: 500
45
+ },
46
+ 'flyers': {
47
+ name: 'Flyers',
48
+ icon: 'file-text',
49
+ image: 'http://static.photos/office/320x240/55',
50
+ basePrice: 35.00,
51
+ sizes: [
52
+ { id: '8.5x11', name: '8.5" x 11"', price: 0 },
53
+ { id: '5.5x8.5', name: '5.5" x 8.5"', price: -3 },
54
+ { id: '11x17', name: '11" x 17"', price: 8 }
55
+ ],
56
+ paperTypes: [
57
+ { id: '100lb-gloss', name: '100lb Gloss', price: 0 },
58
+ { id: '80lb-matte', name: '80lb Matte', price: -2 },
59
+ { id: '100lb-uncoated', name: '100lb Uncoated', price: 2 }
60
+ ],
61
+ finishing: [
62
+ { id: 'none', name: 'None', price: 0 },
63
+ { id: 'matte', name: 'Matte Coating', price: 8 },
64
+ { id: 'gloss', name: 'Gloss Coating', price: 8 }
65
+ ],
66
+ quantityTiers: [
67
+ { min: 50, max: 250, multiplier: 1.0, label: '50-250' },
68
+ { min: 251, max: 500, multiplier: 0.70, label: '251-500' },
69
+ { min: 501, max: 1000, multiplier: 0.50, label: '501-1K' },
70
+ { min: 1001, max: 5000, multiplier: 0.35, label: '1K-5K' },
71
+ { min: 5001, max: 10000, multiplier: 0.25, label: '5K-10K' }
72
+ ],
73
+ turnaround: [
74
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
75
+ { id: 'rush', name: 'Rush (3-4 days)', price: 20 },
76
+ { id: 'express', name: 'Express (1-2 days)', price: 40 }
77
+ ],
78
+ defaultQuantity: 250
79
+ },
80
+ 'brochures': {
81
+ name: 'Brochures',
82
+ icon: 'book-open',
83
+ image: 'http://static.photos/office/320x240/67',
84
+ basePrice: 55.00,
85
+ sizes: [
86
+ { id: '8.5x11-bi', name: '8.5"x11" Bi-Fold', price: 0 },
87
+ { id: '8.5x11-tri', name: '8.5"x11" Tri-Fold', price: 3 },
88
+ { id: '11x17-bi', name: '11"x17" Bi-Fold', price: 10 }
89
+ ],
90
+ paperTypes: [
91
+ { id: '100lb-gloss', name: '100lb Gloss', price: 0 },
92
+ { id: '80lb-matte', name: '80lb Matte', price: -3 },
93
+ { id: '100lb-silk', name: '100lb Silk', price: 5 }
94
+ ],
95
+ finishing: [
96
+ { id: 'none', name: 'None', price: 0 },
97
+ { id: 'matte', name: 'Matte Coating', price: 10 },
98
+ { id: 'gloss', name: 'Gloss Coating', price: 10 }
99
+ ],
100
+ quantityTiers: [
101
+ { min: 25, max: 100, multiplier: 1.0, label: '25-100' },
102
+ { min: 101, max: 250, multiplier: 0.70, label: '101-250' },
103
+ { min: 251, max: 500, multiplier: 0.52, label: '251-500' },
104
+ { min: 501, max: 1000, multiplier: 0.40, label: '501-1K' },
105
+ { min: 1001, max: 5000, multiplier: 0.30, label: '1K-5K' }
106
+ ],
107
+ turnaround: [
108
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
109
+ { id: 'rush', name: 'Rush (3-4 days)', price: 25 },
110
+ { id: 'express', name: 'Express (1-2 days)', price: 50 }
111
+ ],
112
+ defaultQuantity: 100
113
+ },
114
+ 'banners': {
115
+ name: 'Banners',
116
+ icon: 'flag',
117
+ image: 'http://static.photos/office/320x240/78',
118
+ basePrice: 45.00,
119
+ sizes: [
120
+ { id: '24x36', name: '24" x 36"', price: 0 },
121
+ { id: '36x48', name: '36" x 48"', price: 15 },
122
+ { id: '48x72', name: '48" x 72"', price: 35 }
123
+ ],
124
+ paperTypes: [
125
+ { id: 'vinyl', name: '13oz Vinyl', price: 0 },
126
+ { id: 'mesh', name: 'Mesh Vinyl', price: 8 },
127
+ { id: 'fabric', name: 'Fabric', price: 20 }
128
+ ],
129
+ finishing: [
130
+ { id: 'none', name: 'None', price: 0 },
131
+ { id: 'grommets', name: 'Grommets', price: 5 },
132
+ { id: 'pole-pockets', name: 'Pole Pockets', price: 10 }
133
+ ],
134
+ quantityTiers: [
135
+ { min: 1, max: 5, multiplier: 1.0, label: '1-5' },
136
+ { min: 6, max: 10, multiplier: 0.80, label: '6-10' },
137
+ { min: 11, max: 25, multiplier: 0.65, label: '11-25' },
138
+ { min: 26, max: 50, multiplier: 0.50, label: '26-50' }
139
+ ],
140
+ turnaround: [
141
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
142
+ { id: 'rush', name: 'Rush (3-4 days)', price: 20 },
143
+ { id: 'express', name: 'Express (1-2 days)', price: 45 }
144
+ ],
145
+ defaultQuantity: 1
146
+ },
147
+ 'stickers': {
148
+ name: 'Stickers & Labels',
149
+ icon: 'sticky-note',
150
+ image: 'http://static.photos/office/320x240/91',
151
+ basePrice: 25.00,
152
+ sizes: [
153
+ { id: '2x2', name: '2" x 2"', price: 0 },
154
+ { id: '3x3', name: '3" x 3"', price: 5 },
155
+ { id: '4x4', name: '4" x 4"', price: 8 },
156
+ { id: 'custom-die', name: 'Custom Die-Cut', price: 15 }
157
+ ],
158
+ paperTypes: [
159
+ { id: 'white-vinyl', name: 'White Vinyl', price: 0 },
160
+ { id: 'clear-vinyl', name: 'Clear Vinyl', price: 5 },
161
+ { id: 'metallic', name: 'Metallic', price: 10 }
162
+ ],
163
+ finishing: [
164
+ { id: 'none', name: 'None', price: 0 },
165
+ { id: 'matte-lam', name: 'Matte Laminate', price: 6 },
166
+ { id: 'gloss-lam', name: 'Gloss Laminate', price: 6 }
167
+ ],
168
+ quantityTiers: [
169
+ { min: 50, max: 250, multiplier: 1.0, label: '50-250' },
170
+ { min: 251, max: 500, multiplier: 0.70, label: '251-500' },
171
+ { min: 501, max: 1000, multiplier: 0.48, label: '501-1K' },
172
+ { min: 1001, max: 2500, multiplier: 0.35, label: '1K-2.5K' },
173
+ { min: 2501, max: 5000, multiplier: 0.25, label: '2.5K-5K' }
174
+ ],
175
+ turnaround: [
176
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
177
+ { id: 'rush', name: 'Rush (3-4 days)', price: 12 },
178
+ { id: 'express', name: 'Express (1-2 days)', price: 28 }
179
+ ],
180
+ defaultQuantity: 250
181
+ },
182
+ 'postcards': {
183
+ name: 'Postcards',
184
+ icon: 'mail',
185
+ image: 'http://static.photos/office/320x240/33',
186
+ basePrice: 28.00,
187
+ sizes: [
188
+ { id: '4x6', name: '4" x 6"', price: 0 },
189
+ { id: '5x7', name: '5" x 7"', price: 4 },
190
+ { id: '6x9', name: '6" x 9"', price: 7 },
191
+ { id: '6x11', name: '6" x 11"', price: 10 }
192
+ ],
193
+ paperTypes: [
194
+ { id: '14pt-gloss', name: '14pt Gloss', price: 0 },
195
+ { id: '16pt-matte', name: '16pt Matte', price: 5 },
196
+ { id: '14pt-uncoated', name: '14pt Uncoated', price: 2 }
197
+ ],
198
+ finishing: [
199
+ { id: 'none', name: 'None', price: 0 },
200
+ { id: 'matte', name: 'Matte Coating', price: 5 },
201
+ { id: 'gloss', name: 'Gloss Coating', price: 5 },
202
+ { id: 'uv-front', name: 'UV Front Only', price: 8 }
203
+ ],
204
+ quantityTiers: [
205
+ { min: 100, max: 500, multiplier: 1.0, label: '100-500' },
206
+ { min: 501, max: 1000, multiplier: 0.72, label: '501-1K' },
207
+ { min: 1001, max: 2500, multiplier: 0.52, label: '1K-2.5K' },
208
+ { min: 2501, max: 5000, multiplier: 0.38, label: '2.5K-5K' },
209
+ { min: 5001, max: 10000, multiplier: 0.28, label: '5K-10K' }
210
+ ],
211
+ turnaround: [
212
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
213
+ { id: 'rush', name: 'Rush (3-4 days)', price: 12 },
214
+ { id: 'express', name: 'Express (1-2 days)', price: 28 }
215
+ ],
216
+ defaultQuantity: 500
217
+ },
218
+ 'letterhead': {
219
+ name: 'Letterhead',
220
+ icon: 'file',
221
+ image: 'http://static.photos/office/320x240/44',
222
+ basePrice: 40.00,
223
+ sizes: [
224
+ { id: '8.5x11', name: '8.5" x 11"', price: 0 },
225
+ { id: '8.5x14', name: '8.5" x 14"', price: 5 }
226
+ ],
227
+ paperTypes: [
228
+ { id: '24lb-bond', name: '24lb Bond', price: 0 },
229
+ { id: '28lb-bond', name: '28lb Bond', price: 5 },
230
+ { id: '32lb-bond', name: '32lb Bond', price: 10 },
231
+ { id: 'linen', name: 'Linen Finish', price: 15 }
232
+ ],
233
+ finishing: [
234
+ { id: 'none', name: 'None', price: 0 },
235
+ { id: 'watermark', name: 'Watermark', price: 8 }
236
+ ],
237
+ quantityTiers: [
238
+ { min: 100, max: 500, multiplier: 1.0, label: '100-500' },
239
+ { min: 501, max: 1000, multiplier: 0.70, label: '501-1K' },
240
+ { min: 1001, max: 2500, multiplier: 0.48, label: '1K-2.5K' },
241
+ { min: 2501, max: 5000, multiplier: 0.35, label: '2.5K-5K' }
242
+ ],
243
+ turnaround: [
244
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
245
+ { id: 'rush', name: 'Rush (3-4 days)', price: 18 },
246
+ { id: 'express', name: 'Express (1-2 days)', price: 38 }
247
+ ],
248
+ defaultQuantity: 500
249
+ },
250
+ 'envelopes': {
251
+ name: 'Envelopes',
252
+ icon: 'mail-open',
253
+ image: 'http://static.photos/office/320x240/56',
254
+ basePrice: 32.00,
255
+ sizes: [
256
+ { id: '10-standard', name: '#10 Standard', price: 0 },
257
+ { id: 'a7', name: 'A7 (5.25x7.25)', price: 3 },
258
+ { id: 'a2', name: 'A2 (4.375x5.75)', price: 0 }
259
+ ],
260
+ paperTypes: [
261
+ { id: '24lb-white', name: '24lb White Wove', price: 0 },
262
+ { id: '28lb-white', name: '28lb White Wove', price: 4 },
263
+ { id: 'kraft', name: 'Kraft Brown', price: 3 }
264
+ ],
265
+ finishing: [
266
+ { id: 'none', name: 'None', price: 0 },
267
+ { id: 'window', name: 'Window', price: 5 },
268
+ { id: 'peel-seal', name: 'Peel & Seal', price: 4 }
269
+ ],
270
+ quantityTiers: [
271
+ { min: 100, max: 500, multiplier: 1.0, label: '100-500' },
272
+ { min: 501, max: 1000, multiplier: 0.72, label: '501-1K' },
273
+ { min: 1001, max: 2500, multiplier: 0.52, label: '1K-2.5K' },
274
+ { min: 2501, max: 5000, multiplier: 0.36, label: '2.5K-5K' }
275
+ ],
276
+ turnaround: [
277
+ { id: 'standard', name: 'Standard (5-7 days)', price: 0 },
278
+ { id: 'rush', name: 'Rush (3-4 days)', price: 15 },
279
+ { id: 'express', name: 'Express (1-2 days)', price: 32 }
280
+ ],
281
+ defaultQuantity: 250
282
+ }
283
+ }
284
+ };
285
+
286
+ // --- INITIALIZE PRICING ---
287
+ function getPricing() {
288
+ const stored = localStorage.getItem('printpro_pricing');
289
+ if (stored) {
290
+ try {
291
+ return JSON.parse(stored);
292
+ } catch (e) {
293
+ return DEFAULT_PRICING;
294
+ }
295
+ }
296
+ // Set default on first load
297
+ localStorage.setItem('printpro_pricing', JSON.stringify(DEFAULT_PRICING));
298
+ return DEFAULT_PRICING;
299
+ }
300
+
301
+ // --- STATE ---
302
+ let pricing = getPricing();
303
+ let selectedProduct = 'business-cards';
304
+ let selectedSize = null;
305
+ let selectedPaper = null;
306
+ let selectedFinishing = null;
307
+ let selectedTurnaround = null;
308
+ let currentQuantity = 500;
309
+
310
+ // --- DOM ELEMENTS ---
311
+ const productGrid = document.getElementById('productGrid');
312
+ const productSelector = document.getElementById('productSelector');
313
+ const sizeOptions = document.getElementById('sizeOptions');
314
+ const paperOptions = document.getElementById('paperOptions');
315
+ const finishingOptions = document.getElementById('finishingOptions');
316
+ const turnaroundOptions = document.getElementById('turnaroundOptions');
317
+ const quantitySlider = document.getElementById('quantitySlider');
318
+ const quantityDisplay = document.getElementById('quantityDisplay');
319
+ const qtyMin = document.getElementById('qtyMin');
320
+ const qtyMax = document.getElementById('qtyMax');
321
+ const totalPrice = document.getElementById('totalPrice');
322
+ const pricePerUnit = document.getElementById('pricePerUnit');
323
+ const heroPrice = document.getElementById('heroPrice');
324
+ const toast = document.getElementById('toast');
325
+ const toastMsg = document.getElementById('toastMsg');
326
+ const mobileMenuBtn = document.getElementById('mobileMenuBtn');
327
+ const mobileMenu = document.getElementById('mobileMenu');
328
+
329
+ // --- MOBILE MENU ---
330
+ mobileMenuBtn.addEventListener('click', () => {
331
+ mobileMenu.classList.toggle('hidden');
332
+ });
333
+
334
+ // Close mobile menu when clicking a link
335
+ mobileMenu.querySelectorAll('a').forEach(link => {
336
+ link.addEventListener('click', () => {
337
+ mobileMenu.classList.add('hidden');
338
+ });
339
+ });
340
+
341
+ // --- RENDER PRODUCT GRID ---
342
+ function renderProductGrid() {
343
+ const products = pricing.products;
344
+ productGrid.innerHTML = Object.entries(products).map(([key, product]) => `
345
+ <div class="product-card bg-white rounded-xl border border-gray-100 shadow-sm overflow-hidden cursor-pointer group relative"
346
+ onclick="selectProductAndScroll('${key}')">
347
+ <div class="relative overflow-hidden h-48">
348
+ <img src="${product.image}" alt="${product.name}" class="w-full h-full object-cover group-hover:scale-110 transition-transform duration-500">
349
+ <div class="product-overlay absolute inset-0 bg-gradient-to-t from-black/60 to-transparent flex items-end p-4">
350
+ <span class="text-white font-bold text-sm">Get Quote →</span>
351
+ </div>
352
+ </div>
353
+ <div class="p-4">
354
+ <div class="flex items-center gap-2 mb-1">
355
+ <i data-lucide="${product.icon}" class="w-4 h-4 text-brand-500"></i>
356
+ <h3 class="font-semibold text-gray-900 text-sm">${product.name}</h3>
357
+ </div>
358
+ <p class="text-brand-600 font-bold">From $${product.basePrice.toFixed(2)}</p>
359
+ </div>
360
+ </div>
361
+ `).join('');
362
+ lucide.createIcons();
363
+ }
364
+
365
+ // --- RENDER PRODUCT SELECTOR PILLS ---
366
+ function renderProductSelector() {
367
+ const products = pricing.products;
368
+ productSelector.innerHTML = Object.entries(products).map(([key, product]) => `
369
+ <button class="option-btn px-4 py-2.5 rounded-xl text-sm font-semibold transition-all border-2 flex items-center gap-2
370
+ ${selectedProduct === key
371
+ ? 'bg-brand-600 text-white border-brand-600 shadow-md active'
372
+ : 'bg-white text-gray-600 border-gray-200 hover:border-brand-300 hover:text-brand-600'}"
373
+ onclick="selectProduct('${key}')">
374
+ <i data-lucide="${product.icon}" class="w-4 h-4"></i>
375
+ ${product.name}
376
+ </button>
377
+ `).join('');
378
+ lucide.createIcons();
379
+ }
380
+
381
+ // --- RENDER OPTION BUTTONS ---
382
+ function renderOptionButtons(containerId, options, selectedValue, onChange) {
383
+ const container = document.getElementById(containerId);
384
+ if (!container) return;
385
+ container.innerHTML = options.map(opt => `
386
+ <button class="option-btn px-3.5 py-2 rounded-lg text-sm font-medium transition-all border-2
387
+ ${selectedValue === opt.id
388
+ ? 'bg-brand-50 text-brand-700 border-brand-500 active shadow-sm'
389
+ : 'bg-white text-gray-600 border-gray-200 hover:border-gray-300'}"
390
+ onclick="${onChange}('${opt.id}')">
391
+ ${opt.name}
392
+ ${opt.price > 0 ? `<span class="text-xs text-gray-400 ml-1">+$${opt.price.toFixed(2)}</span>` : ''}
393
+ ${opt.price < 0 ? `<span class="text-xs text-green-500 ml-1">-$${Math.abs(opt.price).toFixed(2)}</span>` : ''}
394
+ ${opt.price === 0 && opt.id !== 'none' && opt.id !== 'standard' ? `<span class="text-xs text-gray-300 ml-1">Included</span>` : ''}
395
+ </button>
396
+ `).join('');
397
+ }
398
+
399
+ // --- SELECT PRODUCT ---
400
+ function selectProduct(productKey) {
401
+ selectedProduct = productKey;
402
+ const product = pricing.products[productKey];
403
+ selectedSize = product.sizes[0].id;
404
+ selectedPaper = product.paperTypes[0].id;
405
+ selectedFinishing = product.finishing[0].id;
406
+ selectedTurnaround = product.turnaround[0].id;
407
+ currentQuantity = product.defaultQuantity;
408
+
409
+ renderAllOptions();
410
+ updateSlider();
411
+ calculatePrice();
412
+ renderProductSelector();
413
+ }
414
+
415
+ function selectProductAndScroll(productKey) {
416
+ selectProduct(productKey);
417
+ document.getElementById('calculator').scrollIntoView({ behavior: 'smooth' });
418
+ }
419
+
420
+ // --- UPDATE SLIDER ---
421
+ function updateSlider() {
422
+ const product = pricing.products[selectedProduct];
423
+ const tiers = product.quantityTiers;
424
+ const minQty = tiers[0].min;
425
+ const maxQty = tiers[tiers.length - 1].max;
426
+
427
+ qtyMin.textContent = minQty;
428
+ qtyMax.textContent = maxQty;
429
+
430
+ // Map current quantity to slider position (0-100)
431
+ const sliderPos = ((currentQuantity - minQty) / (maxQty - minQty)) * 100;
432
+ quantitySlider.value = Math.round(sliderPos);
433
+ quantityDisplay.textContent = currentQuantity;
434
+ }
435
+
436
+ // --- GET QUANTITY FROM SLIDER ---
437
+ function getQuantityFromSlider(sliderValue) {
438
+ const product = pricing.products[selectedProduct];
439
+ const tiers = product.quantityTiers;
440
+ const minQty = tiers[0].min;
441
+ const maxQty = tiers[tiers.length - 1].max;
442
+ return Math.round(minQty + (sliderValue / 100) * (maxQty - minQty));
443
+ }
444
+
445
+ // --- GET TIER MULTIPLIER ---
446
+ function getTierMultiplier(quantity) {
447
+ const product = pricing.products[selectedProduct];
448
+ const tiers = product.quantityTiers;
449
+ for (const tier of tiers) {
450
+ if (quantity >= tier.min && quantity <= tier.max) {
451
+ return tier.multiplier;
452
+ }
453
+ }
454
+ // Fallback
455
+ return tiers[tiers.length - 1].multiplier;
456
+ }
457
+
458
+ // --- CALCULATE PRICE ---
459
+ function calculatePrice() {
460
+ const product = pricing.products[selectedProduct];
461
+
462
+ const sizeObj = product.sizes.find(s => s.id === selectedSize) || product.sizes[0];
463
+ const paperObj = product.paperTypes.find(p => p.id === selectedPaper) || product.paperTypes[0];
464
+ const finishObj = product.finishing.find(f => f.id === selectedFinishing) || product.finishing[0];
465
+ const turnaroundObj = product.turnaround.find(t => t.id === selectedTurnaround) || product.turnaround[0];
466
+ const multiplier = getTierMultiplier(currentQuantity);
467
+
468
+ const baseWithAddons = product.basePrice + sizeObj.price + paperObj.price + finishObj.price;
469
+ const subtotal = baseWithAddons * multiplier * (currentQuantity / 100);
470
+ const total = subtotal + turnaroundObj.price;
471
+
472
+ const formattedTotal = total.toFixed(2);
473
+ const perUnit = total / currentQuantity;
474
+
475
+ // Animate price
476
+ totalPrice.classList.remove('price-pulse');
477
+ void totalPrice.offsetWidth;
478
+ totalPrice.classList.add('price-pulse');
479
+ totalPrice.textContent = `$${formattedTotal}`;
480
+ pricePerUnit.textContent = `$${perUnit.toFixed(3)} / unit`;
481
+
482
+ // Update hero price for business cards
483
+ if (selectedProduct === 'business-cards' && currentQuantity === 500 &&
484
+ selectedSize === '3.5x2' && selectedPaper === '14pt-gloss' &&
485
+ selectedFinishing === 'none' && selectedTurnaround === 'standard') {
486
+ heroPrice.textContent = formattedTotal;
487
+ }
488
+ }
489
+
490
+ // --- RENDER ALL OPTIONS ---
491
+ function renderAllOptions() {
492
+ const product = pricing.products[selectedProduct];
493
+
494
+ renderOptionButtons('sizeOptions', product.sizes, selectedSize,
495
+ (val) => { selectedSize = val; renderAllOptions(); calculatePrice(); });
496
+
497
+ renderOptionButtons('paperOptions', product.paperTypes, selectedPaper,
498
+ (val) => { selectedPaper = val; renderAllOptions(); calculatePrice(); });
499
+
500
+ renderOptionButtons('finishingOptions', product.finishing, selectedFinishing,
501
+ (val) => { selectedFinishing = val; renderAllOptions(); calculatePrice(); });
502
+
503
+ renderOptionButtons('turnaroundOptions', product.turnaround, selectedTurnaround,
504
+ (val) => { selectedTurnaround = val; renderAllOptions(); calculatePrice(); });
505
+
506
+ lucide.createIcons();
507
+ }
508
+
509
+ // --- EVENT LISTENERS ---
510
+ quantitySlider.addEventListener('input', () => {
511
+ currentQuantity = getQuantityFromSlider(parseInt(quantitySlider.value));
512
+ quantityDisplay.textContent = currentQuantity;
513
+ calculatePrice();
514
+ });
515
+
516
+ // --- TOAST ---
517
+ function showToast(message) {
518
+ toastMsg.textContent = message;
519
+ toast.classList.remove('hidden');
520
+ void toast.offsetWidth;
521
+ toast.classList.add('toast');
522
+ setTimeout(() => {
523
+ toast.classList.add('hidden');
524
+ toast.classList.remove('toast');
525
+ }, 3000);
526
+ }
527
+
528
+ // --- LISTEN FOR PRICING CHANGES FROM ADMIN ---
529
+ window.addEventListener('storage', (e) => {
530
+ if (e.key === 'printpro_pricing') {
531
+ pricing = getPricing();
532
+ selectProduct(selectedProduct);
533
+ renderProductGrid();
534
+ renderProductSelector();
535
+ calculatePrice();
536
+ showToast('Pricing updated from admin panel! 🔄');
537
+ }
538
+ });
539
+
540
+ // Also listen for a custom event (for same-tab updates)
541
+ window.addEventListener('pricingUpdated', () => {
542
+ pricing = getPricing();
543
+ selectProduct(selectedProduct);
544
+ renderProductGrid();
545
+ renderProductSelector();
546
+ calculatePrice();
547
+ });
548
+
549
+ // --- INIT ---
550
+ function init() {
551
+ renderProductGrid();
552
+ renderProductSelector();
553
+ selectProduct('business-cards');
554
+ updateSlider();
555
+ calculatePrice();
556
+ }
557
+
558
+ // Run on load
559
+ document.addEventListener('DOMContentLoaded', init);
560
+
561
+ // Expose functions to global scope for onclick handlers
562
+ window.selectProduct = selectProduct;
563
+ window.selectProductAndScroll = selectProductAndScroll;
564
+ window.showToast = showToast;