@okinoxis/hero-scene 0.4.2 → 0.5.1

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/dist/index.cjs CHANGED
@@ -128,6 +128,14 @@ function useReducedMotion() {
128
128
 
129
129
  // src/hero-scene.tsx
130
130
  var import_jsx_runtime = require("react/jsx-runtime");
131
+ var DARK_STYLES = `
132
+ [data-hs-dark-only]{display:none}
133
+ [data-hs-light-only]{display:block}
134
+ [data-hs-img]{filter:none}
135
+ .dark [data-hs-dark-only]{display:block}
136
+ .dark [data-hs-light-only]{display:none}
137
+ .dark [data-hs-img]{filter:grayscale(1)}
138
+ `;
131
139
  function HeroSceneRoot({
132
140
  images,
133
141
  initialIndex = 0,
@@ -158,7 +166,7 @@ function HeroSceneRoot({
158
166
  return () => clearInterval(id);
159
167
  }, [initialIndex, interval, images.length, onIndexChange]);
160
168
  const activeColor = images[index]?.color ?? "128, 128, 128";
161
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
169
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
162
170
  HeroSceneContext.Provider,
163
171
  {
164
172
  value: {
@@ -169,52 +177,55 @@ function HeroSceneRoot({
169
177
  isInViewport,
170
178
  containerRef: rootRef
171
179
  },
172
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
173
- "div",
174
- {
175
- ref: rootRef,
176
- className,
177
- style: { position: "relative", overflow: "hidden" },
178
- children: [
179
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
180
- "div",
181
- {
182
- "data-hero-images": "",
183
- "aria-hidden": "true",
184
- style: {
185
- position: "absolute",
186
- inset: 0,
187
- backgroundColor: `rgb(${activeColor})`,
188
- transition: reducedMotion ? "none" : "background-color 1s ease"
189
- },
190
- children: images.map((img, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
191
- import_image.default,
192
- {
193
- src: img.src,
194
- alt: "",
195
- width: 1920,
196
- height: 1080,
197
- priority: i === initialIndex,
198
- sizes: "100vw",
199
- className: "dark:grayscale",
200
- style: {
201
- position: "absolute",
202
- inset: 0,
203
- width: "100%",
204
- height: "100%",
205
- objectFit: "cover",
206
- opacity: i === index ? 1 : 0,
207
- transition: reducedMotion ? "none" : `opacity ${transitionDuration}ms ease`
208
- }
180
+ children: [
181
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { dangerouslySetInnerHTML: { __html: DARK_STYLES } }),
182
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
183
+ "div",
184
+ {
185
+ ref: rootRef,
186
+ className,
187
+ style: { position: "relative", overflow: "hidden" },
188
+ children: [
189
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
190
+ "div",
191
+ {
192
+ "data-hero-images": "",
193
+ "aria-hidden": "true",
194
+ style: {
195
+ position: "absolute",
196
+ inset: "-15%",
197
+ backgroundColor: `rgb(${activeColor})`,
198
+ transition: reducedMotion ? "none" : "background-color 1s ease"
209
199
  },
210
- img.src
211
- ))
212
- }
213
- ),
214
- children
215
- ]
216
- }
217
- )
200
+ children: images.map((img, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
201
+ import_image.default,
202
+ {
203
+ src: img.src,
204
+ alt: "",
205
+ width: 1920,
206
+ height: 1080,
207
+ priority: i === initialIndex,
208
+ sizes: "100vw",
209
+ "data-hs-img": "",
210
+ style: {
211
+ position: "absolute",
212
+ inset: 0,
213
+ width: "100%",
214
+ height: "100%",
215
+ objectFit: "cover",
216
+ opacity: i === index ? 1 : 0,
217
+ transition: reducedMotion ? "none" : `opacity ${transitionDuration}ms ease`
218
+ }
219
+ },
220
+ img.src
221
+ ))
222
+ }
223
+ ),
224
+ children
225
+ ]
226
+ }
227
+ )
228
+ ]
218
229
  }
219
230
  );
220
231
  }
@@ -230,20 +241,6 @@ function Parallax({
230
241
  const targetMouseY = (0, import_react4.useRef)(0);
231
242
  const currentMouseX = (0, import_react4.useRef)(0);
232
243
  const currentMouseY = (0, import_react4.useRef)(0);
233
- (0, import_react4.useEffect)(() => {
234
- if (reducedMotion) return;
235
- const root = containerRef.current;
236
- if (!root) return;
237
- const imagesEl = root.querySelector("[data-hero-images]");
238
- if (!imagesEl) return;
239
- imagesEl.style.inset = "-15%";
240
- imagesEl.style.willChange = "transform";
241
- return () => {
242
- imagesEl.style.inset = "0";
243
- imagesEl.style.willChange = "";
244
- imagesEl.style.transform = "";
245
- };
246
- }, [reducedMotion, containerRef]);
247
244
  (0, import_react4.useEffect)(() => {
248
245
  if (reducedMotion) return;
249
246
  const root = containerRef.current;
@@ -304,8 +301,8 @@ function Vignette({
304
301
  images.map((img, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
305
302
  "div",
306
303
  {
304
+ "data-hs-light-only": "",
307
305
  "aria-hidden": "true",
308
- className: "dark:hidden",
309
306
  style: {
310
307
  position: "absolute",
311
308
  inset: 0,
@@ -326,8 +323,8 @@ function Vignette({
326
323
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
327
324
  "div",
328
325
  {
326
+ "data-hs-dark-only": "",
329
327
  "aria-hidden": "true",
330
- className: "hidden dark:block",
331
328
  style: {
332
329
  position: "absolute",
333
330
  inset: 0,
@@ -383,8 +380,8 @@ function Pattern({
383
380
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
384
381
  "div",
385
382
  {
383
+ "data-hs-light-only": "",
386
384
  "aria-hidden": "true",
387
- className: "dark:hidden",
388
385
  style: {
389
386
  position: "absolute",
390
387
  inset: 0,
@@ -398,8 +395,8 @@ function Pattern({
398
395
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
399
396
  "div",
400
397
  {
398
+ "data-hs-dark-only": "",
401
399
  "aria-hidden": "true",
402
- className: "hidden dark:block",
403
400
  style: {
404
401
  position: "absolute",
405
402
  inset: 0,
@@ -417,12 +414,13 @@ function DarkOverlay({ opacity = 0.4 }) {
417
414
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
418
415
  "div",
419
416
  {
417
+ "data-hs-dark-only": "",
420
418
  "aria-hidden": "true",
421
- className: "pointer-events-none hidden dark:block",
422
419
  style: {
423
420
  position: "absolute",
424
421
  inset: 0,
425
422
  zIndex: 10,
423
+ pointerEvents: "none",
426
424
  backgroundColor: `rgba(0 0 0 / ${opacity})`
427
425
  }
428
426
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/hero-scene.tsx","../src/hero-scene-context.ts","../src/utils.ts","../src/use-in-viewport.ts","../src/use-reduced-motion.ts"],"sourcesContent":["export {\n HeroScene,\n HeroParallax,\n HeroVignette,\n HeroBlur,\n HeroPattern,\n HeroDarkOverlay,\n HeroContent,\n} from './hero-scene'\n\n/** Event name dispatched on `globalThis` when the active image changes. `event.detail` is the new index. */\nexport const HERO_SCENE_INDEX_EVENT = 'hero-scene-index-change'\nexport { buildVignetteGradient, buildBlurMask } from './utils'\nexport { useReducedMotion } from './use-reduced-motion'\n\nexport type {\n HeroSceneProps,\n HeroImage,\n ParallaxProps,\n VignetteProps,\n BlurProps,\n PatternProps,\n DarkOverlayProps,\n ContentProps,\n} from './types'\n","'use client'\n\nimport Image from 'next/image'\nimport { useEffect, useRef, useState } from 'react'\n\nimport { HeroSceneContext, useHeroScene } from './hero-scene-context'\nimport type {\n BlurProps,\n ContentProps,\n DarkOverlayProps,\n HeroSceneProps,\n ParallaxProps,\n PatternProps,\n VignetteProps,\n} from './types'\nimport { buildBlurMask, buildVignetteGradient } from './utils'\nimport { useInViewport } from './use-in-viewport'\nimport { useReducedMotion } from './use-reduced-motion'\n\n// ─── Root Component ──────────────────────────────────────────\n\nfunction HeroSceneRoot({\n images,\n initialIndex = 0,\n interval = 30_000,\n transitionDuration = 700,\n className,\n onIndexChange,\n children,\n}: HeroSceneProps) {\n const [index, setIndex] = useState(initialIndex)\n const rootRef = useRef<HTMLDivElement>(null)\n const reducedMotion = useReducedMotion()\n const isInViewport = useInViewport(rootRef)\n\n // ── Image rotation ──\n useEffect(() => {\n if (interval <= 0 || images.length <= 1) return\n\n let current = initialIndex\n const id = setInterval(() => {\n const next = (current + 1) % images.length\n current = next\n setIndex(next)\n setTimeout(() => {\n onIndexChange?.(next)\n globalThis.dispatchEvent(\n new CustomEvent('hero-scene-index-change', { detail: next }),\n )\n }, 0)\n }, interval)\n return () => clearInterval(id)\n }, [initialIndex, interval, images.length, onIndexChange])\n\n const activeColor = images[index]?.color ?? '128, 128, 128'\n\n return (\n <HeroSceneContext.Provider\n value={{\n images,\n index,\n transitionDuration,\n reducedMotion,\n isInViewport,\n containerRef: rootRef,\n }}\n >\n <div\n ref={rootRef}\n className={className}\n style={{ position: 'relative', overflow: 'hidden' }}\n >\n {/* ── Background images (no parallax — Parallax child wraps these) ── */}\n <div\n data-hero-images=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n backgroundColor: `rgb(${activeColor})`,\n transition: reducedMotion ? 'none' : 'background-color 1s ease',\n }}\n >\n {images.map((img, i) => (\n <Image\n key={img.src}\n src={img.src}\n alt=\"\"\n width={1920}\n height={1080}\n priority={i === initialIndex}\n sizes=\"100vw\"\n className=\"dark:grayscale\"\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n opacity: i === index ? 1 : 0,\n transition: reducedMotion\n ? 'none'\n : `opacity ${transitionDuration}ms ease`,\n }}\n />\n ))}\n </div>\n\n {children}\n </div>\n </HeroSceneContext.Provider>\n )\n}\n\n// ─── Parallax ────────────────────────────────────────────────\n\nfunction Parallax({\n speed = 0.4,\n mouseShiftX = 25,\n mouseShiftY = 15,\n mouseLerp = 0.04,\n}: ParallaxProps) {\n const { reducedMotion, isInViewport, containerRef } = useHeroScene()\n\n const scrollY = useRef(0)\n const targetMouseX = useRef(0)\n const targetMouseY = useRef(0)\n const currentMouseX = useRef(0)\n const currentMouseY = useRef(0)\n\n useEffect(() => {\n if (reducedMotion) return\n\n const root = containerRef.current\n if (!root) return\n\n const imagesEl = root.querySelector<HTMLDivElement>('[data-hero-images]')\n if (!imagesEl) return\n\n // Expand the images container to allow parallax overflow\n imagesEl.style.inset = '-15%'\n imagesEl.style.willChange = 'transform'\n\n return () => {\n imagesEl.style.inset = '0'\n imagesEl.style.willChange = ''\n imagesEl.style.transform = ''\n }\n }, [reducedMotion, containerRef])\n\n useEffect(() => {\n if (reducedMotion) return\n\n const root = containerRef.current\n if (!root) return\n\n const imagesEl = root.querySelector<HTMLDivElement>('[data-hero-images]')\n if (!imagesEl) return\n\n let rafId: number\n let running = true\n\n function loop() {\n if (!running) return\n\n currentMouseX.current +=\n (targetMouseX.current - currentMouseX.current) * mouseLerp\n currentMouseY.current +=\n (targetMouseY.current - currentMouseY.current) * mouseLerp\n\n const y = scrollY.current * speed\n const mx = currentMouseX.current * mouseShiftX\n const my = currentMouseY.current * mouseShiftY\n imagesEl!.style.transform = `translate3d(${mx}px, ${y + my}px, 0)`\n\n rafId = requestAnimationFrame(loop)\n }\n\n function onScroll() {\n if (!isInViewport) return\n scrollY.current = globalThis.scrollY\n }\n\n function onMouseMove(e: MouseEvent) {\n if (!isInViewport) return\n targetMouseX.current = (e.clientX / globalThis.innerWidth - 0.5) * 2\n targetMouseY.current = (e.clientY / globalThis.innerHeight - 0.5) * 2\n }\n\n globalThis.addEventListener('scroll', onScroll, { passive: true })\n globalThis.addEventListener('mousemove', onMouseMove, { passive: true })\n rafId = requestAnimationFrame(loop)\n\n return () => {\n running = false\n globalThis.removeEventListener('scroll', onScroll)\n globalThis.removeEventListener('mousemove', onMouseMove)\n cancelAnimationFrame(rafId)\n }\n }, [\n reducedMotion,\n isInViewport,\n containerRef,\n speed,\n mouseShiftX,\n mouseShiftY,\n mouseLerp,\n ])\n\n // Parallax is a behavior-only component — renders nothing\n return null\n}\n\n// ─── Vignette ────────────────────────────────────────────────\n\nfunction Vignette({\n centerX = 50,\n centerY = 100,\n shape = 'circle',\n stops,\n transitionDuration = 1000,\n}: VignetteProps) {\n const { images, index, reducedMotion } = useHeroScene()\n const transitionMs = reducedMotion ? '0ms' : `${transitionDuration}ms`\n\n return (\n <>\n {/* Light mode — color-matched vignettes with crossfade */}\n {images.map((img, i) => (\n <div\n key={`vignette-light-${img.color}`}\n aria-hidden=\"true\"\n className=\"dark:hidden\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n opacity: i === index ? 1 : 0,\n transition: `opacity ${transitionMs} ease`,\n background: buildVignetteGradient(img.color, {\n centerX,\n centerY,\n shape,\n stops,\n }),\n }}\n />\n ))}\n {/* Dark mode — black vignette */}\n <div\n aria-hidden=\"true\"\n className=\"hidden dark:block\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n background: buildVignetteGradient('0, 0, 0', {\n centerX,\n centerY,\n shape,\n stops,\n }),\n }}\n />\n </>\n )\n}\n\n// ─── Blur ────────────────────────────────────────────────────\n\nfunction Blur({\n amount = 24,\n centerX = 50,\n centerY = 65,\n innerRadius = 15,\n outerRadius = 55,\n}: BlurProps) {\n useHeroScene() // validate context\n\n const mask = buildBlurMask({ centerX, centerY, innerRadius, outerRadius })\n\n return (\n <div\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 15,\n backdropFilter: `blur(${amount}px)`,\n WebkitBackdropFilter: `blur(${amount}px)`,\n maskImage: mask,\n WebkitMaskImage: mask,\n pointerEvents: 'none',\n }}\n />\n )\n}\n\n// ─── Pattern ─────────────────────────────────────────────────\n\nfunction Pattern({\n dotSize = 1,\n spacing = 20,\n lightColor = 'rgba(0 0 0 / 0.15)',\n darkColor = 'rgba(255 255 255 / 0.1)',\n}: PatternProps) {\n useHeroScene() // validate context\n\n const bgImage = (color: string) =>\n `radial-gradient(circle, ${color} ${dotSize}px, transparent ${dotSize}px)`\n const bgSize = `${spacing}px ${spacing}px`\n\n return (\n <>\n <div\n aria-hidden=\"true\"\n className=\"dark:hidden\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n backgroundImage: bgImage(lightColor),\n backgroundSize: bgSize,\n }}\n />\n <div\n aria-hidden=\"true\"\n className=\"hidden dark:block\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n backgroundImage: bgImage(darkColor),\n backgroundSize: bgSize,\n }}\n />\n </>\n )\n}\n\n// ─── DarkOverlay ─────────────────────────────────────────────\n\nfunction DarkOverlay({ opacity = 0.4 }: DarkOverlayProps) {\n useHeroScene() // validate context\n\n return (\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none hidden dark:block\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 10,\n backgroundColor: `rgba(0 0 0 / ${opacity})`,\n }}\n />\n )\n}\n\n// ─── Content ─────────────────────────────────────────────────\n\nfunction Content({ className, children }: ContentProps) {\n useHeroScene() // validate context\n\n return (\n <div\n className={className}\n style={{ position: 'relative', zIndex: 30 }}\n >\n {children}\n </div>\n )\n}\n\n// ─── Exports ────────────────────────────────────────────────\n\nexport {\n HeroSceneRoot as HeroScene,\n Parallax as HeroParallax,\n Vignette as HeroVignette,\n Blur as HeroBlur,\n Pattern as HeroPattern,\n DarkOverlay as HeroDarkOverlay,\n Content as HeroContent,\n}\n","'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { HeroSceneContextValue } from './types'\n\nexport const HeroSceneContext = createContext<HeroSceneContextValue | null>(null)\n\nexport function useHeroScene(): HeroSceneContextValue {\n const ctx = useContext(HeroSceneContext)\n if (!ctx) {\n throw new Error(\n 'HeroScene compound components (Parallax, Vignette, Blur, Pattern, DarkOverlay, Content) ' +\n 'must be rendered inside a <HeroScene> parent.',\n )\n }\n return ctx\n}\n","import type { VignetteProps, BlurProps } from './types'\n\nconst DEFAULT_STOPS: [number, number][] = [\n [0, 0],\n [15, 0.08],\n [30, 0.15],\n [45, 0.25],\n [60, 0.35],\n [75, 0.45],\n [88, 0.55],\n [100, 0.6],\n]\n\nexport function buildVignetteGradient(\n color: string,\n config: Pick<VignetteProps, 'shape' | 'centerX' | 'centerY' | 'stops'>,\n): string {\n const shape = config.shape ?? 'circle'\n const cx = config.centerX ?? 50\n const cy = config.centerY ?? 100\n const stops = config.stops ?? DEFAULT_STOPS\n\n const gradientStops = stops\n .map(([pos, opacity]) =>\n opacity === 0\n ? `transparent ${pos}%`\n : `color-mix(in srgb, rgb(${color}) ${Math.round(opacity * 100)}%, transparent) ${pos}%`,\n )\n .join(', ')\n\n return `radial-gradient(${shape} at ${cx}% ${cy}%, ${gradientStops})`\n}\n\nexport function buildBlurMask(\n config: Pick<BlurProps, 'centerX' | 'centerY' | 'innerRadius' | 'outerRadius'>,\n): string {\n const cx = config.centerX ?? 50\n const cy = config.centerY ?? 65\n const inner = config.innerRadius ?? 15\n const outer = config.outerRadius ?? 55\n\n return `radial-gradient(circle at ${cx}% ${cy}%, transparent ${inner}%, black ${outer}%)`\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\nimport type { RefObject } from 'react'\n\n/**\n * Returns true when the referenced element is at least partially visible\n * in the viewport, using IntersectionObserver. SSR-safe — defaults to true\n * so effects run immediately on first paint before the observer fires.\n */\nexport function useInViewport(ref: RefObject<HTMLElement | null>): boolean {\n const [inViewport, setInViewport] = useState(true)\n\n useEffect(() => {\n const el = ref.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n setInViewport(entry.isIntersecting)\n },\n { threshold: 0 },\n )\n\n observer.observe(el)\n return () => observer.disconnect()\n }, [ref])\n\n return inViewport\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\n/**\n * Returns true when the user has enabled \"prefers-reduced-motion: reduce\"\n * in their OS or browser settings. SSR-safe — defaults to false.\n */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false)\n\n useEffect(() => {\n const mql = globalThis.matchMedia('(prefers-reduced-motion: reduce)')\n setReduced(mql.matches)\n\n function onChange(e: MediaQueryListEvent) {\n setReduced(e.matches)\n }\n\n mql.addEventListener('change', onChange)\n return () => mql.removeEventListener('change', onChange)\n }, [])\n\n return reduced\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","Image"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/hero-scene.tsx","../src/hero-scene-context.ts","../src/utils.ts","../src/use-in-viewport.ts","../src/use-reduced-motion.ts"],"sourcesContent":["export {\n HeroScene,\n HeroParallax,\n HeroVignette,\n HeroBlur,\n HeroPattern,\n HeroDarkOverlay,\n HeroContent,\n} from './hero-scene'\n\n/** Event name dispatched on `globalThis` when the active image changes. `event.detail` is the new index. */\nexport const HERO_SCENE_INDEX_EVENT = 'hero-scene-index-change'\nexport { buildVignetteGradient, buildBlurMask } from './utils'\nexport { useReducedMotion } from './use-reduced-motion'\n\nexport type {\n HeroSceneProps,\n HeroImage,\n ParallaxProps,\n VignetteProps,\n BlurProps,\n PatternProps,\n DarkOverlayProps,\n ContentProps,\n} from './types'\n","'use client'\n\nimport Image from 'next/image'\nimport { useEffect, useRef, useState } from 'react'\n\nimport { HeroSceneContext, useHeroScene } from './hero-scene-context'\nimport type {\n BlurProps,\n ContentProps,\n DarkOverlayProps,\n HeroSceneProps,\n ParallaxProps,\n PatternProps,\n VignetteProps,\n} from './types'\nimport { buildBlurMask, buildVignetteGradient } from './utils'\nimport { useInViewport } from './use-in-viewport'\nimport { useReducedMotion } from './use-reduced-motion'\n\n/**\n * Injected once — handles dark mode via `.dark` ancestor class.\n * Uses data-attributes so no Tailwind scanning is needed.\n */\nconst DARK_STYLES = `\n[data-hs-dark-only]{display:none}\n[data-hs-light-only]{display:block}\n[data-hs-img]{filter:none}\n.dark [data-hs-dark-only]{display:block}\n.dark [data-hs-light-only]{display:none}\n.dark [data-hs-img]{filter:grayscale(1)}\n`\n\n// ─── Root Component ──────────────────────────────────────────\n\nfunction HeroSceneRoot({\n images,\n initialIndex = 0,\n interval = 30_000,\n transitionDuration = 700,\n className,\n onIndexChange,\n children,\n}: HeroSceneProps) {\n const [index, setIndex] = useState(initialIndex)\n const rootRef = useRef<HTMLDivElement>(null)\n const reducedMotion = useReducedMotion()\n const isInViewport = useInViewport(rootRef)\n\n // ── Image rotation ──\n useEffect(() => {\n if (interval <= 0 || images.length <= 1) return\n\n let current = initialIndex\n const id = setInterval(() => {\n const next = (current + 1) % images.length\n current = next\n setIndex(next)\n setTimeout(() => {\n onIndexChange?.(next)\n globalThis.dispatchEvent(\n new CustomEvent('hero-scene-index-change', { detail: next }),\n )\n }, 0)\n }, interval)\n return () => clearInterval(id)\n }, [initialIndex, interval, images.length, onIndexChange])\n\n const activeColor = images[index]?.color ?? '128, 128, 128'\n\n return (\n <HeroSceneContext.Provider\n value={{\n images,\n index,\n transitionDuration,\n reducedMotion,\n isInViewport,\n containerRef: rootRef,\n }}\n >\n <style dangerouslySetInnerHTML={{ __html: DARK_STYLES }} />\n <div\n ref={rootRef}\n className={className}\n style={{ position: 'relative', overflow: 'hidden' }}\n >\n {/* ── Background images ── */}\n <div\n data-hero-images=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: '-15%',\n backgroundColor: `rgb(${activeColor})`,\n transition: reducedMotion ? 'none' : 'background-color 1s ease',\n }}\n >\n {images.map((img, i) => (\n <Image\n key={img.src}\n src={img.src}\n alt=\"\"\n width={1920}\n height={1080}\n priority={i === initialIndex}\n sizes=\"100vw\"\n data-hs-img=\"\"\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n opacity: i === index ? 1 : 0,\n transition: reducedMotion\n ? 'none'\n : `opacity ${transitionDuration}ms ease`,\n }}\n />\n ))}\n </div>\n\n {children}\n </div>\n </HeroSceneContext.Provider>\n )\n}\n\n// ─── Parallax ────────────────────────────────────────────────\n\nfunction Parallax({\n speed = 0.4,\n mouseShiftX = 25,\n mouseShiftY = 15,\n mouseLerp = 0.04,\n}: ParallaxProps) {\n const { reducedMotion, isInViewport, containerRef } = useHeroScene()\n\n const scrollY = useRef(0)\n const targetMouseX = useRef(0)\n const targetMouseY = useRef(0)\n const currentMouseX = useRef(0)\n const currentMouseY = useRef(0)\n\n useEffect(() => {\n if (reducedMotion) return\n\n const root = containerRef.current\n if (!root) return\n\n const imagesEl = root.querySelector<HTMLDivElement>('[data-hero-images]')\n if (!imagesEl) return\n\n let rafId: number\n let running = true\n\n function loop() {\n if (!running) return\n\n currentMouseX.current +=\n (targetMouseX.current - currentMouseX.current) * mouseLerp\n currentMouseY.current +=\n (targetMouseY.current - currentMouseY.current) * mouseLerp\n\n const y = scrollY.current * speed\n const mx = currentMouseX.current * mouseShiftX\n const my = currentMouseY.current * mouseShiftY\n imagesEl!.style.transform = `translate3d(${mx}px, ${y + my}px, 0)`\n\n rafId = requestAnimationFrame(loop)\n }\n\n function onScroll() {\n if (!isInViewport) return\n scrollY.current = globalThis.scrollY\n }\n\n function onMouseMove(e: MouseEvent) {\n if (!isInViewport) return\n targetMouseX.current = (e.clientX / globalThis.innerWidth - 0.5) * 2\n targetMouseY.current = (e.clientY / globalThis.innerHeight - 0.5) * 2\n }\n\n globalThis.addEventListener('scroll', onScroll, { passive: true })\n globalThis.addEventListener('mousemove', onMouseMove, { passive: true })\n rafId = requestAnimationFrame(loop)\n\n return () => {\n running = false\n globalThis.removeEventListener('scroll', onScroll)\n globalThis.removeEventListener('mousemove', onMouseMove)\n cancelAnimationFrame(rafId)\n }\n }, [\n reducedMotion,\n isInViewport,\n containerRef,\n speed,\n mouseShiftX,\n mouseShiftY,\n mouseLerp,\n ])\n\n return null\n}\n\n// ─── Vignette ────────────────────────────────────────────────\n\nfunction Vignette({\n centerX = 50,\n centerY = 100,\n shape = 'circle',\n stops,\n transitionDuration = 1000,\n}: VignetteProps) {\n const { images, index, reducedMotion } = useHeroScene()\n const transitionMs = reducedMotion ? '0ms' : `${transitionDuration}ms`\n\n return (\n <>\n {/* Light mode — color-matched vignettes with crossfade */}\n {images.map((img, i) => (\n <div\n key={`vignette-light-${img.color}`}\n data-hs-light-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n opacity: i === index ? 1 : 0,\n transition: `opacity ${transitionMs} ease`,\n background: buildVignetteGradient(img.color, {\n centerX,\n centerY,\n shape,\n stops,\n }),\n }}\n />\n ))}\n {/* Dark mode — black vignette */}\n <div\n data-hs-dark-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n background: buildVignetteGradient('0, 0, 0', {\n centerX,\n centerY,\n shape,\n stops,\n }),\n }}\n />\n </>\n )\n}\n\n// ─── Blur ────────────────────────────────────────────────────\n\nfunction Blur({\n amount = 24,\n centerX = 50,\n centerY = 65,\n innerRadius = 15,\n outerRadius = 55,\n}: BlurProps) {\n useHeroScene()\n\n const mask = buildBlurMask({ centerX, centerY, innerRadius, outerRadius })\n\n return (\n <div\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 15,\n backdropFilter: `blur(${amount}px)`,\n WebkitBackdropFilter: `blur(${amount}px)`,\n maskImage: mask,\n WebkitMaskImage: mask,\n pointerEvents: 'none',\n }}\n />\n )\n}\n\n// ─── Pattern ─────────────────────────────────────────────────\n\nfunction Pattern({\n dotSize = 1,\n spacing = 20,\n lightColor = 'rgba(0 0 0 / 0.15)',\n darkColor = 'rgba(255 255 255 / 0.1)',\n}: PatternProps) {\n useHeroScene()\n\n const bgImage = (color: string) =>\n `radial-gradient(circle, ${color} ${dotSize}px, transparent ${dotSize}px)`\n const bgSize = `${spacing}px ${spacing}px`\n\n return (\n <>\n <div\n data-hs-light-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n backgroundImage: bgImage(lightColor),\n backgroundSize: bgSize,\n }}\n />\n <div\n data-hs-dark-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n backgroundImage: bgImage(darkColor),\n backgroundSize: bgSize,\n }}\n />\n </>\n )\n}\n\n// ─── DarkOverlay ─────────────────────────────────────────────\n\nfunction DarkOverlay({ opacity = 0.4 }: DarkOverlayProps) {\n useHeroScene()\n\n return (\n <div\n data-hs-dark-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 10,\n pointerEvents: 'none',\n backgroundColor: `rgba(0 0 0 / ${opacity})`,\n }}\n />\n )\n}\n\n// ─── Content ─────────────────────────────────────────────────\n\nfunction Content({ className, children }: ContentProps) {\n useHeroScene()\n\n return (\n <div\n className={className}\n style={{ position: 'relative', zIndex: 30 }}\n >\n {children}\n </div>\n )\n}\n\n// ─── Exports ────────────────────────────────────────────────\n\nexport {\n HeroSceneRoot as HeroScene,\n Parallax as HeroParallax,\n Vignette as HeroVignette,\n Blur as HeroBlur,\n Pattern as HeroPattern,\n DarkOverlay as HeroDarkOverlay,\n Content as HeroContent,\n}\n","'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { HeroSceneContextValue } from './types'\n\nexport const HeroSceneContext = createContext<HeroSceneContextValue | null>(null)\n\nexport function useHeroScene(): HeroSceneContextValue {\n const ctx = useContext(HeroSceneContext)\n if (!ctx) {\n throw new Error(\n 'HeroScene compound components (Parallax, Vignette, Blur, Pattern, DarkOverlay, Content) ' +\n 'must be rendered inside a <HeroScene> parent.',\n )\n }\n return ctx\n}\n","import type { VignetteProps, BlurProps } from './types'\n\nconst DEFAULT_STOPS: [number, number][] = [\n [0, 0],\n [15, 0.08],\n [30, 0.15],\n [45, 0.25],\n [60, 0.35],\n [75, 0.45],\n [88, 0.55],\n [100, 0.6],\n]\n\nexport function buildVignetteGradient(\n color: string,\n config: Pick<VignetteProps, 'shape' | 'centerX' | 'centerY' | 'stops'>,\n): string {\n const shape = config.shape ?? 'circle'\n const cx = config.centerX ?? 50\n const cy = config.centerY ?? 100\n const stops = config.stops ?? DEFAULT_STOPS\n\n const gradientStops = stops\n .map(([pos, opacity]) =>\n opacity === 0\n ? `transparent ${pos}%`\n : `color-mix(in srgb, rgb(${color}) ${Math.round(opacity * 100)}%, transparent) ${pos}%`,\n )\n .join(', ')\n\n return `radial-gradient(${shape} at ${cx}% ${cy}%, ${gradientStops})`\n}\n\nexport function buildBlurMask(\n config: Pick<BlurProps, 'centerX' | 'centerY' | 'innerRadius' | 'outerRadius'>,\n): string {\n const cx = config.centerX ?? 50\n const cy = config.centerY ?? 65\n const inner = config.innerRadius ?? 15\n const outer = config.outerRadius ?? 55\n\n return `radial-gradient(circle at ${cx}% ${cy}%, transparent ${inner}%, black ${outer}%)`\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\nimport type { RefObject } from 'react'\n\n/**\n * Returns true when the referenced element is at least partially visible\n * in the viewport, using IntersectionObserver. SSR-safe — defaults to true\n * so effects run immediately on first paint before the observer fires.\n */\nexport function useInViewport(ref: RefObject<HTMLElement | null>): boolean {\n const [inViewport, setInViewport] = useState(true)\n\n useEffect(() => {\n const el = ref.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n setInViewport(entry.isIntersecting)\n },\n { threshold: 0 },\n )\n\n observer.observe(el)\n return () => observer.disconnect()\n }, [ref])\n\n return inViewport\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\n/**\n * Returns true when the user has enabled \"prefers-reduced-motion: reduce\"\n * in their OS or browser settings. SSR-safe — defaults to false.\n */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false)\n\n useEffect(() => {\n const mql = globalThis.matchMedia('(prefers-reduced-motion: reduce)')\n setReduced(mql.matches)\n\n function onChange(e: MediaQueryListEvent) {\n setReduced(e.matches)\n }\n\n mql.addEventListener('change', onChange)\n return () => mql.removeEventListener('change', onChange)\n }, [])\n\n return reduced\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","Image"]}
package/dist/index.js CHANGED
@@ -83,6 +83,14 @@ function useReducedMotion() {
83
83
 
84
84
  // src/hero-scene.tsx
85
85
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
86
+ var DARK_STYLES = `
87
+ [data-hs-dark-only]{display:none}
88
+ [data-hs-light-only]{display:block}
89
+ [data-hs-img]{filter:none}
90
+ .dark [data-hs-dark-only]{display:block}
91
+ .dark [data-hs-light-only]{display:none}
92
+ .dark [data-hs-img]{filter:grayscale(1)}
93
+ `;
86
94
  function HeroSceneRoot({
87
95
  images,
88
96
  initialIndex = 0,
@@ -113,7 +121,7 @@ function HeroSceneRoot({
113
121
  return () => clearInterval(id);
114
122
  }, [initialIndex, interval, images.length, onIndexChange]);
115
123
  const activeColor = images[index]?.color ?? "128, 128, 128";
116
- return /* @__PURE__ */ jsx(
124
+ return /* @__PURE__ */ jsxs(
117
125
  HeroSceneContext.Provider,
118
126
  {
119
127
  value: {
@@ -124,52 +132,55 @@ function HeroSceneRoot({
124
132
  isInViewport,
125
133
  containerRef: rootRef
126
134
  },
127
- children: /* @__PURE__ */ jsxs(
128
- "div",
129
- {
130
- ref: rootRef,
131
- className,
132
- style: { position: "relative", overflow: "hidden" },
133
- children: [
134
- /* @__PURE__ */ jsx(
135
- "div",
136
- {
137
- "data-hero-images": "",
138
- "aria-hidden": "true",
139
- style: {
140
- position: "absolute",
141
- inset: 0,
142
- backgroundColor: `rgb(${activeColor})`,
143
- transition: reducedMotion ? "none" : "background-color 1s ease"
144
- },
145
- children: images.map((img, i) => /* @__PURE__ */ jsx(
146
- Image,
147
- {
148
- src: img.src,
149
- alt: "",
150
- width: 1920,
151
- height: 1080,
152
- priority: i === initialIndex,
153
- sizes: "100vw",
154
- className: "dark:grayscale",
155
- style: {
156
- position: "absolute",
157
- inset: 0,
158
- width: "100%",
159
- height: "100%",
160
- objectFit: "cover",
161
- opacity: i === index ? 1 : 0,
162
- transition: reducedMotion ? "none" : `opacity ${transitionDuration}ms ease`
163
- }
135
+ children: [
136
+ /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: DARK_STYLES } }),
137
+ /* @__PURE__ */ jsxs(
138
+ "div",
139
+ {
140
+ ref: rootRef,
141
+ className,
142
+ style: { position: "relative", overflow: "hidden" },
143
+ children: [
144
+ /* @__PURE__ */ jsx(
145
+ "div",
146
+ {
147
+ "data-hero-images": "",
148
+ "aria-hidden": "true",
149
+ style: {
150
+ position: "absolute",
151
+ inset: "-15%",
152
+ backgroundColor: `rgb(${activeColor})`,
153
+ transition: reducedMotion ? "none" : "background-color 1s ease"
164
154
  },
165
- img.src
166
- ))
167
- }
168
- ),
169
- children
170
- ]
171
- }
172
- )
155
+ children: images.map((img, i) => /* @__PURE__ */ jsx(
156
+ Image,
157
+ {
158
+ src: img.src,
159
+ alt: "",
160
+ width: 1920,
161
+ height: 1080,
162
+ priority: i === initialIndex,
163
+ sizes: "100vw",
164
+ "data-hs-img": "",
165
+ style: {
166
+ position: "absolute",
167
+ inset: 0,
168
+ width: "100%",
169
+ height: "100%",
170
+ objectFit: "cover",
171
+ opacity: i === index ? 1 : 0,
172
+ transition: reducedMotion ? "none" : `opacity ${transitionDuration}ms ease`
173
+ }
174
+ },
175
+ img.src
176
+ ))
177
+ }
178
+ ),
179
+ children
180
+ ]
181
+ }
182
+ )
183
+ ]
173
184
  }
174
185
  );
175
186
  }
@@ -185,20 +196,6 @@ function Parallax({
185
196
  const targetMouseY = useRef(0);
186
197
  const currentMouseX = useRef(0);
187
198
  const currentMouseY = useRef(0);
188
- useEffect3(() => {
189
- if (reducedMotion) return;
190
- const root = containerRef.current;
191
- if (!root) return;
192
- const imagesEl = root.querySelector("[data-hero-images]");
193
- if (!imagesEl) return;
194
- imagesEl.style.inset = "-15%";
195
- imagesEl.style.willChange = "transform";
196
- return () => {
197
- imagesEl.style.inset = "0";
198
- imagesEl.style.willChange = "";
199
- imagesEl.style.transform = "";
200
- };
201
- }, [reducedMotion, containerRef]);
202
199
  useEffect3(() => {
203
200
  if (reducedMotion) return;
204
201
  const root = containerRef.current;
@@ -259,8 +256,8 @@ function Vignette({
259
256
  images.map((img, i) => /* @__PURE__ */ jsx(
260
257
  "div",
261
258
  {
259
+ "data-hs-light-only": "",
262
260
  "aria-hidden": "true",
263
- className: "dark:hidden",
264
261
  style: {
265
262
  position: "absolute",
266
263
  inset: 0,
@@ -281,8 +278,8 @@ function Vignette({
281
278
  /* @__PURE__ */ jsx(
282
279
  "div",
283
280
  {
281
+ "data-hs-dark-only": "",
284
282
  "aria-hidden": "true",
285
- className: "hidden dark:block",
286
283
  style: {
287
284
  position: "absolute",
288
285
  inset: 0,
@@ -338,8 +335,8 @@ function Pattern({
338
335
  /* @__PURE__ */ jsx(
339
336
  "div",
340
337
  {
338
+ "data-hs-light-only": "",
341
339
  "aria-hidden": "true",
342
- className: "dark:hidden",
343
340
  style: {
344
341
  position: "absolute",
345
342
  inset: 0,
@@ -353,8 +350,8 @@ function Pattern({
353
350
  /* @__PURE__ */ jsx(
354
351
  "div",
355
352
  {
353
+ "data-hs-dark-only": "",
356
354
  "aria-hidden": "true",
357
- className: "hidden dark:block",
358
355
  style: {
359
356
  position: "absolute",
360
357
  inset: 0,
@@ -372,12 +369,13 @@ function DarkOverlay({ opacity = 0.4 }) {
372
369
  return /* @__PURE__ */ jsx(
373
370
  "div",
374
371
  {
372
+ "data-hs-dark-only": "",
375
373
  "aria-hidden": "true",
376
- className: "pointer-events-none hidden dark:block",
377
374
  style: {
378
375
  position: "absolute",
379
376
  inset: 0,
380
377
  zIndex: 10,
378
+ pointerEvents: "none",
381
379
  backgroundColor: `rgba(0 0 0 / ${opacity})`
382
380
  }
383
381
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hero-scene.tsx","../src/hero-scene-context.ts","../src/utils.ts","../src/use-in-viewport.ts","../src/use-reduced-motion.ts","../src/index.ts"],"sourcesContent":["'use client'\n\nimport Image from 'next/image'\nimport { useEffect, useRef, useState } from 'react'\n\nimport { HeroSceneContext, useHeroScene } from './hero-scene-context'\nimport type {\n BlurProps,\n ContentProps,\n DarkOverlayProps,\n HeroSceneProps,\n ParallaxProps,\n PatternProps,\n VignetteProps,\n} from './types'\nimport { buildBlurMask, buildVignetteGradient } from './utils'\nimport { useInViewport } from './use-in-viewport'\nimport { useReducedMotion } from './use-reduced-motion'\n\n// ─── Root Component ──────────────────────────────────────────\n\nfunction HeroSceneRoot({\n images,\n initialIndex = 0,\n interval = 30_000,\n transitionDuration = 700,\n className,\n onIndexChange,\n children,\n}: HeroSceneProps) {\n const [index, setIndex] = useState(initialIndex)\n const rootRef = useRef<HTMLDivElement>(null)\n const reducedMotion = useReducedMotion()\n const isInViewport = useInViewport(rootRef)\n\n // ── Image rotation ──\n useEffect(() => {\n if (interval <= 0 || images.length <= 1) return\n\n let current = initialIndex\n const id = setInterval(() => {\n const next = (current + 1) % images.length\n current = next\n setIndex(next)\n setTimeout(() => {\n onIndexChange?.(next)\n globalThis.dispatchEvent(\n new CustomEvent('hero-scene-index-change', { detail: next }),\n )\n }, 0)\n }, interval)\n return () => clearInterval(id)\n }, [initialIndex, interval, images.length, onIndexChange])\n\n const activeColor = images[index]?.color ?? '128, 128, 128'\n\n return (\n <HeroSceneContext.Provider\n value={{\n images,\n index,\n transitionDuration,\n reducedMotion,\n isInViewport,\n containerRef: rootRef,\n }}\n >\n <div\n ref={rootRef}\n className={className}\n style={{ position: 'relative', overflow: 'hidden' }}\n >\n {/* ── Background images (no parallax — Parallax child wraps these) ── */}\n <div\n data-hero-images=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n backgroundColor: `rgb(${activeColor})`,\n transition: reducedMotion ? 'none' : 'background-color 1s ease',\n }}\n >\n {images.map((img, i) => (\n <Image\n key={img.src}\n src={img.src}\n alt=\"\"\n width={1920}\n height={1080}\n priority={i === initialIndex}\n sizes=\"100vw\"\n className=\"dark:grayscale\"\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n opacity: i === index ? 1 : 0,\n transition: reducedMotion\n ? 'none'\n : `opacity ${transitionDuration}ms ease`,\n }}\n />\n ))}\n </div>\n\n {children}\n </div>\n </HeroSceneContext.Provider>\n )\n}\n\n// ─── Parallax ────────────────────────────────────────────────\n\nfunction Parallax({\n speed = 0.4,\n mouseShiftX = 25,\n mouseShiftY = 15,\n mouseLerp = 0.04,\n}: ParallaxProps) {\n const { reducedMotion, isInViewport, containerRef } = useHeroScene()\n\n const scrollY = useRef(0)\n const targetMouseX = useRef(0)\n const targetMouseY = useRef(0)\n const currentMouseX = useRef(0)\n const currentMouseY = useRef(0)\n\n useEffect(() => {\n if (reducedMotion) return\n\n const root = containerRef.current\n if (!root) return\n\n const imagesEl = root.querySelector<HTMLDivElement>('[data-hero-images]')\n if (!imagesEl) return\n\n // Expand the images container to allow parallax overflow\n imagesEl.style.inset = '-15%'\n imagesEl.style.willChange = 'transform'\n\n return () => {\n imagesEl.style.inset = '0'\n imagesEl.style.willChange = ''\n imagesEl.style.transform = ''\n }\n }, [reducedMotion, containerRef])\n\n useEffect(() => {\n if (reducedMotion) return\n\n const root = containerRef.current\n if (!root) return\n\n const imagesEl = root.querySelector<HTMLDivElement>('[data-hero-images]')\n if (!imagesEl) return\n\n let rafId: number\n let running = true\n\n function loop() {\n if (!running) return\n\n currentMouseX.current +=\n (targetMouseX.current - currentMouseX.current) * mouseLerp\n currentMouseY.current +=\n (targetMouseY.current - currentMouseY.current) * mouseLerp\n\n const y = scrollY.current * speed\n const mx = currentMouseX.current * mouseShiftX\n const my = currentMouseY.current * mouseShiftY\n imagesEl!.style.transform = `translate3d(${mx}px, ${y + my}px, 0)`\n\n rafId = requestAnimationFrame(loop)\n }\n\n function onScroll() {\n if (!isInViewport) return\n scrollY.current = globalThis.scrollY\n }\n\n function onMouseMove(e: MouseEvent) {\n if (!isInViewport) return\n targetMouseX.current = (e.clientX / globalThis.innerWidth - 0.5) * 2\n targetMouseY.current = (e.clientY / globalThis.innerHeight - 0.5) * 2\n }\n\n globalThis.addEventListener('scroll', onScroll, { passive: true })\n globalThis.addEventListener('mousemove', onMouseMove, { passive: true })\n rafId = requestAnimationFrame(loop)\n\n return () => {\n running = false\n globalThis.removeEventListener('scroll', onScroll)\n globalThis.removeEventListener('mousemove', onMouseMove)\n cancelAnimationFrame(rafId)\n }\n }, [\n reducedMotion,\n isInViewport,\n containerRef,\n speed,\n mouseShiftX,\n mouseShiftY,\n mouseLerp,\n ])\n\n // Parallax is a behavior-only component — renders nothing\n return null\n}\n\n// ─── Vignette ────────────────────────────────────────────────\n\nfunction Vignette({\n centerX = 50,\n centerY = 100,\n shape = 'circle',\n stops,\n transitionDuration = 1000,\n}: VignetteProps) {\n const { images, index, reducedMotion } = useHeroScene()\n const transitionMs = reducedMotion ? '0ms' : `${transitionDuration}ms`\n\n return (\n <>\n {/* Light mode — color-matched vignettes with crossfade */}\n {images.map((img, i) => (\n <div\n key={`vignette-light-${img.color}`}\n aria-hidden=\"true\"\n className=\"dark:hidden\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n opacity: i === index ? 1 : 0,\n transition: `opacity ${transitionMs} ease`,\n background: buildVignetteGradient(img.color, {\n centerX,\n centerY,\n shape,\n stops,\n }),\n }}\n />\n ))}\n {/* Dark mode — black vignette */}\n <div\n aria-hidden=\"true\"\n className=\"hidden dark:block\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n background: buildVignetteGradient('0, 0, 0', {\n centerX,\n centerY,\n shape,\n stops,\n }),\n }}\n />\n </>\n )\n}\n\n// ─── Blur ────────────────────────────────────────────────────\n\nfunction Blur({\n amount = 24,\n centerX = 50,\n centerY = 65,\n innerRadius = 15,\n outerRadius = 55,\n}: BlurProps) {\n useHeroScene() // validate context\n\n const mask = buildBlurMask({ centerX, centerY, innerRadius, outerRadius })\n\n return (\n <div\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 15,\n backdropFilter: `blur(${amount}px)`,\n WebkitBackdropFilter: `blur(${amount}px)`,\n maskImage: mask,\n WebkitMaskImage: mask,\n pointerEvents: 'none',\n }}\n />\n )\n}\n\n// ─── Pattern ─────────────────────────────────────────────────\n\nfunction Pattern({\n dotSize = 1,\n spacing = 20,\n lightColor = 'rgba(0 0 0 / 0.15)',\n darkColor = 'rgba(255 255 255 / 0.1)',\n}: PatternProps) {\n useHeroScene() // validate context\n\n const bgImage = (color: string) =>\n `radial-gradient(circle, ${color} ${dotSize}px, transparent ${dotSize}px)`\n const bgSize = `${spacing}px ${spacing}px`\n\n return (\n <>\n <div\n aria-hidden=\"true\"\n className=\"dark:hidden\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n backgroundImage: bgImage(lightColor),\n backgroundSize: bgSize,\n }}\n />\n <div\n aria-hidden=\"true\"\n className=\"hidden dark:block\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n backgroundImage: bgImage(darkColor),\n backgroundSize: bgSize,\n }}\n />\n </>\n )\n}\n\n// ─── DarkOverlay ─────────────────────────────────────────────\n\nfunction DarkOverlay({ opacity = 0.4 }: DarkOverlayProps) {\n useHeroScene() // validate context\n\n return (\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none hidden dark:block\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 10,\n backgroundColor: `rgba(0 0 0 / ${opacity})`,\n }}\n />\n )\n}\n\n// ─── Content ─────────────────────────────────────────────────\n\nfunction Content({ className, children }: ContentProps) {\n useHeroScene() // validate context\n\n return (\n <div\n className={className}\n style={{ position: 'relative', zIndex: 30 }}\n >\n {children}\n </div>\n )\n}\n\n// ─── Exports ────────────────────────────────────────────────\n\nexport {\n HeroSceneRoot as HeroScene,\n Parallax as HeroParallax,\n Vignette as HeroVignette,\n Blur as HeroBlur,\n Pattern as HeroPattern,\n DarkOverlay as HeroDarkOverlay,\n Content as HeroContent,\n}\n","'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { HeroSceneContextValue } from './types'\n\nexport const HeroSceneContext = createContext<HeroSceneContextValue | null>(null)\n\nexport function useHeroScene(): HeroSceneContextValue {\n const ctx = useContext(HeroSceneContext)\n if (!ctx) {\n throw new Error(\n 'HeroScene compound components (Parallax, Vignette, Blur, Pattern, DarkOverlay, Content) ' +\n 'must be rendered inside a <HeroScene> parent.',\n )\n }\n return ctx\n}\n","import type { VignetteProps, BlurProps } from './types'\n\nconst DEFAULT_STOPS: [number, number][] = [\n [0, 0],\n [15, 0.08],\n [30, 0.15],\n [45, 0.25],\n [60, 0.35],\n [75, 0.45],\n [88, 0.55],\n [100, 0.6],\n]\n\nexport function buildVignetteGradient(\n color: string,\n config: Pick<VignetteProps, 'shape' | 'centerX' | 'centerY' | 'stops'>,\n): string {\n const shape = config.shape ?? 'circle'\n const cx = config.centerX ?? 50\n const cy = config.centerY ?? 100\n const stops = config.stops ?? DEFAULT_STOPS\n\n const gradientStops = stops\n .map(([pos, opacity]) =>\n opacity === 0\n ? `transparent ${pos}%`\n : `color-mix(in srgb, rgb(${color}) ${Math.round(opacity * 100)}%, transparent) ${pos}%`,\n )\n .join(', ')\n\n return `radial-gradient(${shape} at ${cx}% ${cy}%, ${gradientStops})`\n}\n\nexport function buildBlurMask(\n config: Pick<BlurProps, 'centerX' | 'centerY' | 'innerRadius' | 'outerRadius'>,\n): string {\n const cx = config.centerX ?? 50\n const cy = config.centerY ?? 65\n const inner = config.innerRadius ?? 15\n const outer = config.outerRadius ?? 55\n\n return `radial-gradient(circle at ${cx}% ${cy}%, transparent ${inner}%, black ${outer}%)`\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\nimport type { RefObject } from 'react'\n\n/**\n * Returns true when the referenced element is at least partially visible\n * in the viewport, using IntersectionObserver. SSR-safe — defaults to true\n * so effects run immediately on first paint before the observer fires.\n */\nexport function useInViewport(ref: RefObject<HTMLElement | null>): boolean {\n const [inViewport, setInViewport] = useState(true)\n\n useEffect(() => {\n const el = ref.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n setInViewport(entry.isIntersecting)\n },\n { threshold: 0 },\n )\n\n observer.observe(el)\n return () => observer.disconnect()\n }, [ref])\n\n return inViewport\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\n/**\n * Returns true when the user has enabled \"prefers-reduced-motion: reduce\"\n * in their OS or browser settings. SSR-safe — defaults to false.\n */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false)\n\n useEffect(() => {\n const mql = globalThis.matchMedia('(prefers-reduced-motion: reduce)')\n setReduced(mql.matches)\n\n function onChange(e: MediaQueryListEvent) {\n setReduced(e.matches)\n }\n\n mql.addEventListener('change', onChange)\n return () => mql.removeEventListener('change', onChange)\n }, [])\n\n return reduced\n}\n","export {\n HeroScene,\n HeroParallax,\n HeroVignette,\n HeroBlur,\n HeroPattern,\n HeroDarkOverlay,\n HeroContent,\n} from './hero-scene'\n\n/** Event name dispatched on `globalThis` when the active image changes. `event.detail` is the new index. */\nexport const HERO_SCENE_INDEX_EVENT = 'hero-scene-index-change'\nexport { buildVignetteGradient, buildBlurMask } from './utils'\nexport { useReducedMotion } from './use-reduced-motion'\n\nexport type {\n HeroSceneProps,\n HeroImage,\n ParallaxProps,\n VignetteProps,\n BlurProps,\n PatternProps,\n DarkOverlayProps,\n ContentProps,\n} from './types'\n"],"mappings":"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","names":["useEffect","useState","useEffect","useState","useState","useEffect"]}
1
+ {"version":3,"sources":["../src/hero-scene.tsx","../src/hero-scene-context.ts","../src/utils.ts","../src/use-in-viewport.ts","../src/use-reduced-motion.ts","../src/index.ts"],"sourcesContent":["'use client'\n\nimport Image from 'next/image'\nimport { useEffect, useRef, useState } from 'react'\n\nimport { HeroSceneContext, useHeroScene } from './hero-scene-context'\nimport type {\n BlurProps,\n ContentProps,\n DarkOverlayProps,\n HeroSceneProps,\n ParallaxProps,\n PatternProps,\n VignetteProps,\n} from './types'\nimport { buildBlurMask, buildVignetteGradient } from './utils'\nimport { useInViewport } from './use-in-viewport'\nimport { useReducedMotion } from './use-reduced-motion'\n\n/**\n * Injected once — handles dark mode via `.dark` ancestor class.\n * Uses data-attributes so no Tailwind scanning is needed.\n */\nconst DARK_STYLES = `\n[data-hs-dark-only]{display:none}\n[data-hs-light-only]{display:block}\n[data-hs-img]{filter:none}\n.dark [data-hs-dark-only]{display:block}\n.dark [data-hs-light-only]{display:none}\n.dark [data-hs-img]{filter:grayscale(1)}\n`\n\n// ─── Root Component ──────────────────────────────────────────\n\nfunction HeroSceneRoot({\n images,\n initialIndex = 0,\n interval = 30_000,\n transitionDuration = 700,\n className,\n onIndexChange,\n children,\n}: HeroSceneProps) {\n const [index, setIndex] = useState(initialIndex)\n const rootRef = useRef<HTMLDivElement>(null)\n const reducedMotion = useReducedMotion()\n const isInViewport = useInViewport(rootRef)\n\n // ── Image rotation ──\n useEffect(() => {\n if (interval <= 0 || images.length <= 1) return\n\n let current = initialIndex\n const id = setInterval(() => {\n const next = (current + 1) % images.length\n current = next\n setIndex(next)\n setTimeout(() => {\n onIndexChange?.(next)\n globalThis.dispatchEvent(\n new CustomEvent('hero-scene-index-change', { detail: next }),\n )\n }, 0)\n }, interval)\n return () => clearInterval(id)\n }, [initialIndex, interval, images.length, onIndexChange])\n\n const activeColor = images[index]?.color ?? '128, 128, 128'\n\n return (\n <HeroSceneContext.Provider\n value={{\n images,\n index,\n transitionDuration,\n reducedMotion,\n isInViewport,\n containerRef: rootRef,\n }}\n >\n <style dangerouslySetInnerHTML={{ __html: DARK_STYLES }} />\n <div\n ref={rootRef}\n className={className}\n style={{ position: 'relative', overflow: 'hidden' }}\n >\n {/* ── Background images ── */}\n <div\n data-hero-images=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: '-15%',\n backgroundColor: `rgb(${activeColor})`,\n transition: reducedMotion ? 'none' : 'background-color 1s ease',\n }}\n >\n {images.map((img, i) => (\n <Image\n key={img.src}\n src={img.src}\n alt=\"\"\n width={1920}\n height={1080}\n priority={i === initialIndex}\n sizes=\"100vw\"\n data-hs-img=\"\"\n style={{\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit: 'cover',\n opacity: i === index ? 1 : 0,\n transition: reducedMotion\n ? 'none'\n : `opacity ${transitionDuration}ms ease`,\n }}\n />\n ))}\n </div>\n\n {children}\n </div>\n </HeroSceneContext.Provider>\n )\n}\n\n// ─── Parallax ────────────────────────────────────────────────\n\nfunction Parallax({\n speed = 0.4,\n mouseShiftX = 25,\n mouseShiftY = 15,\n mouseLerp = 0.04,\n}: ParallaxProps) {\n const { reducedMotion, isInViewport, containerRef } = useHeroScene()\n\n const scrollY = useRef(0)\n const targetMouseX = useRef(0)\n const targetMouseY = useRef(0)\n const currentMouseX = useRef(0)\n const currentMouseY = useRef(0)\n\n useEffect(() => {\n if (reducedMotion) return\n\n const root = containerRef.current\n if (!root) return\n\n const imagesEl = root.querySelector<HTMLDivElement>('[data-hero-images]')\n if (!imagesEl) return\n\n let rafId: number\n let running = true\n\n function loop() {\n if (!running) return\n\n currentMouseX.current +=\n (targetMouseX.current - currentMouseX.current) * mouseLerp\n currentMouseY.current +=\n (targetMouseY.current - currentMouseY.current) * mouseLerp\n\n const y = scrollY.current * speed\n const mx = currentMouseX.current * mouseShiftX\n const my = currentMouseY.current * mouseShiftY\n imagesEl!.style.transform = `translate3d(${mx}px, ${y + my}px, 0)`\n\n rafId = requestAnimationFrame(loop)\n }\n\n function onScroll() {\n if (!isInViewport) return\n scrollY.current = globalThis.scrollY\n }\n\n function onMouseMove(e: MouseEvent) {\n if (!isInViewport) return\n targetMouseX.current = (e.clientX / globalThis.innerWidth - 0.5) * 2\n targetMouseY.current = (e.clientY / globalThis.innerHeight - 0.5) * 2\n }\n\n globalThis.addEventListener('scroll', onScroll, { passive: true })\n globalThis.addEventListener('mousemove', onMouseMove, { passive: true })\n rafId = requestAnimationFrame(loop)\n\n return () => {\n running = false\n globalThis.removeEventListener('scroll', onScroll)\n globalThis.removeEventListener('mousemove', onMouseMove)\n cancelAnimationFrame(rafId)\n }\n }, [\n reducedMotion,\n isInViewport,\n containerRef,\n speed,\n mouseShiftX,\n mouseShiftY,\n mouseLerp,\n ])\n\n return null\n}\n\n// ─── Vignette ────────────────────────────────────────────────\n\nfunction Vignette({\n centerX = 50,\n centerY = 100,\n shape = 'circle',\n stops,\n transitionDuration = 1000,\n}: VignetteProps) {\n const { images, index, reducedMotion } = useHeroScene()\n const transitionMs = reducedMotion ? '0ms' : `${transitionDuration}ms`\n\n return (\n <>\n {/* Light mode — color-matched vignettes with crossfade */}\n {images.map((img, i) => (\n <div\n key={`vignette-light-${img.color}`}\n data-hs-light-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n opacity: i === index ? 1 : 0,\n transition: `opacity ${transitionMs} ease`,\n background: buildVignetteGradient(img.color, {\n centerX,\n centerY,\n shape,\n stops,\n }),\n }}\n />\n ))}\n {/* Dark mode — black vignette */}\n <div\n data-hs-dark-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n background: buildVignetteGradient('0, 0, 0', {\n centerX,\n centerY,\n shape,\n stops,\n }),\n }}\n />\n </>\n )\n}\n\n// ─── Blur ────────────────────────────────────────────────────\n\nfunction Blur({\n amount = 24,\n centerX = 50,\n centerY = 65,\n innerRadius = 15,\n outerRadius = 55,\n}: BlurProps) {\n useHeroScene()\n\n const mask = buildBlurMask({ centerX, centerY, innerRadius, outerRadius })\n\n return (\n <div\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 15,\n backdropFilter: `blur(${amount}px)`,\n WebkitBackdropFilter: `blur(${amount}px)`,\n maskImage: mask,\n WebkitMaskImage: mask,\n pointerEvents: 'none',\n }}\n />\n )\n}\n\n// ─── Pattern ─────────────────────────────────────────────────\n\nfunction Pattern({\n dotSize = 1,\n spacing = 20,\n lightColor = 'rgba(0 0 0 / 0.15)',\n darkColor = 'rgba(255 255 255 / 0.1)',\n}: PatternProps) {\n useHeroScene()\n\n const bgImage = (color: string) =>\n `radial-gradient(circle, ${color} ${dotSize}px, transparent ${dotSize}px)`\n const bgSize = `${spacing}px ${spacing}px`\n\n return (\n <>\n <div\n data-hs-light-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n backgroundImage: bgImage(lightColor),\n backgroundSize: bgSize,\n }}\n />\n <div\n data-hs-dark-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 20,\n pointerEvents: 'none',\n backgroundImage: bgImage(darkColor),\n backgroundSize: bgSize,\n }}\n />\n </>\n )\n}\n\n// ─── DarkOverlay ─────────────────────────────────────────────\n\nfunction DarkOverlay({ opacity = 0.4 }: DarkOverlayProps) {\n useHeroScene()\n\n return (\n <div\n data-hs-dark-only=\"\"\n aria-hidden=\"true\"\n style={{\n position: 'absolute',\n inset: 0,\n zIndex: 10,\n pointerEvents: 'none',\n backgroundColor: `rgba(0 0 0 / ${opacity})`,\n }}\n />\n )\n}\n\n// ─── Content ─────────────────────────────────────────────────\n\nfunction Content({ className, children }: ContentProps) {\n useHeroScene()\n\n return (\n <div\n className={className}\n style={{ position: 'relative', zIndex: 30 }}\n >\n {children}\n </div>\n )\n}\n\n// ─── Exports ────────────────────────────────────────────────\n\nexport {\n HeroSceneRoot as HeroScene,\n Parallax as HeroParallax,\n Vignette as HeroVignette,\n Blur as HeroBlur,\n Pattern as HeroPattern,\n DarkOverlay as HeroDarkOverlay,\n Content as HeroContent,\n}\n","'use client'\n\nimport { createContext, useContext } from 'react'\nimport type { HeroSceneContextValue } from './types'\n\nexport const HeroSceneContext = createContext<HeroSceneContextValue | null>(null)\n\nexport function useHeroScene(): HeroSceneContextValue {\n const ctx = useContext(HeroSceneContext)\n if (!ctx) {\n throw new Error(\n 'HeroScene compound components (Parallax, Vignette, Blur, Pattern, DarkOverlay, Content) ' +\n 'must be rendered inside a <HeroScene> parent.',\n )\n }\n return ctx\n}\n","import type { VignetteProps, BlurProps } from './types'\n\nconst DEFAULT_STOPS: [number, number][] = [\n [0, 0],\n [15, 0.08],\n [30, 0.15],\n [45, 0.25],\n [60, 0.35],\n [75, 0.45],\n [88, 0.55],\n [100, 0.6],\n]\n\nexport function buildVignetteGradient(\n color: string,\n config: Pick<VignetteProps, 'shape' | 'centerX' | 'centerY' | 'stops'>,\n): string {\n const shape = config.shape ?? 'circle'\n const cx = config.centerX ?? 50\n const cy = config.centerY ?? 100\n const stops = config.stops ?? DEFAULT_STOPS\n\n const gradientStops = stops\n .map(([pos, opacity]) =>\n opacity === 0\n ? `transparent ${pos}%`\n : `color-mix(in srgb, rgb(${color}) ${Math.round(opacity * 100)}%, transparent) ${pos}%`,\n )\n .join(', ')\n\n return `radial-gradient(${shape} at ${cx}% ${cy}%, ${gradientStops})`\n}\n\nexport function buildBlurMask(\n config: Pick<BlurProps, 'centerX' | 'centerY' | 'innerRadius' | 'outerRadius'>,\n): string {\n const cx = config.centerX ?? 50\n const cy = config.centerY ?? 65\n const inner = config.innerRadius ?? 15\n const outer = config.outerRadius ?? 55\n\n return `radial-gradient(circle at ${cx}% ${cy}%, transparent ${inner}%, black ${outer}%)`\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\nimport type { RefObject } from 'react'\n\n/**\n * Returns true when the referenced element is at least partially visible\n * in the viewport, using IntersectionObserver. SSR-safe — defaults to true\n * so effects run immediately on first paint before the observer fires.\n */\nexport function useInViewport(ref: RefObject<HTMLElement | null>): boolean {\n const [inViewport, setInViewport] = useState(true)\n\n useEffect(() => {\n const el = ref.current\n if (!el) return\n\n const observer = new IntersectionObserver(\n ([entry]) => {\n setInViewport(entry.isIntersecting)\n },\n { threshold: 0 },\n )\n\n observer.observe(el)\n return () => observer.disconnect()\n }, [ref])\n\n return inViewport\n}\n","'use client'\n\nimport { useEffect, useState } from 'react'\n\n/**\n * Returns true when the user has enabled \"prefers-reduced-motion: reduce\"\n * in their OS or browser settings. SSR-safe — defaults to false.\n */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false)\n\n useEffect(() => {\n const mql = globalThis.matchMedia('(prefers-reduced-motion: reduce)')\n setReduced(mql.matches)\n\n function onChange(e: MediaQueryListEvent) {\n setReduced(e.matches)\n }\n\n mql.addEventListener('change', onChange)\n return () => mql.removeEventListener('change', onChange)\n }, [])\n\n return reduced\n}\n","export {\n HeroScene,\n HeroParallax,\n HeroVignette,\n HeroBlur,\n HeroPattern,\n HeroDarkOverlay,\n HeroContent,\n} from './hero-scene'\n\n/** Event name dispatched on `globalThis` when the active image changes. `event.detail` is the new index. */\nexport const HERO_SCENE_INDEX_EVENT = 'hero-scene-index-change'\nexport { buildVignetteGradient, buildBlurMask } from './utils'\nexport { useReducedMotion } from './use-reduced-motion'\n\nexport type {\n HeroSceneProps,\n HeroImage,\n ParallaxProps,\n VignetteProps,\n BlurProps,\n PatternProps,\n DarkOverlayProps,\n ContentProps,\n} from './types'\n"],"mappings":"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","names":["useEffect","useState","useEffect","useState","useState","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@okinoxis/hero-scene",
3
- "version": "0.4.2",
3
+ "version": "0.5.1",
4
4
  "description": "Cinematic hero sections for Next.js — compound component API with image rotation, parallax, vignette overlays, blur masks, and dot patterns.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",