Commit ·
00d05c7
0
Parent(s):
Space clean deploy: minimal files only
Browse files- .dockerignore +8 -0
- .gitattributes +2 -0
- .gitignore +9 -0
- Dockerfile +21 -0
- README.md +45 -0
- frontend/css/artefact-context.css +402 -0
- frontend/images/dark_logo.png +3 -0
- frontend/images/favicon.png +3 -0
- frontend/index.html +383 -0
- frontend/js/artefact-context.js +1421 -0
- frontend/js/jquery.min.js +2 -0
- requirements.txt +2 -0
- space_app.py +27 -0
.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
|
frontend/images/favicon.png
ADDED
|
|
Git LFS Details
|
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 <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 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 Stuart 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 |
+
{'&': '&', '<': '<', '>': '>', "'": ''', '"': '"'}[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 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)
|