hd-garden 0.9.1 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -235,18 +235,22 @@
|
|
|
235
235
|
z-index: 2;
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
-
.bookshelf-demo .cover.is-placeholder
|
|
239
|
-
|
|
238
|
+
.bookshelf-demo .cover.is-placeholder,
|
|
239
|
+
.bookshelf-demo .spine.is-placeholder {
|
|
240
|
+
/* Empty seat while the flyer owns the motion: shelf surface only.
|
|
241
|
+
A bk-* fill here is the gray/lavender plate behind the real cover. */
|
|
240
242
|
opacity: 0 !important;
|
|
241
243
|
visibility: hidden !important;
|
|
242
244
|
background-color: transparent !important;
|
|
243
245
|
background-image: none !important;
|
|
244
|
-
filter: none;
|
|
246
|
+
filter: none !important;
|
|
245
247
|
pointer-events: none;
|
|
246
|
-
box-shadow: none;
|
|
248
|
+
box-shadow: none !important;
|
|
249
|
+
color: transparent !important;
|
|
247
250
|
transform: none !important;
|
|
248
251
|
}
|
|
249
|
-
.bookshelf-demo .cover.is-placeholder > .cover-art
|
|
252
|
+
.bookshelf-demo .cover.is-placeholder > .cover-art,
|
|
253
|
+
.bookshelf-demo .spine.is-placeholder > .cover-art {
|
|
250
254
|
visibility: hidden !important;
|
|
251
255
|
opacity: 0 !important;
|
|
252
256
|
}
|
|
@@ -342,15 +346,7 @@
|
|
|
342
346
|
z-index: 2;
|
|
343
347
|
}
|
|
344
348
|
}
|
|
345
|
-
.bookshelf-demo .spine.is-placeholder {
|
|
346
|
-
/* ux13: invisible seat — ban gray ghost plate */
|
|
347
|
-
opacity: 0;
|
|
348
|
-
filter: none;
|
|
349
|
-
pointer-events: none;
|
|
350
|
-
box-shadow: none;
|
|
351
|
-
transform: none !important;
|
|
352
|
-
}
|
|
353
|
-
.bookshelf-demo .spine.is-placeholder span { opacity: 0; }
|
|
349
|
+
.bookshelf-demo .spine.is-placeholder span { opacity: 0 !important; visibility: hidden !important; }
|
|
354
350
|
.bookshelf-demo .cover.is-placeholder.is-landing,
|
|
355
351
|
.bookshelf-demo .spine.is-placeholder.is-landing {
|
|
356
352
|
opacity: 0;
|
|
@@ -359,6 +355,25 @@
|
|
|
359
355
|
|
|
360
356
|
/* Mode C: temporary expand on spine wall */
|
|
361
357
|
/* resting spines: solid hue only; cover image only when expanded */
|
|
358
|
+
/* Expanded / extracting face must not fall back to bk-* (#513688 etc.).
|
|
359
|
+
The jacket is .cover-art. A missing image stays cream shelf, not a shell pad. */
|
|
360
|
+
.bookshelf-demo .spine.is-expanded,
|
|
361
|
+
.bookshelf-demo .spine.is-extracting {
|
|
362
|
+
background-color: transparent !important;
|
|
363
|
+
}
|
|
364
|
+
.bookshelf-demo .spine.is-expanded > .cover-art,
|
|
365
|
+
.bookshelf-demo .spine.is-extracting > .cover-art {
|
|
366
|
+
position: absolute;
|
|
367
|
+
inset: 0;
|
|
368
|
+
width: 100%;
|
|
369
|
+
height: 100%;
|
|
370
|
+
object-fit: contain;
|
|
371
|
+
object-position: center top;
|
|
372
|
+
display: block;
|
|
373
|
+
pointer-events: none;
|
|
374
|
+
z-index: 1;
|
|
375
|
+
border-radius: inherit;
|
|
376
|
+
}
|
|
362
377
|
.bookshelf-demo .spine[class*="img-"] {
|
|
363
378
|
background-image: none !important;
|
|
364
379
|
}
|
|
@@ -559,15 +574,32 @@
|
|
|
559
574
|
align-items: flex-start;
|
|
560
575
|
margin-bottom: 36px;
|
|
561
576
|
}
|
|
577
|
+
.bookshelf-demo .note-meta {
|
|
578
|
+
min-width: 0;
|
|
579
|
+
flex: 1 1 auto;
|
|
580
|
+
}
|
|
581
|
+
/* Settled note header: size the box to the picture. A fixed 2:3 frame with
|
|
582
|
+
background-size:cover center-crops landscape heroes (Obsidian → "sid"). */
|
|
562
583
|
.bookshelf-demo .note-cover {
|
|
563
|
-
width:
|
|
564
|
-
|
|
565
|
-
|
|
584
|
+
width: max-content;
|
|
585
|
+
max-width: min(420px, 46%);
|
|
586
|
+
height: auto;
|
|
587
|
+
flex: 0 0 auto;
|
|
566
588
|
border-radius: 2px 3px 0 0;
|
|
567
|
-
background-
|
|
568
|
-
background-
|
|
569
|
-
background-color: #2a2a2a;
|
|
589
|
+
background-color: transparent;
|
|
590
|
+
background-image: none;
|
|
570
591
|
box-shadow: 0 8px 20px rgba(40,30,20,0.12);
|
|
592
|
+
line-height: 0;
|
|
593
|
+
}
|
|
594
|
+
.bookshelf-demo .note-cover-img {
|
|
595
|
+
display: block;
|
|
596
|
+
width: auto;
|
|
597
|
+
height: auto;
|
|
598
|
+
max-width: 100%;
|
|
599
|
+
max-height: 180px;
|
|
600
|
+
object-fit: contain;
|
|
601
|
+
object-position: center;
|
|
602
|
+
border-radius: inherit;
|
|
571
603
|
}
|
|
572
604
|
.bookshelf-demo .note-cat {
|
|
573
605
|
margin: 0 0 8px;
|
|
@@ -713,21 +745,16 @@
|
|
|
713
745
|
backface-visibility: hidden;
|
|
714
746
|
}
|
|
715
747
|
|
|
716
|
-
/*
|
|
748
|
+
/* ux27 plate ban: this used to be a full-spread shell (fallback #6a7a98 / #5a6a88,
|
|
749
|
+
and shell-hi of dark covers reads the same gray-lavender). It sat wider than
|
|
750
|
+
the jacket during lift and put-back. Fore-edge stays on .flyer-thickness. */
|
|
717
751
|
.bookshelf-demo .flyer-board {
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
var(--shell-hi, #6a7a98) 0%,
|
|
725
|
-
var(--shell, #5a6a88) 55%,
|
|
726
|
-
var(--shell-lo, #3a4a68) 100%
|
|
727
|
-
);
|
|
728
|
-
box-shadow: 0 1px 0 rgba(255,255,255,0.06) inset;
|
|
729
|
-
transform: translateZ(-8px);
|
|
730
|
-
z-index: 0;
|
|
752
|
+
display: none !important;
|
|
753
|
+
background: none !important;
|
|
754
|
+
background-color: transparent !important;
|
|
755
|
+
box-shadow: none !important;
|
|
756
|
+
opacity: 0 !important;
|
|
757
|
+
visibility: hidden !important;
|
|
731
758
|
}
|
|
732
759
|
|
|
733
760
|
.bookshelf-demo .flyer-leaf {
|
|
@@ -759,7 +786,7 @@
|
|
|
759
786
|
/* Cover on BACK of left leaf — camera-facing when leaf folded (180°) */
|
|
760
787
|
.bookshelf-demo .flyer-face-cover {
|
|
761
788
|
transform: rotateY(180deg);
|
|
762
|
-
background-color: transparent; /* ux24:
|
|
789
|
+
background-color: transparent !important; /* ux24/ux27: bk-* must not fill behind the img */
|
|
763
790
|
/* No background-image here — .img-* / inline applyCoverImage owns the photo.
|
|
764
791
|
Gradients-only used to override img classes → flat brown/gray close flash. */
|
|
765
792
|
box-shadow:
|
|
@@ -782,17 +809,18 @@
|
|
|
782
809
|
}
|
|
783
810
|
.bookshelf-demo .flyer-spine-label { pointer-events: none; }
|
|
784
811
|
|
|
785
|
-
/* ux15 plate kept inert; ux16 uses full-height .flyer-spine-slot instead
|
|
812
|
+
/* ux15 plate kept inert; ux16 uses full-height .flyer-spine-slot instead.
|
|
813
|
+
Never paint the lavender shell fallback if a leftover plate node is mounted. */
|
|
786
814
|
.bookshelf-demo .flyer-spine-land-plate {
|
|
787
815
|
position: absolute;
|
|
788
816
|
inset: 0;
|
|
789
817
|
pointer-events: none;
|
|
790
818
|
border-radius: inherit;
|
|
791
819
|
z-index: 1;
|
|
792
|
-
display: none;
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
820
|
+
display: none !important;
|
|
821
|
+
background: none !important;
|
|
822
|
+
background-color: transparent !important;
|
|
823
|
+
opacity: 0 !important;
|
|
796
824
|
}
|
|
797
825
|
|
|
798
826
|
/* ux16: exact homeRect standing spine — full height, spine width, slides out/in */
|
|
@@ -1002,6 +1030,18 @@
|
|
|
1002
1030
|
overflow: auto;
|
|
1003
1031
|
}
|
|
1004
1032
|
|
|
1033
|
+
/* Close: flyer owns the motion. The note used to stay at z-index 75, so the
|
|
1034
|
+
article sat on top of the return for the whole fade and the shrink was hidden. */
|
|
1035
|
+
.bookshelf-demo.is-return .stage { z-index: 80; }
|
|
1036
|
+
.bookshelf-demo.is-return .note-view,
|
|
1037
|
+
.bookshelf-demo.is-return .note-view.is-preparing,
|
|
1038
|
+
.bookshelf-demo.is-return .note-view.is-open { z-index: 60; }
|
|
1039
|
+
.bookshelf-demo.is-site.is-return .stage { z-index: 36; }
|
|
1040
|
+
.bookshelf-demo.is-site.is-return .open-backdrop { z-index: 30; }
|
|
1041
|
+
.bookshelf-demo.is-site.is-return .note-view,
|
|
1042
|
+
.bookshelf-demo.is-site.is-return .note-view.is-preparing,
|
|
1043
|
+
.bookshelf-demo.is-site.is-return .note-view.is-open { z-index: 31; }
|
|
1044
|
+
|
|
1005
1045
|
.bookshelf-demo .shelf-view.is-dim {
|
|
1006
1046
|
/* Backdrop covers shelf — dim is unused during open; keep inert */
|
|
1007
1047
|
opacity: 1;
|
|
@@ -1059,7 +1099,7 @@
|
|
|
1059
1099
|
inset: 0;
|
|
1060
1100
|
border-radius: inherit;
|
|
1061
1101
|
overflow: hidden;
|
|
1062
|
-
background-color: transparent; /* ux24: no
|
|
1102
|
+
background-color: transparent !important; /* ux24/ux27: no solid shell behind the img */
|
|
1063
1103
|
background-size: cover;
|
|
1064
1104
|
background-position: center top;
|
|
1065
1105
|
background-repeat: no-repeat;
|
|
@@ -1162,7 +1202,13 @@
|
|
|
1162
1202
|
.bookshelf-demo .spine.is-expanded.is-collapsing.s-mid { width: 24px; height: 134px; }
|
|
1163
1203
|
.bookshelf-demo .spine.is-expanded.is-collapsing.s-short { width: 20px; height: 120px; }
|
|
1164
1204
|
.bookshelf-demo .note-hero { flex-direction: column; gap: 16px; }
|
|
1165
|
-
.bookshelf-demo .note-cover { width:
|
|
1205
|
+
.bookshelf-demo .note-cover { width: max-content; max-width: 100%; }
|
|
1206
|
+
.bookshelf-demo .note-cover-img {
|
|
1207
|
+
width: auto;
|
|
1208
|
+
max-width: 100%;
|
|
1209
|
+
max-height: 180px;
|
|
1210
|
+
height: auto;
|
|
1211
|
+
}
|
|
1166
1212
|
.bookshelf-demo .qa-hint { width: 100%; margin-left: 0; margin-top: 4px; }
|
|
1167
1213
|
}
|
|
1168
1214
|
|
|
@@ -312,6 +312,30 @@ export function mountBookshelf(root, options = {}) {
|
|
|
312
312
|
return Math.max(96, Math.round(w * (nat.h / nat.w)));
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
// Real jacket on the expanded spine. CSS background-image is stripped by
|
|
316
|
+
// `.spine[class*="img-"] { background-image: none !important }` and by the
|
|
317
|
+
// collapse rule, which left bk-* (#513688) as a solid pad at extract start.
|
|
318
|
+
function mountSeatCover(el, book) {
|
|
319
|
+
if (!el || !book || !book.cover || book.filler) return;
|
|
320
|
+
el.style.backgroundColor = "transparent";
|
|
321
|
+
let art = el.querySelector(":scope > .cover-art");
|
|
322
|
+
if (!art) {
|
|
323
|
+
art = document.createElement("img");
|
|
324
|
+
art.className = "cover-art";
|
|
325
|
+
art.alt = "";
|
|
326
|
+
art.draggable = false;
|
|
327
|
+
el.appendChild(art);
|
|
328
|
+
}
|
|
329
|
+
const src = coverSrc(book);
|
|
330
|
+
if (art.getAttribute("src") !== src) art.src = src;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function clearSeatCover(el) {
|
|
334
|
+
if (!el || !el.classList.contains("spine")) return;
|
|
335
|
+
el.querySelectorAll(":scope > .cover-art").forEach((node) => node.remove());
|
|
336
|
+
try { el.style.removeProperty("background-color"); } catch (_) {}
|
|
337
|
+
}
|
|
338
|
+
|
|
315
339
|
function applyFaceSize(el, book, { expanded = false } = {}) {
|
|
316
340
|
if (!el || !book) return;
|
|
317
341
|
const faceH = bookFaceHeight(book);
|
|
@@ -580,6 +604,7 @@ export function mountBookshelf(root, options = {}) {
|
|
|
580
604
|
function restoreRestingSpine(el, bookId) {
|
|
581
605
|
if (!el) return;
|
|
582
606
|
el.classList.remove("is-expanded", "is-collapsing");
|
|
607
|
+
clearSeatCover(el);
|
|
583
608
|
const found = bookById(bookId);
|
|
584
609
|
if (found && found.book.cover && !found.book.filler) {
|
|
585
610
|
applyFaceSize(el, found.book, { expanded: false });
|
|
@@ -621,6 +646,7 @@ export function mountBookshelf(root, options = {}) {
|
|
|
621
646
|
el.classList.remove("is-collapsing");
|
|
622
647
|
el.classList.add("is-expanded");
|
|
623
648
|
applyFaceSize(el, found.book, { expanded: true });
|
|
649
|
+
mountSeatCover(el, found.book);
|
|
624
650
|
}
|
|
625
651
|
}
|
|
626
652
|
|
|
@@ -686,7 +712,20 @@ export function mountBookshelf(root, options = {}) {
|
|
|
686
712
|
$("#noteTitle").textContent = book.title;
|
|
687
713
|
$("#noteAuthor").textContent = book.author || "";
|
|
688
714
|
const coverEl = $("#noteCover");
|
|
689
|
-
|
|
715
|
+
// Intrinsic <img>, not the .img-* background. That background is
|
|
716
|
+
// background-size:cover inside a fixed 2:3 frame and clips landscape art.
|
|
717
|
+
coverEl.className = "note-cover";
|
|
718
|
+
coverEl.style.backgroundImage = "none";
|
|
719
|
+
coverEl.style.backgroundColor = "transparent";
|
|
720
|
+
coverEl.replaceChildren();
|
|
721
|
+
if (book.cover) {
|
|
722
|
+
const img = document.createElement("img");
|
|
723
|
+
img.className = "note-cover-img";
|
|
724
|
+
img.alt = "";
|
|
725
|
+
img.draggable = false;
|
|
726
|
+
img.src = coverSrc(book);
|
|
727
|
+
coverEl.appendChild(img);
|
|
728
|
+
}
|
|
690
729
|
const note = NOTES[book.note];
|
|
691
730
|
const read = $("#noteRead");
|
|
692
731
|
if (read) {
|
|
@@ -969,6 +1008,42 @@ export function mountBookshelf(root, options = {}) {
|
|
|
969
1008
|
return { offset, ...pathPose(transform) };
|
|
970
1009
|
}
|
|
971
1010
|
|
|
1011
|
+
// Close samples. T0 still carries the separate sx/sy height pin.
|
|
1012
|
+
// The old first stop was Tp3 (only 25% of the way home) at 0.28, so the
|
|
1013
|
+
// book was still nearly full-size for the first ~1s and read as overlap.
|
|
1014
|
+
function returnMoveKeys(Tfull, Tp1, T0) {
|
|
1015
|
+
return [
|
|
1016
|
+
pathKey(0, Tfull),
|
|
1017
|
+
pathKey(0.12, lerpTS(Tfull, T0, 0.35)),
|
|
1018
|
+
pathKey(0.32, lerpTS(Tfull, T0, 0.62)),
|
|
1019
|
+
pathKey(0.52, Tp1),
|
|
1020
|
+
pathKey(0.74, T0),
|
|
1021
|
+
pathKey(0.88, T0),
|
|
1022
|
+
pathKey(1, T0),
|
|
1023
|
+
];
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Note stays out until the flyer leaves, then a short handoff.
|
|
1027
|
+
// Rising from 0.65 while the flyer stayed solid until 0.90 was the double exposure.
|
|
1028
|
+
function noteInKeys() {
|
|
1029
|
+
return [
|
|
1030
|
+
{ offset: 0, opacity: 0 },
|
|
1031
|
+
{ offset: 0.82, opacity: 0 },
|
|
1032
|
+
{ offset: 0.90, opacity: 1 },
|
|
1033
|
+
{ offset: 1, opacity: 1 },
|
|
1034
|
+
];
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// Article is gone by ~0.12 (230ms). Shrink has already started on frame 0.
|
|
1038
|
+
function noteOutKeys() {
|
|
1039
|
+
return [
|
|
1040
|
+
{ offset: 0, opacity: 1 },
|
|
1041
|
+
{ offset: 0.05, opacity: 0.35 },
|
|
1042
|
+
{ offset: 0.12, opacity: 0 },
|
|
1043
|
+
{ offset: 1, opacity: 0 },
|
|
1044
|
+
];
|
|
1045
|
+
}
|
|
1046
|
+
|
|
972
1047
|
// ux14 geometric core: ONE diagonal arc slot-center ↔ screen-center.
|
|
973
1048
|
// X, Y, AND scale on the SAME beat — ban L-path (lift-only then slide-only).
|
|
974
1049
|
// Transforms are lerpTS(T0→Tfull) samples so WAAPI stays collinear in tx/ty/s.
|
|
@@ -1279,6 +1354,7 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1279
1354
|
"is-expanded",
|
|
1280
1355
|
"is-collapsing"
|
|
1281
1356
|
);
|
|
1357
|
+
clearSeatCover(el);
|
|
1282
1358
|
// Explicit removeProperty — kills commitStyles leftovers like opacity:0 / transform
|
|
1283
1359
|
[
|
|
1284
1360
|
"width",
|
|
@@ -1324,6 +1400,9 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1324
1400
|
|
|
1325
1401
|
const board = document.createElement("div");
|
|
1326
1402
|
board.className = "flyer-board";
|
|
1403
|
+
// ux27: never a solid shell behind the jacket. CSS also forces this off.
|
|
1404
|
+
board.style.opacity = "0";
|
|
1405
|
+
board.style.visibility = "hidden";
|
|
1327
1406
|
|
|
1328
1407
|
const left = document.createElement("div");
|
|
1329
1408
|
left.className = "flyer-leaf flyer-leaf-left";
|
|
@@ -1401,7 +1480,6 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1401
1480
|
left.style.transform = "rotateY(180deg)";
|
|
1402
1481
|
right.style.transform = "rotateY(0deg)";
|
|
1403
1482
|
right.style.opacity = "0";
|
|
1404
|
-
board.style.opacity = "0";
|
|
1405
1483
|
leftThick.style.opacity = "0";
|
|
1406
1484
|
rightThick.style.opacity = "0";
|
|
1407
1485
|
bookEl.style.transform = "rotateX(8deg) translate3d(0,0,0)";
|
|
@@ -1475,13 +1553,9 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1475
1553
|
noteView.classList.add("is-preparing");
|
|
1476
1554
|
noteView.classList.remove("is-open");
|
|
1477
1555
|
noteView.style.opacity = "0";
|
|
1478
|
-
const
|
|
1479
|
-
|
|
1480
|
-
const m = bg && bg.match(/url\(["']?([^"')]+)["']?\)/);
|
|
1481
|
-
if (m) {
|
|
1556
|
+
const img = $("#noteCover") && $("#noteCover").querySelector(".note-cover-img");
|
|
1557
|
+
if (img && !coverImgReady(img)) {
|
|
1482
1558
|
try {
|
|
1483
|
-
const img = new Image();
|
|
1484
|
-
img.src = m[1];
|
|
1485
1559
|
const DECODE_MS = 400;
|
|
1486
1560
|
const decodeOrLoad = img.decode
|
|
1487
1561
|
? img.decode()
|
|
@@ -1528,6 +1602,7 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1528
1602
|
busy = true;
|
|
1529
1603
|
const myFlow = ++flowSeq;
|
|
1530
1604
|
document.body.classList.add("is-busy");
|
|
1605
|
+
root.classList.remove("is-return");
|
|
1531
1606
|
openBook = { book, cat, asSpine, fromExpanded: !!fromExpanded, openSeatRect: null };
|
|
1532
1607
|
openFromExpanded = !!fromExpanded;
|
|
1533
1608
|
|
|
@@ -1568,6 +1643,11 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1568
1643
|
sourceEl.style.transition = "none";
|
|
1569
1644
|
sourceEl.style.transform = "none"; // kill :hover translateY / is-expanded lift
|
|
1570
1645
|
sourceEl.classList.add("is-extracting"); // CSS: suppress :hover transform while extracting
|
|
1646
|
+
// Keep the real jacket on the seat until the flyer exists. Dropping
|
|
1647
|
+
// is-expanded used to reveal the bk-* fill for the frames before the flyer.
|
|
1648
|
+
if (sourceEl.classList.contains("spine") && book && book.cover) {
|
|
1649
|
+
mountSeatCover(sourceEl, book);
|
|
1650
|
+
}
|
|
1571
1651
|
if (fromExpanded) {
|
|
1572
1652
|
sourceEl.classList.remove("is-expanded", "is-collapsing");
|
|
1573
1653
|
// Drop the expanded cover's width. Do NOT drop a resting spine's height:
|
|
@@ -1722,14 +1802,14 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1722
1802
|
{ offset: 0, opacity: 0 },
|
|
1723
1803
|
{ offset: 0.14, opacity: 0 },
|
|
1724
1804
|
{ offset: 0.22, opacity: 1 },
|
|
1725
|
-
{ offset: 0.
|
|
1726
|
-
{ offset: 0.
|
|
1805
|
+
{ offset: 0.82, opacity: 1 },
|
|
1806
|
+
{ offset: 0.90, opacity: 0 },
|
|
1727
1807
|
{ offset: 1.0, opacity: 0 },
|
|
1728
1808
|
]
|
|
1729
1809
|
: [
|
|
1730
1810
|
{ offset: 0, opacity: 1 },
|
|
1731
|
-
{ offset: 0.
|
|
1732
|
-
{ offset: 0.
|
|
1811
|
+
{ offset: 0.82, opacity: 1 },
|
|
1812
|
+
{ offset: 0.90, opacity: 0 },
|
|
1733
1813
|
{ offset: 1.0, opacity: 0 },
|
|
1734
1814
|
],
|
|
1735
1815
|
opts
|
|
@@ -1755,6 +1835,7 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1755
1835
|
const aExtractCoverIn = null;
|
|
1756
1836
|
|
|
1757
1837
|
// Pose fade on SAME layer while diagonal continues — ban origin-swapping phase.
|
|
1838
|
+
// Board stays out: fading it in painted a wide shell behind the cover by ~1350ms.
|
|
1758
1839
|
const closedFaceKf = [
|
|
1759
1840
|
{ offset: 0, opacity: 0 },
|
|
1760
1841
|
{ offset: 0.28, opacity: 0 },
|
|
@@ -1762,7 +1843,11 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1762
1843
|
{ offset: 0.52, opacity: 1 },
|
|
1763
1844
|
{ offset: 1.0, opacity: 1 },
|
|
1764
1845
|
];
|
|
1765
|
-
|
|
1846
|
+
if (dual._board) {
|
|
1847
|
+
dual._board.style.opacity = "0";
|
|
1848
|
+
dual._board.style.visibility = "hidden";
|
|
1849
|
+
}
|
|
1850
|
+
const aBoard = null;
|
|
1766
1851
|
const aRightFace = dual._right.animate(
|
|
1767
1852
|
[
|
|
1768
1853
|
{ offset: 0, opacity: 0, transform: "rotateY(0deg)" },
|
|
@@ -1803,20 +1888,9 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1803
1888
|
)
|
|
1804
1889
|
: null;
|
|
1805
1890
|
|
|
1806
|
-
//
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
{ offset: 0, opacity: 0 },
|
|
1810
|
-
{ offset: 0.65, opacity: 0 },
|
|
1811
|
-
{ offset: 0.70, opacity: 0.15 },
|
|
1812
|
-
{ offset: 0.78, opacity: 0.55 },
|
|
1813
|
-
{ offset: 0.85, opacity: 0.90 },
|
|
1814
|
-
{ offset: 0.90, opacity: 0.99 },
|
|
1815
|
-
{ offset: 0.92, opacity: 1 },
|
|
1816
|
-
{ offset: 1.0, opacity: 1 },
|
|
1817
|
-
],
|
|
1818
|
-
opts
|
|
1819
|
-
);
|
|
1891
|
+
// Note stays at 0 while the flyer owns the flight, then hands off by unlock (0.90).
|
|
1892
|
+
// FORBIDDEN: note body readable while the flyer is still solid (double exposure).
|
|
1893
|
+
const aNote = noteView.animate(noteInKeys(), opts);
|
|
1820
1894
|
|
|
1821
1895
|
const aBack = openBackdrop
|
|
1822
1896
|
? openBackdrop.animate(
|
|
@@ -1961,6 +2035,9 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1961
2035
|
// Clock first. Shelf measure, freeze, and rAF used to run before this and
|
|
1962
2036
|
// the 0.12 Tfull keyframe then held 672×504 for ~240ms. Stash from open is
|
|
1963
2037
|
// enough to start the return; height pin is already in those samples.
|
|
2038
|
+
// is-return puts the stage above the note so the shrink is not hidden
|
|
2039
|
+
// under an opaque article (that read as ~1s of overlap, then a jump).
|
|
2040
|
+
root.classList.add("is-return");
|
|
1964
2041
|
const closeMotion = { duration: CLOSE_MS, easing: "linear", fill: "forwards" };
|
|
1965
2042
|
const closeOpts = { duration: CLOSE_MS, easing: "linear", fill: "forwards" };
|
|
1966
2043
|
const stashEarly = openBook.pathTs;
|
|
@@ -1982,29 +2059,21 @@ export function mountBookshelf(root, options = {}) {
|
|
|
1982
2059
|
heldEarly.style.visibility = "visible";
|
|
1983
2060
|
heldEarly.style.zIndex = "70";
|
|
1984
2061
|
heldEarly.style.opacity = "1";
|
|
2062
|
+
if (heldEarly._board) {
|
|
2063
|
+
heldEarly._board.style.opacity = "0";
|
|
2064
|
+
heldEarly._board.style.visibility = "hidden";
|
|
2065
|
+
}
|
|
1985
2066
|
promote(heldEarly);
|
|
1986
2067
|
earlyMove = heldEarly.animate(
|
|
1987
|
-
|
|
1988
|
-
pathKey(0, stashEarly.Tfull),
|
|
1989
|
-
pathKey(0.28, stashEarly.Tp3),
|
|
1990
|
-
pathKey(0.44, stashEarly.Tp2),
|
|
1991
|
-
pathKey(0.60, stashEarly.Tp1),
|
|
1992
|
-
pathKey(0.78, stashEarly.T0),
|
|
1993
|
-
pathKey(0.90, stashEarly.T0),
|
|
1994
|
-
pathKey(1, stashEarly.T0),
|
|
1995
|
-
],
|
|
2068
|
+
returnMoveKeys(stashEarly.Tfull, stashEarly.Tp1, stashEarly.T0),
|
|
1996
2069
|
closeMotion
|
|
1997
2070
|
);
|
|
1998
|
-
earlyNote = noteView.animate(
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
{ offset: 1.0, opacity: 0 },
|
|
2005
|
-
],
|
|
2006
|
-
closeOpts
|
|
2007
|
-
);
|
|
2071
|
+
earlyNote = noteView.animate(noteOutKeys(), closeOpts);
|
|
2072
|
+
// Backup if the fade is dropped: the article must not still be up after ~230ms.
|
|
2073
|
+
wait(Math.round(CLOSE_MS * 0.12)).then(() => {
|
|
2074
|
+
if (myFlow !== flowSeq || !noteView) return;
|
|
2075
|
+
noteView.style.opacity = "0";
|
|
2076
|
+
});
|
|
2008
2077
|
if (heldEarly._left) {
|
|
2009
2078
|
earlyLeft = heldEarly._left.animate(
|
|
2010
2079
|
[
|
|
@@ -2058,6 +2127,7 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2058
2127
|
try { earlyNote && earlyNote.cancel(); } catch (_) {}
|
|
2059
2128
|
try { earlyLeft && earlyLeft.cancel(); } catch (_) {}
|
|
2060
2129
|
try { earlyRight && earlyRight.cancel(); } catch (_) {}
|
|
2130
|
+
root.classList.remove("is-return");
|
|
2061
2131
|
noteView.classList.remove("is-open", "is-preparing");
|
|
2062
2132
|
noteView.hidden = true;
|
|
2063
2133
|
noteView.style.opacity = "";
|
|
@@ -2081,7 +2151,10 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2081
2151
|
].forEach((p) => {
|
|
2082
2152
|
try { slot.style.removeProperty(p); } catch (_) {}
|
|
2083
2153
|
});
|
|
2084
|
-
slot.classList.remove("is-collapsing", "is-
|
|
2154
|
+
slot.classList.remove("is-collapsing", "is-expanded");
|
|
2155
|
+
// Keep the seat empty until handover. Dropping is-placeholder here let the
|
|
2156
|
+
// bk-* fill paint a solid rectangle under the returning cover.
|
|
2157
|
+
slot.classList.add("is-placeholder");
|
|
2085
2158
|
expandedId = null;
|
|
2086
2159
|
// Spines: re-pin the resting face BEFORE reading the seat. A cleared inline
|
|
2087
2160
|
// height is the shorter size-class box, and put-back would keep it.
|
|
@@ -2116,13 +2189,13 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2116
2189
|
});
|
|
2117
2190
|
shelfView.classList.add("is-frozen");
|
|
2118
2191
|
|
|
2119
|
-
//
|
|
2120
|
-
//
|
|
2192
|
+
// Note stays in the tree so its fade can finish, but must not cover the flyer.
|
|
2193
|
+
// is-return (set before the clock) owns stacking. Do not pin z-index 75.
|
|
2121
2194
|
noteView.classList.add("is-open");
|
|
2122
2195
|
noteView.classList.remove("is-preparing");
|
|
2123
2196
|
noteView.hidden = false;
|
|
2124
|
-
noteView.style.opacity = "1";
|
|
2125
|
-
noteView.style.zIndex = "
|
|
2197
|
+
if (!earlyNote) noteView.style.opacity = "1";
|
|
2198
|
+
noteView.style.zIndex = "";
|
|
2126
2199
|
|
|
2127
2200
|
unparkShelf();
|
|
2128
2201
|
shelfView.style.opacity = "1";
|
|
@@ -2217,7 +2290,10 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2217
2290
|
flyer._right.style.transform = "rotateY(-8deg)";
|
|
2218
2291
|
flyer._right.style.opacity = "1";
|
|
2219
2292
|
}
|
|
2220
|
-
if (flyer._board)
|
|
2293
|
+
if (flyer._board) {
|
|
2294
|
+
flyer._board.style.opacity = "0";
|
|
2295
|
+
flyer._board.style.visibility = "hidden";
|
|
2296
|
+
}
|
|
2221
2297
|
if (flyer._leftThick) flyer._leftThick.style.opacity = "1";
|
|
2222
2298
|
if (flyer._rightThick) flyer._rightThick.style.opacity = "1";
|
|
2223
2299
|
if (flyer._gutter) flyer._gutter.style.opacity = "1";
|
|
@@ -2280,29 +2356,13 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2280
2356
|
|
|
2281
2357
|
resetVeil();
|
|
2282
2358
|
|
|
2283
|
-
//
|
|
2284
|
-
const aNoteOut = earlyNote || noteView.animate(
|
|
2285
|
-
[
|
|
2286
|
-
{ offset: 0, opacity: 1 },
|
|
2287
|
-
{ offset: 0.06, opacity: 0.85 },
|
|
2288
|
-
{ offset: 0.12, opacity: 0.45 },
|
|
2289
|
-
{ offset: 0.22, opacity: 0 },
|
|
2290
|
-
{ offset: 1.0, opacity: 0 },
|
|
2291
|
-
],
|
|
2292
|
-
closeOpts
|
|
2293
|
-
);
|
|
2359
|
+
// Note fades out in the first ~230ms. Shrink already started on frame 0.
|
|
2360
|
+
const aNoteOut = earlyNote || noteView.animate(noteOutKeys(), closeOpts);
|
|
2294
2361
|
|
|
2295
|
-
// Shrink starts at offset 0
|
|
2362
|
+
// Shrink starts at offset 0 and is already well under way by ~400ms.
|
|
2363
|
+
// A first stop on Tp3 (25% home) at 0.28 left the book looking full for ~1s.
|
|
2296
2364
|
const aFlyerMove = earlyMove || flyer.animate(
|
|
2297
|
-
|
|
2298
|
-
pathKey(0, Tfull),
|
|
2299
|
-
pathKey(0.28, Tp3),
|
|
2300
|
-
pathKey(0.44, Tp2),
|
|
2301
|
-
pathKey(0.60, Tp1),
|
|
2302
|
-
pathKey(0.78, T0),
|
|
2303
|
-
pathKey(0.90, T0),
|
|
2304
|
-
pathKey(1, T0),
|
|
2305
|
-
],
|
|
2365
|
+
returnMoveKeys(Tfull, Tp1, T0),
|
|
2306
2366
|
closeMotion
|
|
2307
2367
|
);
|
|
2308
2368
|
// Reverse hinge on SAME layer while shrink (open was 0.32→0.68 hinge).
|
|
@@ -2336,7 +2396,7 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2336
2396
|
{ offset: 0.28, opacity: 0 },
|
|
2337
2397
|
{ offset: 1.0, opacity: 0 },
|
|
2338
2398
|
];
|
|
2339
|
-
const aBoardClose =
|
|
2399
|
+
const aBoardClose = null;
|
|
2340
2400
|
const aThickLClose = flyer._leftThick ? flyer._leftThick.animate(closedFaceOut, closeOpts) : null;
|
|
2341
2401
|
const aThickRClose = flyer._rightThick ? flyer._rightThick.animate(closedFaceOut, closeOpts) : null;
|
|
2342
2402
|
const aGutterClose = flyer._gutter
|
|
@@ -2377,9 +2437,11 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2377
2437
|
const aFlyerFade = flyer.animate(
|
|
2378
2438
|
targetMode === "spines"
|
|
2379
2439
|
? [
|
|
2440
|
+
// Stay through the shrink. Cutting at 0.58 dropped a still-large
|
|
2441
|
+
// cover and made the first ~1.1s look like overlap, then a pop.
|
|
2380
2442
|
{ offset: 0, opacity: 1 },
|
|
2381
|
-
{ offset: 0.
|
|
2382
|
-
{ offset: 0.
|
|
2443
|
+
{ offset: 0.68, opacity: 1 },
|
|
2444
|
+
{ offset: 0.80, opacity: 0 },
|
|
2383
2445
|
{ offset: 1.0, opacity: 0 },
|
|
2384
2446
|
]
|
|
2385
2447
|
: [
|
|
@@ -2411,10 +2473,10 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2411
2473
|
? spineSlotClose.animate(
|
|
2412
2474
|
[
|
|
2413
2475
|
{ offset: 0, transform: outClose, opacity: 0 },
|
|
2414
|
-
{ offset: 0.
|
|
2415
|
-
{ offset: 0.
|
|
2416
|
-
{ offset: 0.
|
|
2417
|
-
{ offset: 0.
|
|
2476
|
+
{ offset: 0.66, transform: outClose, opacity: 0 },
|
|
2477
|
+
{ offset: 0.74, transform: outClose, opacity: 1 },
|
|
2478
|
+
{ offset: 0.86, transform: "translate3d(0px, 0px, 0)", opacity: 1 },
|
|
2479
|
+
{ offset: 0.94, transform: "translate3d(0px, 0px, 0)", opacity: 1 },
|
|
2418
2480
|
{ offset: 1.0, transform: "translate3d(0px, 0px, 0)", opacity: 0 },
|
|
2419
2481
|
],
|
|
2420
2482
|
closeMotion
|
|
@@ -2429,8 +2491,8 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2429
2491
|
targetMode === "spines"
|
|
2430
2492
|
? [
|
|
2431
2493
|
{ offset: 0, opacity: 1 },
|
|
2432
|
-
{ offset: 0.
|
|
2433
|
-
{ offset: 0.
|
|
2494
|
+
{ offset: 0.68, opacity: 1 },
|
|
2495
|
+
{ offset: 0.80, opacity: 0 },
|
|
2434
2496
|
{ offset: 1, opacity: 0 },
|
|
2435
2497
|
]
|
|
2436
2498
|
: [
|
|
@@ -2452,6 +2514,13 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2452
2514
|
slot.style.backgroundColor = "";
|
|
2453
2515
|
}
|
|
2454
2516
|
|
|
2517
|
+
// Seat may show its real face only as the flyer hands off. Until then the
|
|
2518
|
+
// placeholder ban (transparent, invisible) blocks the solid bk plate.
|
|
2519
|
+
wait(Math.round(CLOSE_MS * Math.max(0, slotInAt - 0.02))).then(() => {
|
|
2520
|
+
if (myFlow !== flowSeq || !slot.isConnected) return;
|
|
2521
|
+
slot.classList.remove("is-placeholder");
|
|
2522
|
+
});
|
|
2523
|
+
|
|
2455
2524
|
const aSlotIn = slot.animate(
|
|
2456
2525
|
[
|
|
2457
2526
|
{ offset: 0, opacity: 0 },
|
|
@@ -2510,6 +2579,7 @@ export function mountBookshelf(root, options = {}) {
|
|
|
2510
2579
|
flyer.style.opacity = "0";
|
|
2511
2580
|
slot.style.opacity = "1";
|
|
2512
2581
|
slot.style.transform = "none";
|
|
2582
|
+
root.classList.remove("is-return");
|
|
2513
2583
|
noteView.classList.remove("is-open", "is-preparing");
|
|
2514
2584
|
noteView.hidden = true;
|
|
2515
2585
|
noteView.style.opacity = "";
|