@rmdes/indiekit-endpoint-activitypub 2.6.0 → 2.6.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.
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Blurhash placeholder backgrounds for gallery images.
3
+ *
4
+ * Extracts the average (DC) color from a blurhash string and applies it
5
+ * as a background-color on images with a data-blurhash attribute.
6
+ * This provides a meaningful colored placeholder while images load.
7
+ *
8
+ * The DC component is encoded in the first 4 characters of the blurhash
9
+ * after the size byte, as a base-83 integer representing an sRGB color.
10
+ */
11
+
12
+ const BASE83_CHARS =
13
+ "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
14
+
15
+ function decode83(str) {
16
+ let value = 0;
17
+ for (const c of str) {
18
+ const digit = BASE83_CHARS.indexOf(c);
19
+ if (digit === -1) return 0;
20
+ value = value * 83 + digit;
21
+ }
22
+ return value;
23
+ }
24
+
25
+ function decodeDC(value) {
26
+ return {
27
+ r: (value >> 16) & 255,
28
+ g: (value >> 8) & 255,
29
+ b: value & 255,
30
+ };
31
+ }
32
+
33
+ function blurhashToColor(hash) {
34
+ if (!hash || hash.length < 6) return null;
35
+ const dcValue = decode83(hash.slice(1, 5));
36
+ const { r, g, b } = decodeDC(dcValue);
37
+ return `rgb(${r},${g},${b})`;
38
+ }
39
+
40
+ document.addEventListener("DOMContentLoaded", () => {
41
+ for (const img of document.querySelectorAll("img[data-blurhash]")) {
42
+ const color = blurhashToColor(img.dataset.blurhash);
43
+ if (color) {
44
+ img.style.backgroundColor = color;
45
+ }
46
+ }
47
+
48
+ // Handle dynamically loaded images (infinite scroll)
49
+ const observer = new MutationObserver((mutations) => {
50
+ for (const mutation of mutations) {
51
+ for (const node of mutation.addedNodes) {
52
+ if (node.nodeType !== 1) continue;
53
+ const imgs = node.querySelectorAll
54
+ ? node.querySelectorAll("img[data-blurhash]")
55
+ : [];
56
+ for (const img of imgs) {
57
+ const color = blurhashToColor(img.dataset.blurhash);
58
+ if (color) {
59
+ img.style.backgroundColor = color;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ });
65
+ observer.observe(document.body, { childList: true, subtree: true });
66
+ });
@@ -15,16 +15,16 @@
15
15
  .ap-link-preview {
16
16
  display: flex;
17
17
  overflow: hidden;
18
- border-radius: var(--border-radius-small);
19
- border: 1px solid var(--color-neutral-lighter);
18
+ border-radius: 8px;
19
+ border: 1px solid var(--color-outline);
20
20
  background-color: var(--color-offset);
21
21
  text-decoration: none;
22
22
  color: inherit;
23
- transition: border-color 0.2s ease;
23
+ transition: border-color 0.15s ease;
24
24
  }
25
25
 
26
26
  .ap-link-preview:hover {
27
- border-color: var(--color-primary);
27
+ border-color: var(--color-outline-variant);
28
28
  }
29
29
 
30
30
  /* Text content area (left side) */
@@ -105,7 +105,7 @@
105
105
 
106
106
  .ap-post-detail__back-link {
107
107
  font-size: 0.875rem;
108
- color: var(--color-primary);
108
+ color: var(--color-primary-on-background);
109
109
  text-decoration: none;
110
110
  }
111
111
 
@@ -121,7 +121,7 @@
121
121
  letter-spacing: 0.05em;
122
122
  margin: var(--space-l) 0 var(--space-s);
123
123
  padding-bottom: var(--space-xs);
124
- border-bottom: 1px solid var(--color-neutral-lighter);
124
+ border-bottom: 1px solid var(--color-outline);
125
125
  }
126
126
 
127
127
  .ap-post-detail__main {
@@ -142,7 +142,7 @@
142
142
  .ap-post-detail__parents .ap-post-detail__parent-item {
143
143
  position: relative;
144
144
  padding-left: var(--space-m);
145
- border-left: 2px solid var(--color-neutral-lighter);
145
+ border-left: 2px solid var(--color-outline);
146
146
  }
147
147
 
148
148
  /* Main post highlight */
package/assets/reader.css CHANGED
@@ -18,7 +18,7 @@
18
18
  }
19
19
 
20
20
  .ap-breadcrumb a {
21
- color: var(--color-accent);
21
+ color: var(--color-primary-on-background);
22
22
  text-decoration: none;
23
23
  }
24
24
 
@@ -115,7 +115,7 @@
115
115
 
116
116
  .ap-tab--active {
117
117
  border-bottom-color: var(--color-primary);
118
- color: var(--color-primary);
118
+ color: var(--color-primary-on-background);
119
119
  font-weight: 600;
120
120
  }
121
121
 
@@ -150,10 +150,11 @@
150
150
  .ap-card {
151
151
  background: var(--color-offset);
152
152
  border: var(--border-width-thin) solid var(--color-outline);
153
- border-left: var(--border-width-thickest) solid var(--color-outline);
154
- border-radius: var(--border-radius-small);
153
+ border-left: 3px solid var(--color-outline);
154
+ border-radius: 8px;
155
155
  overflow: hidden;
156
156
  padding: var(--space-m);
157
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
157
158
  transition:
158
159
  box-shadow 0.2s ease,
159
160
  border-color 0.2s ease;
@@ -162,6 +163,7 @@
162
163
  .ap-card:hover {
163
164
  border-color: var(--color-outline-variant);
164
165
  border-left-color: var(--color-outline-variant);
166
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
165
167
  }
166
168
 
167
169
  /* ==========================================================================
@@ -240,7 +242,7 @@
240
242
  }
241
243
 
242
244
  .ap-card__reply-to a {
243
- color: var(--color-primary);
245
+ color: var(--color-primary-on-background);
244
246
  text-decoration: none;
245
247
  }
246
248
 
@@ -263,9 +265,9 @@
263
265
  border: var(--border-width-thin) solid var(--color-outline);
264
266
  border-radius: 50%;
265
267
  flex-shrink: 0;
266
- height: 40px;
268
+ height: 44px;
267
269
  object-fit: cover;
268
- width: 40px;
270
+ width: 44px;
269
271
  }
270
272
 
271
273
  .ap-card__avatar--default {
@@ -282,10 +284,12 @@
282
284
  display: flex;
283
285
  flex-direction: column;
284
286
  flex: 1;
287
+ gap: 1px;
285
288
  min-width: 0;
286
289
  }
287
290
 
288
291
  .ap-card__author-name {
292
+ font-size: 0.95em;
289
293
  font-weight: 600;
290
294
  overflow: hidden;
291
295
  text-overflow: ellipsis;
@@ -327,7 +331,7 @@
327
331
  .ap-card__timestamp {
328
332
  color: var(--color-on-offset);
329
333
  flex-shrink: 0;
330
- font-size: var(--font-size-xs);
334
+ font-size: var(--font-size-s);
331
335
  }
332
336
 
333
337
  .ap-card__edited {
@@ -345,7 +349,7 @@
345
349
 
346
350
  .ap-card__timestamp-link:hover {
347
351
  text-decoration: underline;
348
- color: var(--color-primary);
352
+ color: var(--color-primary-on-background);
349
353
  }
350
354
 
351
355
  /* ==========================================================================
@@ -374,14 +378,14 @@
374
378
 
375
379
  .ap-card__content {
376
380
  color: var(--color-on-background);
377
- line-height: var(--line-height-prose);
381
+ line-height: calc(4 / 3 * 1em);
378
382
  margin-bottom: var(--space-s);
379
383
  overflow-wrap: break-word;
380
384
  word-break: break-word;
381
385
  }
382
386
 
383
387
  .ap-card__content a {
384
- color: var(--color-primary);
388
+ color: var(--color-primary-on-background);
385
389
  }
386
390
 
387
391
  .ap-card__content p {
@@ -443,7 +447,7 @@
443
447
 
444
448
  .ap-card__content .h-card a:hover,
445
449
  .ap-card__content a.u-url.mention:hover {
446
- color: var(--color-primary);
450
+ color: var(--color-primary-on-background);
447
451
  text-decoration: underline;
448
452
  }
449
453
 
@@ -460,7 +464,7 @@
460
464
  }
461
465
 
462
466
  .ap-card__content a.mention.hashtag:hover {
463
- color: var(--color-primary);
467
+ color: var(--color-primary-on-background);
464
468
  text-decoration: underline;
465
469
  }
466
470
 
@@ -504,7 +508,7 @@
504
508
  ========================================================================== */
505
509
 
506
510
  .ap-card__gallery {
507
- border-radius: var(--border-radius-small);
511
+ border-radius: 6px;
508
512
  display: grid;
509
513
  gap: 2px;
510
514
  margin-bottom: var(--space-s);
@@ -524,9 +528,14 @@
524
528
  .ap-card__gallery img {
525
529
  background: var(--color-offset-variant);
526
530
  display: block;
527
- height: 200px;
531
+ height: 220px;
528
532
  object-fit: cover;
529
533
  width: 100%;
534
+ transition: filter 0.2s ease;
535
+ }
536
+
537
+ .ap-card__gallery-link:hover img {
538
+ filter: brightness(0.92);
530
539
  }
531
540
 
532
541
  .ap-card__gallery-link--more::after {
@@ -557,7 +566,7 @@
557
566
 
558
567
  .ap-card__gallery--1 img {
559
568
  height: auto;
560
- max-height: 400px;
569
+ max-height: 500px;
561
570
  }
562
571
 
563
572
  /* 2 photos — side by side */
@@ -711,17 +720,17 @@
711
720
  }
712
721
 
713
722
  .ap-card__mention {
714
- background: color-mix(in srgb, var(--color-accent) 12%, transparent);
723
+ background: color-mix(in srgb, var(--color-primary) 12%, transparent);
715
724
  border-radius: var(--border-radius-large);
716
- color: var(--color-accent);
725
+ color: var(--color-primary-on-background);
717
726
  font-size: var(--font-size-s);
718
727
  padding: 2px var(--space-xs);
719
728
  text-decoration: none;
720
729
  }
721
730
 
722
731
  .ap-card__mention:hover {
723
- background: color-mix(in srgb, var(--color-accent) 22%, transparent);
724
- color: var(--color-accent);
732
+ background: color-mix(in srgb, var(--color-primary) 22%, transparent);
733
+ color: var(--color-primary-on-background);
725
734
  }
726
735
 
727
736
  .ap-card__mention--legacy {
@@ -761,60 +770,86 @@
761
770
  border-top: var(--border-width-thin) solid var(--color-outline);
762
771
  display: flex;
763
772
  flex-wrap: wrap;
764
- gap: var(--space-s);
773
+ gap: 2px;
765
774
  padding-top: var(--space-s);
766
775
  }
767
776
 
768
777
  .ap-card__action {
769
778
  align-items: center;
770
779
  background: transparent;
771
- border: var(--border-width-thin) solid var(--color-outline);
772
- border-radius: var(--border-radius-small);
780
+ border: 0;
781
+ border-radius: 6px;
773
782
  color: var(--color-on-offset);
774
783
  cursor: pointer;
775
784
  display: inline-flex;
776
785
  font-size: var(--font-size-s);
777
- gap: var(--space-xs);
778
- padding: var(--space-xs) var(--space-s);
786
+ gap: 0.3em;
787
+ min-height: 36px;
788
+ padding: 0.25em 0.6em;
779
789
  text-decoration: none;
780
- transition: all 0.2s ease;
790
+ transition:
791
+ background-color 0.15s ease,
792
+ color 0.15s ease;
781
793
  }
782
794
 
783
795
  .ap-card__action:hover {
784
796
  background: var(--color-offset-variant);
785
- border-color: var(--color-outline-variant);
786
797
  color: var(--color-on-background);
787
798
  }
788
799
 
789
- /* Active interaction states using Indiekit's color palette */
800
+ /* Color-coded hover states per action type */
801
+ .ap-card__action--reply:hover {
802
+ background: color-mix(in srgb, var(--color-primary) 12%, transparent);
803
+ color: var(--color-primary);
804
+ }
805
+
806
+ .ap-card__action--boost:hover {
807
+ background: color-mix(in srgb, var(--color-green50) 12%, transparent);
808
+ color: var(--color-green50);
809
+ }
810
+
811
+ .ap-card__action--like:hover {
812
+ background: color-mix(in srgb, var(--color-red45) 12%, transparent);
813
+ color: var(--color-red45);
814
+ }
815
+
816
+ .ap-card__action--link:hover {
817
+ background: var(--color-offset-variant);
818
+ color: var(--color-on-background);
819
+ }
820
+
821
+ .ap-card__action--save:hover {
822
+ background: color-mix(in srgb, var(--color-primary) 12%, transparent);
823
+ color: var(--color-primary);
824
+ }
825
+
826
+ /* Active interaction states */
790
827
  .ap-card__action--like.ap-card__action--active {
791
- background: var(--color-red90);
792
- border-color: var(--color-red45);
828
+ background: color-mix(in srgb, var(--color-red45) 12%, transparent);
793
829
  color: var(--color-red45);
794
830
  }
795
831
 
796
832
  .ap-card__action--boost.ap-card__action--active {
797
- background: var(--color-green90);
798
- border-color: var(--color-green50);
833
+ background: color-mix(in srgb, var(--color-green50) 12%, transparent);
799
834
  color: var(--color-green50);
800
835
  }
801
836
 
802
837
  .ap-card__action--save.ap-card__action--active {
803
- background: #4a9eff22;
804
- border-color: #4a9eff;
805
- color: #4a9eff;
838
+ background: color-mix(in srgb, var(--color-primary) 12%, transparent);
839
+ color: var(--color-primary);
806
840
  }
807
841
 
808
842
  .ap-card__action:disabled {
809
843
  cursor: wait;
810
- opacity: 0.6;
844
+ opacity: 0.5;
811
845
  }
812
846
 
813
847
  /* Interaction counts */
814
848
  .ap-card__count {
815
849
  font-size: var(--font-size-xs);
816
- color: var(--color-on-offset);
817
- margin-left: 0.25rem;
850
+ color: inherit;
851
+ opacity: 0.7;
852
+ margin-left: 0.1em;
818
853
  font-variant-numeric: tabular-nums;
819
854
  }
820
855
 
@@ -839,7 +874,7 @@
839
874
  }
840
875
 
841
876
  .ap-pagination a {
842
- color: var(--color-primary);
877
+ color: var(--color-primary-on-background);
843
878
  text-decoration: none;
844
879
  }
845
880
 
@@ -1085,11 +1120,11 @@
1085
1120
  }
1086
1121
 
1087
1122
  .ap-notifications__btn--danger {
1088
- color: var(--color-red45);
1123
+ color: var(--color-error);
1089
1124
  }
1090
1125
 
1091
1126
  .ap-notifications__btn--danger:hover {
1092
- border-color: var(--color-red45);
1127
+ border-color: var(--color-error);
1093
1128
  }
1094
1129
 
1095
1130
  .ap-notification {
@@ -1195,7 +1230,7 @@
1195
1230
 
1196
1231
  .ap-notification__dismiss-btn:hover {
1197
1232
  background: var(--color-offset-variant);
1198
- color: var(--color-red45);
1233
+ color: var(--color-error);
1199
1234
  }
1200
1235
 
1201
1236
  .ap-notification__actions {
@@ -1281,7 +1316,7 @@
1281
1316
  }
1282
1317
 
1283
1318
  .ap-profile__bio a {
1284
- color: var(--color-primary);
1319
+ color: var(--color-primary-on-background);
1285
1320
  }
1286
1321
 
1287
1322
  /* Override upstream .mention { display: grid } for bio content */
@@ -1428,7 +1463,7 @@
1428
1463
  }
1429
1464
 
1430
1465
  .ap-my-profile__bio a {
1431
- color: var(--color-primary);
1466
+ color: var(--color-primary-on-background);
1432
1467
  }
1433
1468
 
1434
1469
  /* Override upstream .mention { display: grid } for bio content */
@@ -1478,7 +1513,7 @@
1478
1513
  }
1479
1514
 
1480
1515
  .ap-my-profile__field-value a {
1481
- color: var(--color-primary);
1516
+ color: var(--color-primary-on-background);
1482
1517
  }
1483
1518
 
1484
1519
  .ap-my-profile__stats {
@@ -1760,7 +1795,7 @@
1760
1795
  }
1761
1796
 
1762
1797
  .ap-post-detail__back-link {
1763
- color: var(--color-primary);
1798
+ color: var(--color-primary-on-background);
1764
1799
  font-size: var(--font-size-s);
1765
1800
  text-decoration: none;
1766
1801
  }
@@ -1844,10 +1879,10 @@
1844
1879
  }
1845
1880
 
1846
1881
  .ap-tag-header__follow-btn {
1847
- background: var(--color-accent);
1882
+ background: var(--color-primary);
1848
1883
  border: none;
1849
1884
  border-radius: var(--border-radius-small);
1850
- color: var(--color-on-accent);
1885
+ color: var(--color-on-primary, var(--color-neutral99));
1851
1886
  cursor: pointer;
1852
1887
  font-size: var(--font-size-s);
1853
1888
  padding: var(--space-xs) var(--space-s);
@@ -1988,10 +2023,10 @@
1988
2023
  }
1989
2024
 
1990
2025
  .ap-explore-error {
1991
- background: color-mix(in srgb, var(--color-red45) 10%, transparent);
1992
- border: var(--border-width-thin) solid var(--color-red45);
2026
+ background: color-mix(in srgb, var(--color-error) 10%, transparent);
2027
+ border: var(--border-width-thin) solid var(--color-error);
1993
2028
  border-radius: var(--border-radius-small);
1994
- color: var(--color-red45);
2029
+ color: var(--color-error);
1995
2030
  margin-bottom: var(--space-m);
1996
2031
  padding: var(--space-s) var(--space-m);
1997
2032
  }
@@ -2261,7 +2296,7 @@
2261
2296
  }
2262
2297
 
2263
2298
  .ap-tab-control--remove:hover {
2264
- color: var(--color-red45);
2299
+ color: var(--color-error);
2265
2300
  }
2266
2301
 
2267
2302
  /* Truncate long domain names in tab labels */
@@ -2286,13 +2321,13 @@
2286
2321
  }
2287
2322
 
2288
2323
  .ap-tab__badge--local {
2289
- background: color-mix(in srgb, var(--color-blue40, #2563eb) 15%, transparent);
2290
- color: var(--color-blue40, #2563eb);
2324
+ background: color-mix(in srgb, var(--color-primary) 15%, transparent);
2325
+ color: var(--color-primary-on-background);
2291
2326
  }
2292
2327
 
2293
2328
  .ap-tab__badge--federated {
2294
- background: color-mix(in srgb, var(--color-purple45, #7c3aed) 15%, transparent);
2295
- color: var(--color-purple45, #7c3aed);
2329
+ background: color-mix(in srgb, var(--color-purple45) 15%, transparent);
2330
+ color: var(--color-purple45);
2296
2331
  }
2297
2332
 
2298
2333
  /* +# button for adding hashtag tabs */
@@ -2358,9 +2393,9 @@
2358
2393
 
2359
2394
  .ap-explore-pin-btn {
2360
2395
  background: none;
2361
- border: var(--border-width-thin) solid var(--color-primary);
2396
+ border: var(--border-width-thin) solid var(--color-primary-on-background);
2362
2397
  border-radius: var(--border-radius-small);
2363
- color: var(--color-primary);
2398
+ color: var(--color-primary-on-background);
2364
2399
  cursor: pointer;
2365
2400
  font-family: inherit;
2366
2401
  font-size: var(--font-size-s);
@@ -2440,23 +2475,23 @@
2440
2475
  }
2441
2476
 
2442
2477
  .ap-explore-tab-error__message {
2443
- color: var(--color-red45);
2478
+ color: var(--color-error);
2444
2479
  font-size: var(--font-size-s);
2445
2480
  margin: 0;
2446
2481
  }
2447
2482
 
2448
2483
  .ap-explore-tab-error__retry {
2449
2484
  background: none;
2450
- border: 1px solid var(--color-accent);
2485
+ border: 1px solid var(--color-primary-on-background);
2451
2486
  border-radius: var(--border-radius-small);
2452
- color: var(--color-accent);
2487
+ color: var(--color-primary-on-background);
2453
2488
  cursor: pointer;
2454
2489
  font-size: var(--font-size-s);
2455
2490
  padding: var(--space-xs) var(--space-s);
2456
2491
  }
2457
2492
 
2458
2493
  .ap-explore-tab-error__retry:hover {
2459
- background: color-mix(in srgb, var(--color-accent) 10%, transparent);
2494
+ background: color-mix(in srgb, var(--color-primary) 10%, transparent);
2460
2495
  }
2461
2496
 
2462
2497
  /* Empty state */
@@ -2536,9 +2571,14 @@
2536
2571
 
2537
2572
  .ap-quote-embed {
2538
2573
  border: var(--border-width-thin) solid var(--color-outline);
2539
- border-radius: var(--border-radius-small);
2574
+ border-radius: 8px;
2540
2575
  margin-top: var(--space-s);
2541
2576
  overflow: hidden;
2577
+ transition: border-color 0.15s ease;
2578
+ }
2579
+
2580
+ .ap-quote-embed:hover {
2581
+ border-color: var(--color-outline-variant);
2542
2582
  }
2543
2583
 
2544
2584
  .ap-quote-embed--pending {
@@ -2553,7 +2593,7 @@
2553
2593
  }
2554
2594
 
2555
2595
  .ap-quote-embed__link:hover {
2556
- background: var(--color-offset);
2596
+ background: color-mix(in srgb, var(--color-offset) 50%, transparent);
2557
2597
  }
2558
2598
 
2559
2599
  .ap-quote-embed__author {
@@ -2618,8 +2658,8 @@
2618
2658
  .ap-quote-embed__content {
2619
2659
  color: var(--color-on-background);
2620
2660
  font-size: var(--font-size-s);
2621
- line-height: 1.5;
2622
- max-height: calc(1.5em * 6);
2661
+ line-height: calc(4 / 3 * 1em);
2662
+ max-height: calc(1.333em * 6);
2623
2663
  overflow: hidden;
2624
2664
  }
2625
2665
 
@@ -2709,3 +2749,103 @@
2709
2749
  z-index: 2;
2710
2750
  }
2711
2751
 
2752
+ /* ==========================================================================
2753
+ Dark Mode Overrides
2754
+ Softens saturated colors that are uncomfortable on dark backgrounds.
2755
+ Uses Indiekit's existing light-variant tokens (red80, green90, yellow90)
2756
+ which are designed for dark surfaces.
2757
+ ========================================================================== */
2758
+
2759
+ @media (prefers-color-scheme: dark) {
2760
+
2761
+ /* --- Action button hover states: softer colors, more visible tinted backgrounds --- */
2762
+ .ap-card__action--reply:hover {
2763
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
2764
+ color: var(--color-primary-on-background);
2765
+ }
2766
+
2767
+ .ap-card__action--boost:hover {
2768
+ background: color-mix(in srgb, var(--color-green50) 18%, transparent);
2769
+ color: var(--color-green90);
2770
+ }
2771
+
2772
+ .ap-card__action--like:hover {
2773
+ background: color-mix(in srgb, var(--color-red45) 18%, transparent);
2774
+ color: var(--color-red80);
2775
+ }
2776
+
2777
+ .ap-card__action--save:hover {
2778
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
2779
+ color: var(--color-primary-on-background);
2780
+ }
2781
+
2782
+ /* --- Active interaction states --- */
2783
+ .ap-card__action--like.ap-card__action--active {
2784
+ background: color-mix(in srgb, var(--color-red45) 18%, transparent);
2785
+ color: var(--color-red80);
2786
+ }
2787
+
2788
+ .ap-card__action--boost.ap-card__action--active {
2789
+ background: color-mix(in srgb, var(--color-green50) 18%, transparent);
2790
+ color: var(--color-green90);
2791
+ }
2792
+
2793
+ .ap-card__action--save.ap-card__action--active {
2794
+ background: color-mix(in srgb, var(--color-primary) 18%, transparent);
2795
+ color: var(--color-primary-on-background);
2796
+ }
2797
+
2798
+ /* --- Post-type left border accents: desaturated for dark surfaces --- */
2799
+ .ap-card--note,
2800
+ .ap-card--note:hover {
2801
+ border-left-color: var(--color-purple90);
2802
+ }
2803
+
2804
+ .ap-card--article,
2805
+ .ap-card--article:hover {
2806
+ border-left-color: var(--color-green90);
2807
+ }
2808
+
2809
+ .ap-card--boost,
2810
+ .ap-card--boost:hover {
2811
+ border-left-color: var(--color-yellow90);
2812
+ }
2813
+
2814
+ .ap-card--reply,
2815
+ .ap-card--reply:hover {
2816
+ border-left-color: var(--color-primary-on-background);
2817
+ }
2818
+
2819
+ /* --- Notification unread glow: toned down --- */
2820
+ .ap-notification--unread {
2821
+ border-color: var(--color-yellow90);
2822
+ box-shadow: 0 0 6px 0 color-mix(in srgb, var(--color-yellow50) 15%, transparent);
2823
+ }
2824
+
2825
+ /* --- Post detail highlight ring: softened --- */
2826
+ .ap-post-detail__main .ap-card {
2827
+ border-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
2828
+ box-shadow: 0 0 0 1px color-mix(in srgb, var(--color-primary) 50%, transparent);
2829
+ }
2830
+
2831
+ /* --- Card shadows: use light tint instead of black --- */
2832
+ .ap-card {
2833
+ box-shadow: 0 1px 2px rgba(255, 255, 255, 0.04);
2834
+ }
2835
+
2836
+ .ap-card:hover {
2837
+ box-shadow: 0 2px 8px rgba(255, 255, 255, 0.06);
2838
+ }
2839
+
2840
+ /* --- Compose counter warning --- */
2841
+ .ap-compose__counter--warn {
2842
+ color: var(--color-yellow90);
2843
+ }
2844
+
2845
+ /* --- Tab badge federated: soften purple --- */
2846
+ .ap-tab__badge--federated {
2847
+ color: var(--color-purple90);
2848
+ background: color-mix(in srgb, var(--color-purple45) 18%, transparent);
2849
+ }
2850
+ }
2851
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-activitypub",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "ActivityPub federation endpoint for Indiekit via Fedify. Adds full fediverse support: actor, inbox, outbox, followers, following, syndication, and Mastodon migration.",
5
5
  "keywords": [
6
6
  "indiekit",
@@ -19,6 +19,8 @@
19
19
 
20
20
  {# AP link interception for internal navigation #}
21
21
  <script defer src="/assets/@rmdes-indiekit-endpoint-activitypub/reader-links.js"></script>
22
+ {# Blurhash placeholder backgrounds for gallery images #}
23
+ <script defer src="/assets/@rmdes-indiekit-endpoint-activitypub/reader-blurhash.js"></script>
22
24
 
23
25
  {% if readerParent %}
24
26
  <nav class="ap-breadcrumb" aria-label="Breadcrumb">
@@ -10,10 +10,18 @@
10
10
  {# Support both old string format and new object format #}
11
11
  {% set photoSrc = photo.url if photo.url else photo %}
12
12
  {% set photoAlt = photo.alt if photo.alt else "" %}
13
+ {% set photoBlurhash = photo.blurhash if photo.blurhash else "" %}
14
+ {# Focus-point cropping: convert -1..1 range to CSS object-position percentages #}
15
+ {% set focusStyle = "" %}
16
+ {% if photo.focus and photo.focus.x != null and photo.focus.y != null %}
17
+ {% set fpX = ((photo.focus.x + 1) / 2 * 100) %}
18
+ {% set fpY = ((1 - (photo.focus.y + 1) / 2) * 100) %}
19
+ {% set focusStyle = "object-position:" + fpX + "% " + fpY + "%;" %}
20
+ {% endif %}
13
21
  {% if loop.index0 < 4 %}
14
22
  <div class="ap-card__gallery-item" x-data="{ showAlt: false }">
15
23
  <button type="button" @click="idx = {{ loop.index0 }}; lightbox = true" class="ap-card__gallery-link{% if loop.index0 == 3 and extraCount > 0 %} ap-card__gallery-link--more{% endif %}">
16
- <img src="{{ photoSrc }}" alt="{{ photoAlt }}" loading="lazy">
24
+ <img src="{{ photoSrc }}" alt="{{ photoAlt }}" loading="lazy"{% if focusStyle %} style="{{ focusStyle }}"{% endif %}{% if photoBlurhash %} data-blurhash="{{ photoBlurhash }}"{% endif %}>
17
25
  {% if loop.index0 == 3 and extraCount > 0 %}
18
26
  <span class="ap-card__gallery-more">+{{ extraCount }}</span>
19
27
  {% endif %}
@@ -26,8 +26,14 @@
26
26
  {% endif %}
27
27
  {% if item.quote.photo and item.quote.photo.length > 0 %}
28
28
  {% set qPhoto = item.quote.photo[0] %}
29
+ {% set qFocusStyle = "" %}
30
+ {% if qPhoto.focus and qPhoto.focus.x != null and qPhoto.focus.y != null %}
31
+ {% set qFpX = ((qPhoto.focus.x + 1) / 2 * 100) %}
32
+ {% set qFpY = ((1 - (qPhoto.focus.y + 1) / 2) * 100) %}
33
+ {% set qFocusStyle = "object-position:" + qFpX + "% " + qFpY + "%;" %}
34
+ {% endif %}
29
35
  <div class="ap-quote-embed__media">
30
- <img src="{{ qPhoto.url if qPhoto.url else qPhoto }}" alt="{{ qPhoto.alt if qPhoto.alt else '' }}" loading="lazy" class="ap-quote-embed__photo">
36
+ <img src="{{ qPhoto.url if qPhoto.url else qPhoto }}" alt="{{ qPhoto.alt if qPhoto.alt else '' }}" loading="lazy" class="ap-quote-embed__photo"{% if qFocusStyle %} style="{{ qFocusStyle }}"{% endif %}>
31
37
  </div>
32
38
  {% endif %}
33
39
  </a>