hd-garden 0.8.4 → 0.9.1

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.
Files changed (49) hide show
  1. package/app/bookshelf/load.mjs +96 -0
  2. package/app/bookshelf/load.ts +16 -0
  3. package/app/bookshelf/page.tsx +52 -0
  4. package/app/components/SiteChrome.tsx +17 -3
  5. package/app/components/SiteHeader.tsx +3 -3
  6. package/app/components/bookshelf/Bookshelf.tsx +117 -0
  7. package/app/components/bookshelf/bookshelf.css +1288 -0
  8. package/app/components/bookshelf/catalog.mjs +116 -0
  9. package/app/components/bookshelf/catalog.ts +23 -0
  10. package/app/components/bookshelf/demo-data.ts +93 -0
  11. package/app/components/bookshelf/density.ts +32 -0
  12. package/app/components/bookshelf/engine.ts +2690 -0
  13. package/app/components/bookshelf/index.ts +12 -0
  14. package/app/components/bookshelf/model.ts +16 -0
  15. package/app/components/bookshelf/schema.mjs +86 -0
  16. package/app/components/bookshelf/schema.ts +68 -0
  17. package/app/components/bookshelf/types.ts +55 -0
  18. package/app/garden-modules.d.ts +6 -0
  19. package/app/layout.tsx +2 -1
  20. package/app/lib/i18n.ts +2 -2
  21. package/app/sitemap.ts +3 -0
  22. package/docs/handbook/config.md +41 -2
  23. package/docs/handbook/en/config.md +41 -2
  24. package/package.json +2 -1
  25. package/public/covers/accelerate.jpg +0 -0
  26. package/public/covers/clean-architecture.jpg +0 -0
  27. package/public/covers/clean-code.jpg +0 -0
  28. package/public/covers/continuous-delivery.jpg +0 -0
  29. package/public/covers/ddd.jpg +0 -0
  30. package/public/covers/ddia.jpg +0 -0
  31. package/public/covers/design-patterns.jpg +0 -0
  32. package/public/covers/eaa.jpg +0 -0
  33. package/public/covers/fundamentals-sa.jpg +0 -0
  34. package/public/covers/implementing-ddd.jpg +0 -0
  35. package/public/covers/lean-startup.jpg +0 -0
  36. package/public/covers/microservices.jpg +0 -0
  37. package/public/covers/mythical-man-month.jpg +0 -0
  38. package/public/covers/phoenix-project.jpg +0 -0
  39. package/public/covers/pragmatic-programmer.jpg +0 -0
  40. package/public/covers/refactoring.jpg +0 -0
  41. package/public/covers/release-it.jpg +0 -0
  42. package/public/covers/sre.jpg +0 -0
  43. package/public/covers/systems-performance.jpg +0 -0
  44. package/public/covers/tdd.jpg +0 -0
  45. package/public/covers/team-topologies.jpg +0 -0
  46. package/scripts/build-content.mjs +17 -1
  47. package/scripts/handbook-facts.mjs +2 -1
  48. package/ui/garden/site-nav.mjs +12 -0
  49. package/vite.config.ts +12 -3
@@ -0,0 +1,1288 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500&family=Noto+Serif+SC:wght@500;600&display=swap');
2
+ /* tip: ux26-open-pages held-book-s10-ux26-20261028 — dual pages: cover palette shell + left title + right summary; keep ux25 frame/plate */
3
+
4
+ /* ux14: kill :hover / expand lift while extracting — freeze resting rect */
5
+ .bookshelf-demo .cover.is-extracting,
6
+ .bookshelf-demo .cover.is-extracting:hover,
7
+ .bookshelf-demo .spine.is-extracting,
8
+ .bookshelf-demo .spine.is-extracting:hover,
9
+ .bookshelf-demo .spine.is-extracting.is-expanded,
10
+ .bookshelf-demo .spine.is-seat,
11
+ .bookshelf-demo .spine.is-seat:hover,
12
+ .bookshelf-demo .spine.is-seat:focus-visible {
13
+ transform: none !important;
14
+ box-shadow: none !important;
15
+ transition: none !important;
16
+ }
17
+
18
+
19
+
20
+ .bookshelf-demo {
21
+ --wall: #faf9f5;
22
+ --ink: #1b1a18;
23
+ --muted: #6e6b64;
24
+ --faint: #a09b91;
25
+ --rail-top: #e8e2d4;
26
+ --rail-bot: #cfc7b6;
27
+ --rail-shadow: rgba(60, 50, 30, 0.08);
28
+ --line: #e6e3dc;
29
+ --col: 740px;
30
+ --hover-ring: rgba(27, 26, 24, 0.18);
31
+ --hover-lift: 0 10px 22px rgba(40, 30, 20, 0.14);
32
+ --pad: 24px;
33
+
34
+ --t-expand: 250ms;
35
+ --t-collapse: 230ms;
36
+ --t-pull: 500ms;
37
+ --t-flip: 1000ms;
38
+ --t-settle: 580ms;
39
+ --t-putback: 500ms;
40
+ --t-land: 480ms;
41
+ --t-veil: 580ms;
42
+ --ease-motion: cubic-bezier(0.32, 0.72, 0, 1);
43
+ --ease-out: var(--ease-motion);
44
+ --ease-in: var(--ease-motion);
45
+ --ease-inout: var(--ease-motion);
46
+ --ease-pull: var(--ease-motion);
47
+ }
48
+
49
+ .bookshelf-demo, .bookshelf-demo * { box-sizing: border-box; }
50
+ .bookshelf-demo,
51
+ .bookshelf-demo { margin: 0; overflow-x: hidden; overflow-x: clip; }
52
+ .bookshelf-demo {
53
+ background: var(--wall);
54
+ color: var(--ink);
55
+ font-family: "Noto Sans SC", "PingFang SC", sans-serif;
56
+ font-size: 16px;
57
+ line-height: normal;
58
+ -webkit-font-smoothing: antialiased;
59
+ min-height: 100vh;
60
+ }
61
+
62
+ .bookshelf-demo { position: relative; min-height: 100vh; overflow-x: hidden; max-width: 100%; }
63
+ .bookshelf-demo .shelf-view { overflow-x: hidden; max-width: 100%; }
64
+
65
+ .bookshelf-demo .page {
66
+ width: min(100%, var(--col));
67
+ max-width: 100%;
68
+ margin: 0 auto;
69
+ padding: 40px var(--pad) 56px;
70
+ overflow-x: hidden;
71
+ }
72
+
73
+ .bookshelf-demo .brand {
74
+ font-size: 13px;
75
+ color: var(--muted);
76
+ letter-spacing: 0.06em;
77
+ margin-bottom: 24px;
78
+ }
79
+ .bookshelf-demo h1 {
80
+ margin: 0 0 10px;
81
+ font-family: "Noto Serif SC", "Songti SC", serif;
82
+ font-size: clamp(28px, 5vw, 36px);
83
+ font-weight: 600;
84
+ }
85
+ .bookshelf-demo .lede {
86
+ margin: 0 0 20px;
87
+ font-size: 14px;
88
+ line-height: 1.7;
89
+ color: var(--muted);
90
+ }
91
+
92
+ .bookshelf-demo .qa-bar {
93
+ display: flex;
94
+ flex-wrap: wrap;
95
+ align-items: center;
96
+ gap: 8px;
97
+ margin-bottom: 36px;
98
+ padding: 10px 12px;
99
+ background: rgba(255,255,255,0.45);
100
+ border: 1px solid var(--line);
101
+ border-radius: 6px;
102
+ }
103
+ .bookshelf-demo .qa-label {
104
+ font-size: 11px;
105
+ color: var(--faint);
106
+ letter-spacing: 0.08em;
107
+ margin-right: 4px;
108
+ }
109
+ .bookshelf-demo .qa-btn {
110
+ border: 1px solid var(--line);
111
+ background: #fff;
112
+ color: var(--ink);
113
+ font-size: 12px;
114
+ padding: 5px 10px;
115
+ border-radius: 4px;
116
+ cursor: pointer;
117
+ font-family: inherit;
118
+ }
119
+ .bookshelf-demo .qa-btn.is-active {
120
+ background: var(--ink);
121
+ color: var(--wall);
122
+ border-color: var(--ink);
123
+ }
124
+ .bookshelf-demo .qa-hint {
125
+ font-size: 11px;
126
+ color: var(--muted);
127
+ margin-left: auto;
128
+ }
129
+
130
+ .bookshelf-demo .bay { margin-bottom: 36px; min-width: 0; max-width: 100%; }
131
+ .bookshelf-demo .row-label {
132
+ display: flex;
133
+ align-items: baseline;
134
+ gap: 12px;
135
+ margin-bottom: 12px;
136
+ }
137
+ .bookshelf-demo .row-label .name {
138
+ font-size: 12px;
139
+ color: var(--muted);
140
+ letter-spacing: 0.12em;
141
+ }
142
+ .bookshelf-demo .row-label .hint {
143
+ font-size: 11px;
144
+ color: var(--faint);
145
+ letter-spacing: 0.04em;
146
+ }
147
+
148
+ .bookshelf-demo .ledge {
149
+ position: relative;
150
+ padding-bottom: 10px;
151
+ width: 100%;
152
+ max-width: 100%;
153
+ min-width: 0;
154
+ overflow: hidden; /* books scroll inside .covers; rail stays bay-wide */
155
+ }
156
+ .bookshelf-demo .rail {
157
+ height: 7px;
158
+ width: 100%;
159
+ background: linear-gradient(180deg, var(--rail-top) 0%, var(--rail-bot) 100%);
160
+ border-radius: 1px;
161
+ box-shadow: 0 6px 14px var(--rail-shadow);
162
+ }
163
+
164
+ .bookshelf-demo .covers {
165
+ display: flex;
166
+ align-items: flex-end;
167
+ gap: 14px;
168
+ min-height: 210px;
169
+ padding-bottom: 2px;
170
+ flex-wrap: nowrap;
171
+ /* ux17: density gate must prevent overflow; never scroll-clip half covers */
172
+ overflow-x: visible;
173
+ }
174
+
175
+ /* ux22: mobile — each .covers row is a shelf rail; page X stays put */
176
+ @media (hover: none), (max-width: 640px) {
177
+ .bookshelf-demo .covers {
178
+ flex-wrap: nowrap;
179
+ gap: 10px;
180
+ width: 100%;
181
+ max-width: 100%;
182
+ min-width: 0;
183
+ overflow-x: auto;
184
+ overflow-y: hidden;
185
+ -webkit-overflow-scrolling: touch;
186
+ overscroll-behavior-x: contain;
187
+ scrollbar-width: none;
188
+ touch-action: pan-x pan-y;
189
+ }
190
+ .bookshelf-demo .covers::-webkit-scrollbar { display: none; }
191
+ .bookshelf-demo .cover {
192
+ width: 102px;
193
+ flex-shrink: 0;
194
+ }
195
+ }
196
+
197
+ .bookshelf-demo .cover {
198
+ width: 118px;
199
+ height: 178px; /* fallback; JS sets natural aspect height */
200
+ border: none;
201
+ padding: 0;
202
+ display: block;
203
+ cursor: pointer;
204
+ border-radius: 2px 3px 0 0;
205
+ background-color: transparent; /* ux24: no preset plate behind art */
206
+ background-size: contain; /* ux17: ban crop */
207
+ background-position: center top;
208
+ background-repeat: no-repeat;
209
+ box-shadow:
210
+ inset -2px 0 0 rgba(0,0,0,0.12),
211
+ inset 1px 0 0 rgba(255,255,255,0.08),
212
+ 0 6px 14px rgba(40, 30, 20, 0.08);
213
+ transform: translateY(0) scale(1);
214
+ transition: transform 0.18s ease, box-shadow 0.18s ease, opacity 0.2s ease;
215
+ position: relative;
216
+ overflow: hidden;
217
+ flex-shrink: 0;
218
+ }
219
+ .bookshelf-demo .cover:focus-visible {
220
+ transform: translateY(-10px) scale(1.04);
221
+ box-shadow:
222
+ 0 0 0 2px rgba(255,255,255,0.85),
223
+ 0 0 0 3px var(--hover-ring),
224
+ var(--hover-lift);
225
+ z-index: 2;
226
+ outline: none;
227
+ }
228
+ @media (hover: hover) and (pointer: fine) {
229
+ .bookshelf-demo .cover:hover {
230
+ transform: translateY(-10px) scale(1.04);
231
+ box-shadow:
232
+ 0 0 0 2px rgba(255,255,255,0.85),
233
+ 0 0 0 3px var(--hover-ring),
234
+ var(--hover-lift);
235
+ z-index: 2;
236
+ }
237
+ }
238
+ .bookshelf-demo .cover.is-placeholder {
239
+ /* ux13+ux24: invisible seat — no gray/wood/bk plate while flyer is out */
240
+ opacity: 0 !important;
241
+ visibility: hidden !important;
242
+ background-color: transparent !important;
243
+ background-image: none !important;
244
+ filter: none;
245
+ pointer-events: none;
246
+ box-shadow: none;
247
+ transform: none !important;
248
+ }
249
+ .bookshelf-demo .cover.is-placeholder > .cover-art {
250
+ visibility: hidden !important;
251
+ opacity: 0 !important;
252
+ }
253
+ /* ux24: resting covers must show art, never solid bk-* shell */
254
+ .bookshelf-demo .cover[class*="img-"] {
255
+ background-color: transparent !important;
256
+ }
257
+ .bookshelf-demo .cover > .cover-art {
258
+ position: absolute;
259
+ inset: 0;
260
+ width: 100%;
261
+ height: 100%;
262
+ /* ux25: seat = nat aspect (JS) — contain fills exactly; no letterbox plate */
263
+ object-fit: contain;
264
+ object-position: center top;
265
+ display: block;
266
+ pointer-events: none;
267
+ border-radius: inherit;
268
+ z-index: 0;
269
+ }
270
+
271
+ .bookshelf-demo .spines {
272
+ display: flex;
273
+ align-items: flex-end;
274
+ gap: 4px;
275
+ min-height: 188px;
276
+ flex-wrap: nowrap;
277
+ overflow-x: auto;
278
+ -webkit-overflow-scrolling: touch;
279
+ overscroll-behavior-x: contain;
280
+ scrollbar-width: none;
281
+ }
282
+ .bookshelf-demo .spines::-webkit-scrollbar { display: none; }
283
+
284
+ .bookshelf-demo .spine {
285
+ height: 150px;
286
+ width: 28px;
287
+ border: none;
288
+ padding: 10px 0;
289
+ writing-mode: vertical-rl;
290
+ text-orientation: mixed;
291
+ font-family: "Noto Serif SC", serif;
292
+ font-size: 12px;
293
+ letter-spacing: 0.16em;
294
+ color: #F7F3EA;
295
+ cursor: pointer;
296
+ border-radius: 1px 2px 0 0;
297
+ box-shadow:
298
+ inset -1px 0 0 rgba(255,255,255,0.12),
299
+ inset 1px 0 0 rgba(0,0,0,0.18);
300
+ display: flex;
301
+ align-items: center;
302
+ justify-content: center;
303
+ transform: translateY(0) scale(1);
304
+ transition:
305
+ transform 0.16s ease,
306
+ box-shadow 0.16s ease,
307
+ filter 0.16s ease,
308
+ width var(--t-expand) var(--ease-out),
309
+ height var(--t-expand) var(--ease-out),
310
+ margin var(--t-expand) var(--ease-out),
311
+ opacity 0.2s ease,
312
+ background-size var(--t-expand) var(--ease-out);
313
+ flex-shrink: 0;
314
+ position: relative;
315
+ overflow: hidden;
316
+ background-size: contain;
317
+ background-position: center top;
318
+ background-repeat: no-repeat;
319
+ }
320
+ .bookshelf-demo .spine.thin { width: 18px; font-size: 10px; height: 122px; color: #2A261E; }
321
+ .bookshelf-demo .spine.light-text { color: #F7F3EA; }
322
+ .bookshelf-demo .spine.dark-text { color: #1b1a18; }
323
+ .bookshelf-demo .spine span { pointer-events: none; transition: opacity 120ms ease; }
324
+ .bookshelf-demo .spine:focus-visible {
325
+ transform: translateY(-8px) scale(1.06);
326
+ box-shadow:
327
+ 0 0 0 2px rgba(255,255,255,0.9),
328
+ 0 0 0 3px var(--hover-ring),
329
+ 0 8px 16px rgba(40, 30, 20, 0.16);
330
+ filter: brightness(1.08);
331
+ z-index: 2;
332
+ outline: none;
333
+ }
334
+ @media (hover: hover) and (pointer: fine) {
335
+ .bookshelf-demo .spine:hover {
336
+ transform: translateY(-8px) scale(1.06);
337
+ box-shadow:
338
+ 0 0 0 2px rgba(255,255,255,0.9),
339
+ 0 0 0 3px var(--hover-ring),
340
+ 0 8px 16px rgba(40, 30, 20, 0.16);
341
+ filter: brightness(1.08);
342
+ z-index: 2;
343
+ }
344
+ }
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; }
354
+ .bookshelf-demo .cover.is-placeholder.is-landing,
355
+ .bookshelf-demo .spine.is-placeholder.is-landing {
356
+ opacity: 0;
357
+ transition: opacity 220ms var(--ease-motion);
358
+ }
359
+
360
+ /* Mode C: temporary expand on spine wall */
361
+ /* resting spines: solid hue only; cover image only when expanded */
362
+ .bookshelf-demo .spine[class*="img-"] {
363
+ background-image: none !important;
364
+ }
365
+ .bookshelf-demo .spine.is-expanded[class*="img-"] {
366
+ background-image: unset;
367
+ }
368
+ .bookshelf-demo .spine.is-expanded.img-clean-code { background-image: url("/covers/clean-code.jpg") !important; }
369
+ .bookshelf-demo .spine.is-expanded.img-refactoring { background-image: url("/covers/refactoring.jpg") !important; }
370
+ .bookshelf-demo .spine.is-expanded.img-design-patterns { background-image: url("/covers/design-patterns.jpg") !important; }
371
+ .bookshelf-demo .spine.is-expanded.img-pragmatic-programmer { background-image: url("/covers/pragmatic-programmer.jpg") !important; }
372
+ .bookshelf-demo .spine.is-expanded.img-continuous-delivery { background-image: url("/covers/continuous-delivery.jpg") !important; }
373
+ .bookshelf-demo .spine.is-expanded.img-phoenix-project { background-image: url("/covers/phoenix-project.jpg") !important; }
374
+ .bookshelf-demo .spine.is-expanded.img-mythical-man-month { background-image: url("/covers/mythical-man-month.jpg") !important; }
375
+ .bookshelf-demo .spine.is-expanded.img-tdd { background-image: url("/covers/tdd.jpg") !important; }
376
+ .bookshelf-demo .spine.is-expanded.img-lean-startup { background-image: url("/covers/lean-startup.jpg") !important; }
377
+ .bookshelf-demo .spine.is-expanded.img-release-it { background-image: url("/covers/release-it.jpg") !important; }
378
+ .bookshelf-demo .spine.is-expanded.img-sre { background-image: url("/covers/sre.jpg") !important; }
379
+ .bookshelf-demo .spine.is-expanded.img-team-topologies { background-image: url("/covers/team-topologies.jpg") !important; }
380
+ .bookshelf-demo .spine.is-expanded.img-accelerate { background-image: url("/covers/accelerate.jpg") !important; }
381
+ .bookshelf-demo .spine.is-expanded.img-ddd { background-image: url("/covers/ddd.jpg") !important; }
382
+ .bookshelf-demo .spine.is-expanded.img-microservices { background-image: url("/covers/microservices.jpg") !important; }
383
+ .bookshelf-demo .spine.is-expanded.img-eaa { background-image: url("/covers/eaa.jpg") !important; }
384
+ .bookshelf-demo .spine.is-expanded.img-ddia { background-image: url("/covers/ddia.jpg") !important; }
385
+ .bookshelf-demo .spine.is-expanded.img-fundamentals-sa { background-image: url("/covers/fundamentals-sa.jpg") !important; }
386
+ .bookshelf-demo .spine.is-expanded.img-implementing-ddd { background-image: url("/covers/implementing-ddd.jpg") !important; }
387
+ .bookshelf-demo .spine.is-expanded.img-systems-performance { background-image: url("/covers/systems-performance.jpg") !important; }
388
+ .bookshelf-demo .spine.is-expanded.img-clean-architecture { background-image: url("/covers/clean-architecture.jpg") !important; }
389
+
390
+ .bookshelf-demo .spine.is-expanded {
391
+ width: 100px;
392
+ height: 152px;
393
+ margin: 0 4px 0 2px;
394
+ padding: 0;
395
+ writing-mode: horizontal-tb;
396
+ text-orientation: initial;
397
+ letter-spacing: 0;
398
+ transform: translateY(-10px) scale(1.03);
399
+ box-shadow:
400
+ inset -2px 0 0 rgba(0,0,0,0.14),
401
+ 0 0 0 2px rgba(255,255,255,0.9),
402
+ 0 0 0 3px var(--hover-ring),
403
+ var(--hover-lift);
404
+ filter: none;
405
+ z-index: 3;
406
+ transition:
407
+ width var(--t-expand) var(--ease-out),
408
+ height var(--t-expand) var(--ease-out),
409
+ margin var(--t-expand) var(--ease-out),
410
+ transform var(--t-expand) var(--ease-out),
411
+ box-shadow var(--t-expand) var(--ease-out);
412
+ }
413
+ .bookshelf-demo .spine.is-expanded span { opacity: 0; position: absolute; }
414
+ .bookshelf-demo .spine.is-collapsing {
415
+ transition:
416
+ width var(--t-collapse) var(--ease-motion),
417
+ height var(--t-collapse) var(--ease-motion),
418
+ margin var(--t-collapse) var(--ease-motion),
419
+ transform var(--t-collapse) var(--ease-motion),
420
+ box-shadow var(--t-collapse) var(--ease-motion),
421
+ background-size var(--t-collapse) var(--ease-motion);
422
+ }
423
+ /* C collapse: shrink while still "expanded" class — drop cover image to solid hue immediately, never flash a wide black plate */
424
+ .bookshelf-demo .spine.is-expanded.is-collapsing {
425
+ width: 28px;
426
+ height: 150px;
427
+ margin: 0;
428
+ padding: 10px 0;
429
+ writing-mode: vertical-rl;
430
+ text-orientation: mixed;
431
+ letter-spacing: 0.16em;
432
+ transform: translateY(0) scale(1);
433
+ filter: none;
434
+ box-shadow:
435
+ inset -1px 0 0 rgba(255,255,255,0.12),
436
+ inset 1px 0 0 rgba(0,0,0,0.18);
437
+ background-image: none !important;
438
+ background-size: auto;
439
+ z-index: 2;
440
+ }
441
+ .bookshelf-demo .spine.is-expanded.is-collapsing.s-tall { width: 30px; height: 168px; }
442
+ .bookshelf-demo .spine.is-expanded.is-collapsing.s-mid { width: 28px; height: 152px; }
443
+ .bookshelf-demo .spine.is-expanded.is-collapsing.s-short { width: 24px; height: 136px; }
444
+ .bookshelf-demo .spine.is-expanded.is-collapsing.thin { width: 18px; height: 122px; }
445
+ .bookshelf-demo .spine.is-expanded.is-collapsing span {
446
+ opacity: 1;
447
+ position: static;
448
+ }
449
+
450
+ .bookshelf-demo .s-tall { height: 168px; width: 30px; }
451
+ .bookshelf-demo .s-mid { height: 152px; width: 28px; }
452
+ .bookshelf-demo .s-short { height: 136px; width: 24px; }
453
+ .bookshelf-demo .spine.is-expanded.s-tall,
454
+ .bookshelf-demo .spine.is-expanded.s-mid,
455
+ .bookshelf-demo .spine.is-expanded.s-short {
456
+ width: 100px;
457
+ height: 152px;
458
+ }
459
+
460
+ /* book colors */
461
+ .bookshelf-demo .bk-clean-code { background-color: #0d1b2a; }
462
+ .bookshelf-demo .bk-refactoring { background-color: #7a0d1b; }
463
+ .bookshelf-demo .bk-design-patterns { background-color: #0d1b1b; }
464
+ .bookshelf-demo .bk-continuous-delivery { background-color: #be2828; }
465
+ .bookshelf-demo .bk-phoenix-project { background-color: #3a7ab8; }
466
+ .bookshelf-demo .bk-mythical-man-month { background-color: #1b3688; }
467
+ .bookshelf-demo .bk-pragmatic-programmer { background-color: #1a1a1a; }
468
+ .bookshelf-demo .bk-tdd { background-color: #513688; }
469
+ .bookshelf-demo .bk-lean-startup { background-color: #3a3a3a; }
470
+ .bookshelf-demo .bk-release-it { background-color: #5a6a88; }
471
+ .bookshelf-demo .bk-sre { background-color: #00367a; }
472
+ .bookshelf-demo .bk-team-topologies { background-color: #2a6a88; }
473
+ .bookshelf-demo .bk-accelerate { background-color: #36445f; }
474
+ .bookshelf-demo .bk-ddd { background-color: #1e4a7a; }
475
+ .bookshelf-demo .bk-microservices { background-color: #a38851; }
476
+ .bookshelf-demo .bk-eaa { background-color: #950d1b; }
477
+ .bookshelf-demo .bk-ddia { background-color: #8b3a2f; }
478
+ .bookshelf-demo .bk-fundamentals-sa { background-color: #44881b; }
479
+ .bookshelf-demo .bk-implementing-ddd { background-color: #b03644; }
480
+ .bookshelf-demo .bk-systems-performance { background-color: #00447a; }
481
+ .bookshelf-demo .bk-clean-architecture { background-color: #0d1b44; }
482
+ .bookshelf-demo .bk-sand { background-color: #c4b7a2; color: #2A261E; }
483
+ .bookshelf-demo .bk-cream { background-color: #d2c4ae; color: #2A261E; }
484
+
485
+ .bookshelf-demo .img-clean-code { background-image: url("/covers/clean-code.jpg"); }
486
+ .bookshelf-demo .img-refactoring { background-image: url("/covers/refactoring.jpg"); }
487
+ .bookshelf-demo .img-design-patterns { background-image: url("/covers/design-patterns.jpg"); }
488
+ .bookshelf-demo .img-pragmatic-programmer { background-image: url("/covers/pragmatic-programmer.jpg"); }
489
+ .bookshelf-demo .img-continuous-delivery { background-image: url("/covers/continuous-delivery.jpg"); }
490
+ .bookshelf-demo .img-phoenix-project { background-image: url("/covers/phoenix-project.jpg"); }
491
+ .bookshelf-demo .img-mythical-man-month { background-image: url("/covers/mythical-man-month.jpg"); }
492
+ .bookshelf-demo .img-tdd { background-image: url("/covers/tdd.jpg"); }
493
+ .bookshelf-demo .img-lean-startup { background-image: url("/covers/lean-startup.jpg"); }
494
+ .bookshelf-demo .img-release-it { background-image: url("/covers/release-it.jpg"); }
495
+ .bookshelf-demo .img-sre { background-image: url("/covers/sre.jpg"); }
496
+ .bookshelf-demo .img-team-topologies { background-image: url("/covers/team-topologies.jpg"); }
497
+ .bookshelf-demo .img-accelerate { background-image: url("/covers/accelerate.jpg"); }
498
+ .bookshelf-demo .img-ddd { background-image: url("/covers/ddd.jpg"); }
499
+ .bookshelf-demo .img-microservices { background-image: url("/covers/microservices.jpg"); }
500
+ .bookshelf-demo .img-eaa { background-image: url("/covers/eaa.jpg"); }
501
+ .bookshelf-demo .img-ddia { background-image: url("/covers/ddia.jpg"); }
502
+ .bookshelf-demo .img-fundamentals-sa { background-image: url("/covers/fundamentals-sa.jpg"); }
503
+ .bookshelf-demo .img-implementing-ddd { background-image: url("/covers/implementing-ddd.jpg"); }
504
+ .bookshelf-demo .img-systems-performance { background-image: url("/covers/systems-performance.jpg"); }
505
+ .bookshelf-demo .img-clean-architecture { background-image: url("/covers/clean-architecture.jpg"); }
506
+
507
+ .bookshelf-demo .foot {
508
+ margin-top: 40px;
509
+ padding-top: 16px;
510
+ border-top: 1px solid var(--line);
511
+ display: flex;
512
+ justify-content: space-between;
513
+ gap: 16px;
514
+ font-size: 12px;
515
+ color: var(--muted);
516
+ flex-wrap: wrap;
517
+ }
518
+
519
+ /* —— Note view —— */
520
+ .bookshelf-demo .note-view {
521
+ position: fixed;
522
+ inset: 0;
523
+ z-index: 60;
524
+ background: #faf9f5; /* solid paper — never translucent wall flash */
525
+ overflow: auto;
526
+ opacity: 0;
527
+ pointer-events: none;
528
+ /* opacity driven by WAAPI / inline — no CSS transition */
529
+ transition: none;
530
+ }
531
+ .bookshelf-demo .note-view.is-preparing,
532
+ .bookshelf-demo .note-view.is-open {
533
+ /* Above stage so note can cover flyer without a kill-flash */
534
+ z-index: 75;
535
+ }
536
+ .bookshelf-demo .note-view.is-open {
537
+ opacity: 1;
538
+ pointer-events: auto;
539
+ }
540
+ .bookshelf-demo .note {
541
+ width: min(100%, var(--col));
542
+ margin: 0 auto;
543
+ padding: 32px var(--pad) 80px;
544
+ }
545
+ .bookshelf-demo .back-btn {
546
+ border: none;
547
+ background: transparent;
548
+ color: var(--muted);
549
+ font-size: 14px;
550
+ cursor: pointer;
551
+ padding: 8px 0;
552
+ margin-bottom: 28px;
553
+ font-family: inherit;
554
+ }
555
+ .bookshelf-demo .back-btn:hover { color: var(--ink); }
556
+ .bookshelf-demo .note-hero {
557
+ display: flex;
558
+ gap: 24px;
559
+ align-items: flex-start;
560
+ margin-bottom: 36px;
561
+ }
562
+ .bookshelf-demo .note-cover {
563
+ width: 120px;
564
+ height: 180px;
565
+ flex-shrink: 0;
566
+ border-radius: 2px 3px 0 0;
567
+ background-size: cover;
568
+ background-position: center top;
569
+ background-color: #2a2a2a;
570
+ box-shadow: 0 8px 20px rgba(40,30,20,0.12);
571
+ }
572
+ .bookshelf-demo .note-cat {
573
+ margin: 0 0 8px;
574
+ font-size: 12px;
575
+ color: var(--muted);
576
+ letter-spacing: 0.1em;
577
+ }
578
+ .bookshelf-demo .note-meta h1 {
579
+ font-size: clamp(24px, 4vw, 32px);
580
+ margin: 0 0 8px;
581
+ }
582
+ .bookshelf-demo .note-author {
583
+ margin: 0;
584
+ color: var(--muted);
585
+ font-size: 14px;
586
+ }
587
+ .bookshelf-demo .note-body {
588
+ font-size: 16px;
589
+ line-height: 1.85;
590
+ color: var(--ink);
591
+ }
592
+ .bookshelf-demo .note-body h2 {
593
+ font-family: "Noto Serif SC", serif;
594
+ font-size: 20px;
595
+ margin: 32px 0 12px;
596
+ font-weight: 600;
597
+ }
598
+ .bookshelf-demo .note-body p { margin: 0 0 1.1em; }
599
+ .bookshelf-demo .note-body ul { padding-left: 1.2em; margin: 0 0 1.1em; }
600
+ .bookshelf-demo .note-body li { margin-bottom: 0.4em; }
601
+ .bookshelf-demo .note-body .placeholder {
602
+ color: var(--muted);
603
+ font-style: italic;
604
+ }
605
+
606
+ /* —— Stage / backdrop / veil / held-book flyer (§10) —— */
607
+ .bookshelf-demo .open-backdrop {
608
+ position: fixed;
609
+ inset: 0;
610
+ z-index: 58;
611
+ pointer-events: none;
612
+ background: #faf9f5;
613
+ opacity: 0;
614
+ }
615
+ .bookshelf-demo .open-backdrop.is-on {
616
+ /* Soft — shelf can still read through slightly; never the only visible layer */
617
+ opacity: 0.55;
618
+ }
619
+ .bookshelf-demo .open-backdrop.is-solid {
620
+ opacity: 1;
621
+ }
622
+
623
+ .bookshelf-demo .stage {
624
+ position: fixed;
625
+ inset: 0;
626
+ z-index: 70;
627
+ pointer-events: none;
628
+ perspective: 1800px;
629
+ perspective-origin: 50% 45%;
630
+ }
631
+
632
+ .bookshelf-demo .open-veil {
633
+ position: fixed;
634
+ inset: 0;
635
+ z-index: 80;
636
+ pointer-events: none;
637
+ overflow: hidden;
638
+ opacity: 0;
639
+ }
640
+ .bookshelf-demo .open-veil.is-on {
641
+ opacity: 1;
642
+ }
643
+ .bookshelf-demo .open-veil-blob {
644
+ position: absolute;
645
+ width: 280vmax;
646
+ height: 280vmax;
647
+ left: var(--vx, 50%);
648
+ top: var(--vy, 45%);
649
+ margin-left: -140vmax;
650
+ margin-top: -140vmax;
651
+ border-radius: 50%;
652
+ background:
653
+ radial-gradient(
654
+ circle at center,
655
+ #fffcf6 0%,
656
+ #faf9f5 42%,
657
+ #faf9f5 100%
658
+ );
659
+ transform: translateZ(0) scale(0);
660
+ transform-origin: center center;
661
+ will-change: transform, opacity;
662
+ opacity: 1;
663
+ }
664
+
665
+ .bookshelf-demo .flyer {
666
+ position: fixed;
667
+ left: 0;
668
+ top: 0;
669
+ width: 680px;
670
+ height: 400px;
671
+ margin: 0;
672
+ transform-origin: top left;
673
+ /* FLAT: scaling a preserve-3d parent causes shimmer/jitter */
674
+ transform-style: flat;
675
+ opacity: 1;
676
+ overflow: visible;
677
+ contain: layout style;
678
+ }
679
+ .bookshelf-demo .flyer.is-ready {
680
+ will-change: translate, scale, opacity;
681
+ }
682
+
683
+ /* Soft oval foot shadow — no filter:blur (blur+anim = jitter) */
684
+ .bookshelf-demo .flyer-shadow {
685
+ position: absolute;
686
+ left: 8%;
687
+ right: 8%;
688
+ bottom: -10%;
689
+ height: 28%;
690
+ border-radius: 50%;
691
+ background: radial-gradient(
692
+ ellipse at center,
693
+ rgba(40, 30, 20, 0.16) 0%,
694
+ rgba(40, 30, 20, 0.07) 42%,
695
+ rgba(40, 30, 20, 0.02) 65%,
696
+ rgba(40, 30, 20, 0) 80%
697
+ );
698
+ opacity: 0.4;
699
+ z-index: 0;
700
+ pointer-events: none;
701
+ transform: translate3d(0, 0, 0) scale(1, 0.45);
702
+ will-change: opacity;
703
+ }
704
+
705
+ .bookshelf-demo .flyer-book {
706
+ position: absolute;
707
+ inset: 0;
708
+ transform-style: preserve-3d;
709
+ transform-origin: center center;
710
+ /* Static mild tilt — do NOT animate rotateX (competes with flyer scale) */
711
+ transform: rotateX(8deg) translate3d(0, 0, 0);
712
+ z-index: 1;
713
+ backface-visibility: hidden;
714
+ }
715
+
716
+ /* Thin cover board peeking under page block (thickness cue) */
717
+ .bookshelf-demo .flyer-board {
718
+ position: absolute;
719
+ inset: -2% -1.2% -3.5% -1.2%;
720
+ border-radius: 3px 4px 2px 2px;
721
+ /* ux26: shell = this book's cover dominant (via --shell); ban site-wide brown */
722
+ background: linear-gradient(
723
+ 180deg,
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;
731
+ }
732
+
733
+ .bookshelf-demo .flyer-leaf {
734
+ position: absolute;
735
+ top: 0;
736
+ width: 50%;
737
+ height: 100%;
738
+ transform-style: preserve-3d;
739
+ z-index: 2;
740
+ }
741
+ .bookshelf-demo .flyer-leaf-left {
742
+ left: 0;
743
+ transform-origin: right center;
744
+ }
745
+ .bookshelf-demo .flyer-leaf-right {
746
+ left: 50%;
747
+ transform-origin: left center;
748
+ }
749
+
750
+ .bookshelf-demo .flyer-face {
751
+ position: absolute;
752
+ inset: 0;
753
+ border-radius: 2px 3px 0 0;
754
+ overflow: hidden;
755
+ backface-visibility: hidden;
756
+ -webkit-backface-visibility: hidden;
757
+ }
758
+
759
+ /* Cover on BACK of left leaf — camera-facing when leaf folded (180°) */
760
+ .bookshelf-demo .flyer-face-cover {
761
+ transform: rotateY(180deg);
762
+ background-color: transparent; /* ux24: no preset wood plate */
763
+ /* No background-image here — .img-* / inline applyCoverImage owns the photo.
764
+ Gradients-only used to override img classes → flat brown/gray close flash. */
765
+ box-shadow:
766
+ inset -2px 0 0 rgba(0,0,0,0.18),
767
+ 2px 0 10px rgba(40, 30, 20, 0.14);
768
+ }
769
+ .bookshelf-demo .flyer-face-cover.is-spine-face {
770
+ writing-mode: vertical-rl;
771
+ text-orientation: mixed;
772
+ font-family: "Noto Serif SC", serif;
773
+ font-size: 12px;
774
+ letter-spacing: 0.16em;
775
+ color: #F7F3EA;
776
+ display: flex;
777
+ align-items: center;
778
+ justify-content: center;
779
+ background-image:
780
+ linear-gradient(90deg, rgba(0,0,0,0.18) 0%, transparent 10%),
781
+ linear-gradient(180deg, rgba(255,255,255,0.06), transparent 40%);
782
+ }
783
+ .bookshelf-demo .flyer-spine-label { pointer-events: none; }
784
+
785
+ /* ux15 plate kept inert; ux16 uses full-height .flyer-spine-slot instead */
786
+ .bookshelf-demo .flyer-spine-land-plate {
787
+ position: absolute;
788
+ inset: 0;
789
+ pointer-events: none;
790
+ border-radius: inherit;
791
+ z-index: 1;
792
+ display: none;
793
+ }
794
+ .bookshelf-demo .flyer-spine-land-plate:not([class*="bk-"]) {
795
+ background: linear-gradient(180deg, var(--shell-hi, #6a7a98) 0%, var(--shell, #5a6a88) 100%);
796
+ }
797
+
798
+ /* ux16: exact homeRect standing spine — full height, spine width, slides out/in */
799
+ .bookshelf-demo .flyer.flyer-spine-slot {
800
+ position: fixed;
801
+ margin: 0;
802
+ padding: 10px 0;
803
+ box-sizing: border-box;
804
+ border-radius: 1px 2px 0 0;
805
+ box-shadow:
806
+ inset -1px 0 0 rgba(255,255,255,0.12),
807
+ inset 1px 0 0 rgba(0,0,0,0.18),
808
+ 0 6px 14px rgba(40, 30, 20, 0.18);
809
+ display: flex;
810
+ align-items: center;
811
+ justify-content: center;
812
+ overflow: hidden;
813
+ pointer-events: none;
814
+ transform-origin: center center;
815
+ contain: layout style;
816
+ will-change: transform, opacity;
817
+ }
818
+ .bookshelf-demo .flyer-spine-slot-label {
819
+ writing-mode: vertical-rl;
820
+ text-orientation: mixed;
821
+ font-family: "Noto Serif SC", serif;
822
+ font-size: 12px;
823
+ letter-spacing: 0.16em;
824
+ color: #F7F3EA;
825
+ pointer-events: none;
826
+ line-height: 1.2;
827
+ max-height: 100%;
828
+ overflow: hidden;
829
+ }
830
+ .bookshelf-demo .flyer-spine-slot.bk-sand .flyer-spine-slot-label,
831
+ .bookshelf-demo .flyer-spine-slot.bk-cream .flyer-spine-slot-label {
832
+ color: #2A261E;
833
+ font-size: 10px;
834
+ }
835
+ .bookshelf-demo .flyer-face-cover.is-cover-ready {
836
+ writing-mode: horizontal-tb;
837
+ }
838
+ .bookshelf-demo .flyer-face-cover.is-cover-ready[class*="img-"] {
839
+ background-size: contain !important;
840
+ background-position: center top !important;
841
+ background-repeat: no-repeat !important;
842
+ }
843
+ .bookshelf-demo .flyer-face-cover.is-cover-ready .flyer-spine-label {
844
+ display: none;
845
+ }
846
+ .bookshelf-demo .flyer-cover-img {
847
+ position: absolute;
848
+ inset: 0;
849
+ width: 100%;
850
+ height: 100%;
851
+ object-fit: contain; /* ux25: mid page aspect = book nat — contain fills, no plate */
852
+ object-position: center top;
853
+ display: block;
854
+ pointer-events: none;
855
+ border-radius: inherit;
856
+ /* Sit under optional spine label; above empty board fill */
857
+ z-index: 0;
858
+ transform: translateZ(0);
859
+ }
860
+
861
+ /* Cream page with gutter shade + micro-arch (radial) — not flat fill */
862
+ .bookshelf-demo .flyer-face-page {
863
+ /* ux26: page wash from cover palette (--page*); text --ink */
864
+ background-color: var(--page, #fffcf6);
865
+ background-image:
866
+ radial-gradient(120% 40% at 50% -5%, rgba(255,255,255,0.45) 0%, transparent 55%),
867
+ linear-gradient(180deg, var(--page-hi, #fffcf6) 0%, var(--page, #f7f4ef) 55%, var(--page-lo, #efe8dc) 100%);
868
+ padding: 16px 14px 16px 16px;
869
+ display: flex;
870
+ flex-direction: column;
871
+ gap: 8px;
872
+ color: var(--ink, #2a261e);
873
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--shell, #5a6a88) 14%, transparent);
874
+ }
875
+ .bookshelf-demo .flyer-leaf-left .flyer-face-page {
876
+ background-image:
877
+ radial-gradient(120% 40% at 50% -5%, rgba(255,255,255,0.4) 0%, transparent 55%),
878
+ linear-gradient(90deg, transparent 55%, color-mix(in srgb, var(--shell, #5a6a88) 10%, transparent) 82%, color-mix(in srgb, var(--shell, #5a6a88) 28%, transparent) 100%),
879
+ linear-gradient(180deg, var(--page-hi, #fffcf6) 0%, var(--page, #f7f4ef) 55%, var(--page-lo, #efe8dc) 100%);
880
+ border-radius: 3px 0 0 2px;
881
+ padding-right: 18px;
882
+ justify-content: center;
883
+ }
884
+ .bookshelf-demo .flyer-leaf-right .flyer-face-page {
885
+ background-image:
886
+ radial-gradient(120% 40% at 50% -5%, rgba(255,255,255,0.4) 0%, transparent 55%),
887
+ linear-gradient(270deg, transparent 55%, color-mix(in srgb, var(--shell, #5a6a88) 10%, transparent) 82%, color-mix(in srgb, var(--shell, #5a6a88) 28%, transparent) 100%),
888
+ linear-gradient(180deg, var(--page-hi, #fffcf6) 0%, var(--page, #f7f4ef) 55%, var(--page-lo, #efe8dc) 100%);
889
+ border-radius: 0 3px 2px 0;
890
+ padding-left: 18px;
891
+ justify-content: flex-start;
892
+ padding-top: 18px;
893
+ }
894
+
895
+ /* Page-block thickness strips on outer fore-edges */
896
+ .bookshelf-demo .flyer-thickness {
897
+ position: absolute;
898
+ top: 1%;
899
+ bottom: 2%;
900
+ width: 7px;
901
+ pointer-events: none;
902
+ z-index: 3;
903
+ /* ux26: fore-edge from cover palette, not generic cream paper */
904
+ background:
905
+ repeating-linear-gradient(
906
+ 180deg,
907
+ var(--edge-a, #d8cfc0) 0px,
908
+ var(--edge-b, #c4b7a2) 1px,
909
+ var(--edge-c, #b0a08a) 2px,
910
+ var(--edge-a, #d8cfc0) 3px
911
+ );
912
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--shell, #5a6a88) 22%, transparent);
913
+ opacity: 0.92;
914
+ transform: translateZ(1px);
915
+ }
916
+ .bookshelf-demo .flyer-leaf-left .flyer-thickness {
917
+ left: -5px;
918
+ border-radius: 1px 0 0 1px;
919
+ }
920
+ .bookshelf-demo .flyer-leaf-right .flyer-thickness {
921
+ right: -5px;
922
+ border-radius: 0 1px 1px 0;
923
+ }
924
+
925
+ /* Center gutter accent (binding dip) */
926
+ .bookshelf-demo .flyer-gutter {
927
+ position: absolute;
928
+ top: 2%;
929
+ bottom: 3%;
930
+ left: 50%;
931
+ width: 14px;
932
+ margin-left: -7px;
933
+ z-index: 4;
934
+ pointer-events: none;
935
+ background: linear-gradient(
936
+ 90deg,
937
+ rgba(40, 30, 18, 0) 0%,
938
+ rgba(40, 30, 18, 0.18) 35%,
939
+ rgba(30, 22, 12, 0.32) 50%,
940
+ rgba(40, 30, 18, 0.18) 65%,
941
+ rgba(40, 30, 18, 0) 100%
942
+ );
943
+ opacity: 0;
944
+ transform: translateZ(2px);
945
+ }
946
+
947
+ .bookshelf-demo .flyer-page-title {
948
+ font-family: "Noto Serif SC", "Songti SC", serif;
949
+ font-size: 13px;
950
+ font-weight: 650;
951
+ color: var(--ink, #2a261e);
952
+ line-height: 1.4;
953
+ margin: 0 0 6px;
954
+ letter-spacing: 0.02em;
955
+ opacity: 1; /* ux26: left page shows book title mid-open */
956
+ }
957
+ .bookshelf-demo .flyer-page-author {
958
+ font-family: system-ui, sans-serif;
959
+ font-size: 9px;
960
+ font-weight: 500;
961
+ color: var(--ink-muted, #5a5348);
962
+ line-height: 1.3;
963
+ margin: 0;
964
+ opacity: 0.85;
965
+ }
966
+ .bookshelf-demo .flyer-page-kicker {
967
+ font-family: system-ui, sans-serif;
968
+ font-size: 9px;
969
+ font-weight: 600;
970
+ letter-spacing: 0.08em;
971
+ text-transform: uppercase;
972
+ color: var(--ink-muted, #5a5348);
973
+ margin: 0 0 8px;
974
+ opacity: 0.75;
975
+ }
976
+ .bookshelf-demo .flyer-page-summary {
977
+ font-family: "Noto Serif SC", "Songti SC", serif;
978
+ font-size: 10px;
979
+ font-weight: 400;
980
+ color: var(--ink, #2a261e);
981
+ line-height: 1.55;
982
+ margin: 0;
983
+ opacity: 1; /* ux26: right page shows reading-notes summary */
984
+ display: -webkit-box;
985
+ -webkit-box-orient: vertical;
986
+ -webkit-line-clamp: 9;
987
+ overflow: hidden;
988
+ }
989
+ .bookshelf-demo .flyer-page-line {
990
+ display: none; /* ux26: no fake rule lines */
991
+ }
992
+
993
+ /* Note pre-layout: no scrollbar until open (scrollbar on cream void looked like a flash) */
994
+ .bookshelf-demo .note-view.is-preparing {
995
+ display: block !important;
996
+ pointer-events: none;
997
+ transition: none;
998
+ overflow: hidden;
999
+ }
1000
+ .bookshelf-demo .note-view.is-open {
1001
+ pointer-events: auto;
1002
+ overflow: auto;
1003
+ }
1004
+
1005
+ .bookshelf-demo .shelf-view.is-dim {
1006
+ /* Backdrop covers shelf — dim is unused during open; keep inert */
1007
+ opacity: 1;
1008
+ }
1009
+ .bookshelf-demo .shelf-view.is-frozen {
1010
+ pointer-events: none !important;
1011
+ }
1012
+ /* Freeze cover/spine interactions — keep resting min-height (do NOT inherit/collapse;
1013
+ inherit was collapsing .covers 210→~178 and causing +~30px end-frame shelf jump). */
1014
+ .bookshelf-demo .shelf-view.is-frozen .cover,
1015
+ .bookshelf-demo .shelf-view.is-frozen .spine {
1016
+ transition: none !important;
1017
+ transform: none !important;
1018
+ filter: none !important;
1019
+ }
1020
+ .bookshelf-demo .shelf-view.is-frozen .cover:hover,
1021
+ .bookshelf-demo .shelf-view.is-frozen .cover:focus-visible,
1022
+ .bookshelf-demo .shelf-view.is-frozen .spine:hover,
1023
+ .bookshelf-demo .shelf-view.is-frozen .spine:focus-visible {
1024
+ transform: none !important;
1025
+ box-shadow: inherit;
1026
+ z-index: auto;
1027
+ }
1028
+ .bookshelf-demo .shelf-view.is-frozen .spine.is-expanded,
1029
+ .bookshelf-demo .shelf-view.is-frozen .spine.is-collapsing {
1030
+ /* Lock expanded footprint — no collapse animation mid-close */
1031
+ transition: none !important;
1032
+ }
1033
+
1034
+
1035
+ /* Park shelf without display:none — preserves geometry, kills close-end bounce */
1036
+ .bookshelf-demo .shelf-view.is-parked {
1037
+ visibility: hidden !important;
1038
+ pointer-events: none !important;
1039
+ /* stay in document flow (no height collapse) */
1040
+ }
1041
+ .bookshelf-demo .shelf-view,
1042
+ .bookshelf-demo .spines,
1043
+ .bookshelf-demo .covers {
1044
+ overflow-anchor: none;
1045
+ }
1046
+
1047
+ /* Close path: flat single cover card — durable <img>, no cream/board mid-flight */
1048
+ .bookshelf-demo .flyer.flyer-close-cover {
1049
+ transform-style: flat;
1050
+ overflow: hidden;
1051
+ border-radius: 2px 3px 0 0;
1052
+ box-shadow:
1053
+ 0 14px 32px rgba(40, 30, 20, 0.2),
1054
+ 0 1px 0 rgba(255, 255, 255, 0.06) inset;
1055
+ contain: layout style paint;
1056
+ }
1057
+ .bookshelf-demo .flyer-close-face {
1058
+ position: absolute;
1059
+ inset: 0;
1060
+ border-radius: inherit;
1061
+ overflow: hidden;
1062
+ background-color: transparent; /* ux24: no preset wood plate */
1063
+ background-size: cover;
1064
+ background-position: center top;
1065
+ background-repeat: no-repeat;
1066
+ transform: translateZ(0);
1067
+ }
1068
+ .bookshelf-demo .flyer-close-face .flyer-cover-img {
1069
+ position: absolute;
1070
+ inset: 0;
1071
+ width: 100%;
1072
+ height: 100%;
1073
+ object-fit: contain; /* ux17: ban crop */
1074
+ object-position: center top;
1075
+ display: block;
1076
+ pointer-events: none;
1077
+ z-index: 1;
1078
+ transform: translateZ(0);
1079
+ }
1080
+ .bookshelf-demo .flyer-close-face .flyer-spine-label {
1081
+ z-index: 2;
1082
+ }
1083
+
1084
+ /* reduced motion */
1085
+ @media (prefers-reduced-motion: reduce) {
1086
+ .bookshelf-demo {
1087
+ --t-expand: 1ms;
1088
+ --t-collapse: 1ms;
1089
+ --t-pull: 1ms;
1090
+ --t-flip: 1ms;
1091
+ --t-settle: 1ms;
1092
+ --t-putback: 1ms;
1093
+ --t-land: 1ms;
1094
+ --t-veil: 1ms;
1095
+ }
1096
+ .bookshelf-demo .cover,
1097
+ .bookshelf-demo .spine,
1098
+ .bookshelf-demo .note-view,
1099
+ .bookshelf-demo .shelf-view,
1100
+ .bookshelf-demo .flyer,
1101
+ .bookshelf-demo .open-backdrop,
1102
+ .bookshelf-demo .flyer-leaf,
1103
+ .bookshelf-demo .flyer-face,
1104
+ .bookshelf-demo .flyer-face-cover,
1105
+ .bookshelf-demo .flyer-spine-label,
1106
+ .bookshelf-demo .flyer-gutter,
1107
+ .bookshelf-demo .open-veil,
1108
+ .bookshelf-demo .open-veil-blob {
1109
+ transition: none !important;
1110
+ animation: none !important;
1111
+ }
1112
+ }
1113
+
1114
+ /* mobile */
1115
+ @media (max-width: 640px) {
1116
+ .bookshelf-demo .page { padding: 28px 16px 40px; }
1117
+ .bookshelf-demo .covers { gap: 10px; min-height: 176px; }
1118
+ .bookshelf-demo .cover { width: 102px; height: 154px; /* fallback; JS sets nat height */ }
1119
+ .bookshelf-demo .spines { min-height: 160px; gap: 3px; }
1120
+ .bookshelf-demo .spine { height: 132px; width: 24px; font-size: 11px; }
1121
+ .bookshelf-demo .s-tall { height: 148px; width: 26px; }
1122
+ .bookshelf-demo .s-mid { height: 134px; width: 24px; }
1123
+ .bookshelf-demo .s-short { height: 120px; width: 20px; }
1124
+ /* resting spines: solid hue only; cover image only when expanded */
1125
+ .bookshelf-demo .spine[class*="img-"] {
1126
+ background-image: none !important;
1127
+ }
1128
+ .bookshelf-demo .spine.is-expanded[class*="img-"] {
1129
+ background-image: unset;
1130
+ }
1131
+ .bookshelf-demo .spine.is-expanded.img-clean-code { background-image: url("/covers/clean-code.jpg") !important; }
1132
+ .bookshelf-demo .spine.is-expanded.img-refactoring { background-image: url("/covers/refactoring.jpg") !important; }
1133
+ .bookshelf-demo .spine.is-expanded.img-design-patterns { background-image: url("/covers/design-patterns.jpg") !important; }
1134
+ .bookshelf-demo .spine.is-expanded.img-pragmatic-programmer { background-image: url("/covers/pragmatic-programmer.jpg") !important; }
1135
+ .bookshelf-demo .spine.is-expanded.img-continuous-delivery { background-image: url("/covers/continuous-delivery.jpg") !important; }
1136
+ .bookshelf-demo .spine.is-expanded.img-phoenix-project { background-image: url("/covers/phoenix-project.jpg") !important; }
1137
+ .bookshelf-demo .spine.is-expanded.img-mythical-man-month { background-image: url("/covers/mythical-man-month.jpg") !important; }
1138
+ .bookshelf-demo .spine.is-expanded.img-tdd { background-image: url("/covers/tdd.jpg") !important; }
1139
+ .bookshelf-demo .spine.is-expanded.img-lean-startup { background-image: url("/covers/lean-startup.jpg") !important; }
1140
+ .bookshelf-demo .spine.is-expanded.img-release-it { background-image: url("/covers/release-it.jpg") !important; }
1141
+ .bookshelf-demo .spine.is-expanded.img-sre { background-image: url("/covers/sre.jpg") !important; }
1142
+ .bookshelf-demo .spine.is-expanded.img-team-topologies { background-image: url("/covers/team-topologies.jpg") !important; }
1143
+ .bookshelf-demo .spine.is-expanded.img-accelerate { background-image: url("/covers/accelerate.jpg") !important; }
1144
+ .bookshelf-demo .spine.is-expanded.img-ddd { background-image: url("/covers/ddd.jpg") !important; }
1145
+ .bookshelf-demo .spine.is-expanded.img-microservices { background-image: url("/covers/microservices.jpg") !important; }
1146
+ .bookshelf-demo .spine.is-expanded.img-eaa { background-image: url("/covers/eaa.jpg") !important; }
1147
+ .bookshelf-demo .spine.is-expanded.img-ddia { background-image: url("/covers/ddia.jpg") !important; }
1148
+ .bookshelf-demo .spine.is-expanded.img-fundamentals-sa { background-image: url("/covers/fundamentals-sa.jpg") !important; }
1149
+ .bookshelf-demo .spine.is-expanded.img-implementing-ddd { background-image: url("/covers/implementing-ddd.jpg") !important; }
1150
+ .bookshelf-demo .spine.is-expanded.img-systems-performance { background-image: url("/covers/systems-performance.jpg") !important; }
1151
+ .bookshelf-demo .spine.is-expanded.img-clean-architecture { background-image: url("/covers/clean-architecture.jpg") !important; }
1152
+
1153
+ .bookshelf-demo .spine.is-expanded {
1154
+ width: 90px;
1155
+ height: 136px;
1156
+ }
1157
+ .bookshelf-demo .spine.is-expanded.is-collapsing {
1158
+ width: 24px;
1159
+ height: 132px;
1160
+ }
1161
+ .bookshelf-demo .spine.is-expanded.is-collapsing.s-tall { width: 26px; height: 148px; }
1162
+ .bookshelf-demo .spine.is-expanded.is-collapsing.s-mid { width: 24px; height: 134px; }
1163
+ .bookshelf-demo .spine.is-expanded.is-collapsing.s-short { width: 20px; height: 120px; }
1164
+ .bookshelf-demo .note-hero { flex-direction: column; gap: 16px; }
1165
+ .bookshelf-demo .note-cover { width: 100px; height: 150px; }
1166
+ .bookshelf-demo .qa-hint { width: 100%; margin-left: 0; margin-top: 4px; }
1167
+ }
1168
+
1169
+ /* Reading path: hide demo/QA chrome unless ?debug=1 */
1170
+ body:not(.debug-on) .bookshelf-demo .is-debug { display: none !important; }
1171
+
1172
+ /* Site shelf: same column, type, and header as the blog. Motion rules above stay. */
1173
+ .bookshelf-page.sh-main {
1174
+ display: flex;
1175
+ flex-direction: column;
1176
+ align-items: center;
1177
+ }
1178
+ .bookshelf-page .sh-head {
1179
+ width: 100%;
1180
+ max-width: var(--col);
1181
+ margin-inline: auto;
1182
+ }
1183
+ .bookshelf-page .sh-kicker {
1184
+ margin: 0 0 12px;
1185
+ font-size: var(--fs-meta);
1186
+ letter-spacing: 0.08em;
1187
+ color: var(--muted);
1188
+ }
1189
+ .bookshelf-demo.is-site {
1190
+ --site-sans: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Segoe UI", system-ui, sans-serif;
1191
+ width: 100%;
1192
+ max-width: var(--col);
1193
+ margin-inline: auto;
1194
+ background: transparent;
1195
+ min-height: 0;
1196
+ color: inherit;
1197
+ font-family: var(--site-sans);
1198
+ font-size: inherit;
1199
+ line-height: inherit;
1200
+ }
1201
+ .bookshelf-demo.is-site .page {
1202
+ width: 100%;
1203
+ max-width: none;
1204
+ margin: 0;
1205
+ padding: 0 0 8px;
1206
+ }
1207
+ .bookshelf-demo.is-site .top { display: none; }
1208
+ .bookshelf-page .sh-head h1,
1209
+ .bookshelf-page .sh-deck,
1210
+ .bookshelf-page .sh-kicker,
1211
+ body:has(.bookshelf-demo.is-site) .sh-header,
1212
+ body:has(.bookshelf-demo.is-site) .sh-footer {
1213
+ font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Segoe UI", system-ui, sans-serif;
1214
+ }
1215
+ body:has(.bookshelf-demo.is-site) .sh-header .sh-word {
1216
+ font-family: "Songti SC", "STSong", "Source Serif 4", Georgia, serif;
1217
+ }
1218
+ /* Overlays stay under the sticky header so open/close does not cover it. */
1219
+ .bookshelf-demo.is-site .open-backdrop {
1220
+ top: var(--hdr);
1221
+ z-index: 30;
1222
+ background: var(--paper);
1223
+ }
1224
+ .bookshelf-demo.is-site .stage { z-index: 32; }
1225
+ .bookshelf-demo.is-site .note-view {
1226
+ top: var(--hdr);
1227
+ background: var(--paper);
1228
+ color: var(--text);
1229
+ }
1230
+ .bookshelf-demo.is-site .note-view.is-preparing,
1231
+ .bookshelf-demo.is-site .note-view.is-open { z-index: 34; }
1232
+ .bookshelf-demo.is-site .open-veil { z-index: 36; }
1233
+ .bookshelf-demo.is-site .note {
1234
+ width: min(100%, var(--col));
1235
+ margin: 0 auto;
1236
+ padding: 28px 0 80px;
1237
+ }
1238
+ .bookshelf-demo.is-site .back-btn,
1239
+ .bookshelf-demo.is-site .note-hero,
1240
+ .bookshelf-demo.is-site .note-body,
1241
+ .bookshelf-demo.is-site .note-read {
1242
+ max-width: none;
1243
+ font-family: var(--site-sans);
1244
+ }
1245
+ .bookshelf-demo.is-site .note-body {
1246
+ font-size: var(--fs-body);
1247
+ line-height: 1.75;
1248
+ color: var(--text);
1249
+ }
1250
+ .bookshelf-demo.is-site .note-body h2 {
1251
+ font-family: inherit;
1252
+ font-size: var(--fs-h2);
1253
+ font-weight: 700;
1254
+ line-height: 1.3;
1255
+ color: var(--ink);
1256
+ }
1257
+ .bookshelf-demo.is-site .note-meta h1 {
1258
+ font-family: inherit;
1259
+ font-weight: 700;
1260
+ font-size: var(--fs-display);
1261
+ line-height: 1.2;
1262
+ color: var(--ink);
1263
+ }
1264
+ .bookshelf-demo .flyer-page-more {
1265
+ pointer-events: auto;
1266
+ align-self: flex-start;
1267
+ margin-top: 8px;
1268
+ font-family: system-ui, sans-serif;
1269
+ font-size: 9px;
1270
+ font-weight: 600;
1271
+ letter-spacing: 0.04em;
1272
+ color: var(--ink, #2a261e);
1273
+ text-decoration: underline;
1274
+ text-underline-offset: 2px;
1275
+ }
1276
+ .bookshelf-demo .note-read {
1277
+ display: inline-block;
1278
+ margin: 12px 0 0;
1279
+ font-size: 14px;
1280
+ color: var(--ink);
1281
+ text-decoration: underline;
1282
+ text-underline-offset: 3px;
1283
+ }
1284
+ .bookshelf-demo .note-read[hidden] { display: none; }
1285
+ .bookshelf-demo.is-site .note-read { color: var(--blue); }
1286
+ @media (max-width: 820px) {
1287
+ .bookshelf-demo.is-site .note { padding: 16px 20px 64px; }
1288
+ }