@sarimarcus/content-sites-ui 0.10.0 → 0.11.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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All packages in the platform release in lockstep; entries are per release version.
4
4
 
5
+ ## 0.11.0 (2026-10-01)
6
+
7
+ - New `EntityCard` in `./composed`: the card frame (link, image or fallback, corner overlay, body, footer meta and CTA slots) shared by the tourism Hotel and Activity cards (SLA-2739).
8
+
5
9
  ## 0.10.0 (2026-09-30)
6
10
 
7
11
  - New `./composed`: `TableOfContents` (+ `miniLinkClass`, opt-in `hideMiniPastLastSection`), `CardCarousel` (active dot from `--carousel-dot`), `QuickFacts` (link colours as roles, `labelSizeClass`), `FAQ` and `InlineImage` (rendered HTML in, via `TrustedHtml`; markdown stays in the site), `Pagination` (`href(page)` builder). Controllers are bundled modules, initialised once per instance. `./types`: `TrustedHtml`, `trustedHtml()`, `textAsHtml()` (SLA-2732).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarimarcus/content-sites-ui",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Universal Astro UI components, layout shell and design tokens shared by the content sites.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -42,6 +42,6 @@
42
42
  "@lucide/astro": "^1.23.0",
43
43
  "astro": "^7.0.6",
44
44
  "tailwindcss": "^4.1.18",
45
- "@sarimarcus/content-sites-core": "0.10.0"
45
+ "@sarimarcus/content-sites-core": "0.11.0"
46
46
  }
47
47
  }
@@ -0,0 +1,110 @@
1
+ ---
2
+ /**
3
+ * EntityCard — the shared frame of an entity teaser card: a whole-card link with a focus ring, an image block
4
+ * (optimized Picture, or a fallback img), a content area and a footer row. Everything that varies between card types
5
+ * comes in through slots, so the frame owns no copy and no data shape.
6
+ *
7
+ * Variants:
8
+ * media — fixed-height image, the name heading sits in the body (hotel, lodge).
9
+ * poster — 3:2 image, the name heading is overlaid on the image through the `overlay` slot (activity, tour).
10
+ *
11
+ * Slots:
12
+ * overlay — inside the image block, after the image: corner badges, gradient scrims, an overlaid heading.
13
+ * (default) — the body above the footer.
14
+ * footer-meta — start of the footer row (location, closing time…).
15
+ * footer-cta — end of the footer row.
16
+ */
17
+ import { Picture } from 'astro:assets';
18
+ import type { ImageMetadata } from 'astro';
19
+
20
+ type ImageFormat = 'avif' | 'webp' | 'png' | 'jpeg' | 'jpg' | 'svg';
21
+
22
+ interface Props {
23
+ href: string;
24
+ variant?: 'media' | 'poster';
25
+ image?: ImageMetadata;
26
+ /** Shown when `image` is absent. */
27
+ fallbackSrc: string;
28
+ imageAlt: string;
29
+ formats?: ImageFormat[];
30
+ /** Position in the list: the first three load eagerly, the first one at high priority. */
31
+ index?: number;
32
+ class?: string;
33
+ }
34
+
35
+ const {
36
+ href,
37
+ variant = 'media',
38
+ image,
39
+ fallbackSrc,
40
+ imageAlt,
41
+ formats = ['avif', 'webp'],
42
+ index = 999,
43
+ class: className,
44
+ } = Astro.props;
45
+
46
+ const poster = variant === 'poster';
47
+ const linkClass = poster
48
+ ? 'flex flex-col h-full focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded-xl'
49
+ : 'flex flex-col flex-1 focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded-xl';
50
+ const mediaClass = poster
51
+ ? 'relative aspect-[3/2] overflow-hidden img-placeholder shrink-0'
52
+ : 'relative h-44 sm:h-52 overflow-hidden img-placeholder shrink-0';
53
+ const fallbackWidth = poster ? '400' : '320';
54
+ const fallbackHeight = poster ? '224' : '192';
55
+ const contentClass = poster ? 'p-4 sm:p-5 flex flex-col flex-1' : 'flex-1 p-4 sm:p-5 flex flex-col';
56
+ const footerClass = poster
57
+ ? 'flex items-center justify-between pt-3 border-t border-border mt-auto'
58
+ : 'flex items-center justify-between pt-3 border-t border-border';
59
+ ---
60
+
61
+ <article class:list={['group relative bg-card rounded-xl overflow-hidden card-lift border border-border flex flex-col h-full', className]}>
62
+ <a href={href} class={linkClass}>
63
+ <div class={mediaClass}>
64
+ {image ? (
65
+ <Picture
66
+ src={image}
67
+ alt={imageAlt}
68
+ widths={[240, 320, 400, 480, 640]}
69
+ sizes="(max-width: 639px) 100vw, (max-width: 1023px) 50vw, 33vw"
70
+ formats={formats}
71
+ fallbackFormat="webp"
72
+ pictureAttributes={{ class: 'block w-full h-full' }}
73
+ class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
74
+ loading={index < 3 ? "eager" : "lazy"}
75
+ fetchpriority={index === 0 ? "high" : undefined}
76
+ decoding="async"
77
+ />
78
+ ) : (
79
+ <img
80
+ src={fallbackSrc}
81
+ alt={imageAlt}
82
+ width={fallbackWidth}
83
+ height={fallbackHeight}
84
+ class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-105"
85
+ loading={index < 3 ? "eager" : "lazy"}
86
+ fetchpriority={index === 0 ? "high" : undefined}
87
+ decoding="async"
88
+ />
89
+ )}
90
+
91
+ <slot name="overlay" />
92
+ </div>
93
+
94
+ <div class={contentClass}>
95
+ {poster ? (
96
+ <slot />
97
+ ) : (
98
+ <div class="flex-1">
99
+ <slot />
100
+ </div>
101
+ )}
102
+
103
+ <div class={footerClass}>
104
+ <slot name="footer-meta" />
105
+
106
+ <slot name="footer-cta" />
107
+ </div>
108
+ </div>
109
+ </a>
110
+ </article>
@@ -5,3 +5,4 @@ export { default as QuickFacts } from './QuickFacts.astro';
5
5
  export { default as FAQ } from './FAQ.astro';
6
6
  export { default as InlineImage } from './InlineImage.astro';
7
7
  export { default as Pagination } from './Pagination.astro';
8
+ export { default as EntityCard } from './EntityCard.astro';