Spaces:
Sleeping
Sleeping
Deploy Deep Analog web app
Browse files- .gitattributes +3 -3
- static/index.html +199 -159
- static/lab/tar_1.jpg +3 -0
- static/lab/tar_4.jpg +3 -0
- static/lab/tar_6.jpg +3 -0
.gitattributes
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
static/
|
| 3 |
-
static/
|
| 4 |
-
static/
|
|
|
|
| 1 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
static/lab/tar_1.jpg filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
static/lab/tar_4.jpg filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
static/lab/tar_6.jpg filter=lfs diff=lfs merge=lfs -text
|
static/index.html
CHANGED
|
@@ -319,6 +319,27 @@
|
|
| 319 |
pointer-events: none;
|
| 320 |
}
|
| 321 |
.movie-card:hover .use { opacity: 1; transform: translateY(0); }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
|
| 323 |
/* ---------- film lab ---------- */
|
| 324 |
.lab-films { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 16px; }
|
|
@@ -447,7 +468,7 @@
|
|
| 447 |
<div class="result-card" id="resultBody"></div>
|
| 448 |
</div>
|
| 449 |
|
| 450 |
-
<section class="demo" id="demoSection"
|
| 451 |
<h2>See it in action</h2>
|
| 452 |
<div class="demo-card">
|
| 453 |
<div class="demo-grid">
|
|
@@ -476,33 +497,43 @@
|
|
| 476 |
</div>
|
| 477 |
</section>
|
| 478 |
|
| 479 |
-
<section class="demo" id="
|
| 480 |
-
<h2>
|
| 481 |
-
<p class="section-sub">
|
| 482 |
-
|
| 483 |
-
<div class="movies-grid" id="moviesGrid"></div>
|
| 484 |
-
</section>
|
| 485 |
-
|
| 486 |
-
<section class="demo" id="labSection" style="display:none">
|
| 487 |
-
<h2>Film lab — explore the looks</h2>
|
| 488 |
-
<p class="section-sub">Five real film stocks × seven scenes, pre-rendered for instant comparison.
|
| 489 |
All test photographs shot by <b style="color:var(--text)">Eton Mu</b>.</p>
|
| 490 |
<div class="demo-card">
|
| 491 |
-
<div class="demo-step" style="margin-bottom:
|
| 492 |
-
<div class="
|
| 493 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
<div class="lab-inputs" id="labInputs"></div>
|
| 495 |
-
|
| 496 |
-
<div class="
|
| 497 |
-
<img
|
| 498 |
-
<
|
| 499 |
-
<
|
| 500 |
-
<
|
| 501 |
-
<
|
| 502 |
</div>
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
</div>
|
| 507 |
</div>
|
| 508 |
</section>
|
|
@@ -1240,91 +1271,41 @@
|
|
| 1240 |
return wrap;
|
| 1241 |
}
|
| 1242 |
|
| 1243 |
-
// ------------------------------------------------ demo section
|
| 1244 |
-
// Shown only if demo assets exist on the server (static/demo/*.jpg).
|
| 1245 |
(function initDemo() {
|
| 1246 |
const paths = {
|
| 1247 |
ref: 'static/demo/ref.jpg',
|
| 1248 |
target: 'static/demo/target.jpg',
|
| 1249 |
result: 'static/demo/result.jpg',
|
| 1250 |
};
|
| 1251 |
-
|
| 1252 |
-
|
| 1253 |
-
|
| 1254 |
-
|
| 1255 |
-
|
| 1256 |
-
|
| 1257 |
-
|
| 1258 |
-
|
| 1259 |
-
|
| 1260 |
-
|
| 1261 |
-
|
| 1262 |
-
|
| 1263 |
-
|
| 1264 |
-
|
| 1265 |
-
|
| 1266 |
-
|
| 1267 |
-
|
| 1268 |
-
|
| 1269 |
-
|
| 1270 |
-
|
| 1271 |
-
|
| 1272 |
-
|
| 1273 |
-
|
| 1274 |
-
$('tryDemoBtn').textContent = 'Try this example yourself →';
|
| 1275 |
-
}
|
| 1276 |
-
});
|
| 1277 |
-
};
|
| 1278 |
-
probe.src = paths.ref;
|
| 1279 |
-
})();
|
| 1280 |
-
|
| 1281 |
-
// ------------------------------------------------ movie looks
|
| 1282 |
-
(function initMovies() {
|
| 1283 |
-
const MOVIES = [
|
| 1284 |
-
{ file: 'static/demo/ref.jpg', title: 'Happy Together', meta: '1997 · Wong Kar-wai' },
|
| 1285 |
-
{ file: 'static/movies/mov_01.jpg', title: 'In the Mood for Love', meta: '2000 · Wong Kar-wai' },
|
| 1286 |
-
{ file: 'static/movies/mov_02.jpg', title: 'Days of Being Wild', meta: '1990 · Wong Kar-wai' },
|
| 1287 |
-
{ file: 'static/movies/mov_03.jpg', title: 'Close-Up', meta: '1990 · Abbas Kiarostami' },
|
| 1288 |
-
{ file: 'static/movies/mov_04.jpg', title: 'The Grand Budapest Hotel', meta: '2014 · Wes Anderson' },
|
| 1289 |
-
{ file: 'static/movies/mov_05.jpg', title: 'Barry Lyndon', meta: '1975 · Stanley Kubrick' },
|
| 1290 |
-
{ file: 'static/movies/mov_06.jpg', title: 'The Good, the Bad and the Ugly', meta: '1966 · Sergio Leone' },
|
| 1291 |
-
{ file: 'static/movies/mov_07.jpg', title: 'A Touch of Zen', meta: '1971 · King Hu' },
|
| 1292 |
-
{ file: 'static/movies/mov_08.jpg', title: 'Kagemusha', meta: '1980 · Akira Kurosawa' },
|
| 1293 |
-
];
|
| 1294 |
-
|
| 1295 |
-
function build() {
|
| 1296 |
-
const grid = $('moviesGrid');
|
| 1297 |
-
MOVIES.forEach(m => {
|
| 1298 |
-
const card = document.createElement('div');
|
| 1299 |
-
card.className = 'movie-card';
|
| 1300 |
-
card.innerHTML =
|
| 1301 |
-
'<img class="still" loading="lazy" alt="">' +
|
| 1302 |
-
'<span class="use">USE AS REFERENCE</span>' +
|
| 1303 |
-
'<div class="cap"><div class="title"></div><div class="meta"></div></div>';
|
| 1304 |
-
card.querySelector('img').src = m.file;
|
| 1305 |
-
card.querySelector('img').alt = m.title;
|
| 1306 |
-
card.querySelector('.title').textContent = m.title;
|
| 1307 |
-
card.querySelector('.meta').textContent = m.meta;
|
| 1308 |
-
card.addEventListener('click', async () => {
|
| 1309 |
-
const blob = await fetch(m.file).then(r => r.blob());
|
| 1310 |
-
refFile = new File([blob], 'reference.jpg', { type: 'image/jpeg' });
|
| 1311 |
-
fillZone('refZone', refFile, () => { refFile = null; clearZone('refZone'); update(); });
|
| 1312 |
-
update();
|
| 1313 |
-
window.scrollTo({ top: 0, behavior: 'smooth' });
|
| 1314 |
-
});
|
| 1315 |
-
grid.appendChild(card);
|
| 1316 |
-
});
|
| 1317 |
-
}
|
| 1318 |
-
|
| 1319 |
-
const probe = new Image();
|
| 1320 |
-
probe.onload = () => { $('moviesSection').style.display = 'block'; build(); };
|
| 1321 |
-
probe.src = 'static/movies/mov_01.jpg';
|
| 1322 |
})();
|
| 1323 |
|
| 1324 |
-
// ------------------------------------------------
|
| 1325 |
-
//
|
| 1326 |
-
|
| 1327 |
-
(function initLab() {
|
| 1328 |
const FILMS = [
|
| 1329 |
{ id: 'vision3', name: 'Kodak Vision3 250D 5207', short: '250D',
|
| 1330 |
label: '#E8B23A', text: '#181205',
|
|
@@ -1342,12 +1323,32 @@
|
|
| 1342 |
label: '#A8503A', text: '#f7e8e2',
|
| 1343 |
url: 'https://www.pandacamera.com/products/escura-showa-400-iso-30exp-135-35mm-color-negative-film' },
|
| 1344 |
];
|
| 1345 |
-
const
|
| 1346 |
-
|
| 1347 |
-
|
| 1348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1349 |
];
|
| 1350 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1351 |
|
| 1352 |
function canisterSvg(f) {
|
| 1353 |
return '<svg class="canister" width="34" height="46" viewBox="0 0 34 46" aria-hidden="true">' +
|
|
@@ -1362,67 +1363,106 @@
|
|
| 1362 |
'<rect x="3" y="40" width="28" height="4" rx="2" fill="#333338"/></svg>';
|
| 1363 |
}
|
| 1364 |
|
| 1365 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1366 |
document.querySelectorAll('#labFilms .film-card').forEach((el, i) =>
|
| 1367 |
-
el.classList.toggle('sel', FILMS[i] === film));
|
| 1368 |
document.querySelectorAll('#labInputs img').forEach((el, i) =>
|
| 1369 |
el.classList.toggle('sel', INPUTS[i] === input));
|
| 1370 |
-
|
| 1371 |
-
|
| 1372 |
-
|
| 1373 |
-
$('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1374 |
}
|
| 1375 |
|
| 1376 |
-
function
|
| 1377 |
-
|
| 1378 |
-
|
| 1379 |
-
|
| 1380 |
-
|
| 1381 |
-
|
| 1382 |
-
|
| 1383 |
-
'<div class="meta">' + canisterSvg(f) +
|
| 1384 |
-
'<a class="stock" target="_blank" rel="noopener">' + f.name +
|
| 1385 |
-
' <span class="ext">↗</span></a></div>';
|
| 1386 |
-
const a = card.querySelector('a.stock');
|
| 1387 |
-
a.href = f.url;
|
| 1388 |
-
a.title = 'About ' + f.name;
|
| 1389 |
-
a.addEventListener('click', e => e.stopPropagation());
|
| 1390 |
-
card.addEventListener('click', () => { film = f; refresh(); });
|
| 1391 |
-
filmsBox.appendChild(card);
|
| 1392 |
-
});
|
| 1393 |
-
const inputsBox = $('labInputs');
|
| 1394 |
-
INPUTS.forEach(inp => {
|
| 1395 |
-
const im = document.createElement('img');
|
| 1396 |
-
im.src = 'static/lab/input_' + inp[0] + '.jpg';
|
| 1397 |
-
im.alt = inp[1]; im.title = inp[1];
|
| 1398 |
-
im.addEventListener('click', () => { input = inp; refresh(); });
|
| 1399 |
-
inputsBox.appendChild(im);
|
| 1400 |
-
});
|
| 1401 |
-
initCompare($('labCompare'));
|
| 1402 |
refresh();
|
| 1403 |
-
|
| 1404 |
-
$('tryLabBtn').addEventListener('click', async () => {
|
| 1405 |
-
try {
|
| 1406 |
-
$('tryLabBtn').textContent = 'Loading…';
|
| 1407 |
-
const [rb, tb] = await Promise.all([
|
| 1408 |
-
fetch('static/lab/ref_' + film.id + '.jpg').then(r => r.blob()),
|
| 1409 |
-
fetch('static/lab/input_' + input[0] + '.jpg').then(r => r.blob()),
|
| 1410 |
-
]);
|
| 1411 |
-
refFile = new File([rb], film.id + '-reference.jpg', { type: 'image/jpeg' });
|
| 1412 |
-
tgtFile = new File([tb], 'photo.jpg', { type: 'image/jpeg' });
|
| 1413 |
-
fillZone('refZone', refFile, () => { refFile = null; clearZone('refZone'); update(); });
|
| 1414 |
-
fillZone('tgtZone', tgtFile, () => { tgtFile = null; clearZone('tgtZone'); update(); });
|
| 1415 |
-
update();
|
| 1416 |
-
window.scrollTo({ top: 0, behavior: 'smooth' });
|
| 1417 |
-
} finally {
|
| 1418 |
-
$('tryLabBtn').textContent = 'Open this pair in the app →';
|
| 1419 |
-
}
|
| 1420 |
-
});
|
| 1421 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1422 |
|
| 1423 |
-
|
| 1424 |
-
|
| 1425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1426 |
})();
|
| 1427 |
|
| 1428 |
// ------------------------------------------------ privacy modal
|
|
|
|
| 319 |
pointer-events: none;
|
| 320 |
}
|
| 321 |
.movie-card:hover .use { opacity: 1; transform: translateY(0); }
|
| 322 |
+
.movie-card.sel { border-color: var(--accent); }
|
| 323 |
+
.movie-card.sel .use { opacity: 1; transform: translateY(0); }
|
| 324 |
+
|
| 325 |
+
/* ---------- playground tabs + pair bar ---------- */
|
| 326 |
+
.ref-tabs { display: flex; gap: 8px; margin-bottom: 14px; }
|
| 327 |
+
.tab-btn {
|
| 328 |
+
background: var(--bg-3); border: 1.5px solid var(--line); border-radius: 20px;
|
| 329 |
+
color: var(--text-dim); font-size: 12.5px; font-weight: 600;
|
| 330 |
+
padding: 7px 18px; cursor: pointer; transition: all .15s;
|
| 331 |
+
}
|
| 332 |
+
.tab-btn:hover { color: var(--text); }
|
| 333 |
+
.tab-btn.sel { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }
|
| 334 |
+
.pair-bar {
|
| 335 |
+
display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
|
| 336 |
+
background: var(--bg-3); border: 1px solid var(--line); border-radius: 11px;
|
| 337 |
+
padding: 12px 14px; margin-top: 22px;
|
| 338 |
+
}
|
| 339 |
+
.pair-bar img { height: 52px; width: 78px; object-fit: cover; border-radius: 7px; border: 1px solid var(--line); }
|
| 340 |
+
.pair-plus { color: var(--text-dim); font-size: 16px; }
|
| 341 |
+
.pair-info { flex: 1; min-width: 160px; font-size: 12.5px; color: var(--text-dim); line-height: 1.5; }
|
| 342 |
+
.pair-info b { color: var(--text); font-weight: 600; }
|
| 343 |
|
| 344 |
/* ---------- film lab ---------- */
|
| 345 |
.lab-films { display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 16px; }
|
|
|
|
| 468 |
<div class="result-card" id="resultBody"></div>
|
| 469 |
</div>
|
| 470 |
|
| 471 |
+
<section class="demo" id="demoSection">
|
| 472 |
<h2>See it in action</h2>
|
| 473 |
<div class="demo-card">
|
| 474 |
<div class="demo-grid">
|
|
|
|
| 497 |
</div>
|
| 498 |
</section>
|
| 499 |
|
| 500 |
+
<section class="demo" id="playSection">
|
| 501 |
+
<h2>Try it yourself — pick a pair</h2>
|
| 502 |
+
<p class="section-sub">Choose a reference look and an input photo, then run the pair through the model.
|
| 503 |
+
Film-stock pairs with the seven test photos also show an instant pre-rendered preview.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
All test photographs shot by <b style="color:var(--text)">Eton Mu</b>.</p>
|
| 505 |
<div class="demo-card">
|
| 506 |
+
<div class="demo-step" style="margin-bottom:10px"><b>1</b> Choose a reference</div>
|
| 507 |
+
<div class="ref-tabs">
|
| 508 |
+
<button class="tab-btn sel" id="tabMovies">🎬 Movie looks</button>
|
| 509 |
+
<button class="tab-btn" id="tabFilms">🎞 Film lab</button>
|
| 510 |
+
</div>
|
| 511 |
+
<div class="movies-grid" id="moviesGrid"></div>
|
| 512 |
+
<div class="lab-films" id="labFilms" style="display:none"></div>
|
| 513 |
+
<p class="lab-caption" id="filmsHint" style="display:none;margin-top:10px">
|
| 514 |
+
Click a film's name to read the full story of that emulsion.</p>
|
| 515 |
+
|
| 516 |
+
<div class="demo-step" style="margin:24px 0 10px"><b>2</b> Choose an input photo</div>
|
| 517 |
<div class="lab-inputs" id="labInputs"></div>
|
| 518 |
+
|
| 519 |
+
<div class="pair-bar">
|
| 520 |
+
<img id="pairRef" alt="Selected reference">
|
| 521 |
+
<span class="pair-plus">+</span>
|
| 522 |
+
<img id="pairInput" alt="Selected input">
|
| 523 |
+
<div class="pair-info" id="pairInfo"></div>
|
| 524 |
+
<button class="try-btn" id="runPairBtn">Run this pair in the app →</button>
|
| 525 |
</div>
|
| 526 |
+
|
| 527 |
+
<div id="instantWrap" style="display:none">
|
| 528 |
+
<div class="demo-step" style="margin:24px 0 10px">Instant preview — pre-rendered · drag to compare</div>
|
| 529 |
+
<div class="compare" id="labCompare">
|
| 530 |
+
<img class="base-img" alt="Rendered">
|
| 531 |
+
<img class="top-img" alt="Original">
|
| 532 |
+
<div class="divider"></div>
|
| 533 |
+
<span class="cmp-label" style="left:12px">RENDERED</span>
|
| 534 |
+
<span class="cmp-label" style="right:12px">ORIGINAL</span>
|
| 535 |
+
</div>
|
| 536 |
+
<p class="lab-caption" id="labCaption" style="margin-top:10px"></p>
|
| 537 |
</div>
|
| 538 |
</div>
|
| 539 |
</section>
|
|
|
|
| 1271 |
return wrap;
|
| 1272 |
}
|
| 1273 |
|
| 1274 |
+
// ------------------------------------------------ demo section (Happy Together walkthrough)
|
|
|
|
| 1275 |
(function initDemo() {
|
| 1276 |
const paths = {
|
| 1277 |
ref: 'static/demo/ref.jpg',
|
| 1278 |
target: 'static/demo/target.jpg',
|
| 1279 |
result: 'static/demo/result.jpg',
|
| 1280 |
};
|
| 1281 |
+
$('demoRefImg').src = paths.ref;
|
| 1282 |
+
$('demoInputImg').src = paths.target;
|
| 1283 |
+
$('demoTargetImg').src = paths.target;
|
| 1284 |
+
$('demoResultImg').src = paths.result;
|
| 1285 |
+
initCompare($('demoCompare'));
|
| 1286 |
+
|
| 1287 |
+
$('tryDemoBtn').addEventListener('click', async () => {
|
| 1288 |
+
try {
|
| 1289 |
+
$('tryDemoBtn').textContent = 'Loading…';
|
| 1290 |
+
const [rb, tb] = await Promise.all([
|
| 1291 |
+
fetch(paths.ref).then(r => r.blob()),
|
| 1292 |
+
fetch(paths.target).then(r => r.blob()),
|
| 1293 |
+
]);
|
| 1294 |
+
refFile = new File([rb], 'demo-reference.jpg', { type: 'image/jpeg' });
|
| 1295 |
+
tgtFile = new File([tb], 'demo-photo.jpg', { type: 'image/jpeg' });
|
| 1296 |
+
fillZone('refZone', refFile, () => { refFile = null; clearZone('refZone'); update(); });
|
| 1297 |
+
fillZone('tgtZone', tgtFile, () => { tgtFile = null; clearZone('tgtZone'); update(); });
|
| 1298 |
+
update();
|
| 1299 |
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
| 1300 |
+
} finally {
|
| 1301 |
+
$('tryDemoBtn').textContent = 'Try this example yourself →';
|
| 1302 |
+
}
|
| 1303 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1304 |
})();
|
| 1305 |
|
| 1306 |
+
// ------------------------------------------------ demo playground
|
| 1307 |
+
// Reference (movie stills OR film stocks) × input photo picker.
|
| 1308 |
+
(function initPlayground() {
|
|
|
|
| 1309 |
const FILMS = [
|
| 1310 |
{ id: 'vision3', name: 'Kodak Vision3 250D 5207', short: '250D',
|
| 1311 |
label: '#E8B23A', text: '#181205',
|
|
|
|
| 1323 |
label: '#A8503A', text: '#f7e8e2',
|
| 1324 |
url: 'https://www.pandacamera.com/products/escura-showa-400-iso-30exp-135-35mm-color-negative-film' },
|
| 1325 |
];
|
| 1326 |
+
const MOVIES = [
|
| 1327 |
+
{ file: 'static/demo/ref.jpg', title: 'Happy Together', meta: '1997 · Wong Kar-wai' },
|
| 1328 |
+
{ file: 'static/movies/mov_01.jpg', title: 'In the Mood for Love', meta: '2000 · Wong Kar-wai' },
|
| 1329 |
+
{ file: 'static/movies/mov_02.jpg', title: 'Days of Being Wild', meta: '1990 · Wong Kar-wai' },
|
| 1330 |
+
{ file: 'static/movies/mov_03.jpg', title: 'Close-Up', meta: '1990 · Abbas Kiarostami' },
|
| 1331 |
+
{ file: 'static/movies/mov_04.jpg', title: 'The Grand Budapest Hotel', meta: '2014 · Wes Anderson' },
|
| 1332 |
+
{ file: 'static/movies/mov_05.jpg', title: 'Barry Lyndon', meta: '1975 · Stanley Kubrick' },
|
| 1333 |
+
{ file: 'static/movies/mov_06.jpg', title: 'The Good, the Bad and the Ugly', meta: '1966 · Sergio Leone' },
|
| 1334 |
+
{ file: 'static/movies/mov_07.jpg', title: 'A Touch of Zen', meta: '1971 · King Hu' },
|
| 1335 |
+
{ file: 'static/movies/mov_08.jpg', title: 'Kagemusha', meta: '1980 · Akira Kurosawa' },
|
| 1336 |
];
|
| 1337 |
+
const INPUTS = [
|
| 1338 |
+
['01', 'Tokyo alley', true], ['02', 'Bicycle corridor', true],
|
| 1339 |
+
['03', 'Asakusa & Skytree', true], ['04', 'Rainy night, NYC', true],
|
| 1340 |
+
['05', 'Window still life', true], ['06', 'Portrait at the bar', true],
|
| 1341 |
+
['07', 'Pond reflection', true],
|
| 1342 |
+
['t1', 'Hilltop monastery, Tibet', false],
|
| 1343 |
+
['t4', 'Hanging garden', false],
|
| 1344 |
+
['t6', 'Cypress swamp', false],
|
| 1345 |
+
].map(a => ({
|
| 1346 |
+
id: a[0], name: a[1], lab: a[2],
|
| 1347 |
+
src: a[2] ? 'static/lab/input_' + a[0] + '.jpg'
|
| 1348 |
+
: 'static/lab/tar_' + a[0].slice(1) + '.jpg',
|
| 1349 |
+
}));
|
| 1350 |
+
|
| 1351 |
+
let refType = 'movie', movie = MOVIES[0], film = FILMS[0], input = INPUTS[0];
|
| 1352 |
|
| 1353 |
function canisterSvg(f) {
|
| 1354 |
return '<svg class="canister" width="34" height="46" viewBox="0 0 34 46" aria-hidden="true">' +
|
|
|
|
| 1363 |
'<rect x="3" y="40" width="28" height="4" rx="2" fill="#333338"/></svg>';
|
| 1364 |
}
|
| 1365 |
|
| 1366 |
+
function refSrc() { return refType === 'movie' ? movie.file : 'static/lab/ref_' + film.id + '.jpg'; }
|
| 1367 |
+
function refName() { return refType === 'movie' ? movie.title : film.name; }
|
| 1368 |
+
|
| 1369 |
+
function refresh() {
|
| 1370 |
+
document.querySelectorAll('#moviesGrid .movie-card').forEach((el, i) => {
|
| 1371 |
+
const sel = refType === 'movie' && MOVIES[i] === movie;
|
| 1372 |
+
el.classList.toggle('sel', sel);
|
| 1373 |
+
el.querySelector('.use').textContent = sel ? '✓ SELECTED' : 'SELECT';
|
| 1374 |
+
});
|
| 1375 |
document.querySelectorAll('#labFilms .film-card').forEach((el, i) =>
|
| 1376 |
+
el.classList.toggle('sel', refType === 'film' && FILMS[i] === film));
|
| 1377 |
document.querySelectorAll('#labInputs img').forEach((el, i) =>
|
| 1378 |
el.classList.toggle('sel', INPUTS[i] === input));
|
| 1379 |
+
|
| 1380 |
+
$('pairRef').src = refSrc();
|
| 1381 |
+
$('pairInput').src = input.src;
|
| 1382 |
+
$('pairInfo').innerHTML = '<b>' + refName() + '</b> → <b>' + input.name + '</b>';
|
| 1383 |
+
|
| 1384 |
+
const instant = refType === 'film' && input.lab;
|
| 1385 |
+
$('instantWrap').style.display = instant ? 'block' : 'none';
|
| 1386 |
+
if (instant) {
|
| 1387 |
+
const cmp = $('labCompare');
|
| 1388 |
+
cmp.querySelector('.base-img').src = 'static/lab/' + input.id + '_' + film.id + '.jpg';
|
| 1389 |
+
cmp.querySelector('.top-img').src = input.src;
|
| 1390 |
+
$('labCaption').innerHTML = '<b>' + input.name + '</b> rendered as <b>' + film.name + '</b>';
|
| 1391 |
+
}
|
| 1392 |
}
|
| 1393 |
|
| 1394 |
+
function setTab(t) {
|
| 1395 |
+
refType = t;
|
| 1396 |
+
$('tabMovies').classList.toggle('sel', t === 'movie');
|
| 1397 |
+
$('tabFilms').classList.toggle('sel', t === 'film');
|
| 1398 |
+
$('moviesGrid').style.display = t === 'movie' ? 'grid' : 'none';
|
| 1399 |
+
$('labFilms').style.display = t === 'film' ? 'grid' : 'none';
|
| 1400 |
+
$('filmsHint').style.display = t === 'film' ? 'block' : 'none';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1401 |
refresh();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1402 |
}
|
| 1403 |
+
$('tabMovies').addEventListener('click', () => setTab('movie'));
|
| 1404 |
+
$('tabFilms').addEventListener('click', () => setTab('film'));
|
| 1405 |
+
|
| 1406 |
+
MOVIES.forEach(m => {
|
| 1407 |
+
const card = document.createElement('div');
|
| 1408 |
+
card.className = 'movie-card';
|
| 1409 |
+
card.innerHTML =
|
| 1410 |
+
'<img class="still" loading="lazy" alt="">' +
|
| 1411 |
+
'<span class="use">SELECT</span>' +
|
| 1412 |
+
'<div class="cap"><div class="title"></div><div class="meta"></div></div>';
|
| 1413 |
+
card.querySelector('img').src = m.file;
|
| 1414 |
+
card.querySelector('img').alt = m.title;
|
| 1415 |
+
card.querySelector('.title').textContent = m.title;
|
| 1416 |
+
card.querySelector('.meta').textContent = m.meta;
|
| 1417 |
+
card.addEventListener('click', () => { movie = m; refresh(); });
|
| 1418 |
+
$('moviesGrid').appendChild(card);
|
| 1419 |
+
});
|
| 1420 |
+
|
| 1421 |
+
FILMS.forEach(f => {
|
| 1422 |
+
const card = document.createElement('div');
|
| 1423 |
+
card.className = 'film-card';
|
| 1424 |
+
card.innerHTML =
|
| 1425 |
+
'<img class="thumb" loading="lazy" alt="" src="static/lab/ref_' + f.id + '.jpg">' +
|
| 1426 |
+
'<div class="meta">' + canisterSvg(f) +
|
| 1427 |
+
'<a class="stock" target="_blank" rel="noopener">' + f.name +
|
| 1428 |
+
' <span class="ext">↗</span></a></div>';
|
| 1429 |
+
const a = card.querySelector('a.stock');
|
| 1430 |
+
a.href = f.url;
|
| 1431 |
+
a.title = 'About ' + f.name;
|
| 1432 |
+
a.addEventListener('click', e => e.stopPropagation());
|
| 1433 |
+
card.addEventListener('click', () => { film = f; refresh(); });
|
| 1434 |
+
$('labFilms').appendChild(card);
|
| 1435 |
+
});
|
| 1436 |
|
| 1437 |
+
INPUTS.forEach(inp => {
|
| 1438 |
+
const im = document.createElement('img');
|
| 1439 |
+
im.src = inp.src;
|
| 1440 |
+
im.loading = 'lazy';
|
| 1441 |
+
im.alt = inp.name; im.title = inp.name;
|
| 1442 |
+
im.addEventListener('click', () => { input = inp; refresh(); });
|
| 1443 |
+
$('labInputs').appendChild(im);
|
| 1444 |
+
});
|
| 1445 |
+
|
| 1446 |
+
initCompare($('labCompare'));
|
| 1447 |
+
setTab('movie');
|
| 1448 |
+
|
| 1449 |
+
$('runPairBtn').addEventListener('click', async () => {
|
| 1450 |
+
try {
|
| 1451 |
+
$('runPairBtn').textContent = 'Loading…';
|
| 1452 |
+
const [rb, tb] = await Promise.all([
|
| 1453 |
+
fetch(refSrc()).then(r => r.blob()),
|
| 1454 |
+
fetch(input.src).then(r => r.blob()),
|
| 1455 |
+
]);
|
| 1456 |
+
refFile = new File([rb], 'reference.jpg', { type: 'image/jpeg' });
|
| 1457 |
+
tgtFile = new File([tb], 'photo.jpg', { type: 'image/jpeg' });
|
| 1458 |
+
fillZone('refZone', refFile, () => { refFile = null; clearZone('refZone'); update(); });
|
| 1459 |
+
fillZone('tgtZone', tgtFile, () => { tgtFile = null; clearZone('tgtZone'); update(); });
|
| 1460 |
+
update();
|
| 1461 |
+
window.scrollTo({ top: 0, behavior: 'smooth' });
|
| 1462 |
+
} finally {
|
| 1463 |
+
$('runPairBtn').textContent = 'Run this pair in the app →';
|
| 1464 |
+
}
|
| 1465 |
+
});
|
| 1466 |
})();
|
| 1467 |
|
| 1468 |
// ------------------------------------------------ privacy modal
|
static/lab/tar_1.jpg
ADDED
|
Git LFS Details
|
static/lab/tar_4.jpg
ADDED
|
Git LFS Details
|
static/lab/tar_6.jpg
ADDED
|
Git LFS Details
|