@sarimarcus/content-sites-ui 0.16.0 → 0.18.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,20 @@
2
2
 
3
3
  All packages in the platform release in lockstep; entries are per release version.
4
4
 
5
+ ## 0.18.0 (2026-10-02)
6
+
7
+ - New page templates in `./editorial`: `StaticProsePage` (legal and site-information pages: heading, optional eyebrow, prose slot, `after` slot, closing CTA box; not Pagefind-indexed), `NotFoundPage` (the 404 body, every string a prop; its decoration ships as `./not-found.css` so a barrel import never carries it to other pages), `ArticlesListingPage` (article index and topic hub shell: breadcrumb band, header, `<main>`, Pagefind body optional) and `ArticleFeatureCard` (SLA-2744).
8
+ - `./search`: `SearchPage`, the search page markup (hero, type chips, optional audience row, result states, type-icon template), with its client controller `initSearchPage` on the new `./search-page` entry point (SLA-2744).
9
+ - `./layout`: `ListingShell`, the listing hub frame inside one Pagefind-ignore wrapper; StructuredData stays a direct Layout child so its JSON-LD renders in `<head>` (SLA-2744).
10
+ - `./composed`: `ArrowLinkGrid`, a grid of bordered link tiles (SLA-2744).
11
+
12
+ ## 0.17.0 (2026-10-01)
13
+
14
+ - New `DocumentShell` (`./layout`): the whole HTML document a site page renders into: description, robots (`noindex, follow` when noindex), canonical from the page path, RSS and favicons, Open Graph / article / Twitter (handle optional), title, non-critical stylesheets, the Emerald loader, the skip link, navigation and footer wrapped out of the Pagefind index, `#main-content`, and consent-gated analytics. Slots `head-start`, `fonts`, `head`, `body-start`, `nav`, `footer`. It imports no stylesheet and emits no bundled script, so a site's thin Layout keeps its bundle names (SLA-2741).
15
+ - New `ConsentAnalytics` (`./layout`): the inline GTM + Consent Mode v2 + Silktide bootstrap (consent default before the gtm.js event in one synchronous step; `window.openCookiePreferences`), production only, plus the `<noscript>` GTM iframe. `layout/resources.ts` declares the hosts it loads (SLA-2741).
16
+ - New `EmeraldLoader` (`./affiliate`): the Travelpayouts Emerald script from the site's trs; `emeraldResources` declares its host (SLA-2741).
17
+ - New entry points `./analytics` (`setupAnalytics`, `track`, `pageTypeOf`, delegated, FAQ and scroll-depth tracking) and `./travelpayouts-styler` (`setupTravelpayoutsStyler`, palette, fonts and copy as options), moved from the sites. FAQ tracking now fires: it tested `dataset.faqTrack`, which is empty on the bare `data-faq-track` attribute ui `FAQ` renders (SLA-2741).
18
+
5
19
  ## 0.16.0 (2026-10-01)
6
20
 
7
21
  - New `./navigation`: `SiteHeader` (fixed header, labelled main `<nav>`, logo/desktop/actions/mobile slots, mobile toggle, mobile panel after the header with `data-pagefind-ignore`, `surfaceClass`, Ctrl/Cmd+K → `searchPath?focus=1`), `NavDropdown` (click, hover with a 150 ms close delay, ArrowDown/ArrowUp/Escape, outside click, focus leaving; a click on a hover-opened dropdown keeps it open), `MobileNavSection` (single-open accordion, `aria-controls`), `NavMenuLink` / `MobileNavLink` (panel rows by `NavTone`), `SiteFooter` (sections, `<nav aria-label="Legal">`, cookie-preferences button through `data-cookie-preferences`, no inline handler), `isActivePath`, and the `SiteIdentity` / `NavLink` / `NavGroup` / `FooterSection` / `NavTone` contracts. One bundled controller, idempotent on `astro:page-load`, any number of headers per page; the toggle and chevron icons now switch state (SLA-2735).
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # @sarimarcus/content-sites-ui
2
2
 
3
3
  Universal Astro UI shared by every content site. Named entry points, one per folder: `primitives`,
4
- `composed`, `editorial`, `affiliate`, `seo`, `layout`, `navigation`, `search` and `types`. For example,
4
+ `composed`, `editorial`, `affiliate`, `seo`, `layout`, `navigation`, `search` and `types`, plus the script
5
+ entry points `analytics`, `travelpayouts-styler` and `search-page`. For example,
5
6
  `import { Container } from '@sarimarcus/content-sites-ui/layout'`. `src/internal` is never exported,
6
7
  and `src/styles` holds the Tailwind entry.
7
8
 
@@ -73,7 +74,59 @@ state and one bundled controller (idempotent on `astro:page-load`, any number of
73
74
  `<nav aria-label="Legal">` ending with the cookie-preferences button, which calls `window.openCookiePreferences()`
74
75
  (the site's consent loader defines it). Defaults paint the `inverse` roles; class props restyle each part.
75
76
  - `appendResults` renders Pagefind result cards from the site's type taxonomy; `SearchSuggestions` renders the
76
- empty-state chips. The search page, its copy and its index stay in the site.
77
+ empty-state chips. `SearchPage` is the whole page body (hero, type chips, an audience row when `audienceFacets` is
78
+ passed, result states); the route keeps Layout (`noindex`), imports `search.css` and calls `initSearchPage` from
79
+ `@sarimarcus/content-sites-ui/search-page` in its own `<script>` with the same taxonomy and copy. The copy, the
80
+ taxonomy and the index stay in the site.
81
+
82
+ ## Page templates
83
+
84
+ ```ts
85
+ import { StaticProsePage, NotFoundPage, ArticlesListingPage, ArticleFeatureCard } from '@sarimarcus/content-sites-ui/editorial';
86
+ import { ListingShell } from '@sarimarcus/content-sites-ui/layout';
87
+ ```
88
+
89
+ The route file keeps `getStaticPaths`, queries, URLs, `<Layout title description>` (the SEO length gate reads it) and
90
+ `<StructuredData slot="head">` as a direct Layout child; the template receives normalised props and slots. Hrefs and
91
+ prose stay in the route so the site's link and outbound-domain scanners still see them.
92
+
93
+ - `StaticProsePage`: privacy, terms, contact, editorial standards. Prose in the default slot, `after` slot, optional
94
+ `cta` box. Carries no Pagefind body.
95
+ - `NotFoundPage`: the 404 body; the route passes `noindex` to Layout and imports `@sarimarcus/content-sites-ui/not-found.css`.
96
+ - `ArticlesListingPage` / `ArticleFeatureCard`: article index, topic hubs and their paginated pages.
97
+ - `ListingShell`: listing hubs (breadcrumb, `hero`, `before-main`, Container'd `<main>`, `after-main`), one outer
98
+ `data-pagefind-ignore`.
99
+
100
+ A component exported from a barrel must not carry a `<style>` block: Astro ships it to every page that imports the
101
+ barrel. Put such rules in a stylesheet entry the one route imports.
102
+
103
+ ## Document shell
104
+
105
+ ```astro
106
+ ---
107
+ import { DocumentShell } from '@sarimarcus/content-sites-ui/layout';
108
+ ---
109
+ <DocumentShell title={title} description={description} siteName={site.siteName} siteUrl={site.url} themeColor="#FAFAF8"
110
+ gtmId={site.analytics.gtmId} consentPrompt="…" consentTypes={consentTypes} skipLinkLabel="Skip to main content">
111
+ <Fragment slot="fonts">…</Fragment>
112
+ <Navigation slot="nav" />
113
+ <slot />
114
+ <Footer slot="footer" />
115
+ </DocumentShell>
116
+ ```
117
+
118
+ `DocumentShell` renders the whole document: description, robots (`noindex, follow` when `noindex`), canonical (from
119
+ the page path unless `canonicalUrl`), RSS and favicons, Open Graph / article / Twitter (`twitterHandle` optional),
120
+ title, non-critical `stylesheets`, the Emerald loader (`travelpayoutsTrs`), the skip link, the navigation and footer
121
+ wrapped in `data-pagefind-ignore`, `#main-content` and `ConsentAnalytics`. Slots: `head-start`, `fonts`, `head`,
122
+ `body-start`, `nav`, `footer`, default. It imports no stylesheet and emits no bundled script, so the site's thin
123
+ Layout keeps both (and with them its bundle names).
124
+
125
+ `ConsentAnalytics` is inline on purpose: the Consent Mode default must reach the dataLayer before the `gtm.js` event
126
+ in one synchronous step. It defines `window.openCookiePreferences` for the footer button. `./analytics`
127
+ (`setupAnalytics`, `track`, `pageTypeOf`) and `./travelpayouts-styler` (`setupTravelpayoutsStyler`) are their own
128
+ entry points so a client `<script>` imports no Astro component. The hosts the shell loads are declared in
129
+ `layout/resources.ts`; `scripts/validate-csp-coverage.mjs` checks every site's CSP against them.
77
130
 
78
131
  ## Search and consent styles
79
132
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sarimarcus/content-sites-ui",
3
- "version": "0.16.0",
3
+ "version": "0.18.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",
@@ -30,8 +30,11 @@
30
30
  "./composed": "./src/composed/index.ts",
31
31
  "./editorial": "./src/editorial/index.ts",
32
32
  "./affiliate": "./src/affiliate/index.ts",
33
+ "./travelpayouts-styler": "./src/affiliate/travelpayoutsStyler.ts",
33
34
  "./seo": "./src/seo/index.ts",
34
35
  "./layout": "./src/layout/index.ts",
36
+ "./analytics": "./src/layout/analytics.ts",
37
+ "./search-page": "./src/search/searchPage.ts",
35
38
  "./navigation": "./src/navigation/index.ts",
36
39
  "./search": "./src/search/index.ts",
37
40
  "./types": "./src/types/index.ts",
@@ -39,12 +42,13 @@
39
42
  "./widths.css": "./src/styles/widths.css",
40
43
  "./theme.css": "./src/styles/theme.css",
41
44
  "./consent.css": "./src/styles/consent.css",
42
- "./search.css": "./src/styles/search.css"
45
+ "./search.css": "./src/styles/search.css",
46
+ "./not-found.css": "./src/styles/not-found.css"
43
47
  },
44
48
  "peerDependencies": {
45
49
  "@lucide/astro": "^1.23.0",
46
50
  "astro": "^7.0.6",
47
51
  "tailwindcss": "^4.1.18",
48
- "@sarimarcus/content-sites-core": "0.16.0"
52
+ "@sarimarcus/content-sites-core": "0.18.0"
49
53
  }
50
54
  }
@@ -0,0 +1,40 @@
1
+ ---
2
+ /**
3
+ * The Travelpayouts Emerald script that turns the site's affiliate links into tracked ones. Deferred off the critical
4
+ * path: it loads on the first interaction, or once the page is idle after load. `trs` is the site's Travelpayouts id.
5
+ */
6
+ import { EMERALD_ORIGIN } from './resources.ts';
7
+
8
+ interface Props {
9
+ trs: string;
10
+ }
11
+
12
+ const { trs } = Astro.props;
13
+ const emeraldOrigin = EMERALD_ORIGIN;
14
+ ---
15
+
16
+ <script is:inline define:vars={{ trs, emeraldOrigin }}>
17
+ (function () {
18
+ if (!/^\d+$/.test(trs)) return;
19
+ var loaded = false;
20
+ function loadAffiliate() {
21
+ if (loaded) return;
22
+ loaded = true;
23
+ var script = document.createElement('script');
24
+ script.async = 1;
25
+ script.src = emeraldOrigin + '/' + btoa(trs) + '.js?t=' + trs;
26
+ document.head.appendChild(script);
27
+ }
28
+ var opts = { once: true, passive: true, capture: true };
29
+ ['pointerdown', 'keydown', 'touchstart', 'scroll'].forEach(function (evt) {
30
+ window.addEventListener(evt, loadAffiliate, opts);
31
+ });
32
+ window.addEventListener('load', function () {
33
+ if ('requestIdleCallback' in window) {
34
+ requestIdleCallback(loadAffiliate, { timeout: 5000 });
35
+ } else {
36
+ setTimeout(loadAffiliate, 4000);
37
+ }
38
+ });
39
+ })();
40
+ </script>
@@ -11,7 +11,10 @@ export { default as AffiliateCardSection } from './AffiliateCardSection.astro';
11
11
  export { default as AffiliateCarousel } from './AffiliateCarousel.astro';
12
12
  export { default as AffiliateComparisonTable } from './AffiliateComparisonTable.astro';
13
13
  export { default as HotelAffiliateCTA } from './HotelAffiliateCTA.astro';
14
- export { externalResources } from './resources';
14
+ export { externalResources, emeraldResources, EMERALD_ORIGIN } from './resources';
15
15
  export type { WidgetKind, CspDirective, WidgetResources } from './resources';
16
16
  export type { AffiliateCardLabels, AffiliateCardItem, HotelCtaLabels, ComparisonRow, ComparisonTableLabels } from './types';
17
17
  export { selectActivities, type SelectableActivity, type SelectOptions } from './select';
18
+ export { default as EmeraldLoader } from './EmeraldLoader.astro';
19
+ export { setupTravelpayoutsStyler } from './travelpayoutsStyler.ts';
20
+ export type { TravelpayoutsStylerOptions, TravelpayoutsStylerColors } from './travelpayoutsStyler.ts';
@@ -28,3 +28,10 @@ export const externalResources: Record<WidgetKind, WidgetResources> = {
28
28
  },
29
29
  };
30
30
 
31
+
32
+ /** The Travelpayouts Emerald loader every page carries (EmeraldLoader): https://emrldtp.com/<base64 trs>.js?t=<trs>. */
33
+ export const EMERALD_ORIGIN = 'https://emrldtp.com';
34
+
35
+ export const emeraldResources: Partial<Record<CspDirective, readonly string[]>> = {
36
+ 'script-src': [EMERALD_ORIGIN],
37
+ };