samwaugh commited on
Commit
00d05c7
·
0 Parent(s):

Space clean deploy: minimal files only

Browse files
.dockerignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ .git
2
+ __pycache__/
3
+ *.pyc
4
+ data/
5
+ pipeline/slurm/
6
+ *.pt
7
+ *.bin
8
+ *.safetensors
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ frontend/images/*.png filter=lfs diff=lfs merge=lfs -text
2
+ *.ico filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ data/
2
+ pipeline/
3
+ Papers/
4
+ frontend/paper/
5
+ frontend/images/examples/
6
+ *.pt
7
+ *.safetensors
8
+ *.bin
9
+ *.pdf
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # System deps (for Pillow/OpenCV later; harmless now)
6
+ RUN apt-get update && apt-get install -y --no-install-recommends \
7
+ libgl1 \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Install only what we need to boot the server
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copy repo
15
+ COPY . .
16
+
17
+ # HF routes traffic to $PORT – bind to it
18
+ ENV PORT=7860 PYTHONUNBUFFERED=1
19
+
20
+ # One worker is fine (single-user demo)
21
+ CMD exec gunicorn -w 1 -k gthread -t 300 -b 0.0.0.0:${PORT} space_app:app
README.md ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: ArteFact
3
+ emoji: 🏆
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ short_description: Discover insights into the art history corpus with visual AI
9
+ ---
10
+
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
12
+
13
+ ---
14
+ title: ArteFact
15
+ emoji: 🖼️
16
+ colorFrom: blue
17
+ colorTo: purple
18
+ sdk: docker
19
+ pinned: false
20
+ short_description: Discover insights into the art-history corpus with visual AI
21
+ # Optional metadata (shows on the card; not required to run)
22
+ models:
23
+ - openai/clip-vit-base-patch32
24
+ - samwaugh/paintingclip-lora
25
+ datasets:
26
+ - samwaugh/artefact-embeddings-clip
27
+ - samwaugh/artefact-embeddings-paintingclip
28
+ ---
29
+
30
+ # ArteFact — Hugging Face Space
31
+
32
+ This branch contains the files required to run the **ArteFact** web app on Hugging Face **Spaces** using Docker.
33
+ The full project documentation lives in the main GitHub repo (`main` branch).
34
+
35
+ ## What runs here
36
+ - **Flask server** (`space_app.py`) serving the SPA from `frontend/` (UI + API share one origin).
37
+ - Built with the provided **Dockerfile**; the app listens on `$PORT` (set by Spaces).
38
+
39
+ ## Deploy / update
40
+ ```bash
41
+ # one-time
42
+ git remote add hf https://huggingface.co/spaces/samwaugh/ArteFact
43
+
44
+ # deploy this branch to the Space
45
+ git push hf space:main
frontend/css/artefact-context.css ADDED
@@ -0,0 +1,402 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .selected-img {
2
+ border: 4px solid #ffc107 !important;
3
+ }
4
+ .example-scroll::-webkit-scrollbar {
5
+ height: 10px;
6
+ }
7
+ .example-scroll::-webkit-scrollbar-thumb {
8
+ background-color: #bbb;
9
+ border-radius: 5px;
10
+ }
11
+ .example-scroll::-webkit-scrollbar-track {
12
+ background: #eee;
13
+ }
14
+ .spin {
15
+ animation: spin 2s linear infinite;
16
+ }
17
+ @keyframes spin {
18
+ 0% { transform: rotate(0deg); }
19
+ 100% { transform: rotate(360deg); }
20
+ }
21
+ #topicTags .badge {
22
+ font-size: 1rem;
23
+ margin-bottom: 0.3em;
24
+ /* Add some spacing between tags */
25
+ }
26
+
27
+ /* --- BibTeX panel: eggshell background, black text --- */
28
+ #bibtexContent {
29
+ background:#fdfde7 !important; /* eggshell */
30
+ color:#000 !important;
31
+ }
32
+
33
+ /* --- 2-row image gallery ----------------------------------------------- */
34
+ #galleryScroller{
35
+ display:grid; /* grid instead of flex */
36
+ grid-template-columns:repeat(auto-fill,120px);
37
+ grid-auto-rows:120px; /* second row appears automatically */
38
+ gap:.5rem;
39
+ overflow-y:auto; /* keep horizontal + vertical scroll */
40
+ }
41
+ #galleryScroller img{
42
+ /* existing size rules kept intact */
43
+ width:120px;
44
+ height:120px;
45
+ object-fit:cover;
46
+
47
+ border:2px solid transparent; /* make room for colour change */
48
+ transition:border-color .15s ease;
49
+ }
50
+
51
+ #galleryScroller img:hover{
52
+ border-color:#0d6efd !important; /* same blue used for sentences */
53
+ }
54
+
55
+ /* enlarge the image-tool buttons (≈ 2 × current size) */
56
+ #imageTools .btn {
57
+ width: 56px; /* twice previous 28 px */
58
+ height: 56px;
59
+ font-size: 1.5rem; /* icons scale up */
60
+ padding: 0;
61
+ }
62
+
63
+ /* ── Sentence list interactivity ─────────────────────────────────────────── */
64
+ #sentenceList .sentence-item {
65
+ cursor: pointer;
66
+ border: 1px solid transparent; /* make a full-box border we can tint */
67
+ transition: border-color .15s ease;
68
+
69
+ /* ---------- academic look ---------- */
70
+ font-family: "Georgia","Times New Roman",serif;
71
+ font-size: .95rem; /* a touch smaller than body text */
72
+ }
73
+ #sentenceList .sentence-item:hover {
74
+ border-color:#0d6efd !important; /* Bootstrap primary blue on all sides */
75
+ }
76
+
77
+ /* ── Heatmap button styling ──────────────────────────────────────────────── */
78
+ .heatmap-btn {
79
+ width: 28px;
80
+ height: 28px;
81
+ padding: 0;
82
+ display: inline-flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ }
86
+
87
+ .heatmap-btn i {
88
+ font-size: 0.875rem;
89
+ }
90
+
91
+ /* Sentence text should still be clickable */
92
+ .sentence-item span {
93
+ cursor: pointer;
94
+ }
95
+
96
+ /* Heatmap overlay image styling */
97
+ .heatmap-image {
98
+ pointer-events: none; /* Allow click-through to close button */
99
+ }
100
+
101
+ #closeHeatmapBtn {
102
+ opacity: 0.8;
103
+ transition: opacity 0.2s ease;
104
+ }
105
+
106
+ #closeHeatmapBtn:hover {
107
+ opacity: 1;
108
+ }
109
+
110
+ /* ---------- Home palette ------------------------------------------------- */
111
+ .card:has(#uploadTrigger),
112
+ #exampleContainer { background:#fff!important; color:#000!important; }
113
+ .card:has(#uploadTrigger) * { color:#000!important; }
114
+
115
+ /* topic tags - fix text colors with higher specificity */
116
+ #topicTags .btn-outline-primary {
117
+ border-color: #000 !important;
118
+ color: #000 !important;
119
+ background: #fff !important;
120
+ }
121
+
122
+ #topicTags .btn-outline-primary:hover {
123
+ background: #0d6efd !important;
124
+ color: #fff !important;
125
+ border-color: #0d6efd !important;
126
+ }
127
+
128
+ #topicTags .btn-primary,
129
+ #topicTags .btn-primary:hover,
130
+ #topicTags .btn-primary:focus,
131
+ #topicTags .btn-primary:active,
132
+ #topicTags .btn.btn-primary {
133
+ background: #0d6efd !important;
134
+ color: #fff !important;
135
+ border-color: #0d6efd !important;
136
+ }
137
+
138
+ /* override any card inheritance for topic buttons */
139
+ .card:has(#topicTags) .btn-primary,
140
+ .card:has(#topicTags) .btn-primary * {
141
+ color: #fff !important;
142
+ }
143
+
144
+ /* creator search result pills - fix colors */
145
+ #creatorSearchResults .list-group-item,
146
+ #creatorPanelResults .list-group-item {
147
+ color: #000 !important;
148
+ background: #fff !important;
149
+ border-color: #dee2e6 !important;
150
+ transition: all 0.2s ease;
151
+ }
152
+
153
+ #creatorSearchResults .list-group-item:hover,
154
+ #creatorPanelResults .list-group-item:hover {
155
+ color: #fff !important;
156
+ background: #0d6efd !important;
157
+ border-color: #0d6efd !important;
158
+ }
159
+
160
+ #creatorSearchResults .list-group-item.active,
161
+ #creatorPanelResults .list-group-item.active {
162
+ color: #fff !important;
163
+ background: #0d6efd !important;
164
+ border-color: #0d6efd !important;
165
+ }
166
+
167
+ /* upload button already given dark outline in HTML patch */
168
+ /* ------------------------------------------------------------------------ */
169
+
170
+ /* stretch main column if the sidebar is hidden */
171
+ .col-md-9.fill { flex:0 0 100%!important; max-width:100%!important; }
172
+
173
+ /* give the example-image Select button a solid black outline */
174
+ #selectImageBtn{
175
+ border:2px solid #000 !important;
176
+ }
177
+
178
+ /* light-grey hover (same shade Bootstrap uses for .btn-light) */
179
+ #uploadTrigger:hover{
180
+ background:#e9ecef !important; /* light grey */
181
+ color:#000 !important;
182
+ border-color:#000 !important;
183
+ }
184
+
185
+ /* selected topic (btn-primary) ‑ ensure white label */
186
+ #topicTags .btn-primary,
187
+ #selectedTopicTags .btn-primary{
188
+ color:#fff !important;
189
+ }
190
+
191
+ /* navbar items (model dropdown + about) – always white */
192
+ .navbar-dark .navbar-nav .nav-link{
193
+ color: #fff !important;
194
+ }
195
+
196
+ /* Add hover effect for navbar links */
197
+ .navbar-dark .navbar-nav .nav-link:hover {
198
+ color: #adb5bd !important;
199
+ }
200
+
201
+ /* Ensure dropdown toggle also gets hover effect */
202
+ .navbar-dark .navbar-nav .dropdown-toggle:hover {
203
+ color: #adb5bd !important;
204
+ }
205
+
206
+ /* creator badges - force white text with maximum specificity */
207
+ #creatorTags .badge,
208
+ #selectedCreatorTags .badge,
209
+ #creatorTags span.badge,
210
+ #selectedCreatorTags span.badge,
211
+ #creatorTags .badge.bg-primary,
212
+ #selectedCreatorTags .badge.bg-primary {
213
+ color: #fff !important;
214
+ background: #0d6efd !important;
215
+ }
216
+
217
+ /* override card inheritance for creator badges */
218
+ .card:has(#creatorSearch) .badge,
219
+ .card:has(#creatorSearch) .badge *,
220
+ .card:has(#creatorSearch) span.badge,
221
+ .card:has(#creatorSearch) span.badge * {
222
+ color: #fff !important;
223
+ }
224
+
225
+ /* navbar custom dark blue */
226
+ .navbar-dark{
227
+ background:#010e1d !important;
228
+ }
229
+
230
+ /* prevent stray elements from creating a horizontal scrollbar */
231
+ body{ overflow-x:hidden; }
232
+
233
+ /* hide detailed working log for end-users */
234
+ #workingLog{ display:none !important; }
235
+
236
+ @keyframes dotPulse{
237
+ 0%,20%{opacity:0}
238
+ 40%{opacity:1}
239
+ }
240
+ .loading-dots span{
241
+ animation:dotPulse 1s infinite;
242
+ }
243
+ .loading-dots span:nth-child(2){ animation-delay:.2s }
244
+ .loading-dots span:nth-child(3){ animation-delay:.4s }
245
+
246
+ /* dark-blue footer (re-use navbar colour) */
247
+ .bg-durham{ background:#010e1d !important; }
248
+
249
+ .footer a{
250
+ color:#fff !important;
251
+ text-decoration:underline;
252
+ }
253
+
254
+ .footer a:hover{
255
+ color:#adb5bd !important;
256
+ }
257
+
258
+ /* Ensure all footer links have consistent styling */
259
+ .footer .text-white {
260
+ color: #fff !important;
261
+ text-decoration: underline;
262
+ }
263
+
264
+ .footer .text-white:hover {
265
+ color: #adb5bd !important;
266
+ }
267
+
268
+ /* About modal custom styling */
269
+ #aboutModal .modal-header {
270
+ border-bottom: 2px solid #dee2e6;
271
+ }
272
+
273
+ #aboutModal .card {
274
+ transition: transform 0.2s ease;
275
+ }
276
+
277
+ #aboutModal .card:hover {
278
+ transform: translateY(-2px);
279
+ }
280
+
281
+ #aboutModal section {
282
+ border-bottom: 1px solid #f8f9fa;
283
+ padding-bottom: 1rem;
284
+ }
285
+
286
+ #aboutModal section:last-child {
287
+ border-bottom: none;
288
+ padding-bottom: 0;
289
+ }
290
+
291
+ /* model dropdown custom styling */
292
+ #modelDropdownMenu .dropdown-item {
293
+ color: #000 !important;
294
+ background: #fff !important;
295
+ transition: all 0.2s ease;
296
+ }
297
+
298
+ #modelDropdownMenu .dropdown-item:hover {
299
+ color: #fff !important;
300
+ background: #0d6efd !important;
301
+ }
302
+
303
+ /* new layout: topic/creator cards need white backgrounds (but exclude buttons/badges) */
304
+ .card:has(#topicTags),
305
+ .card:has(#creatorSearch) {
306
+ background: #fff !important;
307
+ color: #000 !important;
308
+ }
309
+
310
+ .card:has(#topicTags) h5,
311
+ .card:has(#topicTags) .card-title,
312
+ .card:has(#creatorSearch) h5,
313
+ .card:has(#creatorSearch) .card-title {
314
+ color: #000 !important;
315
+ }
316
+
317
+ /* override the bg-dark class on the new topic/creator cards */
318
+ .card.bg-dark.bg-opacity-50:has(#topicTags),
319
+ .card.bg-dark.bg-opacity-50:has(#creatorSearch) {
320
+ background: #fff !important;
321
+ color: #000 !important;
322
+ }
323
+
324
+ /* creator search input in new layout */
325
+ .card:has(#creatorSearch) .form-control {
326
+ color: #000 !important;
327
+ background: #fff !important;
328
+ border-color: #ced4da !important;
329
+ }
330
+
331
+ .card:has(#creatorSearch) .form-control:focus {
332
+ border-color: #86b7fe !important;
333
+ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25) !important;
334
+ }
335
+
336
+ /* light grey background for the main upload container */
337
+ #uploadedImageContainer {
338
+ background: #f8f9fa !important; /* very light grey instead of bg-secondary */
339
+ }
340
+
341
+ /* Make sure the image tools stay positioned correctly */
342
+ #uploadedImageContainer:has(#uploadedImage:not(.d-none)) #imageTools {
343
+ position: absolute;
344
+ top: 1rem;
345
+ left: 1rem;
346
+ }
347
+
348
+ /* force white text on all blue badges and buttons */
349
+ .btn-primary *,
350
+ .badge.bg-primary *,
351
+ .bg-primary * {
352
+ color: #fff !important;
353
+ }
354
+
355
+ /* specifically target the creator badge text */
356
+ span.badge.bg-primary {
357
+ color: #fff !important;
358
+ }
359
+
360
+ span.badge.bg-primary * {
361
+ color: #fff !important;
362
+ }
363
+
364
+ /* Ensure footer is always visible below content */
365
+ body {
366
+ min-height: 100vh;
367
+ display: flex;
368
+ flex-direction: column;
369
+ }
370
+
371
+ .container-fluid {
372
+ flex: 1;
373
+ }
374
+
375
+ .footer {
376
+ margin-top: auto;
377
+ }
378
+
379
+ /* ── main painting + sentences row ───────────────────────────────────────── */
380
+ .main-row{
381
+ height: calc(100vh - 220px); /* navbar + bottom cards margin */
382
+ min-height: 420px; /* don't shrink too small */
383
+ }
384
+
385
+ /* image column : fill row height */
386
+ .main-row .col-md-9 > #uploadedImageContainer{
387
+ height:100% !important;
388
+ }
389
+
390
+ /* picture fits inside its box */
391
+ #uploadedImage{
392
+ max-height:100% !important;
393
+ max-width:100%;
394
+ object-fit:contain;
395
+ }
396
+
397
+ /* --- “In Context” paragraph --------------------------------------------- */
398
+ .context-paragraph{
399
+ font-family: "Georgia","Times New Roman",serif;
400
+ font-size: .9rem; /* smaller than body text */
401
+ margin: 0; /* keep compact inside section */
402
+ }
frontend/images/dark_logo.png ADDED

Git LFS Details

  • SHA256: fbd6770a76647896a20830c48156203284f52b000e77d5172855a5d3314fea2e
  • Pointer size: 131 Bytes
  • Size of remote file: 346 kB
frontend/images/favicon.png ADDED

Git LFS Details

  • SHA256: 6bf2baba162fa3baefd2679460fa187e4110ac10a51b46b02bae684dcd061c6c
  • Pointer size: 129 Bytes
  • Size of remote file: 2.86 kB
frontend/index.html ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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" />
6
+ <title>ArteFact</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
10
+ <link rel="stylesheet" href="css/artefact-context.css" />
11
+ <link rel="icon" type="image/png" href="images/favicon.png">
12
+ </head>
13
+ <body class="d-flex flex-column min-vh-100">
14
+ <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
15
+ <div class="container-fluid">
16
+ <a class="navbar-brand" href="#" style="cursor: pointer;">
17
+ <img src="/images/dark_logo.png" alt="ArteFact" height="64">
18
+ </a>
19
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
20
+ aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
21
+ <span class="navbar-toggler-icon"></span>
22
+ </button>
23
+ <div class="collapse navbar-collapse" id="navbarNav">
24
+ <ul class="navbar-nav ms-auto">
25
+ <li class="nav-item dropdown me-3">
26
+ <a class="nav-link dropdown-toggle" href="#" id="modelDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
27
+ AI Model
28
+ </a>
29
+ <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="modelDropdown" id="modelDropdownMenu">
30
+ <!-- Populated by JS -->
31
+ </ul>
32
+ </li>
33
+ <li class="nav-item">
34
+ <a class="nav-link" href="#">About</a>
35
+ </li>
36
+ </ul>
37
+ </div>
38
+ </div>
39
+ </nav>
40
+ <div class="container-fluid py-4 flex-grow-1">
41
+ <div class="row main-row">
42
+ <div class="col-md-9 d-flex justify-content-center align-items-center">
43
+ <div id="uploadedImageContainer" class="position-relative d-flex justify-content-center align-items-center text-white w-100 h-100">
44
+ <div id="gridOverlay"
45
+ class="position-absolute"
46
+ style="pointer-events:none; display:none; z-index:9;"></div>
47
+ <div id="gridHighlightOverlay"
48
+ class="position-absolute"
49
+ style="pointer-events:none; display:none; z-index:11;"></div>
50
+ <div id="landingContent" class="w-100 p-4">
51
+ <!-- Top row: Topics (left) and Creators (right) -->
52
+ <div class="row mb-4">
53
+ <!-- Topics section -->
54
+ <div class="col-md-6">
55
+ <div class="card h-100">
56
+ <div class="card-body">
57
+ <h5 class="card-title">Select Topics</h5>
58
+ <div id="topicTags" class="d-flex flex-wrap gap-2"></div>
59
+ </div>
60
+ </div>
61
+ </div>
62
+
63
+ <!-- Creators section -->
64
+ <div class="col-md-6">
65
+ <div class="card h-100">
66
+ <div class="card-body">
67
+ <h5 class="card-title">Search Creators</h5>
68
+ <input type="text" id="creatorSearch" class="form-control mb-2" placeholder="Search creators..." />
69
+ <div id="creatorSearchResults" class="list-group mb-2"></div>
70
+ <div id="creatorTags" class="d-flex flex-wrap gap-2"></div>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div
75
+
76
+ <!-- Bottom section: Upload and Examples -->
77
+ <div class="card bg-dark bg-opacity-50 text-white">
78
+ <div class="card-body text-center">
79
+ <div class="mb-4">
80
+ <button id="uploadTrigger"
81
+ class="btn btn-outline-dark border-2 mb-3"
82
+ style="font-size:1.2rem; color:#000;">
83
+ <i class="bi bi-upload" style="font-size: 2rem;"></i><br>Upload an Image
84
+ </button>
85
+ <input type="file" id="imageUpload" accept="image/*" class="d-none">
86
+ </div>
87
+
88
+ <h4 class="mb-3">Or</h4>
89
+
90
+ <h5 class="mb-3">Select from one of the historical examples</h5>
91
+ <div id="exampleContainer">
92
+ <div class="example-scroll overflow-auto d-flex flex-row gap-3 px-2" style="max-width: 100%; white-space: nowrap;">
93
+ <!-- 1. The Night Watch -->
94
+ <img class="example-img rounded border" src="images/examples/1200px-The_Night_Watch_-_HD.jpg" alt="" height="300">
95
+
96
+ <!-- 2. Bacchus and Ariadne -->
97
+ <img class="example-img rounded border" src="images/examples/Titian_Bacchus_and_Ariadne.jpg" alt="" height="300">
98
+
99
+ <!-- 3. Café Terrace at Night -->
100
+ <img class="example-img rounded border" src="images/examples/Vincent_van_Gogh_(1853-1890)_Caféterras_bij_nacht_(place_du_Forum)_Kröller-Müller_Museum_Otterlo_23-8-2016_13-35-40.JPG" alt="" height="300">
101
+
102
+ <!-- 4. The School of Athens -->
103
+ <img class="example-img rounded border" src="images/examples/_The_School_of_Athens__by_Raffaello_Sanzio_da_Urbino.jpg" alt="" height="300">
104
+
105
+ <!-- 5. Kiss of Judas -->
106
+ <img class="example-img rounded border" src="images/examples/Giotto_-_Scrovegni_-_-31-_-_Kiss_of_Judas.jpg" alt="" height="300">
107
+
108
+ <!-- 6. San Giorgio Maggiore at Dusk -->
109
+ <img class="example-img rounded border" src="images/examples/Claude_Monet,_Saint-Georges_majeur_au_crépuscule.jpg" alt="" height="300">
110
+
111
+ <!-- 7. The Last Supper -->
112
+ <img class="example-img rounded border" src="images/examples/The_Last_Supper_-_Leonardo_Da_Vinci_-_High_Resolution_32x16.jpg" alt="" height="300">
113
+
114
+ <!-- 8. The Garden of Earthly Delights -->
115
+ <img class="example-img rounded border" src="images/examples/3794px-The_Garden_of_earthly_delights.jpg" alt="" height="300">
116
+ </div>
117
+ <div class="mt-3 text-center">
118
+ <button id="selectImageBtn" class="btn btn-light d-none">Select</button>
119
+ </div>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ <img id="uploadedImage" src="" alt="Uploaded Image" draggable="false" class="img-fluid d-none position-absolute" style="max-height: 100%; max-width: 100%;">
125
+ <div id="imageTools" class="d-none position-absolute top-0 start-0 m-3 bg-white bg-opacity-75 p-2 rounded d-flex flex-column align-items-center">
126
+ <button id="cropToolBtn" class="btn btn-sm btn-outline-dark mb-2" title="Crop">
127
+ <i class="bi bi-crop"></i>
128
+ </button>
129
+ <button id="undoToolBtn" class="btn btn-sm btn-outline-dark mb-2" title="Undo">
130
+ <i class="bi bi-arrow-counterclockwise"></i>
131
+ </button>
132
+ <button id="rerunToolBtn" class="btn btn-sm btn-outline-dark" title="Rerun">
133
+ <i class="bi bi-arrow-repeat"></i>
134
+ </button>
135
+ <button id="gridToolBtn" class="btn btn-sm btn-outline-dark mt-2" title="Toggle Grid">
136
+ <i class="bi bi-grid-3x3-gap"></i>
137
+ </button>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ <div class="col-md-3 bg-light overflow-auto d-none animate__animated animate__fadeInRight" style="height: 100%;">
142
+ <h3 class="mt-3">Sentences</h3>
143
+ <ul id="sentenceList" class="list-group list-group-flush">
144
+ <!-- List items will go here -->
145
+ </ul>
146
+ </div>
147
+ </div>
148
+ <div class="row mt-4 align-items-stretch">
149
+ <div class="col-12 col-md-4 d-none animate__animated animate__fadeInUp d-flex" id="imageHistoryWrapper">
150
+ <div class="bg-light border rounded shadow-sm p-3 mt-3 h-100 w-100">
151
+ <h5 class="mb-3">Image History</h5>
152
+ <div id="imageHistory" class="d-flex flex-row flex-wrap gap-3 overflow-auto" style="max-height: 200px;">
153
+ <!-- Cropped images will be appended here -->
154
+ </div>
155
+ </div>
156
+ </div>
157
+ <div class="col-12 col-md-4 d-none animate__animated animate__fadeInUp d-flex" id="selectedTopicsWrapper">
158
+ <div class="bg-light border rounded shadow-sm p-3 mt-3 h-100 w-100">
159
+ <h5 class="mb-3">Selected Topics</h5>
160
+ <div id="selectedTopicTags" class="d-flex flex-wrap gap-2"></div>
161
+ </div>
162
+ </div>
163
+ <div class="col-12 col-md-4 d-none animate__animated animate__fadeInUp d-flex" id="selectedCreatorsWrapper">
164
+ <div class="bg-light border rounded shadow-sm p-3 mt-3 h-100 w-100">
165
+ <h5 class="mb-3">Selected Creators</h5>
166
+ <input type="text" id="creatorPanelSearch" class="form-control mb-2" placeholder="Search creators..." />
167
+ <div id="creatorPanelResults" class="list-group mb-2"></div>
168
+ <div id="selectedCreatorTags" class="d-flex flex-wrap gap-2"></div>
169
+ </div>
170
+ </div>
171
+ </div>
172
+ </div>
173
+ <script src="js/jquery.min.js"></script>
174
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
175
+ <script src="js/artefact-context.js"></script>
176
+
177
+
178
+ <!-- Working Overlay -->
179
+ <div id="workingOverlay" class="d-none position-fixed top-0 start-0 w-100 h-100 d-flex flex-column justify-content-center align-items-center bg-dark bg-opacity-75 text-white" style="z-index: 1100;">
180
+ <div class="text-center">
181
+ <i class="bi bi-arrow-repeat text-white spin" style="font-size:4rem;"></i>
182
+ <h4 class="mt-2 loading-dots">
183
+ Loading<span>.</span><span>.</span><span>.</span>
184
+ </h4>
185
+ </div>
186
+ </div>
187
+ <!-- Settings Modal -->
188
+ <div class="modal fade" id="settingsModal" tabindex="-1" aria-labelledby="settingsModalLabel" aria-hidden="true">
189
+ <div class="modal-dialog">
190
+ <div class="modal-content">
191
+ <div class="modal-header">
192
+ <h5 class="modal-title" id="settingsModalLabel">Settings</h5>
193
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
194
+ </div>
195
+ <div class="modal-body">
196
+ <div class="form-check form-switch">
197
+ <input class="form-check-input" type="checkbox" id="toggleViewGrid">
198
+ <label class="form-check-label" for="toggleViewGrid">View Grid (7 × 7)</label>
199
+ </div>
200
+ </div>
201
+ <div class="modal-footer">
202
+ <button class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
203
+ </div>
204
+ </div>
205
+ </div>
206
+ </div>
207
+
208
+ <!-- About Modal -->
209
+ <div class="modal fade" id="aboutModal" tabindex="-1" aria-labelledby="aboutModalLabel" aria-hidden="true">
210
+ <div class="modal-dialog modal-lg modal-dialog-scrollable">
211
+ <div class="modal-content">
212
+ <div class="modal-header bg-durham text-white">
213
+ <h4 class="modal-title" id="aboutModalLabel">
214
+ <i class="bi bi-info-circle me-2"></i>About ArteFact
215
+ </h4>
216
+ <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
217
+ </div>
218
+ <div class="modal-body">
219
+
220
+ <!-- Purpose -->
221
+ <section class="mb-4">
222
+ <h5 class="text-primary mb-3"><i class="bi bi-magic me-2"></i>Purpose</h5>
223
+ <p class="lead">By automatically linking visual elements in artworks to scholarly descriptions, ArteFact's ambition is to empower researchers, students, and art enthusiasts to discover new connections and understand artworks in their broader academic context.</p>
224
+ <ul class="list-unstyled">
225
+ <li class="mb-2"><i class="bi bi-upload text-success me-2"></i><strong>Upload or select artwork images</strong> and find scholarly passages that describe similar visual elements</li>
226
+ <li class="mb-2"><i class="bi bi-search text-info me-2"></i><strong>Search by region</strong> - crop specific areas of paintings to find text about those visual details</li>
227
+ <li class="mb-2"><i class="bi bi-filter text-warning me-2"></i><strong>Filter results</strong> by art historical topics or specific creators</li>
228
+ <li class="mb-2"><i class="bi bi-book text-primary me-2"></i><strong>Access scholarly sources</strong> with full citations, DOI links, and BibTeX references</li>
229
+ </ul>
230
+ </section>
231
+
232
+ <!-- What powers the app -->
233
+ <section class="mb-4">
234
+ <h5 class="text-primary mb-3"><i class="bi bi-cpu me-2"></i>What Powers ArteFact?</h5>
235
+ <p><strong>ArtContext Research Pipeline:</strong> A computational system that automatically harvests and processes thousands of art history texts.</p>
236
+ <div class="row g-3">
237
+ <div class="col-md-6">
238
+ <div class="card border-light h-100">
239
+ <div class="card-body p-3">
240
+ <h6 class="card-title text-info">Data Collection</h6>
241
+ <small>Automatically gathers painter information from Wikidata and scholarly articles from OpenAlex database</small>
242
+ </div>
243
+ </div>
244
+ </div>
245
+ <div class="col-md-6">
246
+ <div class="card border-light h-100">
247
+ <div class="card-body p-3">
248
+ <h6 class="card-title text-success">Text Processing</h6>
249
+ <small>Downloads academic PDFs, converts to text, and extracts meaningful sentences</small>
250
+ </div>
251
+ </div>
252
+ </div>
253
+ <div class="col-md-6">
254
+ <div class="card border-light h-100">
255
+ <div class="card-body p-3">
256
+ <h6 class="card-title text-warning">AI Analysis</h6>
257
+ <small>Uses PaintingCLIP (a specialised art-focused AI model) to understand visual-textual connections</small>
258
+ </div>
259
+ </div>
260
+ </div>
261
+ <div class="col-md-6">
262
+ <div class="card border-light h-100">
263
+ <div class="card-body p-3">
264
+ <h6 class="card-title text-danger">Smart Matching</h6>
265
+ <small>Tries to find the most relevant scholarly passages for any artwork image you provide</small>
266
+ </div>
267
+ </div>
268
+ </div>
269
+ </div>
270
+ </section>
271
+
272
+ <!-- App features -->
273
+ <section class="mb-4">
274
+ <h5 class="text-primary mb-3"><i class="bi bi-gear me-2"></i>App Features</h5>
275
+ <div class="row g-3">
276
+ <div class="col-md-6">
277
+ <h6 class="text-secondary"><i class="bi bi-images me-1"></i>Image Input</h6>
278
+ <ul class="small">
279
+ <li>Drag & drop upload</li>
280
+ <li>Choose from historical examples</li>
281
+ <li>Crop and edit images</li>
282
+ <li>Image history tracking</li>
283
+ </ul>
284
+ </div>
285
+ <div class="col-md-6">
286
+ <h6 class="text-secondary"><i class="bi bi-grid-3x3 me-1"></i>Region Analysis</h6>
287
+ <ul class="small">
288
+ <li>7×7 grid overlay</li>
289
+ <li>Click-to-analyze specific areas</li>
290
+ <li>Visual feedback highlighting</li>
291
+ <li>Region-specific text retrieval</li>
292
+ </ul>
293
+ </div>
294
+ <div class="col-md-6">
295
+ <h6 class="text-secondary"><i class="bi bi-funnel me-1"></i>Smart Filtering</h6>
296
+ <ul class="small">
297
+ <li>Topic-based filtering</li>
298
+ <li>Creator/artist selection</li>
299
+ <li>Model switching (CLIP vs PaintingCLIP)</li>
300
+ <li>Real-time result updating</li>
301
+ </ul>
302
+ </div>
303
+ <div class="col-md-6">
304
+ <h6 class="text-secondary"><i class="bi bi-journal-text me-1"></i>Academic Tools</h6>
305
+ <ul class="small">
306
+ <li>Full citation information</li>
307
+ <li>Work image gallery</li>
308
+ <li>One-click BibTeX copying</li>
309
+ <li>DOI links to sources</li>
310
+ <li>Embedded document previews</li>
311
+ </ul>
312
+ </div>
313
+ </div>
314
+ </section>
315
+
316
+ <!-- About production -->
317
+ <section class="mb-4">
318
+ <h5 class="text-primary mb-3"><i class="bi bi-people me-2"></i>About This Project</h5>
319
+ <div class="bg-light p-3 rounded">
320
+ <p class="mb-2"><strong>Created by:</strong> <a href="https://www.linkedin.com/in/samuel-waugh-31903b1bb/" target="_blank">Samuel Waugh</a></p>
321
+ <p class="mb-2"><strong>Supervised by:</strong> <a href="https://stuart-james.com" target="_blank">Dr Stuart James</a>, Department of Computer Science, Durham University</p>
322
+ <p class="mb-2"><strong>Supported by:</strong> <a href="https://n8cir.org.uk/themes/internships/internships-2025/" target="_blank">N8 Centre of Excellence in Computationally Intensive Research (N8 CIR)</a></p>
323
+ <p class="mb-3"><strong>Purpose:</strong> This project was developed as part of the N8 CIR 2025 Internship programme to explore how AI can help bridge the gap between visual art and textual scholarship, making art historical research more accessible and discoverable.</p>
324
+
325
+ <div class="alert alert-info mb-0">
326
+ <small><i class="bi bi-lightbulb me-1"></i><strong>Why it matters:</strong> By automatically linking visual elements in artworks to scholarly descriptions, ArteFact's ambition is to empower researchers, students, and art enthusiasts to discover new connections and understand artworks in their broader academic context.</small>
327
+ </div>
328
+ </div>
329
+ </section>
330
+
331
+ <!-- Technical note -->
332
+ <section>
333
+ <h6 class="text-muted mb-2"><i class="bi bi-code-square me-1"></i>Technical Details</h6>
334
+ <p class="small text-muted">ArteFact uses PaintingCLIP, a fine-tuned version of OpenAI's CLIP model specialised for art historical content. The system processes over thousands of scholarly articles to create a comprehensive knowledge base of art historical descriptions.</p>
335
+ </section>
336
+
337
+ </div>
338
+ <div class="modal-footer">
339
+ <a href="paper/waugh2025artcontext.pdf" target="_blank" class="btn btn-outline-success btn-sm me-2">
340
+ <i class="bi bi-file-earmark-pdf me-1"></i>Download Research Paper
341
+ </a>
342
+ <a href="https://github.com/sammwaughh/artefact-context" target="_blank" class="btn btn-outline-primary btn-sm">
343
+ <i class="bi bi-github me-1"></i>View Source Code
344
+ </a>
345
+ <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
346
+ </div>
347
+ </div>
348
+ </div>
349
+ </div>
350
+ <footer class="footer bg-durham text-white py-3 mt-4">
351
+ <div class="container-lg d-flex flex-column flex-md-row
352
+ justify-content-between align-items-center gap-2">
353
+
354
+ <!-- attribution -->
355
+ <div class="small">
356
+ ArteFact © 2025&nbsp;<span class="d-none d-md-inline">·</span><br class="d-md-none">
357
+ <a href="https://www.linkedin.com/in/samuel-waugh-31903b1bb/" target="_blank" class="text-white">Samuel&nbsp;Waugh</a><br class="d-md-none">
358
+ <span class="d-none d-md-inline">·</span>
359
+ <a href="https://stuart-james.com" target="_blank" class="text-white">Dr&nbsp;Stuart&nbsp;James</a>
360
+ </div>
361
+
362
+ <!-- organisations -->
363
+ <div class="small text-md-center">
364
+ Durham University |
365
+ <a href="https://n8cir.org.uk/themes/internships/internships-2025/"
366
+ target="_blank">N8 CIR Internship 2025</a>
367
+ </div>
368
+
369
+ <!-- social / repo -->
370
+ <div>
371
+ <a href="paper/waugh2025artcontext.pdf"
372
+ class="text-white fs-5 me-3" target="_blank" aria-label="Research Paper">
373
+ <i class="bi bi-file-earmark-pdf"></i>
374
+ </a>
375
+ <a href="https://github.com/sammwaughh/artefact-context"
376
+ class="text-white fs-5 me-3" target="_blank" aria-label="GitHub">
377
+ <i class="bi bi-github"></i>
378
+ </a>
379
+ </div>
380
+ </div>
381
+ </footer>
382
+ </body>
383
+ </html>
frontend/js/artefact-context.js ADDED
@@ -0,0 +1,1421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ==========================
2
+ // == GLOBAL CONFIGURATION ==
3
+ // ==========================
4
+ const API_BASE_URL = "";
5
+
6
+ // Variables to store session/run state
7
+ let runId;
8
+ let imageKey;
9
+ let upload;
10
+
11
+ // --- Grid overlay state ---
12
+ let viewGridEnabled = false;
13
+
14
+ const GRID_ROWS = 7; // ViT-B/32 → 7×7 patch grid
15
+ const GRID_COLS = 7; // keep rows == cols
16
+ const CELL_SIM_K = 25;
17
+
18
+ // --- Available models list ---
19
+ let availableModels = [];
20
+ let selectedModel = '';
21
+ let creatorsMap = {};
22
+
23
+ let selectedCreators = [];
24
+
25
+
26
+ // --- Cell highlight state ---
27
+ let cellHighlightTimeout = null;
28
+
29
+ function updateCreatorTags() {
30
+ const tagContainer = $('#creatorTags');
31
+ tagContainer.empty();
32
+ selectedCreators.forEach(name => {
33
+ const tag = $('<span>')
34
+ .addClass('badge bg-primary px-3 py-2 d-flex align-items-center')
35
+ .html(`${name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} <i class="bi bi-x ms-2" style="cursor:pointer;"></i>`);
36
+ tag.find('i').on('click', function () {
37
+ selectedCreators = selectedCreators.filter(c => c !== name);
38
+ updateCreatorTags();
39
+ });
40
+ tagContainer.append(tag);
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Appends a message to the working log with the specified type.
46
+ * @param {string} message - The message to display.
47
+ * @param {string} [type='text-white'] - The CSS class for the message (e.g., 'text-white', 'text-danger').
48
+ */
49
+ function logWorkingMessage(message, type = 'text-white') {
50
+ const logContainer = $('#workingLog');
51
+ if (!logContainer.length) { // overlay now has no log box
52
+ console.log('[WORKING]', message); // fallback for dev console
53
+ return;
54
+ }
55
+ logContainer.append(`<div class="${type}">${message}</div>`);
56
+ logContainer.scrollTop(logContainer[0].scrollHeight);
57
+ }
58
+
59
+ // ==========================
60
+ // == TOPIC TAG SELECTION ==
61
+ // ==========================
62
+ let selectedTopics = [];
63
+ let topicMap = {};
64
+
65
+ /**
66
+ * Updates the display of selected topics in the #selectedTopicsWrapper.
67
+ * Shows all topics from topicMap, visually indicating which are selected.
68
+ */
69
+ function updateSelectedTopicsDisplay() {
70
+ $('#selectedTopicsWrapper').removeClass('d-none');
71
+ const selectedTagContainer = $('#selectedTopicTags');
72
+ selectedTagContainer.empty();
73
+
74
+ for (const [code, label] of Object.entries(topicMap)) {
75
+ const isSelected = selectedTopics.includes(code);
76
+ const tag = $('<button>')
77
+ .addClass('btn btn-sm px-3 py-1 rounded-pill')
78
+ .addClass(isSelected ? 'btn-primary' : 'btn-outline-secondary')
79
+ .text(label)
80
+ .data('code', code)
81
+ .on('click', function () {
82
+ const idx = selectedTopics.indexOf(code);
83
+ if (idx === -1) {
84
+ selectedTopics.push(code);
85
+ } else {
86
+ selectedTopics.splice(idx, 1);
87
+ }
88
+ updateSelectedTopicsDisplay();
89
+ $(`#topicTags button[data-code="${code}"]`)
90
+ .toggleClass('active')
91
+ .toggleClass('btn-primary')
92
+ .toggleClass('btn-outline-primary');
93
+ });
94
+ selectedTagContainer.append(tag);
95
+ }
96
+ }
97
+
98
+ // Updates the display of selected creators in the #selectedCreatorsWrapper.
99
+ function updateSelectedCreatorsDisplay() {
100
+ $('#selectedCreatorsWrapper').removeClass('d-none');
101
+ const tagContainer = $('#selectedCreatorTags');
102
+ tagContainer.empty();
103
+ selectedCreators.forEach(name => {
104
+ const tag = $('<span>')
105
+ .addClass('badge bg-primary px-3 py-2 d-flex align-items-center')
106
+ .html(`${name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} <i class="bi bi-x ms-2" style="cursor:pointer;"></i>`);
107
+ tag.find('i').on('click', function () {
108
+ selectedCreators = selectedCreators.filter(c => c !== name);
109
+ updateSelectedCreatorsDisplay();
110
+ });
111
+ tagContainer.append(tag);
112
+ });
113
+ }
114
+
115
+ // Main script entry point: sets up event handlers on document ready
116
+ $(document).ready(function () {
117
+ // Add click handler for Artefact Viewer logo/text to refresh the app
118
+ $('.navbar-brand').on('click', function (e) {
119
+ e.preventDefault();
120
+
121
+ // Clear all state variables
122
+ runId = null;
123
+ imageKey = null;
124
+ upload = null;
125
+ viewGridEnabled = false;
126
+ selectedTopics = [];
127
+ selectedCreators = [];
128
+ selectedModel = '';
129
+
130
+ // Reset UI elements
131
+ $('#uploadedImage').addClass('d-none').attr('src', '');
132
+ $('#uploadTrigger').removeClass('d-none');
133
+ $('#imageTools').addClass('d-none');
134
+ $('#workingOverlay').addClass('d-none');
135
+ $('#workDetailsBanner').remove();
136
+ $('#gridOverlay').hide().html('');
137
+ $('#gridHighlightOverlay').hide();
138
+ $('#heatmapOverlay').remove();
139
+
140
+ // Hide panels
141
+ $('.col-md-3').addClass('d-none');
142
+ $('#sentenceList').empty();
143
+ $('#imageHistoryWrapper').addClass('d-none');
144
+ // $('#imageHistory').empty(); // <- REMOVE THIS LINE
145
+ $('#selectedTopicsWrapper').addClass('d-none');
146
+ $('#selectedCreatorsWrapper').addClass('d-none');
147
+
148
+ // Reset topic selections
149
+ $('#topicTags button').removeClass('active btn-primary').addClass('btn-outline-primary');
150
+ $('#selectedTopicTags').empty();
151
+
152
+ // Reset creator selections
153
+ $('#creatorTags').empty();
154
+ $('#selectedCreatorTags').empty();
155
+ $('#creatorSearch').val('');
156
+ $('#creatorSearchResults').empty();
157
+ $('#creatorPanelSearch').val('');
158
+ $('#creatorPanelResults').empty();
159
+
160
+ // Reset model selection to first available
161
+ if (availableModels.length > 0) {
162
+ selectedModel = availableModels[0];
163
+ $('#modelDropdown').text('AI Model: ' + selectedModel);
164
+ $('#modelDropdownMenu a').removeClass('active');
165
+ $('#modelDropdownMenu a').first().addClass('active');
166
+ }
167
+
168
+ // Reset debug panel
169
+ $('#debugStatus').text('Idle');
170
+ $('#debugSessionId').text('N/A');
171
+ $('#workingLog').empty();
172
+
173
+ // Recreate the upload card if it was removed
174
+ if ($('.card:has(#uploadTrigger)').length === 0 && $('#exampleContainer').length === 0) {
175
+ const uploadCard = $(`
176
+ <div class="card h-100 text-center d-flex align-items-center justify-content-center" style="cursor: pointer; background-color: rgba(255,255,255,0.1);">
177
+ <div class="card-body">
178
+ <p class="mb-2">Drop an image here or click to upload</p>
179
+ <button class="btn btn-primary" id="uploadTrigger">
180
+ <i class="bi bi-upload"></i> Upload Image
181
+ </button>
182
+ </div>
183
+ </div>
184
+ `);
185
+ $('#uploadedImageContainer').prepend(uploadCard);
186
+ }
187
+
188
+ // Show example container if it was hidden
189
+ showLandingContent(); // ← NEW
190
+ adjustMainWidth();
191
+ });
192
+
193
+ // --- Load topic tags from /topics ---
194
+ fetch(`${API_BASE_URL}/topics`)
195
+ .then(response => response.json())
196
+ .then(data => {
197
+ topicMap = data;
198
+ const tagContainer = document.getElementById('topicTags');
199
+ for (const [code, label] of Object.entries(data)) {
200
+ const tag = document.createElement('button');
201
+ tag.className = 'btn btn-outline-primary btn-sm px-3 py-1 rounded-pill';
202
+ tag.textContent = label;
203
+ tag.dataset.code = code;
204
+ tag.addEventListener('click', function () {
205
+ this.classList.toggle('active');
206
+ if (this.classList.contains('active')) {
207
+ this.classList.replace('btn-outline-primary', 'btn-primary');
208
+ selectedTopics.push(code);
209
+ } else {
210
+ this.classList.replace('btn-primary', 'btn-outline-primary');
211
+ selectedTopics = selectedTopics.filter(c => c !== code);
212
+ }
213
+ });
214
+ tagContainer.appendChild(tag);
215
+ }
216
+ })
217
+ .catch(error => {
218
+ console.error('Error loading topics:', error);
219
+ });
220
+
221
+ // --- Load model list from /models ---
222
+ fetch(`${API_BASE_URL}/models`)
223
+ .then(response => response.json())
224
+ .then(data => {
225
+ availableModels = data;
226
+ console.log("Available models:", availableModels);
227
+ // Populate the model dropdown
228
+ const dropdownMenu = $('#modelDropdownMenu');
229
+ if (availableModels.length > 0) {
230
+ dropdownMenu.empty();
231
+ availableModels.forEach((model, index) => {
232
+ const item = $('<li><a class="dropdown-item" href="#">' + model + '</a></li>');
233
+ if (index === 0) {
234
+ $('#modelDropdown').text('AI Model: ' + model);
235
+ item.find('a').addClass('active');
236
+ selectedModel = model;
237
+ }
238
+ item.on('click', function () {
239
+ selectedModel = model;
240
+ $('#modelDropdownMenu a').removeClass('active');
241
+ $(this).find('a').addClass('active');
242
+ $('#modelDropdown').text('AI Model: ' + model);
243
+ });
244
+ dropdownMenu.append(item);
245
+ });
246
+ }
247
+ })
248
+ .catch(error => {
249
+ console.error('Error loading models:', error);
250
+ });
251
+
252
+ // --- Load creators list from /creators ---
253
+ fetch(`${API_BASE_URL}/creators`)
254
+ .then(response => response.json())
255
+ .then(data => {
256
+ creatorsMap = data;
257
+ console.log("Available creators:", creatorsMap);
258
+ })
259
+ .catch(error => {
260
+ console.error('Error loading creators:', error);
261
+ });
262
+
263
+ // --- Creator search logic ---
264
+ $('#creatorSearch').on('input', function () {
265
+ const query = $(this).val().toLowerCase();
266
+ const resultsContainer = $('#creatorSearchResults');
267
+ resultsContainer.empty();
268
+ if (query.length > 0) {
269
+ const matches = Object.keys(creatorsMap).filter(name =>
270
+ name.toLowerCase().includes(query)
271
+ );
272
+ matches.forEach((name) => {
273
+ const item = $('<button>')
274
+ .addClass('list-group-item list-group-item-action')
275
+ .text(name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()))
276
+ .on('click', function () {
277
+ if (!selectedCreators.includes(name)) {
278
+ selectedCreators.push(name);
279
+ updateCreatorTags();
280
+ }
281
+ $('#creatorSearch').val('');
282
+ resultsContainer.empty();
283
+ });
284
+ resultsContainer.append(item);
285
+ });
286
+ }
287
+ });
288
+
289
+ // Add enter-to-select first match
290
+ $('#creatorSearch').on('keydown', function (e) {
291
+ if (e.key === 'Enter') {
292
+ e.preventDefault();
293
+ const firstItem = $('#creatorSearchResults button').first();
294
+ if (firstItem.length) {
295
+ firstItem.click();
296
+ }
297
+ }
298
+ });
299
+
300
+ // --- Creator panel search logic ---
301
+ $('#creatorPanelSearch').on('input', function () {
302
+ const query = $(this).val().toLowerCase();
303
+ const resultsContainer = $('#creatorPanelResults');
304
+ resultsContainer.empty();
305
+ if (query.length > 0) {
306
+ const matches = Object.keys(creatorsMap).filter(name =>
307
+ name.toLowerCase().includes(query)
308
+ );
309
+ matches.forEach((name, index) => {
310
+ const item = $('<button>')
311
+ .addClass('list-group-item list-group-item-action')
312
+ .text(name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()))
313
+ .on('click', function () {
314
+ if (!selectedCreators.includes(name)) {
315
+ selectedCreators.push(name);
316
+ updateSelectedCreatorsDisplay();
317
+ }
318
+ $('#creatorPanelSearch').val('');
319
+ resultsContainer.empty();
320
+ });
321
+ if (index === 0) {
322
+ item.addClass('active');
323
+ }
324
+ resultsContainer.append(item);
325
+ });
326
+ }
327
+ });
328
+
329
+ $('#creatorPanelSearch').on('keydown', function (e) {
330
+ if (e.key === 'Enter') {
331
+ e.preventDefault();
332
+ const firstItem = $('#creatorPanelResults button').first();
333
+ if (firstItem.length) {
334
+ firstItem.click();
335
+ }
336
+ }
337
+ });
338
+ // Trigger file upload dialog when upload button is clicked
339
+ $('#uploadTrigger').on('click', function () {
340
+ $('#imageUpload').click();
341
+ });
342
+
343
+ // Handle image file selected from file input
344
+ $('#imageUpload').on('change', function (event) {
345
+ const file = event.target.files[0];
346
+ if (file) {
347
+ const reader = new FileReader();
348
+ reader.onload = function (e) {
349
+ // REMOVED: saveCurrentImageToHistory(); // Save current image before loading new one
350
+ $('#uploadedImage').attr('src', e.target.result).removeClass('d-none');
351
+ $('#uploadTrigger').addClass('d-none');
352
+ $('.card:has(#uploadTrigger)').addClass('d-none');
353
+ $('#exampleContainer').addClass('d-none');
354
+ $('#workingOverlay').removeClass('d-none');
355
+ $('#imageTools').removeClass('d-none');
356
+ fetchPresign();
357
+ };
358
+ reader.readAsDataURL(file);
359
+ }
360
+ });
361
+
362
+ // --- Drag and drop support for uploading images ---
363
+ $('#uploadedImageContainer').on('dragover', function (e) {
364
+ e.preventDefault();
365
+ e.stopPropagation();
366
+ $(this).addClass('border border-light');
367
+ });
368
+
369
+ $('#uploadedImageContainer').on('dragleave', function (e) {
370
+ e.preventDefault();
371
+ e.stopPropagation();
372
+ $(this).removeClass('border border-light');
373
+ });
374
+
375
+ $('#uploadedImageContainer').on('drop', function (e) {
376
+ e.preventDefault();
377
+ e.stopPropagation();
378
+ $(this).removeClass('border border-light');
379
+ const file = e.originalEvent.dataTransfer.files[0];
380
+ if (file && file.type.startsWith('image/')) {
381
+ const reader = new FileReader();
382
+ reader.onload = function (e) {
383
+ // REMOVED: saveCurrentImageToHistory(); // Save current image before loading new one
384
+ $('#uploadedImage').attr('src', e.target.result).removeClass('d-none');
385
+ $('#uploadTrigger').addClass('d-none');
386
+ $('#workingOverlay').removeClass('d-none');
387
+ $('#imageTools').removeClass('d-none');
388
+ fetchPresign();
389
+ };
390
+ reader.readAsDataURL(file);
391
+ }
392
+ });
393
+
394
+ // --- Example image selection logic ---
395
+ let selectedSrc = null;
396
+ $('.example-img').on('click', function () {
397
+ $('.example-img').removeClass('selected-img');
398
+ $(this).addClass('selected-img');
399
+ selectedSrc = $(this).attr('src');
400
+ $('#selectImageBtn').removeClass('d-none');
401
+ });
402
+
403
+ // Handle selection of an example image
404
+ $('#selectImageBtn').on('click', function () {
405
+ if (selectedSrc) {
406
+ // REMOVED: saveCurrentImageToHistory(); // Save current image before loading new one
407
+ $('#uploadedImage').attr('src', selectedSrc).removeClass('d-none');
408
+ $('#uploadTrigger').addClass('d-none');
409
+ $('.card:has(#uploadTrigger)').addClass('d-none');
410
+ $('#exampleContainer').addClass('d-none');
411
+ $('#workingOverlay').removeClass('d-none');
412
+ $('#imageTools').removeClass('d-none');
413
+ fetchPresign();
414
+ }
415
+ });
416
+
417
+ // make sure main column spans full width on initial landing screen
418
+ adjustMainWidth();
419
+
420
+ // About modal trigger
421
+ $('.nav-link[href="#"]:contains("About")').on('click', function(e) {
422
+ e.preventDefault();
423
+ $('#aboutModal').modal('show');
424
+ });
425
+ }); // ← existing closing bracket of document-ready
426
+
427
+ /**
428
+ * Initiates a new session by requesting a presigned upload URL
429
+ * and registering a run. Triggers polling for run status.
430
+ */
431
+ function fetchPresign() {
432
+ $('#debugStatus').text('Requesting ID...');
433
+ logWorkingMessage('Requesting Session ID...', 'text-white');
434
+
435
+ // Save the current image to history immediately when backend processing starts
436
+ saveCurrentImageToHistory();
437
+
438
+ fetch(`${API_BASE_URL}/presign`, {
439
+ method: 'POST',
440
+ headers: {
441
+ 'Content-Type': 'application/json'
442
+ },
443
+ body: JSON.stringify({ fileName: 'selected.jpg' })
444
+ })
445
+ .then(res => res.json())
446
+ .then(data => {
447
+ runId = data.runId;
448
+ imageKey = data.imageKey;
449
+ upload = data.upload;
450
+
451
+ // --- Unified canvas-based image upload logic ---
452
+ const imgElement = document.getElementById('uploadedImage');
453
+ const canvas = document.createElement('canvas');
454
+ canvas.width = imgElement.naturalWidth;
455
+ canvas.height = imgElement.naturalHeight;
456
+ const ctx = canvas.getContext('2d');
457
+ ctx.drawImage(imgElement, 0, 0);
458
+
459
+ canvas.toBlob(function (blob) {
460
+ const file = new File([blob], 'uploaded.jpg', { type: blob.type });
461
+ const formData = new FormData();
462
+ formData.append('file', file);
463
+
464
+ // $('.w-100.p-4').remove(); // Remove instead of just hiding
465
+ hideLandingContent(); // just hide; keep DOM alive
466
+
467
+ fetch(`${API_BASE_URL}/upload/${runId}`, {
468
+ method: 'POST',
469
+ body: formData
470
+ })
471
+ .then(res => {
472
+ if (res.status === 204) {
473
+ logWorkingMessage('Image uploaded successfully (204 No Content)', 'text-white');
474
+ } else {
475
+ return res.json().then(() => {
476
+ logWorkingMessage('Image uploaded successfully', 'text-white');
477
+ });
478
+ }
479
+ })
480
+ .then(() => {
481
+ $('#debugStatus').text('Got ID');
482
+ logWorkingMessage('Session ID: ' + runId, 'text-white');
483
+ logWorkingMessage('Sending /runs request...', 'text-white');
484
+ $('#debugStatus').text('Posting run info');
485
+
486
+ // Show selected topics box using helper
487
+ updateSelectedTopicsDisplay();
488
+ updateSelectedCreatorsDisplay();
489
+
490
+ // ── NEW: keep all three lower-row cards visible during processing ──
491
+ showBottomCards(); // Image History + Topics + Creators
492
+ adjustMainWidth(); // recalc main column width
493
+
494
+ return fetch(`${API_BASE_URL}/runs`, {
495
+ method: 'POST',
496
+ headers: {
497
+ 'Content-Type': 'application/json'
498
+ },
499
+ body: JSON.stringify({
500
+ runId: runId,
501
+ imageKey: imageKey,
502
+ topics: selectedTopics,
503
+ creators: selectedCreators,
504
+ model: selectedModel
505
+ })
506
+ });
507
+ })
508
+ .then(res => {
509
+ // Don't parse empty 202 response as JSON
510
+ if (res.status === 202) {
511
+ return {};
512
+ }
513
+ return res.json();
514
+ })
515
+ .then(response => {
516
+ logWorkingMessage('Run registered successfully', 'text-white');
517
+ $('#debugStatus').text('Run submitted');
518
+ pollRunStatus(runId);
519
+ })
520
+ .catch(err => {
521
+ console.error('Upload or /runs error:', err);
522
+ logWorkingMessage('Error uploading image or submitting run', 'text-danger');
523
+ $('#debugStatus').text('Run submission failed');
524
+ });
525
+ }, 'image/jpeg');
526
+ // --- End unified image upload logic ---
527
+ })
528
+ .catch(err => {
529
+ console.error('Presign error:', err);
530
+ $('#debugStatus').text('Error fetching ID');
531
+ logWorkingMessage('Error fetching ID', 'text-danger');
532
+ });
533
+ }
534
+
535
+ /**
536
+ * Polls the backend for the status of the current run.
537
+ * When complete, fetches and displays output sentences.
538
+ * @param {string} runId - The run/session ID to poll.
539
+ */
540
+ function pollRunStatus(runId) {
541
+ logWorkingMessage('Polling run status...', 'text-white');
542
+
543
+ const intervalId = setInterval(() => {
544
+ fetch(`${API_BASE_URL}/runs/${runId}`)
545
+ .then(res => res.json())
546
+ .then(data => {
547
+ $('#debugStatus').text(`Status: ${data.status}`);
548
+ logWorkingMessage(`Status: ${data.status}`, 'text-white');
549
+
550
+ if (data.status !== 'processing') {
551
+ clearInterval(intervalId);
552
+ logWorkingMessage('Processing complete', 'text-white');
553
+
554
+ if (data.status === 'done') {
555
+ // Use outputKey from backend response instead of deriving from upload URL
556
+ const filePath = data.outputKey ? data.outputKey.split('/').pop() : `${runId}.json`;
557
+
558
+ logWorkingMessage('Fetching outputs from: ' + filePath, 'text-white');
559
+
560
+ // Fetch output sentences from backend
561
+ fetch(`${API_BASE_URL}/outputs/${filePath}`)
562
+ .then(res => res.json())
563
+ .then(output => {
564
+ logWorkingMessage('Outputs received', 'text-white');
565
+ display_sentences(output);
566
+ $('#workingOverlay').addClass('d-none');
567
+ })
568
+ .catch(err => {
569
+ console.error('Error fetching outputs:', err);
570
+ logWorkingMessage('Error fetching outputs', 'text-danger');
571
+ });
572
+ }
573
+ else if (data.status === 'error') {
574
+ logWorkingMessage('An error occurred during processing.', 'text-danger');
575
+ setTimeout(() => {
576
+ $('#workingOverlay').addClass('d-none');
577
+ // $('#uploadedImage').addClass('d-none').attr('src', '');
578
+ // $('#uploadTrigger').removeClass('d-none');
579
+ // $('#imageTools').addClass('d-none');
580
+ // $('.col-md-3').addClass('d-none');
581
+ // $('#imageHistoryWrapper').addClass('d-none');
582
+ // $('#selectedTopicsWrapper').addClass('d-none');
583
+ $('#debugStatus').text('Idle');
584
+ $('#debugSessionId').text('N/A');
585
+ selectedTopics = [];
586
+ // $('#topicTags button').removeClass('active btn-primary').addClass('btn-outline-primary');
587
+ // $('#selectedTopicTags').empty();
588
+ }, 5000);
589
+ }
590
+ }
591
+ })
592
+ .catch(err => {
593
+ console.error('Polling error:', err);
594
+ logWorkingMessage('Error polling status', 'text-danger');
595
+ clearInterval(intervalId);
596
+ });
597
+ }, 1000);
598
+ }
599
+
600
+ /**
601
+ * Escapes HTML special characters in a string to prevent XSS.
602
+ * @param {string} str - The string to escape.
603
+ * @returns {string}
604
+ */
605
+ function escapeHTML(str) {
606
+ return str.replace(/[&<>'"]/g, tag => (
607
+ {'&': '&amp;', '<': '&lt;', '>': '&gt;', "'": '&#39;', '"': '&quot;'}[tag]
608
+ ));
609
+ }
610
+
611
+ /**
612
+ * Displays the list of output sentences in the sidebar.
613
+ * @param {Array|Object} data - Array of sentence objects or {sentences:[…]}
614
+ */
615
+ function display_sentences(data) {
616
+ // normalise payload
617
+ if (!Array.isArray(data)) {
618
+ data = (data && Array.isArray(data.sentences)) ? data.sentences : [];
619
+ }
620
+ if (!data.length) { // nothing to show ⇒ just hide overlay
621
+ $('#workingOverlay').addClass('d-none');
622
+ return;
623
+ }
624
+
625
+ // Show the sentences panel
626
+ $('.col-md-3').removeClass('d-none');
627
+ $('#sentenceList').empty();
628
+
629
+ /* ---------- sentence list construction ---------- */
630
+ data.forEach(item => {
631
+ const li = $(`
632
+ <li class="list-group-item sentence-item mb-1"
633
+ data-work="${item.work}"
634
+ data-sentence="${escapeHTML(item.english_original)}">
635
+ <div class="d-flex align-items-center">
636
+ <span class="flex-grow-1">${escapeHTML(item.english_original)}</span>
637
+ <button class="btn btn-sm btn-outline-dark ms-2 heatmap-btn"
638
+ title="View heatmap"
639
+ data-sentence="${escapeHTML(item.english_original)}">
640
+ <i class="bi bi-thermometer-half"></i>
641
+ </button>
642
+ </div>
643
+ </li>
644
+ `);
645
+ li.find('span').on('click', function () {
646
+ lookupDOI(li.data('work'), li.data('sentence'));
647
+ });
648
+ li.find('.heatmap-btn').on('click', function(e) {
649
+ e.stopPropagation();
650
+ requestHeatmap($(this).data('sentence'));
651
+ });
652
+ $('#sentenceList').append(li);
653
+ });
654
+ showBottomCards();
655
+ adjustMainWidth();
656
+ }
657
+
658
+ // helper runs whenever the right-hand column is shown/hidden
659
+ function adjustMainWidth(){
660
+ const $main = $('#uploadedImageContainer').closest('.col-md-9');
661
+ if ($('.col-md-3').hasClass('d-none')){
662
+ $main.addClass('fill');
663
+ } else{
664
+ $main.removeClass('fill');
665
+ }
666
+ }
667
+
668
+ // ──────────────────────────────────────────────────────────────────────────────
669
+ // NEW helper : always reveal bottom-row cards
670
+ // ──────────────────────────────────────────────────────────────────────────────
671
+ function showBottomCards() {
672
+ $('#imageHistoryWrapper').removeClass('d-none');
673
+ $('#selectedTopicsWrapper').removeClass('d-none');
674
+ $('#selectedCreatorsWrapper').removeClass('d-none');
675
+ }
676
+
677
+ // ──────────────────────────────────────────────────────────────────────────────
678
+ // NEW helper : save current image to history
679
+ // ──────────────────────────────────────────────────────────────────────────────
680
+ function saveCurrentImageToHistory() {
681
+ const currentImg = $('#uploadedImage');
682
+ if (!currentImg.attr('src') || currentImg.hasClass('d-none')) {
683
+ return; // No image to save
684
+ }
685
+
686
+ // Check if this image is already the most recent in history
687
+ const firstHistoryImg = $('#imageHistory img').first();
688
+ if (firstHistoryImg.length && firstHistoryImg.attr('src') === currentImg.attr('src')) {
689
+ return; // Don't duplicate the same image
690
+ }
691
+
692
+ const historyImg = new Image();
693
+ historyImg.src = currentImg.attr('src');
694
+ historyImg.className = "rounded border border-secondary shadow-sm";
695
+ historyImg.style.height = "100px";
696
+ historyImg.style.cursor = "pointer";
697
+ historyImg.title = "Previous image";
698
+ $('#imageHistoryWrapper').removeClass('d-none');
699
+ $('#imageHistory').prepend(historyImg);
700
+ }
701
+
702
+ // --- Begin Crop Tool Functionality ---
703
+ // Variables for cropping state
704
+ let isCropping = false;
705
+ let cropStartX = 0;
706
+ let cropStartY = 0;
707
+ let cropRect = null;
708
+
709
+ // Activate cropping mode when crop tool button is clicked
710
+ $('#cropToolBtn').on('click', function () {
711
+ isCropping = true;
712
+ $('#uploadedImageContainer').css('cursor', 'crosshair');
713
+ });
714
+
715
+ // Start drawing crop rectangle on mouse down
716
+ $('#uploadedImageContainer').on('mousedown', function (e) {
717
+ if (!isCropping) return;
718
+ const rect = this.getBoundingClientRect();
719
+ cropStartX = e.clientX - rect.left;
720
+ cropStartY = e.clientY - rect.top;
721
+
722
+ if (cropRect) {
723
+ cropRect.remove();
724
+ }
725
+
726
+ cropRect = $('<div>')
727
+ .addClass('position-absolute border border-warning')
728
+ .css({
729
+ left: cropStartX,
730
+ top: cropStartY,
731
+ width: 0,
732
+ height: 0,
733
+ zIndex: 10,
734
+ pointerEvents: 'none'
735
+ })
736
+ .appendTo('#uploadedImageContainer');
737
+ });
738
+
739
+ // Update crop rectangle size on mouse move
740
+ $('#uploadedImageContainer').on('mousemove', function (e) {
741
+ if (!isCropping || !cropRect) return;
742
+ const rect = this.getBoundingClientRect();
743
+ const currentX = e.clientX - rect.left;
744
+ const currentY = e.clientY - rect.top;
745
+
746
+ const width = Math.abs(currentX - cropStartX);
747
+ const height = Math.abs(currentY - cropStartY);
748
+ const left = Math.min(currentX, cropStartX);
749
+ const top = Math.min(currentY, cropStartY);
750
+
751
+ cropRect.css({ left, top, width, height });
752
+ });
753
+
754
+ // Complete cropping on mouse up, update image, and save history
755
+ $('#uploadedImageContainer').on('mouseup', function (e) {
756
+ if (!isCropping || !cropRect) return;
757
+ isCropping = false;
758
+ $('#uploadedImageContainer').css('cursor', 'default');
759
+
760
+ const img = document.getElementById('uploadedImage');
761
+ // Use the actual image's bounding box for accurate alignment
762
+ const imageRect = img.getBoundingClientRect();
763
+ const cropOffset = cropRect.offset();
764
+
765
+ // Calculate crop rectangle relative to image's natural size
766
+ const sx = ((cropOffset.left - imageRect.left) / imageRect.width) * img.naturalWidth;
767
+ const sy = ((cropOffset.top - imageRect.top) / imageRect.height) * img.naturalHeight;
768
+ const sw = (cropRect.width() / imageRect.width) * img.naturalWidth;
769
+ const sh = (cropRect.height() / imageRect.height) * img.naturalHeight;
770
+
771
+ // Don't crop if width or height is zero or negative
772
+ if (sw <= 0 || sh <= 0) {
773
+ cropRect.remove();
774
+ cropRect = null;
775
+ return;
776
+ }
777
+
778
+ // REMOVED: Save current image to history using the new helper
779
+ // REMOVED: saveCurrentImageToHistory();
780
+
781
+ // Draw the cropped region onto a canvas and update the image
782
+ const canvas = document.createElement('canvas');
783
+ canvas.width = sw;
784
+ canvas.height = sh;
785
+ const ctx = canvas.getContext('2d');
786
+ ctx.drawImage(img, sx, sy, sw, sh, 0, 0, sw, sh);
787
+
788
+ img.src = canvas.toDataURL();
789
+ $('#uploadedImage').removeClass('d-none');
790
+ cropRect.remove();
791
+ cropRect = null;
792
+
793
+ $('#workingOverlay').removeClass('d-none');
794
+ logWorkingMessage('Rerunning with cropped image...', 'text-white');
795
+ fetchPresign();
796
+ });
797
+ // --- End Crop Tool Functionality ---
798
+
799
+ // --- Begin Undo Tool Functionality ---
800
+ // Restore previous image from history when undo is clicked
801
+ $('#undoToolBtn').on('click', function () {
802
+ const historyImgs = $('#imageHistory img');
803
+ if (historyImgs.length > 0) {
804
+ const firstImg = historyImgs.first();
805
+ const previousSrc = firstImg.attr('src');
806
+ $('#uploadedImage').attr('src', previousSrc).removeClass('d-none');
807
+ firstImg.remove();
808
+ }
809
+ });
810
+
811
+ // --- End Undo Tool Functionality ---
812
+
813
+ // --- Begin Image History Selection Functionality ---
814
+ // When a history image is clicked, make it the current image and rerun the API flow
815
+ $('#imageHistory').on('click', 'img', function () {
816
+ const currentImg = $('#uploadedImage')[0];
817
+ const newSrc = $(this).attr('src');
818
+
819
+ // REMOVED: Only save to history if it's a different image
820
+ // REMOVED: if (currentImg.src && currentImg.src !== newSrc) {
821
+ // REMOVED: saveCurrentImageToHistory();
822
+ // REMOVED: }
823
+
824
+ // Update to the selected history image
825
+ $('#uploadedImage').attr('src', newSrc).removeClass('d-none');
826
+
827
+ // Rerun the API processing
828
+ $('#workingOverlay').removeClass('d-none');
829
+ logWorkingMessage('Rerunning with selected image from history...', 'text-white');
830
+ fetchPresign();
831
+ });
832
+ // --- End Image History Selection Functionality ---
833
+
834
+ // --- Begin Rerun Tool Functionality ---
835
+ // Rerun the backend pipeline with the current image
836
+ $('#rerunToolBtn').on('click', function () {
837
+ $('#workingOverlay').removeClass('d-none');
838
+ logWorkingMessage('Rerunning with current image...', 'text-white');
839
+ fetchPresign();
840
+ });
841
+ // --- End Rerun Tool Functionality ---
842
+
843
+ // NEW: Grid toggle button handler
844
+ $('#gridToolBtn').on('click', function () {
845
+ viewGridEnabled = !viewGridEnabled;
846
+ updateGridVisibility();
847
+ $(this).toggleClass('active'); // visual feedback
848
+ });
849
+
850
+ /**
851
+ * Looks up metadata for a given work ID (e.g., DOI) and displays details.
852
+ * @param {string} work_id - The identifier for the work to look up.
853
+ */
854
+ function lookupDOI(work_id, sentence) {
855
+ const url = `${API_BASE_URL}/work/${encodeURIComponent(work_id)}`
856
+ + `?sentence=${encodeURIComponent(sentence)}`;
857
+ fetch(url)
858
+ .then(res => res.json())
859
+ .then(data => {
860
+ data.Work_ID = work_id;
861
+ showWorkDetails(data); // now contains .context
862
+ })
863
+ .catch(console.error);
864
+ }
865
+
866
+ /**
867
+ * Displays work/DOI details in a centred, scrollable modal rectangle with a dimmed backdrop.
868
+ * @param {Object} workData
869
+ */
870
+ function showWorkDetails(workData) {
871
+ // ――― Clean-up any prior overlay ―――
872
+ $('#workOverlayBackdrop, #workDetailsModal').remove();
873
+
874
+ const d = workData;
875
+ const ctxHtml = d.context
876
+ ? `<section class="mb-3">
877
+ <p class="fw-bold mb-1">In&nbsp;Context</p>
878
+ <blockquote class="context-paragraph"
879
+ style="white-space:pre-wrap;">
880
+ ${escapeHTML(d.context)}
881
+ </blockquote>
882
+ </section>`
883
+ : '';
884
+
885
+ /* ---------- backdrop + centred rectangle ---------- */
886
+ const backdrop = $( // ← MISSING, caused ReferenceError
887
+ `<div id="workOverlayBackdrop"
888
+ class="position-fixed top-0 start-0 w-100 h-100
889
+ bg-dark bg-opacity-50"
890
+ style="z-index:2000;"></div>`
891
+ );
892
+
893
+ const modal = $(`
894
+ <div id="workDetailsModal"
895
+ class="position-fixed bg-white border border-primary rounded shadow p-4"
896
+ style="top:50%; left:50%; transform:translate(-50%,-50%);
897
+ max-width:90vw; max-height:80vh; overflow:auto; z-index:2001;">
898
+
899
+ <!-- close button -->
900
+ <button type="button"
901
+ class="btn btn-sm btn-outline-secondary position-absolute top-0 end-0 m-2"
902
+ id="workDetailsClose">
903
+ <i class="bi bi-x-lg"></i>
904
+ </button>
905
+
906
+ <h5 class="mb-2">${d.Work_Title || 'Unknown Title'}</h5>
907
+ <p class="mb-1"><strong>Author(s):</strong> ${d.Author_Name || 'Unknown Author'}</p>
908
+ <p class="mb-1"><strong>Year:</strong> ${d.Year || 'Unknown'}</p>
909
+
910
+ ${ctxHtml} <!-- ← NEW paragraph section -->
911
+
912
+ <!-- Image gallery (unchanged) -->
913
+ <div id="galleryWrapper" class="mb-2">
914
+ <div class="fw-bold">Images in this work</div>
915
+ <div id="galleryScroller" class="mt-1"></div>
916
+ </div>
917
+
918
+ <p class="mb-1"><strong>DOI:</strong>
919
+ <a href="${d.DOI}" target="_blank" class="text-primary text-decoration-underline">${d.DOI}</a>
920
+ </p>
921
+ <p class="mb-1"><strong>Download Link:</strong>
922
+ <a href="${d.Link}" target="_blank" class="text-primary text-decoration-underline">${d.Link}</a>
923
+ </p>
924
+
925
+ <!-- BibTeX block (unchanged) -->
926
+ <div class="position-relative mt-3">
927
+ <span class="fw-bold">BibTeX Citation</span>
928
+ <button class="btn btn-sm btn-outline-secondary position-absolute top-0 end-0"
929
+ onclick="copyBibTeX()" title="Copy to clipboard">
930
+ <i class="bi bi-clipboard"></i>
931
+ </button>
932
+ <pre id="bibtexContent"
933
+ class="p-2 mt-1 rounded"
934
+ style="white-space:pre-wrap; word-break:break-word;
935
+ font-size:.875rem; background:#fdfde7; color:#000; padding-right:3rem;">
936
+ ${d.BibTeX || 'Citation not available'}
937
+ </pre>
938
+ </div>
939
+
940
+ <iframe src="${d.DOI}"
941
+ style="width:100%; height:50vh; border:none;"
942
+ class="mt-3"></iframe>
943
+ </div>
944
+ `);
945
+
946
+ // inject into DOM
947
+ $('body').append(backdrop, modal);
948
+
949
+ /* ---------- gallery fetch ---------- */
950
+ if (d.Work_ID) {
951
+ fetch(`${API_BASE_URL}/images/${d.Work_ID}`)
952
+ .then(r => r.json())
953
+ .then(urls => {
954
+ if (!urls.length) { $('#galleryWrapper').hide(); return; }
955
+ const scroller = $('#galleryScroller');
956
+ urls.forEach(u => $('<img>')
957
+ .attr('src', u)
958
+ .attr('crossorigin', 'anonymous') // ensure CORS safe for canvas
959
+ .addClass('img-thumbnail')
960
+ .css({ height: '120px', cursor: 'pointer' })
961
+ .on('click', () => loadImageAndRun(u))
962
+ .appendTo(scroller));
963
+ })
964
+ .catch(console.error);
965
+ }
966
+
967
+ /* ---------- close handlers ---------- */
968
+ backdrop.on('click', () => { backdrop.remove(); modal.remove(); });
969
+ modal.on('click', '#workDetailsClose', () => { backdrop.remove(); modal.remove(); });
970
+ }
971
+
972
+ // Add this helper function for copying BibTeX
973
+ function copyBibTeX() {
974
+ const bibtexText = document.getElementById('bibtexContent').textContent.trim();
975
+
976
+ // Create a temporary textarea to copy from
977
+ const tempTextarea = document.createElement('textarea');
978
+ tempTextarea.value = bibtexText;
979
+ tempTextarea.style.position = 'fixed';
980
+ tempTextarea.style.opacity = '0';
981
+ document.body.appendChild(tempTextarea);
982
+
983
+ // Select and copy the text
984
+ tempTextarea.select();
985
+ document.execCommand('copy');
986
+ document.body.removeChild(tempTextarea);
987
+
988
+ // Visual feedback - change icon temporarily
989
+ const copyBtn = event.target.closest('button');
990
+ const icon = copyBtn.querySelector('i');
991
+ icon.classList.remove('bi-clipboard');
992
+ icon.classList.add('bi-clipboard-check');
993
+
994
+ // Change button text temporarily
995
+ copyBtn.setAttribute('title', 'Copied!');
996
+
997
+ // Reset after 2 seconds
998
+ setTimeout(() => {
999
+ icon.classList.remove('bi-clipboard-check');
1000
+ icon.classList.add('bi-clipboard');
1001
+ copyBtn.setAttribute('title', 'Copy to clipboard');
1002
+ }, 2000);
1003
+ }
1004
+
1005
+ /**
1006
+ * Positions the #gridOverlay to exactly cover the visible image area.
1007
+ */
1008
+ function positionGridOverlayToImage() {
1009
+ const container = document.getElementById('uploadedImageContainer');
1010
+ const img = document.getElementById('uploadedImage');
1011
+ const overlay = document.getElementById('gridOverlay');
1012
+ if (!container || !img || !overlay) return;
1013
+ if (img.classList.contains('d-none') || !img.src) return;
1014
+
1015
+ const containerRect = container.getBoundingClientRect();
1016
+ const imageRect = img.getBoundingClientRect();
1017
+ // Compute image rect relative to the container
1018
+ const left = imageRect.left - containerRect.left;
1019
+ const top = imageRect.top - containerRect.top;
1020
+
1021
+ overlay.style.left = `${left}px`;
1022
+ overlay.style.top = `${top}px`;
1023
+ overlay.style.width = `${imageRect.width}px`;
1024
+ overlay.style.height = `${imageRect.height}px`;
1025
+ }
1026
+
1027
+ /**
1028
+ * Draws a 7×7 grid (i.e., 8 vertical + 8 horizontal lines) inside #gridOverlay.
1029
+ */
1030
+ function drawGridOverlay() {
1031
+ const overlay = document.getElementById('gridOverlay');
1032
+ const img = document.getElementById('uploadedImage');
1033
+ if (!overlay || !img || img.classList.contains('d-none') || !img.src) return;
1034
+
1035
+ positionGridOverlayToImage();
1036
+
1037
+ // Clear previous lines
1038
+ overlay.innerHTML = '';
1039
+
1040
+ const cols = GRID_COLS; // 7
1041
+ const rows = GRID_ROWS; // 7
1042
+
1043
+ // Helper to create line
1044
+ const makeLine = (styleObj) => {
1045
+ const line = document.createElement('div');
1046
+ line.style.position = 'absolute';
1047
+ line.style.background = 'rgba(255,255,255,0.6)';
1048
+ // hairline-ish width
1049
+ line.style.boxShadow = '0 0 0 1px rgba(0,0,0,0.1) inset';
1050
+ Object.assign(line.style, styleObj);
1051
+ overlay.appendChild(line);
1052
+ };
1053
+
1054
+ // Vertical lines (9)
1055
+ for (let i = 0; i <= cols; i++) {
1056
+ const xPct = (i / cols) * 100;
1057
+ makeLine({
1058
+ top: '0',
1059
+ bottom: '0',
1060
+ width: '1px',
1061
+ left: `calc(${xPct}% - 0.5px)`,
1062
+ });
1063
+ }
1064
+
1065
+ // Horizontal lines (9)
1066
+ for (let j = 0; j <= rows; j++) {
1067
+ const yPct = (j / rows) * 100;
1068
+ makeLine({
1069
+ left: '0',
1070
+ right: '0',
1071
+ height: '1px',
1072
+ top: `calc(${yPct}% - 0.5px)`,
1073
+ });
1074
+ }
1075
+ }
1076
+
1077
+ /**
1078
+ * Shows/hides and (re)draws the grid depending on toggle state.
1079
+ */
1080
+ function updateGridVisibility() {
1081
+ const overlay = document.getElementById('gridOverlay');
1082
+ if (!overlay) return;
1083
+ if (viewGridEnabled) {
1084
+ overlay.style.display = 'block';
1085
+ drawGridOverlay();
1086
+ } else {
1087
+ overlay.style.display = 'none';
1088
+ overlay.innerHTML = '';
1089
+ }
1090
+ }
1091
+
1092
+ // Ensure the toggle reflects current state when modal opens
1093
+ $('#settingsModal').on('shown.bs.modal', function () {
1094
+ $('#toggleViewGrid').prop('checked', viewGridEnabled);
1095
+ });
1096
+
1097
+ // Toggle handler
1098
+ $(document).on('change', '#toggleViewGrid', function () {
1099
+ viewGridEnabled = $(this).is(':checked');
1100
+ updateGridVisibility();
1101
+ });
1102
+
1103
+ $(window).on('resize', function () {
1104
+ if (viewGridEnabled) {
1105
+ drawGridOverlay();
1106
+ }
1107
+ });
1108
+ // Redraw the grid whenever the image finishes loading / changes
1109
+ $('#uploadedImage').on('load', function () {
1110
+ if (viewGridEnabled) drawGridOverlay();
1111
+ updateGridVisibility(); // positions + draws
1112
+ const hi = document.getElementById('gridHighlightOverlay');
1113
+ if (hi) { hi.style.display = 'none'; }
1114
+ });
1115
+
1116
+
1117
+ function getGridCellFromClick(event) {
1118
+ const img = document.getElementById('uploadedImage');
1119
+ if (!img || img.classList.contains('d-none') || !img.src) return null;
1120
+
1121
+ const rect = img.getBoundingClientRect();
1122
+ const x = event.clientX;
1123
+ const y = event.clientY;
1124
+
1125
+ if (x < rect.left || x > rect.right || y < rect.top || y > rect.bottom) {
1126
+ return null; // clicked outside visible image bounds
1127
+ }
1128
+
1129
+ const dx = (x - rect.left) / rect.width; // 0..1
1130
+ const dy = (y - rect.top) / rect.height; // 0..1
1131
+
1132
+ let col = Math.floor(dx * GRID_COLS);
1133
+ let row = Math.floor(dy * GRID_ROWS);
1134
+
1135
+ // Clamp just in case of boundary rounding
1136
+ col = Math.max(0, Math.min(GRID_COLS - 1, col));
1137
+ row = Math.max(0, Math.min(GRID_ROWS - 1, row));
1138
+
1139
+ return { row, col };
1140
+ }
1141
+
1142
+ $('#uploadedImageContainer').on('click', function (e) {
1143
+ // Ignore if cropping in progress
1144
+ if (typeof isCropping !== 'undefined' && isCropping) return;
1145
+
1146
+ if (!runId) {
1147
+ logWorkingMessage('No run active yet. Upload/select an image first.', 'text-danger');
1148
+ return;
1149
+ }
1150
+
1151
+ const cell = getGridCellFromClick(e);
1152
+ if (!cell) return;
1153
+
1154
+ const { row, col } = cell;
1155
+
1156
+ // NEW: spatial feedback
1157
+ showCellHighlight(row, col);
1158
+
1159
+ logWorkingMessage(`Cell click → row ${row}, col ${col}. Requesting /cell-sim...`, 'text-white');
1160
+
1161
+ const params = new URLSearchParams({
1162
+ runId: runId,
1163
+ row: String(row),
1164
+ col: String(col),
1165
+ K: String(CELL_SIM_K)
1166
+ });
1167
+
1168
+ fetch(`${API_BASE_URL}/cell-sim?${params.toString()}`)
1169
+ .then(res => res.json())
1170
+ .then(data => {
1171
+ logWorkingMessage('Cell similarities received.', 'text-white');
1172
+ display_sentences(data);
1173
+ })
1174
+ .catch(err => {
1175
+ console.error('cell-sim error:', err);
1176
+ logWorkingMessage('Error fetching cell similarities.', 'text-danger');
1177
+ });
1178
+ });
1179
+
1180
+
1181
+ /**
1182
+ * Briefly highlight a specific grid cell on the visible image.
1183
+ * @param {number} row - 0..GRID_ROWS-1
1184
+ * @param {number} col - 0..GRID_COLS-1
1185
+ */
1186
+ function showCellHighlight(row, col) {
1187
+ const container = document.getElementById('uploadedImageContainer');
1188
+ const img = document.getElementById('uploadedImage');
1189
+ const hi = document.getElementById('gridHighlightOverlay');
1190
+ if (!container || !img || !hi) return;
1191
+ if (img.classList.contains('d-none') || !img.src) return;
1192
+
1193
+ // Position relative to container, aligned to visible image rect.
1194
+ const containerRect = container.getBoundingClientRect();
1195
+ const imageRect = img.getBoundingClientRect();
1196
+
1197
+ const cellW = imageRect.width / GRID_COLS;
1198
+ const cellH = imageRect.height / GRID_ROWS;
1199
+
1200
+ const left = (imageRect.left - containerRect.left) + col * cellW;
1201
+ const top = (imageRect.top - containerRect.top) + row * cellH;
1202
+
1203
+ // Style as an outline box with subtle fill, and fade-out transition.
1204
+ hi.style.left = `${left}px`;
1205
+ hi.style.top = `${top}px`;
1206
+ hi.style.width = `${cellW}px`;
1207
+ hi.style.height = `${cellH}px`;
1208
+ hi.style.border = '2px solid rgba(255, 255, 0, 0.9)';
1209
+ hi.style.boxShadow = '0 0 0 1px rgba(0,0,0,0.25) inset';
1210
+ hi.style.background = 'rgba(255, 255, 0, 0.10)';
1211
+ hi.style.opacity = '1';
1212
+ hi.style.transition = 'opacity 200ms ease';
1213
+ hi.style.display = 'block';
1214
+
1215
+ // Clear any previous timer, then fade out and hide.
1216
+ if (cellHighlightTimeout) clearTimeout(cellHighlightTimeout);
1217
+ cellHighlightTimeout = setTimeout(() => {
1218
+ hi.style.opacity = '0';
1219
+ setTimeout(() => {
1220
+ hi.style.display = 'none';
1221
+ }, 210);
1222
+ }, 600);
1223
+ }
1224
+
1225
+ // ──────────────────────────────────────────────────────────────────────────────
1226
+ // NEW helper : use a gallery image as the next run
1227
+ // ──────────────────────────────────────────────────────────────────────────────
1228
+ function loadImageAndRun(imgSrc) {
1229
+ // close the modal/backdrop if still open
1230
+ $('#workOverlayBackdrop, #workDetailsModal').remove();
1231
+
1232
+ // REMOVED: Save current image to history before loading new one
1233
+ // REMOVED: saveCurrentImageToHistory();
1234
+
1235
+ // show the chosen artwork in the main image slot
1236
+ const $img = $('#uploadedImage')
1237
+ .attr('src', imgSrc)
1238
+ .attr('crossorigin', 'anonymous') // allow canvas use
1239
+ .removeClass('d-none');
1240
+
1241
+ // hide the upload card / example images just like other entry paths
1242
+ $('#uploadTrigger').addClass('d-none');
1243
+ $('.card:has(#uploadTrigger)').addClass('d-none');
1244
+ $('#exampleContainer').addClass('d-none');
1245
+
1246
+ // UI bits the normal flow expects
1247
+ $('#workingOverlay').removeClass('d-none');
1248
+ $('#imageTools').removeClass('d-none');
1249
+
1250
+ // make sure we fetch a presign only after the image data is ready
1251
+ $img.one('load', () => fetchPresign());
1252
+ }
1253
+
1254
+ // ============================================================================
1255
+ // Heatmap functionality
1256
+ // ============================================================================
1257
+
1258
+ /**
1259
+ * Request heatmap generation for a sentence and display overlay
1260
+ * @param {string} sentence - The sentence text to visualize
1261
+ */
1262
+ function requestHeatmap(sentence) {
1263
+ if (!runId) {
1264
+ console.error('No active run for heatmap generation');
1265
+ return;
1266
+ }
1267
+
1268
+ // Warn if sentence is very long (might be truncated)
1269
+ if (sentence.length > 300) {
1270
+ console.warn('Long sentence will be truncated for heatmap generation');
1271
+ }
1272
+
1273
+ // Show loading indicator
1274
+ showHeatmapLoading();
1275
+
1276
+ fetch(`${API_BASE_URL}/heatmap`, {
1277
+ method: 'POST',
1278
+ headers: {
1279
+ 'Content-Type': 'application/json'
1280
+ },
1281
+ body: JSON.stringify({
1282
+ runId: runId,
1283
+ sentence: sentence,
1284
+ layerIdx: -1 // Use last layer by default
1285
+ })
1286
+ })
1287
+ .then(res => res.json())
1288
+ .then(data => {
1289
+ if (data.dataUrl) {
1290
+ displayHeatmapOverlay(data.dataUrl);
1291
+ } else {
1292
+ console.error('No heatmap data received');
1293
+ hideHeatmapOverlay();
1294
+ }
1295
+ })
1296
+ .catch(err => {
1297
+ console.error('Heatmap generation error:', err);
1298
+ hideHeatmapOverlay();
1299
+ });
1300
+ }
1301
+
1302
+ /**
1303
+ * Display heatmap overlay on top of current image
1304
+ * @param {string} dataUrl - Base64 encoded image data URL
1305
+ */
1306
+ function displayHeatmapOverlay(dataUrl) {
1307
+ // Remove any existing heatmap overlay
1308
+ $('#heatmapOverlay').remove();
1309
+
1310
+ const container = $('#uploadedImageContainer');
1311
+ const img = $('#uploadedImage');
1312
+
1313
+ // Create heatmap overlay matching image position
1314
+ const heatmapOverlay = $(`
1315
+ <div id="heatmapOverlay" class="position-absolute" style="z-index: 20;">
1316
+ <img src="${dataUrl}"
1317
+ style="width: 100%; height: 100%; object-fit: contain;"
1318
+ class="heatmap-image" />
1319
+ <button class="btn btn-sm btn-dark position-absolute top-0 end-0 m-2"
1320
+ id="closeHeatmapBtn"
1321
+ title="Close heatmap">
1322
+ <i class="bi bi-x-lg"></i>
1323
+ </button>
1324
+ </div>
1325
+ `);
1326
+
1327
+ // Position overlay to match visible image
1328
+ const containerRect = container[0].getBoundingClientRect();
1329
+ const imageRect = img[0].getBoundingClientRect();
1330
+
1331
+ heatmapOverlay.css({
1332
+ left: imageRect.left - containerRect.left,
1333
+ top: imageRect.top - containerRect.top,
1334
+ width: imageRect.width,
1335
+ height: imageRect.height
1336
+ });
1337
+
1338
+ container.append(heatmapOverlay);
1339
+
1340
+ // Close handlers
1341
+ $('#closeHeatmapBtn, #heatmapOverlay').on('click', function(e) {
1342
+ if (e.target === this || $(e.target).closest('#closeHeatmapBtn').length) {
1343
+ hideHeatmapOverlay();
1344
+ }
1345
+ });
1346
+ }
1347
+
1348
+ /**
1349
+ * Show loading indicator while heatmap is being generated
1350
+ */
1351
+ function showHeatmapLoading() {
1352
+ $('#heatmapOverlay').remove();
1353
+
1354
+ const container = $('#uploadedImageContainer');
1355
+ const img = $('#uploadedImage');
1356
+
1357
+ const loadingOverlay = $(`
1358
+ <div id="heatmapOverlay" class="position-absolute d-flex align-items-center justify-content-center"
1359
+ style="z-index: 20; background: rgba(0, 0, 0, 0.5);">
1360
+ <div class="text-white text-center">
1361
+ <i class="bi bi-arrow-repeat spin" style="font-size: 2rem;"></i>
1362
+ <p class="mt-2">Generating heatmap...</p>
1363
+ </div>
1364
+ </div>
1365
+ `);
1366
+
1367
+ // Position to match image
1368
+ const containerRect = container[0].getBoundingClientRect();
1369
+ const imageRect = img[0].getBoundingClientRect();
1370
+
1371
+ loadingOverlay.css({
1372
+ left: imageRect.left - containerRect.left,
1373
+ top: imageRect.top - containerRect.top,
1374
+ width: imageRect.width,
1375
+ height: imageRect.height
1376
+ });
1377
+
1378
+ container.append(loadingOverlay);
1379
+ }
1380
+
1381
+ /**
1382
+ * Remove heatmap overlay
1383
+ */
1384
+ function hideHeatmapOverlay() {
1385
+ $('#heatmapOverlay').fadeOut(200, function() {
1386
+ $(this).remove();
1387
+ });
1388
+ }
1389
+
1390
+ // Update heatmap position when window resizes
1391
+ $(window).on('resize', function() {
1392
+ const heatmapOverlay = $('#heatmapOverlay');
1393
+ if (heatmapOverlay.length && !heatmapOverlay.find('.spin').length) {
1394
+ // Reposition existing heatmap
1395
+ const container = $('#uploadedImageContainer');
1396
+ const img = $('#uploadedImage');
1397
+ const containerRect = container[0].getBoundingClientRect();
1398
+ const imageRect = img[0].getBoundingClientRect();
1399
+
1400
+ heatmapOverlay.css({
1401
+ left: imageRect.left - containerRect.left,
1402
+ top: imageRect.top - containerRect.top,
1403
+ width: imageRect.width,
1404
+ height: imageRect.height
1405
+ });
1406
+ }
1407
+ });
1408
+
1409
+ // --- Begin Landing Content Functionality ---
1410
+ // Hide landing content (upload card + example images)
1411
+ function hideLandingContent() {
1412
+ $('#landingContent').addClass('d-none');
1413
+ }
1414
+
1415
+ // Show landing content (upload card + example images)
1416
+ function showLandingContent() {
1417
+ $('#landingContent').removeClass('d-none');
1418
+ $('#exampleContainer').removeClass('d-none');
1419
+ $('.card:has(#uploadTrigger)').removeClass('d-none');
1420
+ }
1421
+ // --- End Landing Content Functionality ---
frontend/js/jquery.min.js ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ /*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */
2
+ !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}function fe(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}ce.fn=ce.prototype={jquery:t,constructor:ce,length:0,toArray:function(){return ae.call(this)},get:function(e){return null==e?ae.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=ce.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return ce.each(this,e)},map:function(n){return this.pushStack(ce.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(ae.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(ce.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(ce.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:s,sort:oe.sort,splice:oe.splice},ce.extend=ce.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||v(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(ce.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||ce.isPlainObject(n)?n:{},i=!1,a[t]=ce.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},ce.extend({expando:"jQuery"+(t+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==i.call(e))&&(!(t=r(e))||"function"==typeof(n=ue.call(t,"constructor")&&t.constructor)&&o.call(n)===a)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){m(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(c(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},text:function(e){var t,n="",r=0,i=e.nodeType;if(!i)while(t=e[r++])n+=ce.text(t);return 1===i||11===i?e.textContent:9===i?e.documentElement.textContent:3===i||4===i?e.nodeValue:n},makeArray:function(e,t){var n=t||[];return null!=e&&(c(Object(e))?ce.merge(n,"string"==typeof e?[e]:e):s.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:se.call(t,e,n)},isXMLDoc:function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!l.test(t||n&&n.nodeName||"HTML")},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(c(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:le}),"function"==typeof Symbol&&(ce.fn[Symbol.iterator]=oe[Symbol.iterator]),ce.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var pe=oe.pop,de=oe.sort,he=oe.splice,ge="[\\x20\\t\\r\\n\\f]",ve=new RegExp("^"+ge+"+|((?:^|[^\\\\])(?:\\\\.)*)"+ge+"+$","g");ce.contains=function(e,t){var n=t&&t.parentNode;return e===n||!(!n||1!==n.nodeType||!(e.contains?e.contains(n):e.compareDocumentPosition&&16&e.compareDocumentPosition(n)))};var f=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;function p(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e}ce.escapeSelector=function(e){return(e+"").replace(f,p)};var ye=C,me=s;!function(){var e,b,w,o,a,T,r,C,d,i,k=me,S=ce.expando,E=0,n=0,s=W(),c=W(),u=W(),h=W(),l=function(e,t){return e===t&&(a=!0),0},f="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",t="(?:\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",p="\\["+ge+"*("+t+")(?:"+ge+"*([*^$|!~]?=)"+ge+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+t+"))|)"+ge+"*\\]",g=":("+t+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+p+")*)|.*)\\)|)",v=new RegExp(ge+"+","g"),y=new RegExp("^"+ge+"*,"+ge+"*"),m=new RegExp("^"+ge+"*([>+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="<a id='"+S+"' href='' disabled='disabled'></a><select id='"+S+"-\r\\' disabled='disabled'><option selected=''></option></select>",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0<I(t,T,null,[e]).length},I.contains=function(e,t){return(e.ownerDocument||e)!=T&&V(e),ce.contains(e,t)},I.attr=function(e,t){(e.ownerDocument||e)!=T&&V(e);var n=b.attrHandle[t.toLowerCase()],r=n&&ue.call(b.attrHandle,t.toLowerCase())?n(e,t,!C):void 0;return void 0!==r?r:e.getAttribute(t)},I.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},ce.uniqueSort=function(e){var t,n=[],r=0,i=0;if(a=!le.sortStable,o=!le.sortStable&&ae.call(e,0),de.call(e,l),a){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)he.call(e,n[r],1)}return o=null,e},ce.fn.uniqueSort=function(){return this.pushStack(ce.uniqueSort(ae.apply(this)))},(b=ce.expr={cacheLength:50,createPseudo:F,match:D,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(v," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(d,e,t,h,g){var v="nth"!==d.slice(0,3),y="last"!==d.slice(-4),m="of-type"===e;return 1===h&&0===g?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u=v!==y?"nextSibling":"previousSibling",l=e.parentNode,c=m&&e.nodeName.toLowerCase(),f=!n&&!m,p=!1;if(l){if(v){while(u){o=e;while(o=o[u])if(m?fe(o,c):1===o.nodeType)return!1;s=u="only"===d&&!s&&"nextSibling"}return!0}if(s=[y?l.firstChild:l.lastChild],y&&f){p=(a=(r=(i=l[S]||(l[S]={}))[d]||[])[0]===E&&r[1])&&r[2],o=a&&l.childNodes[a];while(o=++a&&o&&o[u]||(p=a=0)||s.pop())if(1===o.nodeType&&++p&&o===e){i[d]=[E,a,p];break}}else if(f&&(p=a=(r=(i=e[S]||(e[S]={}))[d]||[])[0]===E&&r[1]),!1===p)while(o=++a&&o&&o[u]||(p=a=0)||s.pop())if((m?fe(o,c):1===o.nodeType)&&++p&&(f&&((i=o[S]||(o[S]={}))[d]=[E,p]),o===e))break;return(p-=g)===h||p%h==0&&0<=p/h}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||I.error("unsupported pseudo: "+e);return a[S]?a(o):1<a.length?(t=[e,e,"",o],b.setFilters.hasOwnProperty(e.toLowerCase())?F(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=se.call(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:F(function(e){var r=[],i=[],s=ne(e.replace(ve,"$1"));return s[S]?F(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:F(function(t){return function(e){return 0<I(t,e).length}}),contains:F(function(t){return t=t.replace(O,P),function(e){return-1<(e.textContent||ce.text(e)).indexOf(t)}}),lang:F(function(n){return A.test(n||"")||I.error("unsupported lang: "+n),n=n.replace(O,P).toLowerCase(),function(e){var t;do{if(t=C?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=ie.location&&ie.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===r},focus:function(e){return e===function(){try{return T.activeElement}catch(e){}}()&&T.hasFocus()&&!!(e.type||e.href||~e.tabIndex)},enabled:z(!1),disabled:z(!0),checked:function(e){return fe(e,"input")&&!!e.checked||fe(e,"option")&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return q.test(e.nodeName)},input:function(e){return N.test(e.nodeName)},button:function(e){return fe(e,"input")&&"button"===e.type||fe(e,"button")},text:function(e){var t;return fe(e,"input")&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:X(function(){return[0]}),last:X(function(e,t){return[t-1]}),eq:X(function(e,t,n){return[n<0?n+t:n]}),even:X(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:X(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:X(function(e,t,n){var r;for(r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:X(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=B(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=_(e);function G(){}function Y(e,t){var n,r,i,o,a,s,u,l=c[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=y.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=m.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace(ve," ")}),a=a.slice(n.length)),b.filter)!(r=D[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?I.error(e):c(e,s).slice(0)}function Q(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function J(a,e,t){var s=e.dir,u=e.next,l=u||s,c=t&&"parentNode"===l,f=n++;return e.first?function(e,t,n){while(e=e[s])if(1===e.nodeType||c)return a(e,t,n);return!1}:function(e,t,n){var r,i,o=[E,f];if(n){while(e=e[s])if((1===e.nodeType||c)&&a(e,t,n))return!0}else while(e=e[s])if(1===e.nodeType||c)if(i=e[S]||(e[S]={}),u&&fe(e,u))e=e[s]||e;else{if((r=i[l])&&r[0]===E&&r[1]===f)return o[2]=r[2];if((i[l]=o)[2]=a(e,t,n))return!0}return!1}}function K(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Z(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function ee(d,h,g,v,y,e){return v&&!v[S]&&(v=ee(v)),y&&!y[S]&&(y=ee(y,e)),F(function(e,t,n,r){var i,o,a,s,u=[],l=[],c=t.length,f=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)I(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),p=!d||!e&&h?f:Z(f,u,d,n,r);if(g?g(p,s=y||(e?d:c||v)?[]:t,n,r):s=p,v){i=Z(s,l),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(s[l[o]]=!(p[l[o]]=a))}if(e){if(y||d){if(y){i=[],o=s.length;while(o--)(a=s[o])&&i.push(p[o]=a);y(null,s=[],i,r)}o=s.length;while(o--)(a=s[o])&&-1<(i=y?se.call(e,a):u[o])&&(e[i]=!(t[i]=a))}}else s=Z(s===t?s.splice(c,s.length):s),y?y(null,t,s,r):k.apply(t,s)})}function te(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[" "],s=o?1:0,u=J(function(e){return e===i},a,!0),l=J(function(e){return-1<se.call(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!=w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[J(K(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return ee(1<s&&K(c),1<s&&Q(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(ve,"$1"),t,s<n&&te(e.slice(s,n)),n<r&&te(e=e.slice(n)),n<r&&Q(e))}c.push(t)}return K(c)}function ne(e,t){var n,v,y,m,x,r,i=[],o=[],a=u[e+" "];if(!a){t||(t=Y(e)),n=t.length;while(n--)(a=te(t[n]))[S]?i.push(a):o.push(a);(a=u(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG("*",i),h=E+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==T||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==T||(V(o),n=!C);while(s=v[a++])if(s(o,t||T,n)){k.call(r,o);break}i&&(E=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=pe.call(r));f=Z(f)}k.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&ce.uniqueSort(r)}return i&&(E=h,w=p),c},m?F(r):r))).selector=e}return a}function re(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&Y(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&C&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(O,P),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=D.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(O,P),H.test(o[0].type)&&U(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&Q(o)))return k.apply(n,r),n;break}}}return(l||ne(e,c))(r,t,!C,n,!t||H.test(e)&&U(t.parentNode)||t),n}G.prototype=b.filters=b.pseudos,b.setFilters=new G,le.sortStable=S.split("").sort(l).join("")===S,V(),le.sortDetached=$(function(e){return 1&e.compareDocumentPosition(T.createElement("fieldset"))}),ce.find=I,ce.expr[":"]=ce.expr.pseudos,ce.unique=ce.uniqueSort,I.compile=ne,I.select=re,I.setDocument=V,I.tokenize=Y,I.escape=ce.escapeSelector,I.getText=ce.text,I.isXML=ce.isXMLDoc,I.selectors=ce.expr,I.support=ce.support,I.uniqueSort=ce.uniqueSort}();var d=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&ce(e).is(n))break;r.push(e)}return r},h=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},b=ce.expr.match.needsContext,w=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1<se.call(n,e)!==r}):ce.filter(n,e,r)}ce.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?ce.find.matchesSelector(r,e)?[r]:[]:ce.find.matches(e,ce.grep(t,function(e){return 1===e.nodeType}))},ce.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(ce(e).filter(function(){for(t=0;t<r;t++)if(ce.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)ce.find(e,i[t],n);return 1<r?ce.uniqueSort(n):n},filter:function(e){return this.pushStack(T(this,e||[],!1))},not:function(e){return this.pushStack(T(this,e||[],!0))},is:function(e){return!!T(this,"string"==typeof e&&b.test(e)?ce(e):e||[],!1).length}});var k,S=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(ce.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&ce(e);if(!b.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&ce.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?ce.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?se.call(ce(e),this[0]):se.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(ce.uniqueSort(ce.merge(this.get(),ce(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),ce.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return d(e,"parentNode")},parentsUntil:function(e,t,n){return d(e,"parentNode",n)},next:function(e){return A(e,"nextSibling")},prev:function(e){return A(e,"previousSibling")},nextAll:function(e){return d(e,"nextSibling")},prevAll:function(e){return d(e,"previousSibling")},nextUntil:function(e,t,n){return d(e,"nextSibling",n)},prevUntil:function(e,t,n){return d(e,"previousSibling",n)},siblings:function(e){return h((e.parentNode||{}).firstChild,e)},children:function(e){return h(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(fe(e,"template")&&(e=e.content||e),ce.merge([],e.childNodes))}},function(r,i){ce.fn[r]=function(e,t){var n=ce.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=ce.filter(t,n)),1<this.length&&(j[r]||ce.uniqueSort(n),E.test(r)&&n.reverse()),this.pushStack(n)}});var D=/[^\x20\t\r\n\f]+/g;function N(e){return e}function q(e){throw e}function L(e,t,n,r){var i;try{e&&v(i=e.promise)?i.call(e).done(t).fail(n):e&&v(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}ce.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},ce.each(e.match(D)||[],function(e,t){n[t]=!0}),n):ce.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){ce.each(e,function(e,t){v(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==x(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return ce.each(arguments,function(e,t){var n;while(-1<(n=ce.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<ce.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},ce.extend({Deferred:function(e){var o=[["notify","progress",ce.Callbacks("memory"),ce.Callbacks("memory"),2],["resolve","done",ce.Callbacks("once memory"),ce.Callbacks("once memory"),0,"resolved"],["reject","fail",ce.Callbacks("once memory"),ce.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return ce.Deferred(function(r){ce.each(o,function(e,t){var n=v(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&v(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,v(t)?s?t.call(e,l(u,o,N,s),l(u,o,q,s)):(u++,t.call(e,l(u,o,N,s),l(u,o,q,s),l(u,o,N,o.notifyWith))):(a!==N&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){ce.Deferred.exceptionHook&&ce.Deferred.exceptionHook(e,t.error),u<=i+1&&(a!==q&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(ce.Deferred.getErrorHook?t.error=ce.Deferred.getErrorHook():ce.Deferred.getStackHook&&(t.error=ce.Deferred.getStackHook()),ie.setTimeout(t))}}return ce.Deferred(function(e){o[0][3].add(l(0,e,v(r)?r:N,e.notifyWith)),o[1][3].add(l(0,e,v(t)?t:N)),o[2][3].add(l(0,e,v(n)?n:q))}).promise()},promise:function(e){return null!=e?ce.extend(e,a):a}},s={};return ce.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=ae.call(arguments),o=ce.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?ae.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(L(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||v(i[t]&&i[t].then)))return o.then();while(t--)L(i[t],a(t),o.reject);return o.promise()}});var H=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;ce.Deferred.exceptionHook=function(e,t){ie.console&&ie.console.warn&&e&&H.test(e.name)&&ie.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},ce.readyException=function(e){ie.setTimeout(function(){throw e})};var O=ce.Deferred();function P(){C.removeEventListener("DOMContentLoaded",P),ie.removeEventListener("load",P),ce.ready()}ce.fn.ready=function(e){return O.then(e)["catch"](function(e){ce.readyException(e)}),this},ce.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--ce.readyWait:ce.isReady)||(ce.isReady=!0)!==e&&0<--ce.readyWait||O.resolveWith(C,[ce])}}),ce.ready.then=O.then,"complete"===C.readyState||"loading"!==C.readyState&&!C.documentElement.doScroll?ie.setTimeout(ce.ready):(C.addEventListener("DOMContentLoaded",P),ie.addEventListener("load",P));var M=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===x(n))for(s in i=!0,n)M(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,v(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(ce(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},R=/^-ms-/,I=/-([a-z])/g;function W(e,t){return t.toUpperCase()}function F(e){return e.replace(R,"ms-").replace(I,W)}var $=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function B(){this.expando=ce.expando+B.uid++}B.uid=1,B.prototype={cache:function(e){var t=e[this.expando];return t||(t={},$(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[F(t)]=n;else for(r in t)i[F(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][F(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(F):(t=F(t))in r?[t]:t.match(D)||[]).length;while(n--)delete r[t[n]]}(void 0===t||ce.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!ce.isEmptyObject(t)}};var _=new B,z=new B,X=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,U=/[A-Z]/g;function V(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(U,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:X.test(i)?JSON.parse(i):i)}catch(e){}z.set(e,t,n)}else n=void 0;return n}ce.extend({hasData:function(e){return z.hasData(e)||_.hasData(e)},data:function(e,t,n){return z.access(e,t,n)},removeData:function(e,t){z.remove(e,t)},_data:function(e,t,n){return _.access(e,t,n)},_removeData:function(e,t){_.remove(e,t)}}),ce.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=z.get(o),1===o.nodeType&&!_.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=F(r.slice(5)),V(o,r,i[r]));_.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){z.set(this,n)}):M(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=z.get(o,n))?t:void 0!==(t=V(o,n))?t:void 0;this.each(function(){z.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){z.remove(this,e)})}}),ce.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=_.get(e,t),n&&(!r||Array.isArray(n)?r=_.access(e,t,ce.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=ce.queue(e,t),r=n.length,i=n.shift(),o=ce._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){ce.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return _.get(e,n)||_.access(e,n,{empty:ce.Callbacks("once memory").add(function(){_.remove(e,[t+"queue",n])})})}}),ce.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?ce.queue(this[0],t):void 0===n?this:this.each(function(){var e=ce.queue(this,t,n);ce._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&ce.dequeue(this,t)})},dequeue:function(e){return this.each(function(){ce.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=ce.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=_.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var G=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,Y=new RegExp("^(?:([+-])=|)("+G+")([a-z%]*)$","i"),Q=["Top","Right","Bottom","Left"],J=C.documentElement,K=function(e){return ce.contains(e.ownerDocument,e)},Z={composed:!0};J.getRootNode&&(K=function(e){return ce.contains(e.ownerDocument,e)||e.getRootNode(Z)===e.ownerDocument});var ee=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&K(e)&&"none"===ce.css(e,"display")};function te(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return ce.css(e,t,"")},u=s(),l=n&&n[3]||(ce.cssNumber[t]?"":"px"),c=e.nodeType&&(ce.cssNumber[t]||"px"!==l&&+u)&&Y.exec(ce.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)ce.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,ce.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ne={};function re(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=_.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ee(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ne[s])||(o=a.body.appendChild(a.createElement(s)),u=ce.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),ne[s]=u)))):"none"!==n&&(l[c]="none",_.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}ce.fn.extend({show:function(){return re(this,!0)},hide:function(){return re(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ee(this)?ce(this).show():ce(this).hide()})}});var xe,be,we=/^(?:checkbox|radio)$/i,Te=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="<textarea>x</textarea>",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="<option></option>",le.option=!!xe.lastChild;var ke={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n<r;n++)_.set(e[n],"globalEval",!t||_.get(t[n],"globalEval"))}ke.tbody=ke.tfoot=ke.colgroup=ke.caption=ke.thead,ke.th=ke.td,le.option||(ke.optgroup=ke.option=[1,"<select multiple='multiple'>","</select>"]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if("object"===x(o))ce.merge(p,o.nodeType?[o]:o);else if(je.test(o)){a=a||f.appendChild(t.createElement("div")),s=(Te.exec(o)||["",""])[1].toLowerCase(),u=ke[s]||ke._default,a.innerHTML=u[1]+ce.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;ce.merge(p,a.childNodes),(a=f.firstChild).textContent=""}else p.push(t.createTextNode(o));f.textContent="",d=0;while(o=p[d++])if(r&&-1<ce.inArray(o,r))i&&i.push(o);else if(l=K(o),a=Se(f.appendChild(o),"script"),l&&Ee(a),n){c=0;while(o=a[c++])Ce.test(o.type||"")&&n.push(o)}return f}var De=/^([^.]*)(?:\.(.+)|)/;function Ne(){return!0}function qe(){return!1}function Le(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Le(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=qe;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return ce().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=ce.guid++)),e.each(function(){ce.event.add(this,t,i,r,n)})}function He(e,r,t){t?(_.set(e,r,!1),ce.event.add(e,r,{namespace:!1,handler:function(e){var t,n=_.get(this,r);if(1&e.isTrigger&&this[r]){if(n)(ce.event.special[r]||{}).delegateType&&e.stopPropagation();else if(n=ae.call(arguments),_.set(this,r,n),this[r](),t=_.get(this,r),_.set(this,r,!1),n!==t)return e.stopImmediatePropagation(),e.preventDefault(),t}else n&&(_.set(this,r,ce.event.trigger(n[0],n.slice(1),this)),e.stopPropagation(),e.isImmediatePropagationStopped=Ne)}})):void 0===_.get(e,r)&&ce.event.add(e,r,Ne)}ce.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=_.get(t);if($(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&ce.find.matchesSelector(J,i),n.guid||(n.guid=ce.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof ce&&ce.event.triggered!==e.type?ce.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(D)||[""]).length;while(l--)d=g=(s=De.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=ce.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=ce.event.special[d]||{},c=ce.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&ce.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),ce.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=_.hasData(e)&&_.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(D)||[""]).length;while(l--)if(d=g=(s=De.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),d){f=ce.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||ce.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)ce.event.remove(e,d+t[l],n,r,!0);ce.isEmptyObject(u)&&_.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=ce.event.fix(e),l=(_.get(this,"events")||Object.create(null))[u.type]||[],c=ce.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=ce.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((ce.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<ce(i,this).index(l):ce.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(ce.Event.prototype,t,{enumerable:!0,configurable:!0,get:v(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[ce.expando]?e:new ce.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return we.test(t.type)&&t.click&&fe(t,"input")&&He(t,"click",!0),!1},trigger:function(e){var t=this||e;return we.test(t.type)&&t.click&&fe(t,"input")&&He(t,"click"),!0},_default:function(e){var t=e.target;return we.test(t.type)&&t.click&&fe(t,"input")&&_.get(t,"click")||fe(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},ce.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},ce.Event=function(e,t){if(!(this instanceof ce.Event))return new ce.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ne:qe,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&ce.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[ce.expando]=!0},ce.Event.prototype={constructor:ce.Event,isDefaultPrevented:qe,isPropagationStopped:qe,isImmediatePropagationStopped:qe,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ne,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ne,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ne,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},ce.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:!0},ce.event.addProp),ce.each({focus:"focusin",blur:"focusout"},function(r,i){function o(e){if(C.documentMode){var t=_.get(this,"handle"),n=ce.event.fix(e);n.type="focusin"===e.type?"focus":"blur",n.isSimulated=!0,t(e),n.target===n.currentTarget&&t(n)}else ce.event.simulate(i,e.target,ce.event.fix(e))}ce.event.special[r]={setup:function(){var e;if(He(this,r,!0),!C.documentMode)return!1;(e=_.get(this,i))||this.addEventListener(i,o),_.set(this,i,(e||0)+1)},trigger:function(){return He(this,r),!0},teardown:function(){var e;if(!C.documentMode)return!1;(e=_.get(this,i)-1)?_.set(this,i,e):(this.removeEventListener(i,o),_.remove(this,i))},_default:function(e){return _.get(e.target,r)},delegateType:i},ce.event.special[i]={setup:function(){var e=this.ownerDocument||this.document||this,t=C.documentMode?this:e,n=_.get(t,i);n||(C.documentMode?this.addEventListener(i,o):e.addEventListener(r,o,!0)),_.set(t,i,(n||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=C.documentMode?this:e,n=_.get(t,i)-1;n?_.set(t,i,n):(C.documentMode?this.removeEventListener(i,o):e.removeEventListener(r,o,!0),_.remove(t,i))}}}),ce.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){ce.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||ce.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),ce.fn.extend({on:function(e,t,n,r){return Le(this,e,t,n,r)},one:function(e,t,n,r){return Le(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,ce(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=qe),this.each(function(){ce.event.remove(this,e,n,t)})}});var Oe=/<script|<style|<link/i,Pe=/checked\s*(?:[^=]|=\s*.checked.)/i,Me=/^\s*<!\[CDATA\[|\]\]>\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)ce.event.add(t,i,s[i][n]);z.hasData(e)&&(o=z.access(e),a=ce.extend({},o),z.set(t,a))}}function $e(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=v(d);if(h||1<f&&"string"==typeof d&&!le.checkClone&&Pe.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),$e(t,r,i,o)});if(f&&(t=(e=Ae(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=ce.map(Se(e,"script"),Ie)).length;c<f;c++)u=e,c!==p&&(u=ce.clone(u,!0,!0),s&&ce.merge(a,Se(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,ce.map(a,We),c=0;c<s;c++)u=a[c],Ce.test(u.type||"")&&!_.access(u,"globalEval")&&ce.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?ce._evalUrl&&!u.noModule&&ce._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):m(u.textContent.replace(Me,""),u,l))}return n}function Be(e,t,n){for(var r,i=t?ce.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||ce.cleanData(Se(r)),r.parentNode&&(n&&K(r)&&Ee(Se(r,"script")),r.parentNode.removeChild(r));return e}ce.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=K(e);if(!(le.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||ce.isXMLDoc(e)))for(a=Se(c),r=0,i=(o=Se(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&we.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||Se(e),a=a||Se(c),r=0,i=o.length;r<i;r++)Fe(o[r],a[r]);else Fe(e,c);return 0<(a=Se(c,"script")).length&&Ee(a,!f&&Se(e,"script")),c},cleanData:function(e){for(var t,n,r,i=ce.event.special,o=0;void 0!==(n=e[o]);o++)if($(n)){if(t=n[_.expando]){if(t.events)for(r in t.events)i[r]?ce.event.remove(n,r):ce.removeEvent(n,r,t.handle);n[_.expando]=void 0}n[z.expando]&&(n[z.expando]=void 0)}}}),ce.fn.extend({detach:function(e){return Be(this,e,!0)},remove:function(e){return Be(this,e)},text:function(e){return M(this,function(e){return void 0===e?ce.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return $e(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Re(this,e).appendChild(e)})},prepend:function(){return $e(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Re(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return $e(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return $e(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(ce.cleanData(Se(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return ce.clone(this,e,t)})},html:function(e){return M(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Oe.test(e)&&!ke[(Te.exec(e)||["",""])[1].toLowerCase()]){e=ce.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(ce.cleanData(Se(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return $e(this,arguments,function(e){var t=this.parentNode;ce.inArray(this,n)<0&&(ce.cleanData(Se(this)),t&&t.replaceChild(e,this))},n)}}),ce.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){ce.fn[e]=function(e){for(var t,n=[],r=ce(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),ce(r[o])[a](t),s.apply(n,t.get());return this.pushStack(n)}});var _e=new RegExp("^("+G+")(?!px)[a-z%]+$","i"),ze=/^--/,Xe=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=ie),t.getComputedStyle(e)},Ue=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Ve=new RegExp(Q.join("|"),"i");function Ge(e,t,n){var r,i,o,a,s=ze.test(t),u=e.style;return(n=n||Xe(e))&&(a=n.getPropertyValue(t)||n[t],s&&a&&(a=a.replace(ve,"$1")||void 0),""!==a||K(e)||(a=ce.style(e,t)),!le.pixelBoxStyles()&&_e.test(a)&&Ve.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=n.width,u.width=r,u.minWidth=i,u.maxWidth=o)),void 0!==a?a+"":a}function Ye(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",J.appendChild(u).appendChild(l);var e=ie.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),J.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=C.createElement("div"),l=C.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",le.clearCloneStyle="content-box"===l.style.backgroundClip,ce.extend(le,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=C.createElement("table"),t=C.createElement("tr"),n=C.createElement("div"),e.style.cssText="position:absolute;left:-11111px;border-collapse:separate",t.style.cssText="box-sizing:content-box;border:1px solid",t.style.height="1px",n.style.height="9px",n.style.display="block",J.appendChild(e).appendChild(t).appendChild(n),r=ie.getComputedStyle(t),a=parseInt(r.height,10)+parseInt(r.borderTopWidth,10)+parseInt(r.borderBottomWidth,10)===t.offsetHeight,J.removeChild(e)),a}}))}();var Qe=["Webkit","Moz","ms"],Je=C.createElement("div").style,Ke={};function Ze(e){var t=ce.cssProps[e]||Ke[e];return t||(e in Je?e:Ke[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=Qe.length;while(n--)if((e=Qe[n]+t)in Je)return e}(e)||e)}var et=/^(none|table(?!-c[ea]).+)/,tt={position:"absolute",visibility:"hidden",display:"block"},nt={letterSpacing:"0",fontWeight:"400"};function rt(e,t,n){var r=Y.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function it(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0,l=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(l+=ce.css(e,n+Q[a],!0,i)),r?("content"===n&&(u-=ce.css(e,"padding"+Q[a],!0,i)),"margin"!==n&&(u-=ce.css(e,"border"+Q[a]+"Width",!0,i))):(u+=ce.css(e,"padding"+Q[a],!0,i),"padding"!==n?u+=ce.css(e,"border"+Q[a]+"Width",!0,i):s+=ce.css(e,"border"+Q[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u+l}function ot(e,t,n){var r=Xe(e),i=(!le.boxSizingReliable()||n)&&"border-box"===ce.css(e,"boxSizing",!1,r),o=i,a=Ge(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(_e.test(a)){if(!n)return a;a="auto"}return(!le.boxSizingReliable()&&i||!le.reliableTrDimensions()&&fe(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===ce.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===ce.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+it(e,t,n||(i?"border":"content"),o,r,a)+"px"}function at(e,t,n,r,i){return new at.prototype.init(e,t,n,r,i)}ce.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Ge(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,aspectRatio:!0,borderImageSlice:!0,columnCount:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,scale:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeMiterlimit:!0,strokeOpacity:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=F(t),u=ze.test(t),l=e.style;if(u||(t=Ze(s)),a=ce.cssHooks[t]||ce.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=Y.exec(n))&&i[1]&&(n=te(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(ce.cssNumber[s]?"":"px")),le.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=F(t);return ze.test(t)||(t=Ze(s)),(a=ce.cssHooks[t]||ce.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=Ge(e,t,r)),"normal"===i&&t in nt&&(i=nt[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),ce.each(["height","width"],function(e,u){ce.cssHooks[u]={get:function(e,t,n){if(t)return!et.test(ce.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?ot(e,u,n):Ue(e,tt,function(){return ot(e,u,n)})},set:function(e,t,n){var r,i=Xe(e),o=!le.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===ce.css(e,"boxSizing",!1,i),s=n?it(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-it(e,u,"border",!1,i)-.5)),s&&(r=Y.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=ce.css(e,u)),rt(0,t,s)}}}),ce.cssHooks.marginLeft=Ye(le.reliableMarginLeft,function(e,t){if(t)return(parseFloat(Ge(e,"marginLeft"))||e.getBoundingClientRect().left-Ue(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),ce.each({margin:"",padding:"",border:"Width"},function(i,o){ce.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+Q[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(ce.cssHooks[i+o].set=rt)}),ce.fn.extend({css:function(e,t){return M(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Xe(e),i=t.length;a<i;a++)o[t[a]]=ce.css(e,t[a],!1,r);return o}return void 0!==n?ce.style(e,t,n):ce.css(e,t)},e,t,1<arguments.length)}}),((ce.Tween=at).prototype={constructor:at,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||ce.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(ce.cssNumber[n]?"":"px")},cur:function(){var e=at.propHooks[this.prop];return e&&e.get?e.get(this):at.propHooks._default.get(this)},run:function(e){var t,n=at.propHooks[this.prop];return this.options.duration?this.pos=t=ce.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):at.propHooks._default.set(this),this}}).init.prototype=at.prototype,(at.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=ce.css(e.elem,e.prop,""))&&"auto"!==t?t:0},set:function(e){ce.fx.step[e.prop]?ce.fx.step[e.prop](e):1!==e.elem.nodeType||!ce.cssHooks[e.prop]&&null==e.elem.style[Ze(e.prop)]?e.elem[e.prop]=e.now:ce.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=at.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},ce.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},ce.fx=at.prototype.init,ce.fx.step={};var st,ut,lt,ct,ft=/^(?:toggle|show|hide)$/,pt=/queueHooks$/;function dt(){ut&&(!1===C.hidden&&ie.requestAnimationFrame?ie.requestAnimationFrame(dt):ie.setTimeout(dt,ce.fx.interval),ce.fx.tick())}function ht(){return ie.setTimeout(function(){st=void 0}),st=Date.now()}function gt(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i["margin"+(n=Q[r])]=i["padding"+n]=e;return t&&(i.opacity=i.width=e),i}function vt(e,t,n){for(var r,i=(yt.tweeners[t]||[]).concat(yt.tweeners["*"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function yt(o,e,t){var n,a,r=0,i=yt.prefilters.length,s=ce.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=st||ht(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:ce.extend({},e),opts:ce.extend(!0,{specialEasing:{},easing:ce.easing._default},t),originalProperties:e,originalOptions:t,startTime:st||ht(),duration:t.duration,tweens:[],createTween:function(e,t){var n=ce.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=F(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=ce.cssHooks[r])&&"expand"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=yt.prefilters[r].call(l,o,c,l.opts))return v(n.stop)&&(ce._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return ce.map(c,vt,l),v(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),ce.fx.timer(ce.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}ce.Animation=ce.extend(yt,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return te(n.elem,e,Y.exec(t),n),n}]},tweener:function(e,t){v(e)?(t=e,e=["*"]):e=e.match(D);for(var n,r=0,i=e.length;r<i;r++)n=e[r],yt.tweeners[n]=yt.tweeners[n]||[],yt.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f="width"in t||"height"in t,p=this,d={},h=e.style,g=e.nodeType&&ee(e),v=_.get(e,"fxshow");for(r in n.queue||(null==(a=ce._queueHooks(e,"fx")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,ce.queue(e,"fx").length||a.empty.fire()})})),t)if(i=t[r],ft.test(i)){if(delete t[r],o=o||"toggle"===i,i===(g?"hide":"show")){if("show"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||ce.style(e,r)}if((u=!ce.isEmptyObject(t))||!ce.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=_.get(e,"display")),"none"===(c=ce.css(e,"display"))&&(l?c=l:(re([e],!0),l=e.style.display||l,c=ce.css(e,"display"),re([e]))),("inline"===c||"inline-block"===c&&null!=l)&&"none"===ce.css(e,"float")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l="none"===c?"":c)),h.display="inline-block")),n.overflow&&(h.overflow="hidden",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?"hidden"in v&&(g=v.hidden):v=_.access(e,"fxshow",{display:l}),o&&(v.hidden=!g),g&&re([e],!0),p.done(function(){for(r in g||re([e]),_.remove(e,"fxshow"),d)ce.style(e,r,d[r])})),u=vt(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?yt.prefilters.unshift(e):yt.prefilters.push(e)}}),ce.speed=function(e,t,n){var r=e&&"object"==typeof e?ce.extend({},e):{complete:n||!n&&t||v(e)&&e,duration:e,easing:n&&t||t&&!v(t)&&t};return ce.fx.off?r.duration=0:"number"!=typeof r.duration&&(r.duration in ce.fx.speeds?r.duration=ce.fx.speeds[r.duration]:r.duration=ce.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue="fx"),r.old=r.complete,r.complete=function(){v(r.old)&&r.old.call(this),r.queue&&ce.dequeue(this,r.queue)},r},ce.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ee).css("opacity",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=ce.isEmptyObject(t),o=ce.speed(e,n,r),a=function(){var e=yt(this,ce.extend({},t),o);(i||_.get(this,"finish"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return"string"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||"fx",[]),this.each(function(){var e=!0,t=null!=i&&i+"queueHooks",n=ce.timers,r=_.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&pt.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||ce.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||"fx"),this.each(function(){var e,t=_.get(this),n=t[a+"queue"],r=t[a+"queueHooks"],i=ce.timers,o=n?n.length:0;for(t.finish=!0,ce.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),ce.each(["toggle","show","hide"],function(e,r){var i=ce.fn[r];ce.fn[r]=function(e,t,n){return null==e||"boolean"==typeof e?i.apply(this,arguments):this.animate(gt(r,!0),e,t,n)}}),ce.each({slideDown:gt("show"),slideUp:gt("hide"),slideToggle:gt("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(e,r){ce.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),ce.timers=[],ce.fx.tick=function(){var e,t=0,n=ce.timers;for(st=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||ce.fx.stop(),st=void 0},ce.fx.timer=function(e){ce.timers.push(e),ce.fx.start()},ce.fx.interval=13,ce.fx.start=function(){ut||(ut=!0,dt())},ce.fx.stop=function(){ut=null},ce.fx.speeds={slow:600,fast:200,_default:400},ce.fn.delay=function(r,e){return r=ce.fx&&ce.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=ie.setTimeout(e,r);t.stop=function(){ie.clearTimeout(n)}})},lt=C.createElement("input"),ct=C.createElement("select").appendChild(C.createElement("option")),lt.type="checkbox",le.checkOn=""!==lt.value,le.optSelected=ct.selected,(lt=C.createElement("input")).value="t",lt.type="radio",le.radioValue="t"===lt.value;var mt,xt=ce.expr.attrHandle;ce.fn.extend({attr:function(e,t){return M(this,ce.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){ce.removeAttr(this,e)})}}),ce.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?ce.prop(e,t,n):(1===o&&ce.isXMLDoc(e)||(i=ce.attrHooks[t.toLowerCase()]||(ce.expr.match.bool.test(t)?mt:void 0)),void 0!==n?null===n?void ce.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=ce.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!le.radioValue&&"radio"===t&&fe(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(D);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),mt={set:function(e,t,n){return!1===t?ce.removeAttr(e,n):e.setAttribute(n,n),n}},ce.each(ce.expr.match.bool.source.match(/\w+/g),function(e,t){var a=xt[t]||ce.find.attr;xt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=xt[o],xt[o]=r,r=null!=a(e,t,n)?o:null,xt[o]=i),r}});var bt=/^(?:input|select|textarea|button)$/i,wt=/^(?:a|area)$/i;function Tt(e){return(e.match(D)||[]).join(" ")}function Ct(e){return e.getAttribute&&e.getAttribute("class")||""}function kt(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(D)||[]}ce.fn.extend({prop:function(e,t){return M(this,ce.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[ce.propFix[e]||e]})}}),ce.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&ce.isXMLDoc(e)||(t=ce.propFix[t]||t,i=ce.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=ce.find.attr(e,"tabindex");return t?parseInt(t,10):bt.test(e.nodeName)||wt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),le.optSelected||(ce.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),ce.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){ce.propFix[this.toLowerCase()]=this}),ce.fn.extend({addClass:function(t){var e,n,r,i,o,a;return v(t)?this.each(function(e){ce(this).addClass(t.call(this,e,Ct(this)))}):(e=kt(t)).length?this.each(function(){if(r=Ct(this),n=1===this.nodeType&&" "+Tt(r)+" "){for(o=0;o<e.length;o++)i=e[o],n.indexOf(" "+i+" ")<0&&(n+=i+" ");a=Tt(n),r!==a&&this.setAttribute("class",a)}}):this},removeClass:function(t){var e,n,r,i,o,a;return v(t)?this.each(function(e){ce(this).removeClass(t.call(this,e,Ct(this)))}):arguments.length?(e=kt(t)).length?this.each(function(){if(r=Ct(this),n=1===this.nodeType&&" "+Tt(r)+" "){for(o=0;o<e.length;o++){i=e[o];while(-1<n.indexOf(" "+i+" "))n=n.replace(" "+i+" "," ")}a=Tt(n),r!==a&&this.setAttribute("class",a)}}):this:this.attr("class","")},toggleClass:function(t,n){var e,r,i,o,a=typeof t,s="string"===a||Array.isArray(t);return v(t)?this.each(function(e){ce(this).toggleClass(t.call(this,e,Ct(this),n),n)}):"boolean"==typeof n&&s?n?this.addClass(t):this.removeClass(t):(e=kt(t),this.each(function(){if(s)for(o=ce(this),i=0;i<e.length;i++)r=e[i],o.hasClass(r)?o.removeClass(r):o.addClass(r);else void 0!==t&&"boolean"!==a||((r=Ct(this))&&_.set(this,"__className__",r),this.setAttribute&&this.setAttribute("class",r||!1===t?"":_.get(this,"__className__")||""))}))},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+Tt(Ct(n))+" ").indexOf(t))return!0;return!1}});var St=/\r/g;ce.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=v(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,ce(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=ce.map(t,function(e){return null==e?"":e+""})),(r=ce.valHooks[this.type]||ce.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=ce.valHooks[t.type]||ce.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(St,""):null==e?"":e:void 0}}),ce.extend({valHooks:{option:{get:function(e){var t=ce.find.attr(e,"value");return null!=t?t:Tt(ce.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!fe(n.parentNode,"optgroup"))){if(t=ce(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=ce.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<ce.inArray(ce.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),ce.each(["radio","checkbox"],function(){ce.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<ce.inArray(ce(e).val(),t)}},le.checkOn||(ce.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Et=ie.location,jt={guid:Date.now()},At=/\?/;ce.parseXML=function(e){var t,n;if(!e||"string"!=typeof e)return null;try{t=(new ie.DOMParser).parseFromString(e,"text/xml")}catch(e){}return n=t&&t.getElementsByTagName("parsererror")[0],t&&!n||ce.error("Invalid XML: "+(n?ce.map(n.childNodes,function(e){return e.textContent}).join("\n"):e)),t};var Dt=/^(?:focusinfocus|focusoutblur)$/,Nt=function(e){e.stopPropagation()};ce.extend(ce.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||C],d=ue.call(e,"type")?e.type:e,h=ue.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||C,3!==n.nodeType&&8!==n.nodeType&&!Dt.test(d+ce.event.triggered)&&(-1<d.indexOf(".")&&(d=(h=d.split(".")).shift(),h.sort()),u=d.indexOf(":")<0&&"on"+d,(e=e[ce.expando]?e:new ce.Event(d,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:ce.makeArray(t,[e]),c=ce.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!y(n)){for(s=c.delegateType||d,Dt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||C)&&p.push(a.defaultView||a.parentWindow||ie)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(_.get(o,"events")||Object.create(null))[e.type]&&_.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&$(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!$(n)||u&&v(n[d])&&!y(n)&&((a=n[u])&&(n[u]=null),ce.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,Nt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,Nt),ce.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=ce.extend(new ce.Event,n,{type:e,isSimulated:!0});ce.event.trigger(r,null,t)}}),ce.fn.extend({trigger:function(e,t){return this.each(function(){ce.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return ce.event.trigger(e,t,n,!0)}});var qt=/\[\]$/,Lt=/\r?\n/g,Ht=/^(?:submit|button|image|reset|file)$/i,Ot=/^(?:input|select|textarea|keygen)/i;function Pt(n,e,r,i){var t;if(Array.isArray(e))ce.each(e,function(e,t){r||qt.test(n)?i(n,t):Pt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==x(e))i(n,e);else for(t in e)Pt(n+"["+t+"]",e[t],r,i)}ce.param=function(e,t){var n,r=[],i=function(e,t){var n=v(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!ce.isPlainObject(e))ce.each(e,function(){i(this.name,this.value)});else for(n in e)Pt(n,e[n],t,i);return r.join("&")},ce.fn.extend({serialize:function(){return ce.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=ce.prop(this,"elements");return e?ce.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!ce(this).is(":disabled")&&Ot.test(this.nodeName)&&!Ht.test(e)&&(this.checked||!we.test(e))}).map(function(e,t){var n=ce(this).val();return null==n?null:Array.isArray(n)?ce.map(n,function(e){return{name:t.name,value:e.replace(Lt,"\r\n")}}):{name:t.name,value:n.replace(Lt,"\r\n")}}).get()}});var Mt=/%20/g,Rt=/#.*$/,It=/([?&])_=[^&]*/,Wt=/^(.*?):[ \t]*([^\r\n]*)$/gm,Ft=/^(?:GET|HEAD)$/,$t=/^\/\//,Bt={},_t={},zt="*/".concat("*"),Xt=C.createElement("a");function Ut(o){return function(e,t){"string"!=typeof e&&(t=e,e="*");var n,r=0,i=e.toLowerCase().match(D)||[];if(v(t))while(n=i[r++])"+"===n[0]?(n=n.slice(1)||"*",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Vt(t,i,o,a){var s={},u=t===_t;function l(e){var r;return s[e]=!0,ce.each(t[e]||[],function(e,t){var n=t(i,o,a);return"string"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s["*"]&&l("*")}function Gt(e,t){var n,r,i=ce.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&ce.extend(!0,e,r),e}Xt.href=Et.href,ce.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Et.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Et.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":zt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":ce.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Gt(Gt(e,ce.ajaxSettings),t):Gt(ce.ajaxSettings,e)},ajaxPrefilter:Ut(Bt),ajaxTransport:Ut(_t),ajax:function(e,t){"object"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=ce.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?ce(y):ce.event,x=ce.Deferred(),b=ce.Callbacks("once memory"),w=v.statusCode||{},a={},s={},u="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=Wt.exec(p))n[t[1].toLowerCase()+" "]=(n[t[1].toLowerCase()+" "]||[]).concat(t[2])}t=n[e.toLowerCase()+" "]}return null==t?null:t.join(", ")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Et.href)+"").replace($t,Et.protocol+"//"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||"*").toLowerCase().match(D)||[""],null==v.crossDomain){r=C.createElement("a");try{r.href=v.url,r.href=r.href,v.crossDomain=Xt.protocol+"//"+Xt.host!=r.protocol+"//"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&"string"!=typeof v.data&&(v.data=ce.param(v.data,v.traditional)),Vt(Bt,v,t,T),h)return T;for(i in(g=ce.event&&v.global)&&0==ce.active++&&ce.event.trigger("ajaxStart"),v.type=v.type.toUpperCase(),v.hasContent=!Ft.test(v.type),f=v.url.replace(Rt,""),v.hasContent?v.data&&v.processData&&0===(v.contentType||"").indexOf("application/x-www-form-urlencoded")&&(v.data=v.data.replace(Mt,"+")):(o=v.url.slice(f.length),v.data&&(v.processData||"string"==typeof v.data)&&(f+=(At.test(f)?"&":"?")+v.data,delete v.data),!1===v.cache&&(f=f.replace(It,"$1"),o=(At.test(f)?"&":"?")+"_="+jt.guid+++o),v.url=f+o),v.ifModified&&(ce.lastModified[f]&&T.setRequestHeader("If-Modified-Since",ce.lastModified[f]),ce.etag[f]&&T.setRequestHeader("If-None-Match",ce.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader("Content-Type",v.contentType),T.setRequestHeader("Accept",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+("*"!==v.dataTypes[0]?", "+zt+"; q=0.01":""):v.accepts["*"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u="abort",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Vt(_t,v,t,T)){if(T.readyState=1,g&&m.trigger("ajaxSend",[T,v]),h)return T;v.async&&0<v.timeout&&(d=ie.setTimeout(function(){T.abort("timeout")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,"No Transport");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&ie.clearTimeout(d),c=void 0,p=r||"",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while("*"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<ce.inArray("script",v.dataTypes)&&ce.inArray("json",v.dataTypes)<0&&(v.converters["text script"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=l[u+" "+o]||l["* "+o]))for(i in l)if((s=i.split(" "))[1]===o&&(a=l[u+" "+s[0]]||l["* "+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e["throws"])t=a(t);else try{t=a(t)}catch(e){return{state:"parsererror",error:a?e:"No conversion from "+u+" to "+o}}}return{state:"success",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader("Last-Modified"))&&(ce.lastModified[f]=u),(u=T.getResponseHeader("etag"))&&(ce.etag[f]=u)),204===e||"HEAD"===v.type?l="nocontent":304===e?l="notmodified":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l="error",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+"",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?"ajaxSuccess":"ajaxError",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger("ajaxComplete",[T,v]),--ce.active||ce.event.trigger("ajaxStop")))}return T},getJSON:function(e,t,n){return ce.get(e,t,n,"json")},getScript:function(e,t){return ce.get(e,void 0,t,"script")}}),ce.each(["get","post"],function(e,i){ce[i]=function(e,t,n,r){return v(t)&&(r=r||n,n=t,t=void 0),ce.ajax(ce.extend({url:e,type:i,dataType:r,data:t,success:n},ce.isPlainObject(e)&&e))}}),ce.ajaxPrefilter(function(e){var t;for(t in e.headers)"content-type"===t.toLowerCase()&&(e.contentType=e.headers[t]||"")}),ce._evalUrl=function(e,t,n){return ce.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(e){ce.globalEval(e,t,n)}})},ce.fn.extend({wrapAll:function(e){var t;return this[0]&&(v(e)&&(e=e.call(this[0])),t=ce(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return v(n)?this.each(function(e){ce(this).wrapInner(n.call(this,e))}):this.each(function(){var e=ce(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=v(t);return this.each(function(e){ce(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){ce(this).replaceWith(this.childNodes)}),this}}),ce.expr.pseudos.hidden=function(e){return!ce.expr.pseudos.visible(e)},ce.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},ce.ajaxSettings.xhr=function(){try{return new ie.XMLHttpRequest}catch(e){}};var Yt={0:200,1223:204},Qt=ce.ajaxSettings.xhr();le.cors=!!Qt&&"withCredentials"in Qt,le.ajax=Qt=!!Qt,ce.ajaxTransport(function(i){var o,a;if(le.cors||Qt&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e["X-Requested-With"]||(e["X-Requested-With"]="XMLHttpRequest"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,"abort"===e?r.abort():"error"===e?"number"!=typeof r.status?t(0,"error"):t(r.status,r.statusText):t(Yt[r.status]||r.status,r.statusText,"text"!==(r.responseType||"text")||"string"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o("error"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&ie.setTimeout(function(){o&&a()})},o=o("abort");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),ce.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),ce.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return ce.globalEval(e),e}}}),ce.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET")}),ce.ajaxTransport("script",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=ce("<script>").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1<s&&(r=Tt(e.slice(s)),e=e.slice(0,s)),v(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),0<a.length&&ce.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?ce("<div>").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var en=/^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g;ce.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),v(e))return r=ae.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(ae.call(arguments)))}).guid=e.guid=e.guid||ce.guid++,i},ce.holdReady=function(e){e?ce.readyWait++:ce.ready(!0)},ce.isArray=Array.isArray,ce.parseJSON=JSON.parse,ce.nodeName=fe,ce.isFunction=v,ce.isWindow=y,ce.camelCase=F,ce.type=x,ce.now=Date.now,ce.isNumeric=function(e){var t=ce.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},ce.trim=function(e){return null==e?"":(e+"").replace(en,"$1")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return ce});var tn=ie.jQuery,nn=ie.$;return ce.noConflict=function(e){return ie.$===ce&&(ie.$=nn),e&&ie.jQuery===ce&&(ie.jQuery=tn),ce},"undefined"==typeof e&&(ie.jQuery=ie.$=ce),ce});
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ flask>=3
2
+ gunicorn>=21
space_app.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Flask, send_from_directory, jsonify
3
+
4
+ app = Flask(__name__, static_folder="frontend", static_url_path="")
5
+
6
+ @app.get("/health")
7
+ def health():
8
+ return jsonify({"status": "ok"})
9
+
10
+ # Serve the SPA
11
+ @app.get("/")
12
+ def index():
13
+ return send_from_directory("frontend", "index.html")
14
+
15
+ # Serve static assets under /js, /css, /images, etc.
16
+ @app.route("/<path:path>")
17
+ def static_proxy(path):
18
+ # Only serve files that actually exist in frontend/
19
+ full_path = os.path.join(app.static_folder, path)
20
+ if os.path.isfile(full_path):
21
+ return send_from_directory(app.static_folder, path)
22
+ # Fallback to SPA (for hash/relative routes if any)
23
+ return send_from_directory("frontend", "index.html")
24
+
25
+ if __name__ == "__main__":
26
+ port = int(os.environ.get("PORT", 7860))
27
+ app.run(host="0.0.0.0", port=port)