@digilogiclabs/saas-factory-ui 1.26.0 → 1.27.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/dist/index.d.mts CHANGED
@@ -3609,6 +3609,22 @@ declare function useOptimizedMemo<T>(factory: () => T, deps: React.DependencyLis
3609
3609
 
3610
3610
  declare function useLockBody(): void;
3611
3611
 
3612
+ /**
3613
+ * Tracks a CSS media query.
3614
+ *
3615
+ * Always returns `false` on the server and on the client's first
3616
+ * render so SSR markup matches the hydrated DOM — reading
3617
+ * `window.matchMedia` during useState init caused React #418 in
3618
+ * consumers that branched rendered markup on the result (e.g. a
3619
+ * mobile-vs-desktop layout switch) because the server always
3620
+ * rendered `false` while clients matching the query rendered
3621
+ * `true`. The actual value is read in `useEffect` after commit
3622
+ * and kept in sync via a `matchMedia` change listener.
3623
+ *
3624
+ * The trade-off is one extra render on the client before the
3625
+ * correct value applies — acceptable because the correct markup
3626
+ * is an enhancement, not the initial content.
3627
+ */
3612
3628
  declare function useMediaQuery(query: string): boolean;
3613
3629
 
3614
3630
  interface IntersectionObserverOptions {
@@ -3751,10 +3767,19 @@ declare function useDarkMode(forced?: boolean): boolean;
3751
3767
  * Returns `true` when the user has requested reduced motion via
3752
3768
  * `prefers-reduced-motion: reduce`.
3753
3769
  *
3754
- * Reads the media query synchronously during state init so the
3755
- * first render already honors the preference no flash of full
3756
- * motion on first paint. Re-subscribes to changes via `matchMedia`
3757
- * listener. SSR-safe (returns `false` when `window` is undefined).
3770
+ * Always returns `false` on the server and on the client's first
3771
+ * render so that SSR markup and the hydrated DOM match reading
3772
+ * `window.matchMedia` during useState init caused React #418 in
3773
+ * consumers that rendered the value into inline style attributes
3774
+ * (e.g. DynamicHeroBanner's `transition`) because the server
3775
+ * rendered `false` while clients with the preference rendered
3776
+ * `true`. The actual preference is read in `useEffect` after the
3777
+ * initial commit and via a `matchMedia` change listener.
3778
+ *
3779
+ * The trade-off is one extra render/paint before the preference
3780
+ * takes effect — acceptable because reduced-motion is an
3781
+ * accessibility preference about the *next* animation, not a
3782
+ * hard visual flash.
3758
3783
  */
3759
3784
  declare function usePrefersReducedMotion(): boolean;
3760
3785
 
package/dist/index.d.ts CHANGED
@@ -3609,6 +3609,22 @@ declare function useOptimizedMemo<T>(factory: () => T, deps: React.DependencyLis
3609
3609
 
3610
3610
  declare function useLockBody(): void;
3611
3611
 
3612
+ /**
3613
+ * Tracks a CSS media query.
3614
+ *
3615
+ * Always returns `false` on the server and on the client's first
3616
+ * render so SSR markup matches the hydrated DOM — reading
3617
+ * `window.matchMedia` during useState init caused React #418 in
3618
+ * consumers that branched rendered markup on the result (e.g. a
3619
+ * mobile-vs-desktop layout switch) because the server always
3620
+ * rendered `false` while clients matching the query rendered
3621
+ * `true`. The actual value is read in `useEffect` after commit
3622
+ * and kept in sync via a `matchMedia` change listener.
3623
+ *
3624
+ * The trade-off is one extra render on the client before the
3625
+ * correct value applies — acceptable because the correct markup
3626
+ * is an enhancement, not the initial content.
3627
+ */
3612
3628
  declare function useMediaQuery(query: string): boolean;
3613
3629
 
3614
3630
  interface IntersectionObserverOptions {
@@ -3751,10 +3767,19 @@ declare function useDarkMode(forced?: boolean): boolean;
3751
3767
  * Returns `true` when the user has requested reduced motion via
3752
3768
  * `prefers-reduced-motion: reduce`.
3753
3769
  *
3754
- * Reads the media query synchronously during state init so the
3755
- * first render already honors the preference no flash of full
3756
- * motion on first paint. Re-subscribes to changes via `matchMedia`
3757
- * listener. SSR-safe (returns `false` when `window` is undefined).
3770
+ * Always returns `false` on the server and on the client's first
3771
+ * render so that SSR markup and the hydrated DOM match reading
3772
+ * `window.matchMedia` during useState init caused React #418 in
3773
+ * consumers that rendered the value into inline style attributes
3774
+ * (e.g. DynamicHeroBanner's `transition`) because the server
3775
+ * rendered `false` while clients with the preference rendered
3776
+ * `true`. The actual preference is read in `useEffect` after the
3777
+ * initial commit and via a `matchMedia` change listener.
3778
+ *
3779
+ * The trade-off is one extra render/paint before the preference
3780
+ * takes effect — acceptable because reduced-motion is an
3781
+ * accessibility preference about the *next* animation, not a
3782
+ * hard visual flash.
3758
3783
  */
3759
3784
  declare function usePrefersReducedMotion(): boolean;
3760
3785
 
package/dist/index.js CHANGED
@@ -25018,22 +25018,15 @@ function useLockBody() {
25018
25018
  // src/hooks/web/use-media-query.ts
25019
25019
  var import_react50 = require("react");
25020
25020
  function useMediaQuery(query) {
25021
- const [matches, setMatches] = (0, import_react50.useState)(() => {
25022
- if (typeof window !== "undefined") {
25023
- return window.matchMedia(query).matches;
25024
- }
25025
- return false;
25026
- });
25021
+ const [matches, setMatches] = (0, import_react50.useState)(false);
25027
25022
  (0, import_react50.useEffect)(() => {
25028
- if (typeof window === "undefined") return;
25023
+ if (typeof window === "undefined" || !window.matchMedia) return;
25029
25024
  const media = window.matchMedia(query);
25030
- if (media.matches !== matches) {
25031
- setMatches(media.matches);
25032
- }
25025
+ setMatches(media.matches);
25033
25026
  const listener = () => setMatches(media.matches);
25034
25027
  media.addEventListener("change", listener);
25035
25028
  return () => media.removeEventListener("change", listener);
25036
- }, [matches, query]);
25029
+ }, [query]);
25037
25030
  return matches;
25038
25031
  }
25039
25032
 
@@ -25493,13 +25486,11 @@ function useDarkMode(forced) {
25493
25486
  // src/hooks/web/use-prefers-reduced-motion.ts
25494
25487
  var import_react56 = require("react");
25495
25488
  function usePrefersReducedMotion() {
25496
- const [reduced, setReduced] = (0, import_react56.useState)(() => {
25497
- if (typeof window === "undefined" || !window.matchMedia) return false;
25498
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
25499
- });
25489
+ const [reduced, setReduced] = (0, import_react56.useState)(false);
25500
25490
  (0, import_react56.useEffect)(() => {
25501
25491
  if (typeof window === "undefined" || !window.matchMedia) return;
25502
25492
  const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
25493
+ setReduced(mq.matches);
25503
25494
  const read = () => setReduced(mq.matches);
25504
25495
  mq.addEventListener("change", read);
25505
25496
  return () => mq.removeEventListener("change", read);
@@ -34375,9 +34366,21 @@ var TRANSITIONS = {
34375
34366
  }
34376
34367
  };
34377
34368
  var STAT_SIZES = {
34378
- sm: { value: "clamp(18px,2.5vw,26px)", label: "clamp(9px,1vw,11px)", divider: 24 },
34379
- md: { value: "clamp(22px,3.5vw,36px)", label: "clamp(10px,1.2vw,13px)", divider: 32 },
34380
- lg: { value: "clamp(28px,4.5vw,48px)", label: "clamp(11px,1.3vw,14px)", divider: 40 }
34369
+ sm: {
34370
+ value: "clamp(18px,2.5vw,26px)",
34371
+ label: "clamp(9px,1vw,11px)",
34372
+ divider: 24
34373
+ },
34374
+ md: {
34375
+ value: "clamp(22px,3.5vw,36px)",
34376
+ label: "clamp(10px,1.2vw,13px)",
34377
+ divider: 32
34378
+ },
34379
+ lg: {
34380
+ value: "clamp(28px,4.5vw,48px)",
34381
+ label: "clamp(11px,1.3vw,14px)",
34382
+ divider: 40
34383
+ }
34381
34384
  };
34382
34385
  var STAT_BG = {
34383
34386
  glass: {
@@ -34754,9 +34757,7 @@ function DynamicHeroBanner({
34754
34757
  [current, slideCount, goTo]
34755
34758
  );
34756
34759
  const prev = (0, import_react72.useCallback)(
34757
- () => goTo(
34758
- (current - 1 + Math.max(slideCount, 1)) % Math.max(slideCount, 1)
34759
- ),
34760
+ () => goTo((current - 1 + Math.max(slideCount, 1)) % Math.max(slideCount, 1)),
34760
34761
  [current, slideCount, goTo]
34761
34762
  );
34762
34763
  (0, import_react72.useEffect)(() => {
@@ -34841,12 +34842,26 @@ function DynamicHeroBanner({
34841
34842
  overflow: "hidden",
34842
34843
  background: backgroundColor,
34843
34844
  fontFamily: bodyFontFamily,
34845
+ // Expose accent/glow as CSS custom properties so hover rules in the
34846
+ // injected `<style>` block can reference them without prop drilling.
34847
+ ["--dll-hero-accent"]: resolvedTheme.accent,
34848
+ ["--dll-hero-glow"]: resolvedTheme.glow,
34844
34849
  ...style
34845
34850
  },
34846
34851
  children: [
34847
34852
  /* @__PURE__ */ (0, import_jsx_runtime118.jsx)("style", { children: `${loadGoogleFonts ? `@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800;900&family=Space+Mono:wght@400;700&display=swap');` : ""}
34848
34853
  @keyframes dllHeroProgress{from{transform:scaleX(0)}to{transform:scaleX(1)}}
34849
- @media(prefers-reduced-motion:reduce){.dll-hero-banner *{animation-duration:.01ms!important;transition-duration:.01ms!important}}` }),
34854
+ .dll-hero-cta{transition:transform .25s cubic-bezier(.2,.8,.3,1),background-color .25s,box-shadow .25s,color .25s,border-color .25s,filter .25s}
34855
+ .dll-hero-cta:hover{transform:translateY(-2px)}
34856
+ .dll-hero-cta:focus-visible{outline:2px solid var(--dll-hero-accent,#fff);outline-offset:3px}
34857
+ .dll-hero-cta--primary:hover{filter:brightness(1.08);box-shadow:0 10px 32px color-mix(in srgb, var(--dll-hero-glow, #3b82f6) 40%, transparent)}
34858
+ .dll-hero-cta--secondary:hover{background:rgba(255,255,255,0.12)!important;border-color:rgba(255,255,255,0.38)!important;color:#fff!important}
34859
+ .dll-hero-cta--ghost:hover{color:var(--dll-hero-accent,#fff)!important}
34860
+ .dll-hero-dot{transition:all .35s}
34861
+ .dll-hero-dot:hover{background:rgba(255,255,255,0.55)!important;transform:scaleY(1.15)}
34862
+ .dll-hero-arrow{transition:opacity .25s,background-color .25s,transform .25s}
34863
+ .dll-hero-arrow:hover{background:rgba(255,255,255,0.16)!important;transform:translateY(-50%) scale(1.06)}
34864
+ @media(prefers-reduced-motion:reduce){.dll-hero-cta:hover,.dll-hero-dot:hover,.dll-hero-arrow:hover{transform:none!important}}` }),
34850
34865
  slides.map((slide, i) => {
34851
34866
  const isCurrent = i === current;
34852
34867
  const filter = IMAGE_FILTERS[slide.imageFilter ?? imageFilter] ?? IMAGE_FILTERS.cinematic;
@@ -35113,6 +35128,7 @@ function DynamicHeroBanner({
35113
35128
  type: "button",
35114
35129
  onClick: prev,
35115
35130
  "aria-label": "Previous slide",
35131
+ className: "dll-hero-arrow",
35116
35132
  style: {
35117
35133
  position: "absolute",
35118
35134
  top: "50%",
@@ -35144,6 +35160,7 @@ function DynamicHeroBanner({
35144
35160
  type: "button",
35145
35161
  onClick: next,
35146
35162
  "aria-label": "Next slide",
35163
+ className: "dll-hero-arrow",
35147
35164
  style: {
35148
35165
  position: "absolute",
35149
35166
  top: "50%",
@@ -35192,6 +35209,7 @@ function DynamicHeroBanner({
35192
35209
  "aria-selected": i === current,
35193
35210
  "aria-label": `Go to slide ${i + 1}`,
35194
35211
  onClick: () => goTo(i),
35212
+ className: "dll-hero-dot",
35195
35213
  style: {
35196
35214
  width: i === current ? 28 : 10,
35197
35215
  height: 10,
@@ -35199,7 +35217,6 @@ function DynamicHeroBanner({
35199
35217
  border: "none",
35200
35218
  background: i === current ? resolvedTheme.accent : "rgba(255,255,255,0.25)",
35201
35219
  cursor: "pointer",
35202
- transition: "all 0.35s",
35203
35220
  padding: 0
35204
35221
  }
35205
35222
  },
@@ -35250,6 +35267,7 @@ function CtaButton({
35250
35267
  const variant = cta.variant ?? (index === 0 ? "primary" : "secondary");
35251
35268
  const isPrimary = variant === "primary";
35252
35269
  const isGhost = variant === "ghost";
35270
+ const ctaClass = `dll-hero-cta dll-hero-cta--${variant}`;
35253
35271
  const buttonStyle = {
35254
35272
  fontFamily: bodyFont,
35255
35273
  fontSize: "clamp(13px,1.2vw,15px)",
@@ -35263,7 +35281,6 @@ function CtaButton({
35263
35281
  textDecoration: isGhost ? "underline" : "none",
35264
35282
  textUnderlineOffset: isGhost ? 4 : void 0,
35265
35283
  letterSpacing: "-0.01em",
35266
- transition: "all 0.25s",
35267
35284
  boxShadow: isPrimary ? `0 4px 20px ${glow}33` : "none",
35268
35285
  display: "inline-flex",
35269
35286
  alignItems: "center",
@@ -35288,6 +35305,7 @@ function CtaButton({
35288
35305
  href: cta.href,
35289
35306
  onClick: cta.onClick,
35290
35307
  "aria-label": cta.ariaLabel,
35308
+ className: ctaClass,
35291
35309
  style: buttonStyle,
35292
35310
  children: content
35293
35311
  }
@@ -35302,6 +35320,7 @@ function CtaButton({
35302
35320
  target: cta.target,
35303
35321
  rel: cta.rel,
35304
35322
  "aria-label": cta.ariaLabel,
35323
+ className: ctaClass,
35305
35324
  style: buttonStyle,
35306
35325
  children: content
35307
35326
  }
@@ -35313,6 +35332,7 @@ function CtaButton({
35313
35332
  type: "button",
35314
35333
  onClick: cta.onClick,
35315
35334
  "aria-label": cta.ariaLabel,
35335
+ className: ctaClass,
35316
35336
  style: buttonStyle,
35317
35337
  children: content
35318
35338
  }