@xenterprises/nuxt-x-marketing 1.5.2 → 1.7.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
@@ -30,6 +30,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
30
30
  section and its sample product data were dropped with them. Every other section of the
31
31
  demo page is unchanged.
32
32
 
33
+ ## [1.7.0] - 2026-10-12
34
+
35
+ ### Added
36
+
37
+ - **`XMarkHero` `scrimMobile="bottom"`** (opt-in, `overlay="scrim-left"` only). Below md the scrim
38
+ becomes a bottom gradient (90% black over the bottom 55%, fading to clear by 70%) and the text is
39
+ bottom-aligned (`max-md:items-end max-md:pb-10`), so the top of the photo stays clear. md+ is
40
+ unchanged (with `scrimWidth`, the 1.6.0 soft scrim; without, the scrim-left gradient). Keep the
41
+ title and subtitle inside the bottom 55% of the hero on mobile.
42
+ - **`XMarkHero` `img.sources`**: `[{ srcset, type, media?, sizes? }]` renders
43
+ `<picture><source>...<img>` (e.g. AVIF, then WebP, with `img.src` as the fallback).
44
+ Without `sources` the output is byte-identical to 1.6.0.
45
+
46
+ ## [1.6.0] - 2026-10-12
47
+
48
+ ### Added
49
+
50
+ - **`XMarkHero` `scrimWidth` prop** (opt-in, `overlay="scrim-left"` only). CSS length such as
51
+ `"40%"` or `"36rem"`. On md+ the scrim is 90% black up to `scrimWidth`, then fades out over a
52
+ further ~34% of the width so the photo on the right stays vivid. Below md it darkens a 90% band
53
+ from 18% to 82% of the hero height (35% at the top and bottom edges) instead of a flat 90%.
54
+ Without `scrimWidth`, `scrim-left` and every other default render byte-identical to 1.5.2.
55
+ Keep the whole text column inside the 90% zone: white title over 90% black on a pure-white
56
+ pixel is 17.8:1, white/80 subtitle 11.6:1; outside the zone contrast drops fast.
57
+
33
58
  ## [1.5.2] - 2026-10-12
34
59
 
35
60
  ### Fixed
@@ -26,6 +26,26 @@
26
26
  data-parallax-speed="0.3"
27
27
  />
28
28
 
29
+ <!-- Image Background with format sources (opt-in, 1.7.0) -->
30
+ <picture v-else-if="img?.src && img.sources?.length">
31
+ <source
32
+ v-for="(source, i) in img.sources"
33
+ :key="i"
34
+ :srcset="source.srcset"
35
+ :type="source.type"
36
+ :media="source.media"
37
+ :sizes="source.sizes"
38
+ >
39
+ <img
40
+ :src="img.src"
41
+ :alt="img.alt || ''"
42
+ fetchpriority="high"
43
+ class="absolute inset-0 w-full h-full object-cover xParallax"
44
+ :style="imagePositionStyle"
45
+ data-parallax-speed="0.3"
46
+ >
47
+ </picture>
48
+
29
49
  <!-- Image Background -->
30
50
  <img
31
51
  v-else-if="img?.src"
@@ -40,10 +60,23 @@
40
60
 
41
61
  <!-- Overlay -->
42
62
  <div
43
- v-if="overlay !== 'none'"
63
+ v-if="overlay !== 'none' && !softScrim && !mobileBottom"
44
64
  class="absolute inset-0"
45
65
  :class="overlayClass"
46
66
  />
67
+ <!-- scrim-left + scrimMobile="bottom" (opt-in, 1.7.0) -->
68
+ <div
69
+ v-else-if="mobileBottom"
70
+ class="absolute inset-0 x-hero-scrim-mb"
71
+ :class="scrimWidth ? 'x-hero-scrim-w' : 'x-hero-scrim-55'"
72
+ v-bind="scrimWidth ? { style: { '--x-scrim-w': scrimWidth } } : {}"
73
+ />
74
+ <!-- scrim-left + scrimWidth (opt-in, 1.6.0) -->
75
+ <div
76
+ v-else-if="softScrim"
77
+ class="absolute inset-0 x-hero-scrim-w"
78
+ :style="{ '--x-scrim-w': scrimWidth }"
79
+ />
47
80
  </div>
48
81
 
49
82
  <!-- Content -->
@@ -121,11 +154,19 @@
121
154
 
122
155
  <script setup>
123
156
  const props = defineProps({
124
- /** Background image object { src: string, alt?: string } */
157
+ /**
158
+ * Background image object { src: string, alt?: string, sources?: Array<{ srcset, type, media?, sizes? }> }.
159
+ * With sources (1.7.0) it renders <picture><source>...<img>, e.g. AVIF then WebP with src as fallback.
160
+ */
125
161
  img: {
126
162
  type: Object,
127
163
  default: null,
128
- validator: (v) => !v || typeof v.src === "string",
164
+ validator: (v) =>
165
+ !v ||
166
+ (typeof v.src === "string" &&
167
+ (v.sources === undefined ||
168
+ (Array.isArray(v.sources) &&
169
+ v.sources.every((x) => x && typeof x.srcset === "string")))),
129
170
  },
130
171
  /** Background video object { src: string, poster?: string } */
131
172
  video: {
@@ -196,6 +237,29 @@ const props = defineProps({
196
237
  type: String,
197
238
  default: "",
198
239
  },
240
+ /**
241
+ * scrim-left only, opt-in. CSS length for the 90% black zone, e.g. "40%" or "36rem".
242
+ * When set: desktop (md+) keeps 90% up to this width, then a long soft fade so the
243
+ * photo on the right stays vivid; mobile darkens a band behind the text instead of
244
+ * a flat 90% over the whole hero. Keep the text column inside the 90% zone.
245
+ * Unset (default): scrim-left renders exactly as in 1.5.x.
246
+ */
247
+ scrimWidth: {
248
+ type: String,
249
+ default: "",
250
+ validator: (v) => v === "" || /^\d+(\.\d+)?(%|px|rem|em|vw)$/.test(v),
251
+ },
252
+ /**
253
+ * scrim-left only, opt-in (1.7.0). "bottom": below md the scrim becomes a bottom
254
+ * gradient behind the text (90% over the bottom 55%, clear from 70% up) and the text
255
+ * is bottom-aligned, so the top of the photo stays clear. md+ is unchanged.
256
+ * Empty (default) keeps 1.6.0 output.
257
+ */
258
+ scrimMobile: {
259
+ type: String,
260
+ default: "",
261
+ validator: (v) => ["", "bottom"].includes(v),
262
+ },
199
263
  /** Add top padding for non-transparent nav */
200
264
  includeNavPadding: {
201
265
  type: Boolean,
@@ -221,6 +285,14 @@ const overlayClass = computed(() => {
221
285
  return overlays[props.overlay] || "";
222
286
  });
223
287
 
288
+ // scrim-left with scrimWidth renders a separate overlay element; without it the
289
+ // 1.5.x overlay element renders unchanged (no extra class or style attribute).
290
+ const mobileBottom = computed(
291
+ () => props.overlay === "scrim-left" && props.scrimMobile === "bottom",
292
+ );
293
+
294
+ const softScrim = computed(() => props.overlay === "scrim-left" && !!props.scrimWidth);
295
+
224
296
  const heightClass = computed(() => {
225
297
  const heights = {
226
298
  full: "min-h-[calc(100svh-var(--ui-header-height))]",
@@ -248,7 +320,8 @@ const verticalAlignClasses = computed(() => {
248
320
  center: "items-center",
249
321
  bottom: "items-end pb-24",
250
322
  };
251
- return aligns[props.verticalAlign];
323
+ const base = aligns[props.verticalAlign];
324
+ return mobileBottom.value ? `${base} max-md:items-end max-md:pb-10` : base;
252
325
  });
253
326
 
254
327
  const contentAlignmentClasses = computed(() => {
@@ -260,3 +333,56 @@ const contentAlignmentClasses = computed(() => {
260
333
  return aligns[props.align];
261
334
  });
262
335
  </script>
336
+
337
+ <style>
338
+ /* scrim-left + scrimWidth (1.6.0). Mobile: 90% band behind the text column,
339
+ * lighter at the top and bottom edges of the hero. */
340
+ .x-hero-scrim-w {
341
+ background-image: linear-gradient(
342
+ to bottom,
343
+ rgb(0 0 0 / 0.35) 0%,
344
+ rgb(0 0 0 / 0.9) 18%,
345
+ rgb(0 0 0 / 0.9) 82%,
346
+ rgb(0 0 0 / 0.35) 100%
347
+ );
348
+ }
349
+ /* scrimMobile="bottom" (1.7.0), below md: bottom gradient behind bottom-aligned
350
+ * text, top of the photo clear. md+ falls through to the rules below. */
351
+ @media (max-width: 767.98px) {
352
+ .x-hero-scrim-mb {
353
+ background-image: linear-gradient(
354
+ to top,
355
+ rgb(0 0 0 / 0.9) 0%,
356
+ rgb(0 0 0 / 0.9) 55%,
357
+ rgb(0 0 0 / 0.5) 62%,
358
+ rgb(0 0 0 / 0) 70%
359
+ ) !important;
360
+ }
361
+ }
362
+ /* scrimMobile without scrimWidth: md+ matches the 1.5.x scrim-left gradient. */
363
+ @media (min-width: 768px) {
364
+ .x-hero-scrim-55 {
365
+ background-image: linear-gradient(
366
+ to right,
367
+ rgb(0 0 0 / 0.9) 0%,
368
+ rgb(0 0 0 / 0.9) 55%,
369
+ rgb(0 0 0 / 0.3) 100%
370
+ );
371
+ }
372
+ }
373
+ /* md+: 90% up to --x-scrim-w, then a long eased fade to clear. */
374
+ @media (min-width: 768px) {
375
+ .x-hero-scrim-w {
376
+ background-image: linear-gradient(
377
+ to right,
378
+ rgb(0 0 0 / 0.9) 0,
379
+ rgb(0 0 0 / 0.9) var(--x-scrim-w),
380
+ rgb(0 0 0 / 0.72) calc(var(--x-scrim-w) + 4%),
381
+ rgb(0 0 0 / 0.45) calc(var(--x-scrim-w) + 10%),
382
+ rgb(0 0 0 / 0.2) calc(var(--x-scrim-w) + 18%),
383
+ rgb(0 0 0 / 0.06) calc(var(--x-scrim-w) + 26%),
384
+ rgb(0 0 0 / 0) calc(var(--x-scrim-w) + 34%)
385
+ );
386
+ }
387
+ }
388
+ </style>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@xenterprises/nuxt-x-marketing",
3
3
  "license": "SEE LICENSE IN LICENSE",
4
4
  "type": "module",
5
- "version": "1.5.2",
5
+ "version": "1.7.0",
6
6
  "main": "./nuxt.config.ts",
7
7
  "scripts": {
8
8
  "dev": "nuxi dev .playground",