srcdev-nuxt-components 9.1.32 → 9.1.33

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.
@@ -170,6 +170,38 @@ Use an unscoped style block scoped by a page or section wrapper class. No `:deep
170
170
  </style>
171
171
  ```
172
172
 
173
+ ## Gotcha: aspect-ratio is overridden by max-height at wide viewports
174
+
175
+ `max-height` from the depth tier silently wins over `aspect-ratio` once the viewport is wide enough. For example, at `depth="md"` the max-height clamp caps at `56rem` — so changing `aspectRatio` from `"21/9"` to `"1/1"` produces no visible change on a wide desktop because `max-height` is the binding constraint.
176
+
177
+ **If you need `aspect-ratio` to dominate**, override all depth tokens to a large fixed value on the parent:
178
+
179
+ ```css
180
+ .my-page {
181
+ --theme-banner-video-max-height-xs: 80rem;
182
+ --theme-banner-video-max-height-sm: 80rem;
183
+ --theme-banner-video-max-height-md: 80rem;
184
+ --theme-banner-video-max-height-lg: 80rem;
185
+ --theme-banner-video-max-height-xl: 80rem;
186
+ }
187
+ ```
188
+
189
+ Or via inline `:style` on the parent element (useful for dev QA exploration):
190
+
191
+ ```vue
192
+ <div :style="{
193
+ '--theme-banner-video-max-height-xs': '80rem',
194
+ '--theme-banner-video-max-height-sm': '80rem',
195
+ '--theme-banner-video-max-height-md': '80rem',
196
+ '--theme-banner-video-max-height-lg': '80rem',
197
+ '--theme-banner-video-max-height-xl': '80rem',
198
+ }">
199
+ <BannerVideo aspect-ratio="1/1" depth="lg" ... />
200
+ </div>
201
+ ```
202
+
203
+ You must override **all five tier tokens** — overriding only the active depth token is not sufficient because the component resolves the token by name.
204
+
173
205
  ## Notes
174
206
 
175
207
  - `loading="eager"` and `decoding="async"` are hardcoded on the fallback `NuxtImg` — it is above the fold by definition.
@@ -100,8 +100,21 @@ Override via `styleClassPassthrough` or a parent HOC `<style>` block targeting `
100
100
 
101
101
  Key CSS custom properties:
102
102
 
103
- - `--colour-text-accent` colour applied to `.accent` spans and the icon
104
- - `--hero-text-{scale}` font size per scale value
103
+ | Property | Default | Controls |
104
+ | -------- | ------- | -------- |
105
+ | `--colour-text-accent` | — | Colour of `.accent` spans and the icon |
106
+ | `--hero-text-{scale}` | — | Font size per `fontSize` prop value |
107
+ | `--hero-text-vertical-gap` | `0.4em` | Gap between segments in `axis="vertical"` mode |
108
+
109
+ **`--hero-text-vertical-gap`** controls `gap` on the flex column in vertical axis. Override at theme or page level:
110
+
111
+ ```css
112
+ .my-page {
113
+ .hero-text.axis-vertical {
114
+ --hero-text-vertical-gap: 0.6em;
115
+ }
116
+ }
117
+ ```
105
118
 
106
119
  ## Local style override scaffold
107
120
 
@@ -0,0 +1,134 @@
1
+ ---
2
+ name: ProfileSection
3
+ description: ProfileSection molecule — props, dynamic profile-info slots, eyebrowText/heroText/profileLinks slots, layout, accessibility
4
+ type: reference
5
+ ---
6
+
7
+ # ProfileSection
8
+
9
+ ## Overview
10
+
11
+ `ProfileSection` is a molecule that renders a practitioner/author profile: a header area (eyebrow + heading), a profile picture, and a flexible set of bio/info blocks alongside optional profile links. It is landmark-aware — the root element automatically gets `aria-labelledby` wired to the heading inside the `#heroText` slot.
12
+
13
+ ## Props
14
+
15
+ | Prop | Type | Default | Description |
16
+ |------|------|---------|-------------|
17
+ | `profilePicture` | `{ src: string; alt: string }` | — | **Required.** Path and alt text for the profile image. Rendered via `NuxtImg`. |
18
+ | `tag` | `"div" \| "section" \| "article" \| "main"` | `"div"` | HTML element rendered as the root. |
19
+ | `profileInfoCount` | `number` | `3` | Number of `profile-info-N` slots to generate when none are explicitly provided. |
20
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
21
+
22
+ ## Slots
23
+
24
+ | Slot | Description |
25
+ |------|-------------|
26
+ | `#eyebrowText` | Optional eyebrow label above the heading. Use `EyebrowText`. |
27
+ | `#heroText` | Heading slot. Receives `headingId` as a slot prop — bind it to the heading's `id` for accessible `aria-labelledby`. |
28
+ | `#profile-info-1` … `#profile-info-N` | Bio / info blocks. Provide as many numbered slots as needed. They are rendered in ascending numeric order. |
29
+ | `#profileLinks` | Links / actions shown at the bottom-right of the info column (e.g. social icons, booking CTA). |
30
+
31
+ ## Basic usage
32
+
33
+ ```vue
34
+ <ProfileSection
35
+ tag="section"
36
+ :profile-picture="{ src: '/images/profile/mel.jpg', alt: 'Mel Stafford, reflexologist' }"
37
+ >
38
+ <template #eyebrowText>
39
+ <EyebrowText font-size="large" text="About Mel" />
40
+ </template>
41
+
42
+ <template #heroText="{ headingId }">
43
+ <HeroText
44
+ tag="h2"
45
+ :id="headingId"
46
+ font-size="title"
47
+ :text-content="[
48
+ { text: 'Mel Stafford', styleClass: 'normal' },
49
+ { text: 'Reflexologist', styleClass: 'accent' },
50
+ ]"
51
+ />
52
+ </template>
53
+
54
+ <template #profile-info-1>
55
+ <p>With over a decade of practice…</p>
56
+ </template>
57
+
58
+ <template #profile-info-2>
59
+ <p>Mel is based in Lichfield…</p>
60
+ </template>
61
+
62
+ <template #profileLinks>
63
+ <SocialIconsList :items="socialLinks" />
64
+ </template>
65
+ </ProfileSection>
66
+ ```
67
+
68
+ ## Dynamic profile-info slots
69
+
70
+ The `profile-info-N` slots are discovered at runtime by filtering `useSlots()` for keys matching `/^profile-info-\d+$/`. The slots are rendered in ascending numeric order.
71
+
72
+ - If you provide `#profile-info-1`, `#profile-info-2`, `#profile-info-3` — all three render in order.
73
+ - If you provide no `profile-info-*` slots, the component generates `profileInfoCount` empty placeholder blocks.
74
+ - Gaps are supported — you can provide `#profile-info-1` and `#profile-info-3` without `#profile-info-2`; they sort correctly.
75
+
76
+ ## heroText slot prop
77
+
78
+ The `#heroText` slot exposes `headingId` as a slot prop. Bind it to the heading's `id` prop so the component's `aria-labelledby` attribute points to the heading automatically:
79
+
80
+ ```vue
81
+ <template #heroText="{ headingId }">
82
+ <HeroText tag="h2" :id="headingId" ... />
83
+ </template>
84
+ ```
85
+
86
+ If you use a different heading component, bind `headingId` to whatever prop renders the `id` attribute on the heading element.
87
+
88
+ ## Layout
89
+
90
+ - **Mobile**: single column — picture stacked above info.
91
+ - **768px+**: two columns — `384px` picture column, `1fr` info column, `4rem` gap.
92
+ - Picture frame: `aspect-ratio: 3/4`, `border-radius: 8px`, `overflow: hidden`. The `NuxtImg` fills the frame with `object-fit: cover`.
93
+ - Profile links: `align-items: end; justify-content: flex-end` — right-aligned to the bottom of the info column.
94
+
95
+ ## CSS notes
96
+
97
+ - The `.profile-section-header` element has **no default margin-block-end**. If you need spacing between the header and the picture/info grid, add it via a consuming-page style:
98
+
99
+ ```css
100
+ .my-page {
101
+ .profile-section-header {
102
+ margin-block-end: 3.2rem;
103
+ }
104
+ }
105
+ ```
106
+
107
+ - Accent-coloured text within `.location` or `.services` info blocks can use the `.highlight` class for `var(--colour-text-accent)` or `var(--colour-link-default)` colouring respectively.
108
+
109
+ ## Consumer styling scaffold
110
+
111
+ ```vue
112
+ <ProfileSection
113
+ :style-class-passthrough="['my-profile']"
114
+ ...
115
+ >
116
+ ...
117
+ </ProfileSection>
118
+
119
+ <style>
120
+ .profile-section {
121
+ &.my-profile {
122
+ .profile-section-header {
123
+ margin-block-end: 3.2rem;
124
+ }
125
+ }
126
+ }
127
+ </style>
128
+ ```
129
+
130
+ ## Notes
131
+
132
+ - `NuxtImg` is used for the profile picture — provide a real `src` path. A placeholder (grey box) is shown if the image 404s.
133
+ - The component does not expose `imgWidth` / `imgHeight` props for the picture. If IPX optimisation is critical, use a fixed-dimension image and rely on the `384px` column cap as the natural size constraint.
134
+ - Component is auto-imported in Nuxt — no import needed.
@@ -15,7 +15,7 @@ For arbitrary slot content — a grid of images, video, markup — use `ScrollRe
15
15
  ## Props
16
16
 
17
17
  | Prop | Type | Default | Description |
18
- |------|------|---------|-------------|
18
+ | ---- | ---- | ------- | ----------- |
19
19
  | `src` | `string` | — | Image source path. **Required.** |
20
20
  | `alt` | `string` | `""` | Alt text for the image. |
21
21
  | `imgWidth` | `number` | `1920` | Intrinsic width of the source image — required for NuxtImg optimisation. |
@@ -95,7 +95,7 @@ For arbitrary slot content — a grid of images, video, markup — use `ScrollRe
95
95
  The vertical position of the image is driven by the scroll animation (`translateY`). `focalX` controls the **horizontal** crop so the subject stays centred when the frame is narrower than the image.
96
96
 
97
97
  | Value | Crops toward |
98
- |-------|-------------|
98
+ | ----- | ----------- |
99
99
  | `"0%"` or `"left"` | Left edge |
100
100
  | `"50%"` (default) | Centre |
101
101
  | `"75%"` | Right of centre — useful for a subject offset to the right |
@@ -106,13 +106,14 @@ Internally, `focalX` sets `object-position: <focalX> 0%` on the `<img>`. The `Y`
106
106
  ## imgWidth / imgHeight
107
107
 
108
108
  Always provide these to match the intrinsic dimensions of the source file. NuxtImg uses them to:
109
+
109
110
  - Generate the correct `srcset` via the IPX pipeline
110
111
  - Avoid the `w=1536` fallback (not in Vercel's allowed widths: 640, 750, 828, 1080, 1200, 1920, 2048, 3840)
111
112
 
112
113
  Common pairs:
113
114
 
114
115
  | Image type | imgWidth | imgHeight |
115
- |-----------|----------|-----------|
116
+ | ---------- | -------- | --------- |
116
117
  | Portrait (3:4) | `1280` | `1920` |
117
118
  | Landscape / banner (16:9) | `1920` | `1080` |
118
119
  | Wide banner (12:5) | `1920` | `800` |
@@ -143,7 +144,7 @@ Override `--_frame-height` in a scoped style block for responsive control:
143
144
  Set from props via inline `:style` — override in CSS for responsive or contextual control.
144
145
 
145
146
  | Property | Default | Set by prop |
146
- |----------|---------|-------------|
147
+ | -------- | ------- | ----------- |
147
148
  | `--_frame-height` | `540px` | `frameHeight` |
148
149
  | `--_parallax-offset` | `36rem` | `parallaxOffset` |
149
150
  | `--_radius` | `0px` | `radius` |
@@ -155,6 +156,7 @@ See `scroll-reveal-frame.md` for the full guide. For portrait images the default
155
156
 
156
157
  ## Notes
157
158
 
159
+ - The root `<figure>` has `margin: 0` set in the component — browser default `<figure>` margins are neutralised at source.
158
160
  - `loading="lazy"` and `decoding="async"` are hardcoded on the `<img>`. If this component is the LCP image, override with `loading="eager"` via a CSS-only approach is not possible — use `ScrollRevealFrame` with a manual `NuxtImg` instead and set `:loading="'eager'"`.
159
161
  - Do not place inside a container with `overflow: hidden` or `overflow: clip` — breaks the `view-timeline` scroll detection inherited from `ScrollRevealFrame`.
160
162
  - Reduced-motion: animation is disabled and the image falls back to a static crop centred at `object-position: <focalX> 50%`.
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: SectionParallax
3
+ description: SectionParallax CSS fixed-background parallax section — props, browser support, iOS Safari limitation, when to use vs ScrollRevealImage
4
+ type: reference
5
+ ---
6
+
7
+ # SectionParallax
8
+
9
+ ## Overview
10
+
11
+ `SectionParallax` is a full-width section that uses CSS `background-attachment: fixed` to create a parallax scrolling effect — the background image stays stationary while the page content scrolls over it. The effect is implemented with `@supports (background-attachment: fixed)` so it degrades gracefully.
12
+
13
+ **Important browser limitation:** `background-attachment: fixed` does not work on iOS Safari (and mobile Chrome on iOS). On those browsers the background renders as a static image with no parallax motion. Use `SectionParallax` for atmospheric/decorative breaks where this is an acceptable trade-off. For cross-browser scroll-driven parallax, use `ScrollRevealImage` or `ScrollRevealFrame` instead.
14
+
15
+ ## Props
16
+
17
+ | Prop | Type | Default | Description |
18
+ |------|------|---------|-------------|
19
+ | `tag` | `"div" \| "section" \| "article" \| "aside"` | `"div"` | HTML element rendered as the root. |
20
+ | `backgroundImage` | `string` | `undefined` | Path to the background image. Bound as `url("...")` via `v-bind`. |
21
+ | `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root element. |
22
+
23
+ ## Slots
24
+
25
+ | Slot | Description |
26
+ |------|-------------|
27
+ | `default` | Optional content rendered inside the section. Often left empty — the component is typically used as a purely decorative atmospheric break. |
28
+
29
+ ## Basic usage — decorative atmospheric break
30
+
31
+ ```vue
32
+ <SectionParallax
33
+ tag="section"
34
+ background-image="/images/eucalyptus-lavender-and-oil.jpg"
35
+ />
36
+ ```
37
+
38
+ ## With overlay content
39
+
40
+ ```vue
41
+ <SectionParallax
42
+ tag="section"
43
+ background-image="/images/candle-and-stones.jpg"
44
+ :style-class-passthrough="['has-overlay']"
45
+ >
46
+ <div class="overlay-text">
47
+ <p>A quiet moment.</p>
48
+ </div>
49
+ </SectionParallax>
50
+ ```
51
+
52
+ ## How it works
53
+
54
+ The component sets:
55
+ - `background-image: url(...)` via `v-bind`
56
+ - `background-position: center`
57
+ - `background-size: inherit` (fallback when fixed is not supported)
58
+ - `min-height: 120vh`
59
+ - `background-color: light-dark(var(--slate-01), var(--slate-08))` (visible if image fails to load)
60
+
61
+ Inside `@supports (background-attachment: fixed)`:
62
+ - `background-attachment: fixed` — pins the image to the viewport
63
+ - `background-size: cover` — ensures the image fills the viewport
64
+ - `min-height: 120vh` — ensures enough scroll travel to see the parallax motion
65
+
66
+ ## Controlling height
67
+
68
+ Override `min-height` with a consuming-page style:
69
+
70
+ ```css
71
+ .my-page {
72
+ .section-parallax {
73
+ min-height: 60vh; /* shorter atmospheric break */
74
+ }
75
+ }
76
+ ```
77
+
78
+ ## Browser support
79
+
80
+ | Browser | Support |
81
+ |---------|---------|
82
+ | Chrome / Edge (desktop) | ✅ Full parallax |
83
+ | Firefox (desktop) | ✅ Full parallax |
84
+ | Safari (desktop) | ✅ Full parallax |
85
+ | iOS Safari | ❌ Static background (no parallax) |
86
+ | Chrome on iOS | ❌ Static background (no parallax) |
87
+ | Android Chrome | ✅ Usually supported |
88
+
89
+ ## When to use SectionParallax vs ScrollRevealImage
90
+
91
+ | Use case | Recommendation |
92
+ |----------|---------------|
93
+ | Atmospheric break between sections, desktop-first site | `SectionParallax` — simpler, no JS |
94
+ | Cross-browser scroll animation, mobile-first site | `ScrollRevealImage` or `ScrollRevealFrame` |
95
+ | Image with rounded corners, specific frame height | `ScrollRevealImage` |
96
+ | Image inlined within a content grid | `ScrollRevealImage` |
97
+
98
+ ## Notes
99
+
100
+ - The `@supports` guard means the parallax activates only when the browser supports `background-attachment: fixed`. No JS is involved.
101
+ - Slot content is only rendered when the `default` slot is provided (`v-if="slots.default"`).
102
+ - The component has no built-in overlay or gradient — add one via the slot or a `::before` pseudo-element in your consuming-page styles.
@@ -73,6 +73,7 @@ watch(
73
73
  --_parallax-offset: 36rem;
74
74
  --_radius: 0px;
75
75
 
76
+ margin: 0; /* reset browser <figure> default margin */
76
77
  position: relative;
77
78
  height: var(--_frame-height);
78
79
  width: 100%;
@@ -59,7 +59,7 @@ const normalisedContent = computed(() =>
59
59
  }
60
60
  &.axis-vertical {
61
61
  display: flex;
62
- gap: 0.2rem;
62
+ gap: var(--hero-text-vertical-gap, 0.4em);
63
63
  flex-direction: column;
64
64
  }
65
65
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "srcdev-nuxt-components",
3
3
  "type": "module",
4
- "version": "9.1.32",
4
+ "version": "9.1.33",
5
5
  "main": "nuxt.config.ts",
6
6
  "types": "types.d.ts",
7
7
  "license": "MIT",