Inambao commited on
Commit
33f7a59
·
verified ·
1 Parent(s): f5103b2

undefined - Initial Deployment

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +753 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Text To Document Area
3
- emoji: 🐨
4
- colorFrom: gray
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: text-to-document-area
3
+ emoji: 🐳
4
+ colorFrom: red
5
+ colorTo: yellow
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,753 @@
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>Integrated App Suite</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
10
+ <!-- Shared library for PDF generation -->
11
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js" integrity="sha512-GsLlZN/3F2ErC5ifS5QtgpiJtWd43JWSuIgh7mbzZ8zBps+dvLusV+eNQATqgA/HdeKFVgA5v3S/cIrLF7QnIg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
12
+ <style>
13
+ /* --- GLOBAL & LAYOUT STYLES --- */
14
+ :root {
15
+ --primary-color: #00e5ff;
16
+ --danger-color: #ff4757;
17
+ --secondary-color: #1d2b3a;
18
+ --dark-bg: #1a1d20;
19
+ --light-text: #f0f0f0;
20
+ --menu-bg: rgba(29, 43, 58, 0.6);
21
+ --container-bg: rgba(255, 255, 255, 0.08);
22
+ }
23
+ * {
24
+ margin: 0;
25
+ padding: 0;
26
+ box-sizing: border-box;
27
+ }
28
+ body {
29
+ font-family: 'Poppins', sans-serif;
30
+ background-color: var(--dark-bg);
31
+ color: var(--light-text);
32
+ display: flex;
33
+ height: 100vh;
34
+ overflow: hidden;
35
+ }
36
+
37
+ /* --- SIDE MENU STYLES --- */
38
+ #side-menu {
39
+ width: 240px;
40
+ height: 100vh;
41
+ background: var(--menu-bg);
42
+ backdrop-filter: blur(20px);
43
+ -webkit-backdrop-filter: blur(20px);
44
+ border-right: 1px solid rgba(255, 255, 255, 0.1);
45
+ padding: 25px 15px;
46
+ display: flex;
47
+ flex-direction: column;
48
+ transition: width 0.3s ease;
49
+ }
50
+ .menu-header {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 12px;
54
+ padding: 0 10px 25px 10px;
55
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
56
+ margin-bottom: 25px;
57
+ }
58
+ .menu-header .logo {
59
+ width: 40px;
60
+ height: 40px;
61
+ background: var(--primary-color);
62
+ border-radius: 50%;
63
+ display: grid;
64
+ place-items: center;
65
+ font-weight: 700;
66
+ color: #111;
67
+ font-size: 1.5rem;
68
+ }
69
+ .menu-header h2 {
70
+ font-size: 1.3rem;
71
+ font-weight: 600;
72
+ }
73
+ .menu-nav {
74
+ list-style: none;
75
+ }
76
+ .menu-btn {
77
+ display: flex;
78
+ align-items: center;
79
+ gap: 15px;
80
+ width: 100%;
81
+ padding: 15px;
82
+ margin-bottom: 8px;
83
+ border-radius: 8px;
84
+ border: none;
85
+ background: transparent;
86
+ color: var(--light-text);
87
+ cursor: pointer;
88
+ font-size: 1rem;
89
+ font-family: 'Poppins', sans-serif;
90
+ text-align: left;
91
+ transition: background-color 0.2s ease, color 0.2s ease;
92
+ }
93
+ .menu-btn svg {
94
+ width: 22px;
95
+ height: 22px;
96
+ fill: currentColor;
97
+ opacity: 0.7;
98
+ transition: opacity 0.2s ease;
99
+ }
100
+ .menu-btn:hover {
101
+ background-color: rgba(255, 255, 255, 0.1);
102
+ }
103
+ .menu-btn.active {
104
+ background-color: var(--primary-color);
105
+ color: #111;
106
+ font-weight: 600;
107
+ }
108
+ .menu-btn.active svg {
109
+ opacity: 1;
110
+ }
111
+
112
+ /* --- MAIN CONTENT AREA --- */
113
+ #main-content {
114
+ flex-grow: 1;
115
+ height: 100vh;
116
+ overflow-y: auto;
117
+ background-image: linear-gradient(135deg, rgba(35, 37, 38, 0.95) 0%, rgba(65, 67, 69, 0.85) 100%),
118
+ url('https://images.pexels.com/photos/325185/pexels-photo-325185.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2');
119
+ background-size: cover;
120
+ background-position: center;
121
+ }
122
+ .app-view {
123
+ display: none; /* Hidden by default */
124
+ justify-content: center;
125
+ align-items: flex-start;
126
+ padding: 40px 20px;
127
+ min-height: 100%;
128
+ }
129
+ .app-view.active-view {
130
+ display: flex;
131
+ }
132
+
133
+ /* --- WELCOME PAGE STYLES --- */
134
+ #welcome-view .welcome-container {
135
+ display: flex;
136
+ flex-direction: column;
137
+ justify-content: center;
138
+ align-items: center;
139
+ text-align: center;
140
+ height: 100%;
141
+ color: white;
142
+ }
143
+ #welcome-view h1 {
144
+ font-size: 4rem;
145
+ font-weight: 700;
146
+ text-shadow: 0 4px 15px rgba(0,0,0,0.4);
147
+ }
148
+ #welcome-view .tagline {
149
+ font-size: 1.5rem;
150
+ font-weight: 400;
151
+ color: rgba(255,255,255,0.8);
152
+ margin-top: 10px;
153
+ max-width: 600px;
154
+ }
155
+ #welcome-view .highlight {
156
+ color: var(--primary-color);
157
+ font-weight: 600;
158
+ }
159
+
160
+ /* --- SHARED APP COMPONENT STYLES --- */
161
+ .app-container {
162
+ max-width: 900px;
163
+ width: 100%;
164
+ background: var(--container-bg);
165
+ backdrop-filter: blur(15px);
166
+ -webkit-backdrop-filter: blur(15px);
167
+ border-radius: 20px;
168
+ border: 1px solid rgba(255, 255, 255, 0.18);
169
+ padding: 30px;
170
+ box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
171
+ }
172
+ .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
173
+ .header h1 { margin: 0; font-weight: 700; font-size: 2.2rem; text-shadow: 0 2px 4px rgba(0,0,0,0.2); }
174
+ .icon-btn { background: none; border: none; cursor: pointer; padding: 5px; color: rgba(255,255,255,0.7); transition: color 0.3s ease; }
175
+ .icon-btn:hover { color: white; }
176
+ .icon-btn svg { width: 24px; height: 24px; fill: currentColor; }
177
+ .instructions {
178
+ background: rgba(0,0,0,0.2); border-radius: 8px; margin-bottom: 25px;
179
+ max-height: 0; overflow: hidden; transition: max-height 0.5s ease-out, padding 0.5s ease-out; padding: 0 20px;
180
+ }
181
+ .instructions.visible { max-height: 300px; padding: 15px 20px; }
182
+ .instructions ul { list-style-type: '✓ '; padding-left: 20px; }
183
+ .instructions li { margin-bottom: 8px; }
184
+ button { padding: 12px 20px; border: none; border-radius: 8px; font-weight: 600; font-size: 0.9rem; cursor: pointer; user-select: none; transition: transform 0.2s ease, background-color 0.2s ease, opacity 0.2s ease; }
185
+ button:hover:not(:disabled) { transform: translateY(-3px); }
186
+ button:disabled { cursor: not-allowed; opacity: 0.6; }
187
+ .btn-secondary { background-color: transparent; color: var(--primary-color); border: 2px solid var(--primary-color); flex-grow: 1; }
188
+ .btn-secondary:hover:not(:disabled) { background-color: rgba(0, 229, 255, 0.1); }
189
+ .results {
190
+ background: rgba(0, 0, 0, 0.25); padding: 10px 25px; border-radius: 12px;
191
+ margin-bottom: 25px; min-height: 100px; max-height: 500px; overflow-y: auto;
192
+ line-height: 1.6; transition: all 0.3s ease;
193
+ }
194
+ .results[contenteditable="true"] {
195
+ outline: 2px solid var(--primary-color);
196
+ box-shadow: 0 0 15px rgba(0, 229, 255, 0.3);
197
+ }
198
+ .export-buttons { display: flex; gap: 10px; justify-content: space-between; flex-wrap: wrap; }
199
+
200
+ /* --- CV FORGE SPECIFIC STYLES --- */
201
+ #cv_cvForm { display: flex; flex-direction: column; gap: 25px; margin-bottom: 30px; }
202
+ fieldset { border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 12px; padding: 20px; background: rgba(0,0,0,0.15); }
203
+ legend { padding: 0 10px; font-weight: 600; font-size: 1.2rem; color: var(--primary-color); }
204
+ .form-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 15px; }
205
+ .form-group { display: flex; flex-direction: column; }
206
+ .form-group.full-width { grid-column: 1 / -1; }
207
+ label { margin-bottom: 8px; font-weight: 400; font-size: 0.9rem; color: rgba(255,255,255,0.8); }
208
+ input[type="text"], input[type="date"], input[type="tel"], input[type="email"], textarea {
209
+ background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.3); border-radius: 6px; padding: 10px;
210
+ color: white; font-family: 'Poppins', sans-serif; font-size: 1rem; transition: border-color 0.3s, box-shadow 0.3s;
211
+ }
212
+ input:focus, textarea:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 8px rgba(0, 229, 255, 0.5); }
213
+ textarea { resize: vertical; min-height: 100px; }
214
+ .dynamic-section-item { background: rgba(0,0,0,0.2); padding: 15px; border-radius: 8px; margin-bottom: 15px; border-left: 3px solid var(--primary-color); position: relative; }
215
+ .remove-btn { position: absolute; top: 10px; right: 10px; background-color: var(--danger-color); color: white; border: none; border-radius: 50%; width: 24px; height: 24px; font-weight: bold; cursor: pointer; line-height: 24px; text-align: center; z-index: 2; }
216
+ .add-btn { background-color: var(--primary-color); color: #111; font-weight: 600; }
217
+ .add-btn:hover { background-color: #33eaff; }
218
+ #cv_generateBtn { background-color: var(--danger-color); color: white; width: 100%; margin-bottom: 25px; font-size: 1.1rem; }
219
+ #cv_generateBtn:hover:not(:disabled) { background-color: #ff6b81; }
220
+ #cv_results[contenteditable="true"] { background: rgba(255, 255, 255, 0.95); color: #333; }
221
+ .cv-container { padding: 20px; }
222
+ .cv-header { text-align: center; margin-bottom: 25px; }
223
+ .cv-name { font-size: 2.5em; color: var(--primary-color); margin: 0; }
224
+ .cv-contact { font-size: 1em; color: #ccc; }
225
+ .cv-contact a { color: var(--primary-color); text-decoration: none; }
226
+ .cv-section { margin-bottom: 20px; }
227
+ .cv-section-title { font-size: 1.5em; color: var(--primary-color); border-bottom: 2px solid var(--primary-color); padding-bottom: 5px; margin-bottom: 15px; }
228
+ .cv-item { margin-bottom: 15px; }
229
+ .cv-item-title { font-size: 1.1em; font-weight: bold; }
230
+ .cv-item-subtitle { font-size: 1em; font-style: italic; color: #bbb; }
231
+ .cv-item-dates { float: right; font-style: italic; color: #bbb; }
232
+ .cv-item-description { margin-top: 5px; padding-left: 20px; }
233
+ .cv-skills-list { list-style: none; padding: 0; column-count: 2; }
234
+ #cv_results[contenteditable="true"] .cv-section-title, #cv_results[contenteditable="true"] .cv-name { color: #0077b5; }
235
+ #cv_results[contenteditable="true"] .cv-contact, #cv_results[contenteditable="true"] .cv-item-subtitle, #cv_results[contenteditable="true"] .cv-item-dates { color: #555; }
236
+
237
+ /* --- TEXTIFY PRO SPECIFIC STYLES --- */
238
+ #textify_uploadArea {
239
+ border: 2px dashed rgba(255, 255, 255, 0.4); padding: 40px 20px; text-align: center; margin-bottom: 30px;
240
+ background: rgba(0,0,0,0.2); border-radius: 12px; cursor: pointer; transition: background-color 0.3s ease, border-color 0.3s ease; user-select: none;
241
+ }
242
+ #textify_uploadArea:hover, #textify_uploadArea.dragover { background-color: rgba(0, 229, 255, 0.1); border-color: var(--primary-color); }
243
+ #textify_fileInput { display: none; }
244
+ #textify_settingsMenu {
245
+ position: absolute; top: 80px; right: 30px; width: 280px; background: rgba(40, 40, 40, 0.7); backdrop-filter: blur(10px);
246
+ border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.2); padding: 20px; z-index: 100; box-shadow: 0 8px 20px rgba(0,0,0,0.4);
247
+ opacity: 0; transform: translateY(-10px); pointer-events: none; transition: opacity 0.3s ease, transform 0.3s ease;
248
+ }
249
+ #textify_settingsMenu.visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
250
+ .setting-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
251
+ .setting-header h3 { margin: 0; font-size: 1.1rem; }
252
+ .close-btn { font-size: 1.5rem; color: rgba(255,255,255,0.7); cursor: pointer; line-height: 1; }
253
+ .close-btn:hover { color: white; }
254
+ .control-group { margin-bottom: 20px; }
255
+ .control-group label { display: block; margin-bottom: 10px; font-weight: 600; }
256
+ #textify_marginControl { width: 100%; cursor: pointer; }
257
+ .layout-control label { display: inline-block; background: rgba(0,0,0,0.3); padding: 8px 15px; border-radius: 6px; cursor: pointer; margin-right: 10px; border: 2px solid transparent; transition: background-color 0.2s, border-color 0.2s; }
258
+ .layout-control input[type="radio"] { display: none; }
259
+ .layout-control input[type="radio"]:checked + label { background-color: rgba(0, 229, 255, 0.2); border-color: var(--primary-color); color: white; }
260
+ #textify_preview { display: flex; gap: var(--image-margin, 10px); margin-bottom: 30px; min-height: 130px; background: rgba(0, 0, 0, 0.25); padding: 15px; border-radius: 12px; align-content: flex-start; transition: all 0.3s ease; }
261
+ #textify_preview.layout-row { flex-wrap: wrap; flex-direction: row; }
262
+ #textify_preview.layout-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); }
263
+ #textify_preview:empty::before { content: "Your uploaded images will appear here..."; width: 100%; text-align: center; align-self: center; color: rgba(255, 255, 255, 0.5); font-style: italic; }
264
+ .image-thumb { width: 100px; height: 100px; object-fit: cover; border: 3px solid transparent; border-radius: 8px; cursor: grab; box-shadow: 0 4px 10px rgba(0,0,0,0.3); transition: transform 0.2s ease, box-shadow 0.2s ease; }
265
+ .image-thumb:hover { transform: scale(1.05); }
266
+ .dragging { opacity: 0.4; transform: scale(0.95); cursor: grabbing !important; box-shadow: 0 0 20px var(--primary-color); }
267
+ #textify_convertBtn { background-color: var(--danger-color); color: white; width: 100%; margin-bottom: 25px; font-size: 1rem; }
268
+ #textify_convertBtn:hover:not(:disabled) { background-color: #ff6b81; }
269
+ #textify_results[contenteditable="true"] { background: rgba(0, 0, 0, 0.4); }
270
+ #textify_results h1, #textify_results h2, #textify_results h3 { color: var(--primary-color); margin-top: 1.2em; margin-bottom: 0.5em; }
271
+ #textify_results p { margin-bottom: 1em; }
272
+ #textify_results ul, #textify_results ol { padding-left: 25px; }
273
+ #textify_results hr { border: none; height: 1px; background-color: rgba(255,255,255,0.2); margin: 20px 0; }
274
+
275
+ /* --- RESPONSIVE DESIGN --- */
276
+ @media (max-width: 768px) {
277
+ body {
278
+ flex-direction: column;
279
+ }
280
+ #side-menu {
281
+ width: 100%;
282
+ height: auto;
283
+ flex-direction: row;
284
+ align-items: center;
285
+ padding: 10px;
286
+ border-right: none;
287
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
288
+ }
289
+ .menu-header {
290
+ padding: 0;
291
+ margin: 0;
292
+ border: none;
293
+ }
294
+ .menu-header h2 { display: none; }
295
+ .menu-nav {
296
+ display: flex;
297
+ gap: 5px;
298
+ }
299
+ .menu-btn {
300
+ padding: 10px;
301
+ margin: 0;
302
+ gap: 8px;
303
+ }
304
+ .menu-btn span {
305
+ display: none;
306
+ }
307
+ #main-content {
308
+ height: calc(100vh - 65px); /* Adjust based on top bar height */
309
+ }
310
+ .app-view {
311
+ padding: 20px 10px;
312
+ }
313
+ #welcome-view h1 { font-size: 2.5rem; }
314
+ #welcome-view .tagline { font-size: 1.1rem; }
315
+ }
316
+ </style>
317
+ </head>
318
+ <body>
319
+
320
+ <!-- =========== SIDE MENU =========== -->
321
+ <nav id="side-menu">
322
+ <div class="menu-header">
323
+ <div class="logo">S</div>
324
+ <h2>App Suite</h2>
325
+ </div>
326
+ <ul class="menu-nav">
327
+ <li>
328
+ <button class="menu-btn" data-view="welcome-view" onclick="showView('welcome-view', this)">
329
+ <svg viewBox="0 0 24 24"><path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/></svg>
330
+ <span>Home</span>
331
+ </button>
332
+ </li>
333
+ <li>
334
+ <button class="menu-btn" data-view="cv-forge-view" onclick="showView('cv-forge-view', this)">
335
+ <svg viewBox="0 0 24 24"><path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zM6 20V4h7v5h5v11H6z"/></svg>
336
+ <span>CV Forge</span>
337
+ </button>
338
+ </li>
339
+ <li>
340
+ <button class="menu-btn" data-view="textify-pro-view" onclick="showView('textify-pro-view', this)">
341
+ <svg viewBox="0 0 24 24"><path d="M20 4v12H8V4h12m0-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zM4 6H2v14c0 1.1.9 2 2 2h14v-2H4V6z M12 11h-2V9h2v2zm-2-4h2V5h-2v2zm4 4h-2V9h2v2zm0-4h2V5h-2v2z"/></svg>
342
+ <span>Textify Pro</span>
343
+ </button>
344
+ </li>
345
+ </ul>
346
+ </nav>
347
+
348
+ <!-- =========== MAIN CONTENT WRAPPER =========== -->
349
+ <main id="main-content">
350
+
351
+ <!-- =========== WELCOME PAGE =========== -->
352
+ <div id="welcome-view" class="app-view">
353
+ <div class="welcome-container">
354
+ <h1>Integrated App Suite</h1>
355
+ <p class="tagline">Craft your professional story with <span class="highlight">CV Forge</span>, or bring images to life with <span class="highlight">Textify Pro</span>. Your productivity hub starts here.</p>
356
+ </div>
357
+ </div>
358
+
359
+ <!-- =========== CV FORGE APP =========== -->
360
+ <div id="cv-forge-view" class="app-view">
361
+ <div class="app-container" id="cv_appContainer">
362
+ <div class="header">
363
+ <h1>CV Forge</h1>
364
+ <div class="header-icons">
365
+ <button class="icon-btn" id="cv_instructionsBtn" title="Show Instructions">
366
+ <svg viewBox="0 0 24 24"><path d="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10,10-4.48,10-10S17.52,2,12,2zm1,15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>
367
+ </button>
368
+ </div>
369
+ </div>
370
+ <div class="instructions" id="cv_instructionsPanel"><h4>How to Use CV Forge:</h4><ul><li><b>Fill in the Details:</b> Complete the form with your personal and professional information.</li><li><b>Add Entries:</b> Use the "Add" buttons for education, experience, and references.</li><li><b>Generate:</b> Press the big red button to create your formatted CV.</li><li><b>Edit & Export:</b> Click the results to fine-tune, then download as PDF, Word, or TXT.</li></ul></div>
371
+ <form id="cv_cvForm">
372
+ <fieldset><legend>Personal Details</legend><div class="form-grid"><div class="form-group"><label for="cv_fullName">Full Name</label><input type="text" id="cv_fullName" placeholder="e.g., Jane Doe"></div><div class="form-group"><label for="cv_dob">Date of Birth</label><input type="date" id="cv_dob"></div><div class="form-group"><label for="cv_phone">Phone Number</label><input type="tel" id="cv_phone" placeholder="e.g., (123) 456-7890"></div><div class="form-group"><label for="cv_email">Email Address</label><input type="email" id="cv_email" placeholder="e.g., jane.doe@example.com"></div><div class="form-group full-width"><label for="cv_address">Address</label><input type="text" id="cv_address" placeholder="e.g., 123 Main St, Anytown, USA"></div><div class="form-group full-width"><label for="cv_about">About / Professional Summary</label><textarea id="cv_about" placeholder="Write a short summary..."></textarea></div></div></fieldset>
373
+ <fieldset><legend>Work Experience</legend><div id="cv_workExperienceSection"></div><button type="button" class="add-btn" onclick="cv_addDynamicEntry('experience')">+ Add Experience</button></fieldset>
374
+ <fieldset><legend>Education Background</legend><div id="cv_educationSection"></div><button type="button" class="add-btn" onclick="cv_addDynamicEntry('education')">+ Add Education</button></fieldset>
375
+ <fieldset><legend>Skills & Accomplishments</legend><div class="form-grid"><div class="form-group full-width"><label for="cv_skills">Skills (comma-separated)</label><input type="text" id="cv_skills" placeholder="e.g., JavaScript, Project Management"></div><div class="form-group full-width"><label for="cv_accomplishments">Key Accomplishments (one per line)</label><textarea id="cv_accomplishments" placeholder="e.g., Increased sales by 20%"></textarea></div></div></fieldset>
376
+ <fieldset><legend>References</legend><div id="cv_referencesSection"></div><button type="button" class="add-btn" onclick="cv_addDynamicEntry('references')">+ Add Reference</button></fieldset>
377
+ </form>
378
+ <button id="cv_generateBtn">Generate CV</button>
379
+ <div class="results" id="cv_results" contenteditable="false"><p style="color: rgba(255,255,255,0.5); text-align: center; margin-top: 20px;">Your generated CV will appear here.</p></div>
380
+ <div class="export-buttons">
381
+ <button class="btn-secondary" id="cv_downloadPdfBtn">Download PDF</button>
382
+ <button class="btn-secondary" id="cv_downloadWordBtn">Download Word</button>
383
+ <button class="btn-secondary" id="cv_downloadTxtBtn">Download TXT</button>
384
+ <button class="btn-secondary" id="cv_printBtn">Print</button>
385
+ </div>
386
+ </div>
387
+ </div>
388
+
389
+ <!-- =========== TEXTIFY PRO APP =========== -->
390
+ <div id="textify-pro-view" class="app-view">
391
+ <div class="app-container" id="textify_appContainer">
392
+ <div class="header">
393
+ <h1>Textify Pro</h1>
394
+ <div class="header-icons">
395
+ <button class="icon-btn" id="textify_instructionsBtn"><svg viewBox="0 0 24 24"><path d="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10,10-4.48,10-10S17.52,2,12,2zm1,15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg></button>
396
+ <button class="icon-btn" id="textify_settingsBtn"><svg viewBox="0 0 24 24"><path d="M19.44,12.99l.01-0.06c0-0.23-0.04-0.46-0.1-0.68l2.09-1.65c0.19-0.15,0.24-0.42,0.11-0.64l-1.99-3.46c-0.13-0.22-0.39-0.31-0.61-0.22l-2.58,1.03C15.8,7.1,15.17,6.83,14.49,6.6l-0.37-2.82C14.07,3.51,13.82,3.33,13.56,3.33h-3.99c-0.26,0-0.51,0.18-0.56,0.45L8.64,6.6C7.96,6.83,7.33,7.1,6.8,7.36L4.22,6.33C4,6.24,3.74,6.33,3.61,6.55L1.62,10.01c-0.13,0.22-0.08,0.49,0.11,0.64l2.09,1.65c-0.06,0.22-0.1,0.45-0.1,0.68l-0.01,0.06c0,0.23,0.04,0.46,0.1,0.68L1.73,14.97c-0.19,0.15-0.24,0.42-0.11,0.64l1.99,3.46c0.13,0.22,0.39,0.31,0.61,0.22l2.58-1.03c0.53,0.26,1.16,0.53,1.84,0.73l0.37,2.82c0.05,0.27,0.3,0.45,0.56,0.45h3.99c0.26,0,0.51-0.18,0.56-0.45l0.37-2.82c0.68-0.2,1.31-0.47,1.84-0.73l2.58,1.03c0.22,0.09,0.48-0.01,0.61-0.22l1.99-3.46c0.13-0.22,0.08-0.49-0.11-0.64l-2.09-1.65C19.4,13.45,19.44,13.22,19.44,12.99z M11.56,15.5c-1.93,0-3.5-1.57-3.5-3.5s1.57-3.5,3.5-3.5s3.5,1.57,3.5,3.5S13.49,15.5,11.56,15.5z"/></svg></button>
397
+ </div>
398
+ </div>
399
+ <div class="instructions" id="textify_instructionsPanel"><h4>How to Use Textify Pro:</h4><ul><li><b>Upload & Re-order:</b> Click or drag images, then reorder them as needed.</li><li><b>Convert:</b> Press the red button to extract text with the AI.</li><li><b>Edit:</b> Click the results area to fix any errors directly.</li><li><b>Export:</b> Use the buttons to download as PDF, Word, TXT, or Print.</li></ul></div>
400
+ <div class="settings-menu" id="textify_settingsMenu"><div class="setting-header"><h3>Settings</h3><span class="close-btn" id="textify_closeSettingsBtn">×</span></div><div class="control-group"><label for="textify_marginControl">Image Spacing:</label><input type="range" id="textify_marginControl" min="2" max="40" value="10" /></div><div class="control-group"><label>Image Layout:</label><div class="layout-control"><input type="radio" id="textify_layoutRow" name="layout" value="row" checked><label for="textify_layoutRow">Row</label><input type="radio" id="textify_layoutGrid" name="layout" value="grid"><label for="textify_layoutGrid">Grid</label></div></div></div>
401
+ <div class="upload-area" id="textify_uploadArea" onclick="textify_fileInput.click()"><input type="file" id="textify_fileInput" multiple accept="image/*" />Click or Drag & Drop Images Here</div>
402
+ <div class="images-preview layout-row" id="textify_preview"></div>
403
+ <button id="textify_convertBtn" onclick="textify_convertImages()">Convert to Text</button>
404
+ <div class="results" id="textify_results" contenteditable="false"><p style="color: rgba(255,255,255,0.5); text-align: center; margin-top: 20px;">Your converted text will appear here.</p></div>
405
+ <div class="export-buttons">
406
+ <button class="btn-secondary" id="textify_downloadPdfBtn">Download PDF</button>
407
+ <button class="btn-secondary" id="textify_downloadWordBtn">Download Word</button>
408
+ <button class="btn-secondary" id="textify_downloadTxtBtn">Download TXT</button>
409
+ <button class="btn-secondary" id="textify_printBtn">Print</button>
410
+ </div>
411
+ </div>
412
+ </div>
413
+
414
+ </main>
415
+
416
+ <script>
417
+ // =========== GLOBAL APP SWITCHING LOGIC ===========
418
+ function showView(viewId, clickedButton) {
419
+ // Hide all views
420
+ document.querySelectorAll('.app-view').forEach(view => {
421
+ view.classList.remove('active-view');
422
+ });
423
+
424
+ // Deactivate all menu buttons
425
+ document.querySelectorAll('.menu-btn').forEach(btn => {
426
+ btn.classList.remove('active');
427
+ });
428
+
429
+ // Show the selected view
430
+ document.getElementById(viewId).classList.add('active-view');
431
+
432
+ // Activate the clicked button
433
+ if(clickedButton) {
434
+ clickedButton.classList.add('active');
435
+ } else {
436
+ // Fallback for initial load
437
+ document.querySelector(`.menu-btn[data-view="${viewId}"]`).classList.add('active');
438
+ }
439
+ }
440
+
441
+ // Show the welcome page on initial load
442
+ window.onload = () => {
443
+ showView('welcome-view');
444
+ };
445
+
446
+ // =========== CV FORGE SCRIPT ===========
447
+ (function() {
448
+ const instructionsBtn = document.getElementById('cv_instructionsBtn');
449
+ const instructionsPanel = document.getElementById('cv_instructionsPanel');
450
+ const generateBtn = document.getElementById('cv_generateBtn');
451
+ const results = document.getElementById('cv_results');
452
+ const downloadPdfBtn = document.getElementById('cv_downloadPdfBtn');
453
+ const downloadWordBtn = document.getElementById('cv_downloadWordBtn');
454
+ const downloadTxtBtn = document.getElementById('cv_downloadTxtBtn');
455
+ const printBtn = document.getElementById('cv_printBtn');
456
+
457
+ instructionsBtn.addEventListener('click', () => instructionsPanel.classList.toggle('visible'));
458
+ generateBtn.addEventListener('click', cv_generateCv);
459
+ downloadPdfBtn.addEventListener('click', cv_handlePdfDownload);
460
+ downloadWordBtn.addEventListener('click', cv_handleWordDownload);
461
+ downloadTxtBtn.addEventListener('click', cv_handleTxtDownload);
462
+ printBtn.addEventListener('click', cv_handlePrint);
463
+
464
+ document.getElementById('cv-forge-view').addEventListener('click', function(e) {
465
+ if (e.target && e.target.classList.contains('remove-btn')) {
466
+ e.target.closest('.dynamic-section-item').remove();
467
+ }
468
+ });
469
+
470
+ function cv_updateButtons() {
471
+ const hasResults = results.hasAttribute('data-populated');
472
+ downloadPdfBtn.disabled = !hasResults;
473
+ downloadWordBtn.disabled = !hasResults;
474
+ downloadTxtBtn.disabled = !hasResults;
475
+ printBtn.disabled = !hasResults;
476
+ }
477
+
478
+ window.cv_addDynamicEntry = function(type) {
479
+ const container = document.getElementById(`cv_${type}Section`);
480
+ const newItem = document.createElement('div');
481
+ newItem.classList.add('dynamic-section-item');
482
+ let html = '';
483
+ if (type === 'experience') {
484
+ html = `<button type="button" class="remove-btn" title="Remove Entry">X</button><div class="form-grid"><div class="form-group"><label>Job Title</label><input type="text" class="job-title" placeholder="e.g., Senior Developer"></div><div class="form-group"><label>Company</label><input type="text" class="company" placeholder="e.g., Tech Solutions Inc."></div><div class="form-group"><label>Start Date</label><input type="date" class="start-date"></div><div class="form-group"><label>End Date</label><input type="date" class="end-date"></div><div class="form-group full-width"><label>Responsibilities</label><textarea class="description" placeholder="Describe your role..."></textarea></div></div>`;
485
+ } else if (type === 'education') {
486
+ html = `<button type="button" class="remove-btn" title="Remove Entry">X</button><div class="form-grid"><div class="form-group"><label>Degree / Certificate</label><input type="text" class="degree" placeholder="e.g., B.S. in Computer Science"></div><div class="form-group"><label>School / Institution</label><input type="text" class="school" placeholder="e.g., University of Technology"></div><div class="form-group"><label>Start Date</label><input type="date" class="start-date"></div><div class="form-group"><label>End Date</label><input type="date" class="end-date"></div></div>`;
487
+ } else if (type === 'references') {
488
+ html = `<button type="button" class="remove-btn" title="Remove Entry">X</button><div class="form-grid"><div class="form-group"><label>Reference Name</label><input type="text" class="ref-name" placeholder="e.g., John Smith"></div><div class="form-group"><label>Contact Info</label><input type="text" class="ref-contact" placeholder="e.g., j.smith@email.com"></div><div class="form-group full-width"><label>Relationship</label><input type="text" class="ref-relation" placeholder="e.g., Former Manager"></div></div>`;
489
+ }
490
+ newItem.innerHTML = html;
491
+ container.appendChild(newItem);
492
+ }
493
+
494
+ function cv_formatDate(dateString) {
495
+ if (!dateString) return 'Present';
496
+ const date = new Date(dateString);
497
+ return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
498
+ }
499
+
500
+ function cv_generateCv() {
501
+ let html = '<div class="cv-container">';
502
+ const name = document.getElementById('cv_fullName').value;
503
+ const phone = document.getElementById('cv_phone').value;
504
+ const email = document.getElementById('cv_email').value;
505
+ const address = document.getElementById('cv_address').value;
506
+ html += `<div class="cv-header"><h1 class="cv-name">${name}</h1><div class="cv-contact">${address ? address + ' | ' : ''}${phone ? phone + ' | ' : ''}${email ? `<a href="mailto:${email}">${email}</a>` : ''}</div></div>`;
507
+ const about = document.getElementById('cv_about').value;
508
+ if (about) html += `<div class="cv-section"><h2 class="cv-section-title">Professional Summary</h2><p>${about.replace(/\n/g, '<br>')}</p></div>`;
509
+ const workItems = document.querySelectorAll('#cv_workExperienceSection .dynamic-section-item');
510
+ if (workItems.length > 0) {
511
+ html += '<div class="cv-section"><h2 class="cv-section-title">Work Experience</h2>';
512
+ workItems.forEach(item => { html += `<div class="cv-item"><span class="cv-item-dates">${cv_formatDate(item.querySelector('.start-date').value)} - ${cv_formatDate(item.querySelector('.end-date').value)}</span><div class="cv-item-title">${item.querySelector('.job-title').value}</div><div class="cv-item-subtitle">${item.querySelector('.company').value}</div><div class="cv-item-description">${item.querySelector('.description').value.replace(/\n/g, '<br>')}</div></div>`; });
513
+ html += '</div>';
514
+ }
515
+ const eduItems = document.querySelectorAll('#cv_educationSection .dynamic-section-item');
516
+ if (eduItems.length > 0) {
517
+ html += '<div class="cv-section"><h2 class="cv-section-title">Education</h2>';
518
+ eduItems.forEach(item => { html += `<div class="cv-item"><span class="cv-item-dates">${cv_formatDate(item.querySelector('.start-date').value)} - ${cv_formatDate(item.querySelector('.end-date').value)}</span><div class="cv-item-title">${item.querySelector('.degree').value}</div><div class="cv-item-subtitle">${item.querySelector('.school').value}</div></div>`; });
519
+ html += '</div>';
520
+ }
521
+ const skills = document.getElementById('cv_skills').value;
522
+ if (skills) {
523
+ html += '<div class="cv-section"><h2 class="cv-section-title">Skills</h2><ul class="cv-skills-list">';
524
+ skills.split(',').forEach(skill => { html += `<li>${skill.trim()}</li>`; });
525
+ html += '</ul></div>';
526
+ }
527
+ const accomplishments = document.getElementById('cv_accomplishments').value;
528
+ if(accomplishments){
529
+ html += '<div class="cv-section"><h2 class="cv-section-title">Key Accomplishments</h2><ul>';
530
+ accomplishments.split('\n').forEach(acc => { if(acc.trim() !== '') html += `<li>${acc.trim()}</li>`; });
531
+ html += '</ul></div>';
532
+ }
533
+ const refItems = document.querySelectorAll('#cv_referencesSection .dynamic-section-item');
534
+ if (refItems.length > 0) {
535
+ html += '<div class="cv-section"><h2 class="cv-section-title">References</h2>';
536
+ refItems.forEach(item => { html += `<div class="cv-item"><div class="cv-item-title">${item.querySelector('.ref-name').value}</div><div class="cv-item-subtitle">${item.querySelector('.ref-relation').value}</div><div class="cv-item-description">${item.querySelector('.ref-contact').value}</div></div>`; });
537
+ html += '</div>';
538
+ }
539
+ html += '</div>';
540
+ results.innerHTML = html;
541
+ results.setAttribute('contenteditable', 'true');
542
+ results.setAttribute('data-populated', 'true');
543
+ cv_updateButtons();
544
+ }
545
+
546
+ function cv_handlePdfDownload() {
547
+ const element = results;
548
+ const opt = { margin: [0.5, 0.5, 0.5, 0.5], filename: 'my-cv.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, useCORS: true, logging: false }, jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' } };
549
+ element.style.color = '#333';
550
+ element.querySelectorAll('.cv-name, .cv-section-title').forEach(el => el.style.color = '#0077b5');
551
+ element.querySelectorAll('.cv-contact, .cv-item-subtitle, .cv-item-dates').forEach(el => el.style.color = '#555');
552
+ html2pdf().from(element).set(opt).save().then(() => {
553
+ element.style.color = '';
554
+ element.querySelectorAll('.cv-name, .cv-section-title, .cv-contact, .cv-item-subtitle, .cv-item-dates').forEach(el => el.style.color = '');
555
+ });
556
+ }
557
+ function cv_handleWordDownload() {
558
+ const sourceHTML = "<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/REC-html40'><head><meta charset='utf-8'><title>CV Export</title><style>body{font-family:Calibri, sans-serif;} .cv-section-title{color:#0077b5;}</style></head><body>" + results.innerHTML + "<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Inambao/text-to-document-area" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body></html>";
559
+ const source = 'data:application/vnd.ms-word;charset=utf-8,' + encodeURIComponent(sourceHTML);
560
+ const fileDownload = document.createElement("a");
561
+ document.body.appendChild(fileDownload);
562
+ fileDownload.href = source;
563
+ fileDownload.download = 'my-cv.doc';
564
+ fileDownload.click();
565
+ document.body.removeChild(fileDownload);
566
+ }
567
+ function cv_handleTxtDownload() {
568
+ const blob = new Blob([results.innerText], { type: 'text/plain' });
569
+ const url = URL.createObjectURL(blob);
570
+ const a = document.createElement('a');
571
+ a.href = url;
572
+ a.download = 'my-cv.txt';
573
+ document.body.appendChild(a);
574
+ a.click();
575
+ document.body.removeChild(a);
576
+ URL.revokeObjectURL(url);
577
+ }
578
+ function cv_handlePrint() {
579
+ const contentToPrint = results.innerHTML;
580
+ const printWindow = window.open('', '_blank');
581
+ printWindow.document.write(`<!DOCTYPE html><html><head><title>Print CV</title><style>@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');@page{size:A4;margin:1.5cm;}body{font-family:'Poppins',sans-serif;line-height:1.5;color:#333;}h1,h2,h3{font-family:'Poppins',sans-serif;font-weight:700;color:#000;}.cv-container{padding:0;}.cv-header{text-align:center;margin-bottom:25px;}.cv-name{font-size:2.2em;color:#0077b5;margin:0;}.cv-contact{font-size:.9em;color:#555;}.cv-contact a{color:#0077b5;text-decoration:none;}.cv-section{margin-bottom:20px;page-break-inside:avoid;}.cv-section-title{font-size:1.4em;color:#0077b5;border-bottom:2px solid #0077b5;padding-bottom:5px;margin-bottom:15px;}.cv-item{margin-bottom:15px;}.cv-item-title{font-size:1.1em;font-weight:700;}.cv-item-subtitle{font-size:1em;font-style:italic;color:#555;}.cv-item-dates{float:right;font-style:italic;color:#555;}.cv-item-description{margin-top:5px;padding-left:20px;}.cv-skills-list{list-style:none;padding:0;column-count:2;}</style></head><body>${contentToPrint}</body></html>`);
582
+ printWindow.document.close();
583
+ printWindow.focus();
584
+ setTimeout(() => { printWindow.print(); printWindow.close(); }, 500);
585
+ }
586
+ cv_updateButtons();
587
+ cv_addDynamicEntry('experience');
588
+ cv_addDynamicEntry('education');
589
+ })();
590
+
591
+
592
+ // =========== TEXTIFY PRO SCRIPT ===========
593
+ (function() {
594
+ const fileInput = document.getElementById('textify_fileInput');
595
+ const uploadArea = document.getElementById('textify_uploadArea');
596
+ const preview = document.getElementById('textify_preview');
597
+ const results = document.getElementById('textify_results');
598
+ const marginControl = document.getElementById('textify_marginControl');
599
+ const convertBtn = document.getElementById('textify_convertBtn');
600
+ const instructionsBtn = document.getElementById('textify_instructionsBtn');
601
+ const instructionsPanel = document.getElementById('textify_instructionsPanel');
602
+ const settingsBtn = document.getElementById('textify_settingsBtn');
603
+ const settingsMenu = document.getElementById('textify_settingsMenu');
604
+ const closeSettingsBtn = document.getElementById('textify_closeSettingsBtn');
605
+ const layoutRadios = document.querySelectorAll('input[name="layout"]');
606
+ const downloadPdfBtn = document.getElementById('textify_downloadPdfBtn');
607
+ const downloadWordBtn = document.getElementById('textify_downloadWordBtn');
608
+ const downloadTxtBtn = document.getElementById('textify_downloadTxtBtn');
609
+ const printBtn = document.getElementById('textify_printBtn');
610
+
611
+ window.textify_fileInput = fileInput;
612
+ const API_KEY = "AIzaSyBu-tRMTlTKxu2pauDt40f7ibyRcjYtQ7U"; // NOTE: This is a placeholder key.
613
+ const MODEL_ID = "gemini-1.5-flash-latest";
614
+ const API_URL = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:generateContent?key=${API_KEY}`;
615
+ let uploadedImages = [];
616
+ let dragSrcEl = null;
617
+
618
+ fileInput.addEventListener('change', (e) => { textify_addFiles(e.target.files); fileInput.value = ''; });
619
+ uploadArea.addEventListener('dragover', (e) => { e.preventDefault(); uploadArea.classList.add('dragover'); });
620
+ uploadArea.addEventListener('dragleave', () => uploadArea.classList.remove('dragover'));
621
+ uploadArea.addEventListener('drop', (e) => { e.preventDefault(); uploadArea.classList.remove('dragover'); textify_addFiles(e.dataTransfer.files); });
622
+ instructionsBtn.addEventListener('click', () => instructionsPanel.classList.toggle('visible'));
623
+ settingsBtn.addEventListener('click', () => settingsMenu.classList.add('visible'));
624
+ closeSettingsBtn.addEventListener('click', () => settingsMenu.classList.remove('visible'));
625
+ document.addEventListener('click', (e) => { if (!settingsMenu.contains(e.target) && !settingsBtn.contains(e.target)) { settingsMenu.classList.remove('visible'); } });
626
+ marginControl.addEventListener('input', (e) => { preview.style.gap = e.target.value + 'px'; });
627
+ layoutRadios.forEach(radio => radio.addEventListener('change', (e) => {
628
+ preview.classList.remove('layout-row', 'layout-grid');
629
+ preview.classList.add(`layout-${e.target.value}`);
630
+ }));
631
+ downloadPdfBtn.addEventListener('click', textify_handlePdfDownload);
632
+ downloadWordBtn.addEventListener('click', textify_handleWordDownload);
633
+ downloadTxtBtn.addEventListener('click', textify_handleTxtDownload);
634
+ printBtn.addEventListener('click', textify_handlePrint);
635
+
636
+ function textify_addFiles(files) {
637
+ for (const file of files) { if (file.type.startsWith('image/')) uploadedImages.push(file); }
638
+ textify_renderPreviews();
639
+ textify_updateButtons();
640
+ }
641
+ function textify_renderPreviews() {
642
+ preview.innerHTML = '';
643
+ uploadedImages.forEach((file, index) => {
644
+ const reader = new FileReader();
645
+ reader.onload = () => {
646
+ const img = document.createElement('img');
647
+ img.src = reader.result;
648
+ img.className = 'image-thumb'; img.draggable = true; img.dataset.index = index;
649
+ img.addEventListener('dragstart', textify_handleDragStart);
650
+ img.addEventListener('drop', textify_handleDrop);
651
+ img.addEventListener('dragend', textify_handleDragEnd);
652
+ img.addEventListener('dragover', (e) => e.preventDefault());
653
+ preview.appendChild(img);
654
+ };
655
+ reader.readAsDataURL(file);
656
+ });
657
+ }
658
+ function textify_updateButtons() {
659
+ const hasImages = uploadedImages.length > 0;
660
+ const hasResults = results.hasAttribute('data-populated');
661
+ convertBtn.textContent = hasImages ? `Convert ${uploadedImages.length} Image(s)` : 'Convert to Text';
662
+ convertBtn.disabled = !hasImages;
663
+ downloadPdfBtn.disabled = !hasResults;
664
+ downloadWordBtn.disabled = !hasResults;
665
+ downloadTxtBtn.disabled = !hasResults;
666
+ printBtn.disabled = !hasResults;
667
+ }
668
+ function textify_handleDragStart(e) { dragSrcEl = e.target; e.dataTransfer.effectAllowed = 'move'; e.target.classList.add('dragging'); }
669
+ function textify_handleDrop(e) {
670
+ e.stopPropagation();
671
+ if (dragSrcEl !== e.target) {
672
+ const srcIndex = parseInt(dragSrcEl.dataset.index);
673
+ const tgtIndex = parseInt(e.target.dataset.index);
674
+ const draggedItem = uploadedImages.splice(srcIndex, 1)[0];
675
+ uploadedImages.splice(tgtIndex, 0, draggedItem);
676
+ textify_renderPreviews();
677
+ }
678
+ return false;
679
+ }
680
+ function textify_handleDragEnd(e) { e.target.classList.remove('dragging'); }
681
+
682
+ window.textify_convertImages = async function() {
683
+ if (uploadedImages.length === 0) return;
684
+ results.setAttribute('contenteditable', 'false');
685
+ results.removeAttribute('data-populated');
686
+ textify_updateButtons();
687
+ convertBtn.disabled = true;
688
+ let runningLog = '';
689
+ results.innerHTML = '<p><i>🚀 Starting conversion...</i></p>';
690
+ for (let i = 0; i < uploadedImages.length; i++) {
691
+ const file = uploadedImages[i];
692
+ results.innerHTML = runningLog + `<p><i>⏳ Processing image ${i + 1} of ${uploadedImages.length}...</i></p>`;
693
+ try {
694
+ const imagePart = await textify_fileToGenerativePart(file);
695
+ const extractPrompt = "Extract text from this image. Structure the output as clean, semantic HTML. Use headings, paragraphs, and lists where appropriate. Do not include `<html>`, `<head>`, `<body>`, or ````html` markdown tags. Provide only the raw HTML content for the document body.";
696
+ const requestBody = { contents: [{ parts: [{ text: extractPrompt }, imagePart] }] };
697
+ const response = await fetch(API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody) });
698
+ if (!response.ok) throw new Error(`API Error on image ${i+1}: ${response.statusText}`);
699
+ const data = await response.json();
700
+ const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
701
+ if (text) { runningLog += text + '\n<hr>\n'; }
702
+ else { runningLog += `<p style="color: var(--danger-color);">No text found in ${file.name}.</p><hr>`; }
703
+ } catch (error) {
704
+ console.error('API Error:', error);
705
+ runningLog += `<p style="color: var(--danger-color);">❌ ERROR processing ${file.name}. This may be due to an invalid API key or network issue. Displaying mock data.</p><hr><h3>Mock Text for ${file.name}</h3><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p><hr>`;
706
+ }
707
+ }
708
+ results.innerHTML = runningLog;
709
+ results.setAttribute('contenteditable', 'true');
710
+ results.setAttribute('data-populated', 'true');
711
+ convertBtn.textContent = '✅ Conversion Complete!';
712
+ convertBtn.disabled = false;
713
+ textify_updateButtons();
714
+ }
715
+ function textify_fileToGenerativePart(file) {
716
+ return new Promise((resolve, reject) => {
717
+ const reader = new FileReader();
718
+ reader.onloadend = () => resolve({ inlineData: { mimeType: file.type, data: reader.result.split(',')[1] } });
719
+ reader.onerror = (err) => reject(err);
720
+ reader.readAsDataURL(file);
721
+ });
722
+ }
723
+ function textify_handlePdfDownload() {
724
+ const element = results;
725
+ const opt = { margin: [0.5, 0.5, 0.5, 0.5], filename: 'textify-export.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2, useCORS: true }, jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' } };
726
+ html2pdf().from(element).set(opt).save();
727
+ }
728
+ function textify_handleWordDownload() {
729
+ const sourceHTML = "<html xmlns:o='urn:schemas-microsoft-com:office:office' xmlns:w='urn:schemas-microsoft-com:office:word' xmlns='http://www.w3.org/TR/REC-html40'><head><meta charset='utf-8'><title>Textify Export</title></head><body>" + results.innerHTML + "</body></html>";
730
+ const source = 'data:application/vnd.ms-word;charset=utf-8,' + encodeURIComponent(sourceHTML);
731
+ const fileDownload = document.createElement("a");
732
+ document.body.appendChild(fileDownload); fileDownload.href = source; fileDownload.download = 'textify-export.doc'; fileDownload.click(); document.body.removeChild(fileDownload);
733
+ }
734
+ function textify_handleTxtDownload() {
735
+ const blob = new Blob([results.innerText], { type: 'text/plain' });
736
+ const url = URL.createObjectURL(blob);
737
+ const a = document.createElement('a');
738
+ a.href = url; a.download = 'textify-export.txt'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url);
739
+ }
740
+ function textify_handlePrint() {
741
+ const contentToPrint = results.innerHTML;
742
+ const printWindow = window.open('', '_blank');
743
+ printWindow.document.write(`<!DOCTYPE html><html><head><title>Print</title><style>@import url('https://fonts.googleapis.com/css2?family=Georgia&family=Roboto:wght@400;700&display=swap');@page{size:A4;margin:2cm;}body{font-family:'Georgia',serif;line-height:1.6;color:#333;}h1,h2,h3,h4{font-family:'Roboto',sans-serif;color:#000;}hr{border:none;height:1px;background-color:#ccc;margin:2em 0;}</style></head><body>${contentToPrint}</body></html>`);
744
+ printWindow.document.close();
745
+ printWindow.focus();
746
+ setTimeout(() => { printWindow.print(); printWindow.close(); }, 500);
747
+ }
748
+ textify_updateButtons();
749
+ })();
750
+
751
+ </script>
752
+ </body>
753
+ </html>