@gallerist/theme-default 0.4.1 → 0.5.0

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.
package/README.md CHANGED
@@ -54,11 +54,34 @@ drawn as a border, so forced colours keep it.
54
54
  The rail (`.gallerist-thumbs`) has a `scroll-padding` of `--gallerist-rail-fade` (40px) at its ends,
55
55
  inline on a sideways rail and block on a side rail: plugin-thumbnails reads it to keep the current
56
56
  card clear of the end fades and the ‹ › buttons in them, and a card focused with Tab lands clear too.
57
+ On a phone (600px wide or less with a coarse pointer, where the ‹ › buttons never show) the fade is
58
+ 16px (0.5), so the current card kept clear of it leaves room for the next card whole beside it. A
59
+ page's own `--gallerist-rail-fade` wins (the theme's tokens are in a cascade layer).
60
+
61
+ Loading (0.5): an item's `placeholder` image is blurred `--gallerist-placeholder-blur` (6px) and
62
+ clipped to the image's box; the image fades in over it in `--gallerist-dur-reveal` (150ms). A box
63
+ with no size to go by takes `--gallerist-media-ratio` (`3 / 2`). A load still running after
64
+ `--gallerist-loading-delay` (400ms) shows a "Loading full size" pill (the panel, hairline and muted
65
+ text tokens; no warning colours). With no placeholder the box is a skeleton after 300ms:
66
+ `--gallerist-placeholder` with a `--gallerist-shimmer` sweep, gone at once when the image is in. No
67
+ loading animation outlives the load. See the renderer's README.
68
+
69
+ The rail's current label (a flat rail's group label, a timeline's time label) is
70
+ `--gallerist-label-current` (the accent) at `--gallerist-label-current-weight` (500). A focused thumb's
71
+ ring is 2px `--gallerist-focus` in `--gallerist-thumb-focus-style` (`solid`; `dashed` tells it apart
72
+ from a solid selection ring), 2px out, or `--gallerist-thumb-focus-offset-current` (2px) out on the
73
+ current thumb, whose ring (`--gallerist-thumb-ring`, `--gallerist-item-ring`) may sit outside it. A
74
+ focused thumb that isn't current shows at `--gallerist-thumb-focus-opacity` (1, as when hovered);
75
+ `var(--gallerist-thumb-idle-opacity)` keeps it dimmed, so only the current one is bright (hover still
76
+ brightens it).
57
77
 
58
78
  The theme follows `forced-colors: active` (Windows High Contrast): the View popover's chosen segment
59
79
  takes the system's selection colours and the focus rings the text colour. The rail's
60
80
  item clusters (`thumbnails({ cluster })`) use `--gallerist-cluster-inner` (2px) inside a cluster and
61
- `--gallerist-cluster-gap` (8px) between two.
81
+ `--gallerist-cluster-gap` (8px) between two. An item thumb's badge (`thumbnails({ renderItemBadge })`)
82
+ sits `--gallerist-thumb-badge-inset-block` from its image's top and bottom, as `inset-block` takes
83
+ them: `3px auto`, the top end corner; `auto 3px` puts it in the bottom end corner, still on the image
84
+ (a caption band under it stays clear).
62
85
 
63
86
  Full docs: the [monorepo README](https://github.com/shafayet9780/gallerist#readme) and [`docs/`](https://github.com/shafayet9780/gallerist/tree/main/docs).
64
87
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gallerist/theme-default",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Default Gallerist theme — pure CSS, no JS dependency. Driven by --gallerist-* custom properties.",
5
5
  "license": "MIT",
6
6
  "sideEffects": [
package/src/base.css CHANGED
@@ -43,6 +43,13 @@
43
43
  --gallerist-dur-gentle: 280ms;
44
44
  --gallerist-dur-exit: 180ms; /* exits run shorter than entrances */
45
45
  --gallerist-dur-panel: 220ms; /* the rail collapsing, the item column opening (plugin-thumbnails) */
46
+ --gallerist-dur-reveal: 150ms; /* a loaded image fading in over its placeholder */
47
+
48
+ /* loading: an image placeholder's blur, the ratio of a box with no size to go by (a skeleton),
49
+ and how long a load runs before its "Loading full size" pill shows */
50
+ --gallerist-placeholder-blur: 6px;
51
+ --gallerist-media-ratio: 3 / 2;
52
+ --gallerist-loading-delay: 400ms;
46
53
 
47
54
  /* the accent: each skin names its accents as a dark/light pair, the scheme picks one */
48
55
  --gallerist-accent: var(--gallerist-accent-dark);
@@ -97,6 +104,16 @@
97
104
  /* item clusters (thumbnails `cluster`): the gap inside one, and between two */
98
105
  --gallerist-cluster-inner: 2px;
99
106
  --gallerist-cluster-gap: 8px;
107
+ /* an item thumb's badge (thumbnails `renderItemBadge`), its `inset-block` from the image's edges:
108
+ the top end corner; `auto 3px` takes it to the bottom end */
109
+ --gallerist-thumb-badge-inset-block: 3px auto;
110
+ /* the rail's current group label (flat) or time label (timeline), and a focused thumb's ring:
111
+ its outline-style, and its offset on the current thumb (whose ring is outside it). A focused
112
+ thumb that isn't current: --gallerist-thumb-focus-opacity (unset: 1) */
113
+ --gallerist-label-current: var(--gallerist-accent);
114
+ --gallerist-label-current-weight: 500;
115
+ --gallerist-thumb-focus-style: solid;
116
+ --gallerist-thumb-focus-offset-current: 2px;
100
117
 
101
118
  /* popovers (the View menu, the shortcut list) sit a step above the bands; a segmented control
102
119
  is a recessed track with its checked segment raised */
@@ -110,6 +127,13 @@
110
127
  --gallerist-arrow-hover-bg: var(--gallerist-arrow-bg);
111
128
  --gallerist-itemnav-button-hover-bg: var(--gallerist-itemnav-button-bg);
112
129
  }
130
+ /* a phone's rail fades 16px (no ‹ › page buttons sit in it on touch): the current card kept clear
131
+ of the fade leaves room for the card ahead whole beside it */
132
+ @media (max-width: 600px) and (pointer: coarse) {
133
+ .gallerist {
134
+ --gallerist-rail-fade: 16px;
135
+ }
136
+ }
113
137
  }
114
138
 
115
139
  .gallerist {
@@ -307,14 +331,143 @@
307
331
  width: 100%;
308
332
  height: 100%;
309
333
  object-fit: contain;
310
- background-repeat: no-repeat;
311
- background-position: center;
312
334
  opacity: 0;
313
- transition: opacity 180ms ease-out;
335
+ transition: opacity var(--gallerist-dur-reveal) var(--gallerist-ease-out);
314
336
  }
315
337
  .gallerist-media-image[data-gallerist-loaded] {
316
338
  opacity: 1;
317
339
  }
340
+ .gallerist-media-image[data-gallerist-error] {
341
+ visibility: hidden;
342
+ }
343
+ /* `item.placeholder` in the image's box (ratio: naturalSize, the placeholder's, or the token). An
344
+ image one is blurred, overscanned 2× the blur and clipped (no fringe), sharp as the image fades in,
345
+ then hidden; a failure keeps it, sharp. None: a skeleton after 300ms, gone when the image is in. */
346
+ .gallerist-content {
347
+ container-type: size;
348
+ }
349
+ .gallerist-media-placeholder {
350
+ --_i: 0px;
351
+ position: absolute;
352
+ inset: 0;
353
+ margin: auto;
354
+ width: min(100cqw - 2 * var(--_i), (100cqh - 2 * var(--_i)) * var(--gallerist-media-ratio));
355
+ height: min(100cqh - 2 * var(--_i), (100cqw - 2 * var(--_i)) / (var(--gallerist-media-ratio)));
356
+ overflow: hidden;
357
+ }
358
+ .gallerist[data-gallerist-frame] .gallerist-media-placeholder {
359
+ --_i: var(--gallerist-frame-inset);
360
+ }
361
+ .gallerist-media-placeholder > img {
362
+ --_o: calc(-2 * var(--gallerist-placeholder-blur));
363
+ position: absolute;
364
+ inset: var(--_o);
365
+ width: calc(100% - 2 * var(--_o));
366
+ height: calc(100% - 2 * var(--_o));
367
+ object-fit: cover;
368
+ filter: blur(var(--gallerist-placeholder-blur));
369
+ transition: var(--gallerist-dur-reveal) var(--gallerist-ease-out);
370
+ transition-property: filter, inset, width, height;
371
+ }
372
+ .gallerist-media-placeholder:is([data-gallerist-loaded], [data-gallerist-error]) > img {
373
+ --_o: 0px;
374
+ filter: none;
375
+ }
376
+ .gallerist-media-placeholder[data-gallerist-loaded] {
377
+ visibility: hidden;
378
+ transition: visibility 0s var(--gallerist-dur-reveal);
379
+ }
380
+ [data-gallerist-skeleton]:not([data-gallerist-loaded]) {
381
+ background: var(--gallerist-placeholder);
382
+ animation: gallerist-fade var(--gallerist-dur-exit) 0.3s backwards;
383
+ }
384
+ [data-gallerist-skeleton]:not([data-gallerist-loaded])::after {
385
+ content: "";
386
+ position: absolute;
387
+ inset: 0;
388
+ background: linear-gradient(
389
+ 100deg,
390
+ transparent 30%,
391
+ var(--gallerist-shimmer) 50%,
392
+ transparent 70%
393
+ )
394
+ 150% 0 / 220% 100%;
395
+ animation: gallerist-shimmer 1.4s ease-in-out 0.3s infinite;
396
+ }
397
+ @keyframes gallerist-shimmer {
398
+ to {
399
+ background-position: -50% 0;
400
+ }
401
+ }
402
+ /* a slow load's pill, bottom end: after the delay, or at once on a failure (Retry). Neutral */
403
+ .gallerist-media-pill {
404
+ position: absolute;
405
+ bottom: 12px;
406
+ inset-inline-end: 12px;
407
+ display: flex;
408
+ align-items: center;
409
+ gap: 8px;
410
+ max-width: calc(100% - 24px);
411
+ min-height: 30px;
412
+ box-sizing: border-box;
413
+ padding: 2px 12px 2px 10px;
414
+ border: 1px solid var(--gallerist-hairline);
415
+ border-radius: 99px;
416
+ background: var(--gallerist-panel);
417
+ color: var(--gallerist-fg-muted);
418
+ font: 12.5px / 1.3 var(--gallerist-font);
419
+ animation: gallerist-fade var(--gallerist-dur-exit) var(--gallerist-loading-delay) backwards;
420
+ }
421
+ /* the spinner; on a failure, a ! in a ring */
422
+ .gallerist-media-pill::before {
423
+ content: "";
424
+ flex: none;
425
+ box-sizing: border-box;
426
+ width: 13px;
427
+ height: 13px;
428
+ border: 1.5px solid;
429
+ border-inline-end-color: transparent;
430
+ border-radius: 50%;
431
+ font: 700 9px / 10px var(--gallerist-font);
432
+ text-align: center;
433
+ animation: gallerist-spin 0.9s linear infinite;
434
+ }
435
+ [data-gallerist-error] > .gallerist-media-pill {
436
+ color: var(--gallerist-fg);
437
+ animation: none;
438
+ }
439
+ [data-gallerist-error] > .gallerist-media-pill::before {
440
+ content: "!";
441
+ border-color: currentColor;
442
+ animation: none;
443
+ }
444
+ .gallerist-media-pill > button {
445
+ font: inherit;
446
+ font-weight: 600;
447
+ color: inherit;
448
+ background: none;
449
+ border: 0;
450
+ padding: 3px 2px;
451
+ border-radius: 4px;
452
+ text-decoration: underline;
453
+ text-underline-offset: 3px;
454
+ cursor: pointer;
455
+ }
456
+ .gallerist-media-pill > button::before {
457
+ content: "·" / "";
458
+ display: inline-block;
459
+ margin-inline-end: 8px;
460
+ color: var(--gallerist-fg-muted);
461
+ }
462
+ .gallerist-media-pill > button:focus-visible {
463
+ outline: 2px solid var(--gallerist-focus);
464
+ outline-offset: 2px;
465
+ }
466
+ @keyframes gallerist-spin {
467
+ to {
468
+ transform: rotate(1turn);
469
+ }
470
+ }
318
471
  /* A failed load shows a centered error card (icon + the item's alt as a caption) instead of a
319
472
  blank stage or the browser's default top-left broken-image glyph (I-UX3). The broken <img>
320
473
  itself stays hidden (opacity:0); the adapter injects .gallerist-media-error as a sibling. */
@@ -340,34 +493,14 @@
340
493
  max-width: 40ch;
341
494
  color: var(--gallerist-fg);
342
495
  }
343
- /* Loading shimmer for slow media — shown only while an image is still pending (no loaded/error
344
- marker yet), delayed so fast loads never flash it. Degrades to nothing where :has is absent. */
345
- .gallerist-content:has(
346
- .gallerist-media-image:not([data-gallerist-loaded]):not([data-gallerist-error])
347
- )::before {
348
- content: "";
349
- position: absolute;
350
- inset: 0;
351
- pointer-events: none;
352
- background: linear-gradient(
353
- 100deg,
354
- transparent 35%,
355
- var(--gallerist-shimmer) 50%,
356
- transparent 65%
357
- );
358
- background-size: 200% 100%;
359
- animation: gallerist-shimmer 1.4s ease-in-out 0.6s infinite;
360
- }
361
- @keyframes gallerist-shimmer {
362
- from {
363
- background-position: 150% 0;
364
- }
365
- to {
366
- background-position: -50% 0;
367
- }
368
- }
369
496
  @media (prefers-reduced-motion: reduce) {
370
- .gallerist-content::before {
497
+ .gallerist-media-placeholder > img {
498
+ --_o: 0px;
499
+ filter: none;
500
+ transition: none;
501
+ }
502
+ [data-gallerist-skeleton]:not([data-gallerist-loaded])::after,
503
+ .gallerist-media-pill::before {
371
504
  animation: none;
372
505
  }
373
506
  }
@@ -620,21 +753,27 @@
620
753
  border-color: var(--gallerist-thumb-active-border);
621
754
  box-shadow: var(--gallerist-thumb-ring);
622
755
  }
623
- .gallerist-thumb:not([data-active]) > .gallerist-thumb-img,
624
- .gallerist-thumb-item:not([data-active]) > .gallerist-thumb-img {
756
+ :is(.gallerist-thumb, .gallerist-thumb-item):not([data-active]) > .gallerist-thumb-img {
625
757
  opacity: var(--gallerist-thumb-idle-opacity);
626
758
  transition: opacity var(--gallerist-dur-gentle) var(--gallerist-ease-out);
627
759
  }
628
- .gallerist-thumb:is(:hover, :focus-visible) > .gallerist-thumb-img,
629
- .gallerist-thumb-item:is(:hover, :focus-visible) > .gallerist-thumb-img {
760
+ :is(.gallerist-thumb, .gallerist-thumb-item):not([data-active]):focus-visible
761
+ > .gallerist-thumb-img {
762
+ opacity: var(--gallerist-thumb-focus-opacity, 1);
763
+ }
764
+ :is(.gallerist-thumb, .gallerist-thumb-item):not([data-active]):hover > .gallerist-thumb-img {
630
765
  opacity: 1;
631
766
  }
632
767
  .gallerist-thumb:focus-visible,
633
768
  .gallerist-thumb-item:focus-visible,
634
769
  .gallerist-grid-cell:focus-visible {
635
- outline: 2px solid var(--gallerist-focus);
770
+ outline: 2px var(--gallerist-thumb-focus-style) var(--gallerist-focus);
636
771
  outline-offset: 2px;
637
772
  }
773
+ /* the current thumb's ring is outside it: focus there may sit further out */
774
+ :is(.gallerist-thumb, .gallerist-thumb-item)[data-active]:focus-visible {
775
+ outline-offset: var(--gallerist-thumb-focus-offset-current);
776
+ }
638
777
  .gallerist-thumb-img {
639
778
  width: 100%;
640
779
  object-fit: cover;
@@ -721,10 +860,13 @@
721
860
  .gallerist-thumbs-aside[hidden] {
722
861
  display: none;
723
862
  }
863
+ /* a grid, not a flex column: its image's row is a box the badge can sit in (a caption band or a
864
+ touch's 44px under the image stay out of it) */
724
865
  .gallerist-thumb-item {
725
866
  position: relative;
726
- display: flex;
727
- flex-direction: column;
867
+ display: grid;
868
+ grid-template-columns: minmax(0, 1fr);
869
+ align-content: start;
728
870
  flex: 0 0 auto;
729
871
  width: calc(var(--gallerist-thumb-h) * 1.2);
730
872
  padding: 0;
@@ -824,10 +966,11 @@
824
966
  isolation: isolate; /* keeps the track line behind its own label */
825
967
  }
826
968
  .gallerist-thumb-wrap[data-active] .gallerist-thumb-time {
827
- color: var(--gallerist-accent);
969
+ color: var(--gallerist-label-current);
970
+ font-weight: var(--gallerist-label-current-weight);
828
971
  }
829
972
  .gallerist-thumb-wrap[data-active] .gallerist-thumb-time::before {
830
- background: var(--gallerist-accent);
973
+ background: var(--gallerist-label-current);
831
974
  }
832
975
  /* numbered items (thumbnails `numberItems`) */
833
976
  .gallerist-thumb-num {
@@ -946,7 +1089,8 @@
946
1089
  font-family: var(--gallerist-mono);
947
1090
  }
948
1091
  .gallerist-thumb-group[data-active] .gallerist-thumb-group-cap {
949
- color: var(--gallerist-accent);
1092
+ color: var(--gallerist-label-current);
1093
+ font-weight: var(--gallerist-label-current-weight);
950
1094
  }
951
1095
  .gallerist-thumb-group-row {
952
1096
  display: flex;
@@ -1010,10 +1154,13 @@
1010
1154
  width: calc(var(--gallerist-thumb-h) * 1.4);
1011
1155
  min-height: 14px;
1012
1156
  }
1013
- /* an item's badge: the top end corner (a number from `numberItems` has the start) */
1157
+ /* an item's badge: the image's top end corner (a number from `numberItems` has the start), or its
1158
+ bottom end with `--gallerist-thumb-badge-inset-block: auto 3px`; placed in the image's grid row (an
1159
+ absolute box's `auto` grid line is the thumb's edge: the row's end line is named) */
1014
1160
  .gallerist-thumb-badge {
1015
1161
  position: absolute;
1016
- top: 3px;
1162
+ grid-row: 1 / 2;
1163
+ inset-block: var(--gallerist-thumb-badge-inset-block);
1017
1164
  inset-inline-end: 3px;
1018
1165
  display: flex;
1019
1166
  align-items: center;
@@ -2199,8 +2346,8 @@
2199
2346
  }
2200
2347
 
2201
2348
  /* the `tour` preset: the photo keeps clear of the arrows and the item navigator */
2202
- .gallerist[data-gallerist-preset="tour"][data-gallerist-frame] .gallerist-media,
2203
- .gallerist[data-gallerist-preset="tour"][data-gallerist-frame] .gallerist-media-image {
2349
+ .gallerist[data-gallerist-preset="tour"][data-gallerist-frame]
2350
+ :is(.gallerist-media, .gallerist-media-placeholder) {
2204
2351
  border-radius: 16px;
2205
2352
  }
2206
2353
 
@@ -2581,7 +2728,6 @@
2581
2728
  }
2582
2729
 
2583
2730
  @media (prefers-reduced-motion: reduce) {
2584
- .gallerist-media-image,
2585
2731
  .gallerist-bar,
2586
2732
  .gallerist-controls > button,
2587
2733
  .gallerist-itemnav,
package/src/console.css CHANGED
@@ -39,7 +39,7 @@
39
39
  --gallerist-caption-band: transparent;
40
40
  --gallerist-group-bg: #1a1c1f;
41
41
  --gallerist-placeholder: rgb(255 255 255 / 0.04);
42
- --gallerist-shimmer: rgb(255 255 255 / 0.05);
42
+ --gallerist-shimmer: rgb(255 255 255 / 0.09); /* a skeleton's sweep: plain on the dark stage */
43
43
  --gallerist-shadow: 0 24px 60px rgb(0 0 0 / 0.45);
44
44
  --gallerist-stack-shadow: 3px 3px 0 -1px #3a3f46, 6px 6px 0 -2px #2a2e34;
45
45
  --gallerist-blur: none;
package/src/gallery.css CHANGED
@@ -36,7 +36,7 @@
36
36
  --gallerist-caption-band: transparent;
37
37
  --gallerist-group-bg: rgb(255 255 255 / 0.05);
38
38
  --gallerist-placeholder: rgb(255 255 255 / 0.04);
39
- --gallerist-shimmer: rgb(255 255 255 / 0.06);
39
+ --gallerist-shimmer: rgb(255 255 255 / 0.08);
40
40
  --gallerist-shadow: 0 16px 40px rgb(0 0 0 / 0.5);
41
41
  --gallerist-stack-shadow: 3px 3px 0 -1px #2c2c2f, 6px 6px 0 -2px #232326;
42
42
  --gallerist-blur: blur(16px) saturate(140%);