srcdev-nuxt-components 9.1.45 → 9.1.47
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/.claude/skills/components/scroll-reveal-image.md +2 -1
- package/app/components/01.atoms/animations/scroll-reveal-image/ScrollRevealImage.vue +3 -1
- package/app/components/01.atoms/animations/scroll-reveal-image/tests/ScrollRevealImage.spec.ts +9 -1
- package/app/components/01.atoms/animations/scroll-reveal-image/tests/__snapshots__/ScrollRevealImage.spec.ts.snap +1 -1
- package/app/components/02.molecules/profile-section/ProfileSection.vue +0 -5
- package/app/components/02.molecules/samaritan-prompt/SamaritanPromptMixed.vue +57 -49
- package/app/pages/samaritan.vue +0 -5
- package/package.json +1 -1
|
@@ -24,6 +24,7 @@ For arbitrary slot content — a grid of images, video, markup — use `ScrollRe
|
|
|
24
24
|
| `parallaxOffset` | `string` | `"36rem"` | Distance the image travels vertically across the full scroll range. Larger = more dramatic reveal. |
|
|
25
25
|
| `focalX` | `string` | `"50%"` | Horizontal focal point — CSS `object-position` x-axis value. Controls which horizontal slice stays in view. |
|
|
26
26
|
| `radius` | `string` | `"0px"` | `border-radius` applied to the clipping frame. |
|
|
27
|
+
| `loading` | `"lazy" \| "eager"` | `"lazy"` | Image loading strategy. Use `"eager"` if this is the LCP image (e.g. partially in view on load). |
|
|
27
28
|
| `styleClassPassthrough` | `string \| string[]` | `[]` | Extra classes applied to the root `<figure>`. |
|
|
28
29
|
|
|
29
30
|
## Basic usage
|
|
@@ -157,7 +158,7 @@ See `scroll-reveal-frame.md` for the full guide. For portrait images the default
|
|
|
157
158
|
## Notes
|
|
158
159
|
|
|
159
160
|
- The root `<figure>` has `margin: 0` set in the component — browser default `<figure>` margins are neutralised at source.
|
|
160
|
-
- `
|
|
161
|
+
- `decoding="async"` is always set on the `<img>`. Use `:loading="'eager'"` if this component is the LCP image (e.g. a hero partially in view on load) — the default `"lazy"` is correct for below-fold usage.
|
|
161
162
|
- Do not place inside a container with `overflow: hidden` or `overflow: clip` — breaks the `view-timeline` scroll detection inherited from `ScrollRevealFrame`.
|
|
162
163
|
- Reduced-motion: animation is disabled and the image falls back to a static crop centred at `object-position: <focalX> 50%`.
|
|
163
164
|
- Storybook: the `"none"` image provider is active (`nuxt.config.ts`), so `src` paths pass through unchanged. Always provide explicit `img-width` and `img-height` props to avoid the `w=1536` fallback in deployed Storybook.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
:alt="alt"
|
|
13
13
|
:width="imgWidth"
|
|
14
14
|
:height="imgHeight"
|
|
15
|
-
loading="
|
|
15
|
+
:loading="loading"
|
|
16
16
|
decoding="async"
|
|
17
17
|
/>
|
|
18
18
|
</ScrollRevealFrame>
|
|
@@ -51,6 +51,7 @@ interface Props {
|
|
|
51
51
|
focalX?: string;
|
|
52
52
|
/** Optional rounded corners on the frame. */
|
|
53
53
|
radius?: string;
|
|
54
|
+
loading?: "lazy" | "eager";
|
|
54
55
|
styleClassPassthrough?: string | string[];
|
|
55
56
|
}
|
|
56
57
|
|
|
@@ -62,6 +63,7 @@ withDefaults(defineProps<Props>(), {
|
|
|
62
63
|
parallaxOffset: "36rem",
|
|
63
64
|
focalX: "50%",
|
|
64
65
|
radius: "0px",
|
|
66
|
+
loading: "lazy",
|
|
65
67
|
styleClassPassthrough: () => [],
|
|
66
68
|
});
|
|
67
69
|
</script>
|
package/app/components/01.atoms/animations/scroll-reveal-image/tests/ScrollRevealImage.spec.ts
CHANGED
|
@@ -32,6 +32,7 @@ describe("ScrollRevealImage", () => {
|
|
|
32
32
|
parallaxOffset: "20rem",
|
|
33
33
|
focalX: "30%",
|
|
34
34
|
radius: "1.6rem",
|
|
35
|
+
loading: "eager",
|
|
35
36
|
styleClassPassthrough: ["custom-class"],
|
|
36
37
|
},
|
|
37
38
|
});
|
|
@@ -179,13 +180,20 @@ describe("ScrollRevealImage", () => {
|
|
|
179
180
|
expect(img.attributes("height")).toBe("1920");
|
|
180
181
|
});
|
|
181
182
|
|
|
182
|
-
it("sets loading=lazy on the image element", async () => {
|
|
183
|
+
it("sets loading=lazy on the image element by default", async () => {
|
|
183
184
|
const wrapper = await mountSuspended(ScrollRevealImage, {
|
|
184
185
|
props: { src: "/images/test.jpg" },
|
|
185
186
|
});
|
|
186
187
|
expect(wrapper.find("img[data-nuxt-img]").attributes("loading")).toBe("lazy");
|
|
187
188
|
});
|
|
188
189
|
|
|
190
|
+
it("passes loading=eager to the image element when set", async () => {
|
|
191
|
+
const wrapper = await mountSuspended(ScrollRevealImage, {
|
|
192
|
+
props: { src: "/images/test.jpg", loading: "eager" },
|
|
193
|
+
});
|
|
194
|
+
expect(wrapper.find("img[data-nuxt-img]").attributes("loading")).toBe("eager");
|
|
195
|
+
});
|
|
196
|
+
|
|
189
197
|
it("sets decoding=async on the image element", async () => {
|
|
190
198
|
const wrapper = await mountSuspended(ScrollRevealImage, {
|
|
191
199
|
props: { src: "/images/test.jpg" },
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
exports[`ScrollRevealImage > renders correct HTML structure (all props set) 1`] = `
|
|
4
4
|
"<figure class="reveal-frame custom-class" style="--_frame-height: 400px; --_parallax-offset: 20rem; --_radius: 1.6rem; --_focal-x: 30%;">
|
|
5
|
-
<div class="reveal-content"><img width="800" height="600" data-nuxt-img="" srcset="/_ipx/s_800x600/images/test.jpg 1x, /_ipx/s_1600x1200/images/test.jpg 2x" class="reveal-image" alt="All props" loading="
|
|
5
|
+
<div class="reveal-content"><img width="800" height="600" data-nuxt-img="" srcset="/_ipx/s_800x600/images/test.jpg 1x, /_ipx/s_1600x1200/images/test.jpg 2x" class="reveal-image" alt="All props" loading="eager" decoding="async" src="/_ipx/s_800x600/images/test.jpg"></div>
|
|
6
6
|
</figure>"
|
|
7
7
|
`;
|
|
8
8
|
|
|
@@ -172,72 +172,80 @@ onUnmounted(stop);
|
|
|
172
172
|
src: url("/fonts/monoMMM_5.ttf") format("truetype");
|
|
173
173
|
font-weight: normal;
|
|
174
174
|
font-style: normal;
|
|
175
|
-
font-display:
|
|
175
|
+
font-display: optional;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
.samaritan-
|
|
179
|
-
--_font-size: var(--samaritan-font-size, 2rem);
|
|
180
|
-
--_color-text: var(--samaritan-color-text, #ffffff);
|
|
181
|
-
--_color-underline: var(--samaritan-color-underline, #ffffff);
|
|
182
|
-
--_color-cursor: var(--samaritan-color-cursor, #cc0000);
|
|
183
|
-
--_color-cursor-off: var(--samaritan-color-cursor-off, transparent);
|
|
184
|
-
--_font-family: var(--samaritan-font-family, "Mono MMM 5", "Nova Mono", "Courier New", monospace);
|
|
185
|
-
--_letter-spacing: var(--samaritan-letter-spacing, 0.08em);
|
|
186
|
-
|
|
178
|
+
.samaritan-stage {
|
|
187
179
|
display: flex;
|
|
188
|
-
flex-direction: column;
|
|
189
180
|
align-items: center;
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
181
|
+
justify-content: center;
|
|
182
|
+
min-block-size: 100dvh;
|
|
183
|
+
width: 100%;
|
|
184
|
+
|
|
185
|
+
.samaritan-prompt {
|
|
186
|
+
--_font-size: var(--samaritan-font-size, 2rem);
|
|
187
|
+
--_color-text: var(--samaritan-color-text, #ffffff);
|
|
188
|
+
--_color-underline: var(--samaritan-color-underline, #ffffff);
|
|
189
|
+
--_color-cursor: var(--samaritan-color-cursor, #cc0000);
|
|
190
|
+
--_color-cursor-off: var(--samaritan-color-cursor-off, transparent);
|
|
191
|
+
--_font-family: var(--samaritan-font-family, "Mono MMM 5", "Nova Mono", "Courier New", monospace);
|
|
192
|
+
--_letter-spacing: var(--samaritan-letter-spacing, 0.08em);
|
|
194
193
|
|
|
195
|
-
.samaritan-prompt__content {
|
|
196
194
|
display: flex;
|
|
197
195
|
flex-direction: column;
|
|
198
196
|
align-items: center;
|
|
199
197
|
row-gap: 0.6rem;
|
|
200
|
-
|
|
201
|
-
|
|
198
|
+
font-family: var(--_font-family);
|
|
199
|
+
font-size: var(--_font-size);
|
|
200
|
+
letter-spacing: var(--_letter-spacing);
|
|
202
201
|
|
|
203
|
-
.samaritan-
|
|
202
|
+
.samaritan-prompt__content {
|
|
204
203
|
display: flex;
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
flex-direction: column;
|
|
205
|
+
align-items: center;
|
|
206
|
+
row-gap: 0.6rem;
|
|
207
|
+
width: 100%;
|
|
208
|
+
transition: opacity v-bind(fadeDurationCss) ease;
|
|
209
|
+
|
|
210
|
+
.samaritan-prompt__stage {
|
|
211
|
+
display: flex;
|
|
212
|
+
justify-content: center;
|
|
213
|
+
min-height: 1.2em;
|
|
207
214
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
215
|
+
.samaritan-prompt__text {
|
|
216
|
+
color: var(--_color-text);
|
|
217
|
+
white-space: nowrap;
|
|
218
|
+
text-transform: uppercase;
|
|
219
|
+
}
|
|
212
220
|
}
|
|
213
|
-
}
|
|
214
221
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
222
|
+
.samaritan-prompt__underline {
|
|
223
|
+
width: 100%;
|
|
224
|
+
min-width: 4ch;
|
|
225
|
+
height: 0.15rem;
|
|
226
|
+
background: var(--_color-underline);
|
|
227
|
+
}
|
|
220
228
|
}
|
|
221
|
-
}
|
|
222
229
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
+
.samaritan-prompt__cursor {
|
|
231
|
+
color: var(--_color-cursor);
|
|
232
|
+
font-size: 2.4rem;
|
|
233
|
+
line-height: 1;
|
|
234
|
+
animation: samaritan-pulse 2.5s ease-in-out infinite;
|
|
235
|
+
transition: opacity 400ms ease;
|
|
236
|
+
}
|
|
230
237
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
238
|
+
.samaritan-prompt__sr-text {
|
|
239
|
+
position: absolute;
|
|
240
|
+
width: 1px;
|
|
241
|
+
height: 1px;
|
|
242
|
+
padding: 0;
|
|
243
|
+
margin: -1px;
|
|
244
|
+
overflow: hidden;
|
|
245
|
+
clip: rect(0, 0, 0, 0);
|
|
246
|
+
white-space: nowrap;
|
|
247
|
+
border: 0;
|
|
248
|
+
}
|
|
241
249
|
}
|
|
242
250
|
}
|
|
243
251
|
|
package/app/pages/samaritan.vue
CHANGED
|
@@ -230,11 +230,6 @@ const qaHideCursorInCycle = ref(true);
|
|
|
230
230
|
--samaritan-letter-spacing: 0.14em;
|
|
231
231
|
|
|
232
232
|
.samaritan-stage {
|
|
233
|
-
display: flex;
|
|
234
|
-
align-items: center;
|
|
235
|
-
justify-content: center;
|
|
236
|
-
min-block-size: 100dvh;
|
|
237
|
-
|
|
238
233
|
.samaritan-stage--mixed {
|
|
239
234
|
border-top: 1px solid oklch(100% 0 0 / 0.08);
|
|
240
235
|
}
|