eidotter 0.41.0 → 0.41.2

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.
@@ -1,7 +1,23 @@
1
- import { useState, useEffect, useRef } from 'react';
1
+ import { useState, useEffect, useLayoutEffect, useRef } from 'react';
2
2
  import { prefersReducedMotion } from '../utils/prefersReducedMotion.js';
3
3
  const DEFAULT_CHARACTERS = '░▒▓█│┤┐└┴┬├─┼';
4
4
  const CYCLES_PER_CHAR = 4;
5
+ // Layout effect on the client so a client-only mount swaps to the scrambled
6
+ // frame before paint (no flash of the final text); plain effect on the server,
7
+ // where neither runs. Either way the effect runs only after mount/hydration.
8
+ const useClientLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
9
+ function scrambleFrame(targetText, characters, resolvedCount) {
10
+ let result = '';
11
+ for (let i = 0; i < targetText.length; i++) {
12
+ if (i < resolvedCount || targetText[i] === ' ') {
13
+ result += targetText[i];
14
+ }
15
+ else {
16
+ result += characters[Math.floor(Math.random() * characters.length)];
17
+ }
18
+ }
19
+ return result;
20
+ }
5
21
  /**
6
22
  * Hook that animates text with a DOS-style character scramble effect.
7
23
  * Characters resolve left-to-right (CRT beam metaphor).
@@ -11,24 +27,14 @@ const CYCLES_PER_CHAR = 4;
11
27
  */
12
28
  export function useTextScramble(targetText, options) {
13
29
  const { speed = 40, characters = DEFAULT_CHARACTERS, enabled = true, delay = 0, } = options ?? {};
14
- // Initialize with scrambled chars (not empty) to avoid a blank frame
15
- const [display, setDisplay] = useState(() => {
16
- if (!enabled || prefersReducedMotion())
17
- return targetText;
18
- let result = '';
19
- for (let i = 0; i < targetText.length; i++) {
20
- if (targetText[i] === ' ') {
21
- result += ' ';
22
- }
23
- else {
24
- result += characters[Math.floor(Math.random() * characters.length)];
25
- }
26
- }
27
- return result;
28
- });
30
+ // SSR-safe (DMNC-1609): the server render and the first client render both
31
+ // output the final text, byte-identical, so hydration never mismatches and
32
+ // static HTML carries real content. Randomness and the reduced-motion check
33
+ // live in the effect below, which only runs on the client after mount.
34
+ const [display, setDisplay] = useState(targetText);
29
35
  const [isScrambling, setIsScrambling] = useState(false);
30
36
  const rafRef = useRef(0);
31
- useEffect(() => {
37
+ useClientLayoutEffect(() => {
32
38
  // Skip animation if disabled or reduced motion
33
39
  if (!enabled || prefersReducedMotion()) {
34
40
  setDisplay(targetText);
@@ -40,6 +46,9 @@ export function useTextScramble(targetText, options) {
40
46
  let cycleCount = 0;
41
47
  let lastTime = 0;
42
48
  let delayRemaining = delay;
49
+ // First scrambled frame, set before paint so the delay period shows
50
+ // glyphs rather than the final text (as the pre-SSR-fix version did).
51
+ setDisplay(scrambleFrame(targetText, characters, 0));
43
52
  setIsScrambling(true);
44
53
  const animate = (time) => {
45
54
  if (!lastTime)
@@ -63,20 +72,7 @@ export function useTextScramble(targetText, options) {
63
72
  setIsScrambling(false);
64
73
  return;
65
74
  }
66
- // Build display string: resolved chars + scrambled chars
67
- let result = '';
68
- for (let i = 0; i < targetText.length; i++) {
69
- if (i < resolvedCount) {
70
- result += targetText[i];
71
- }
72
- else if (targetText[i] === ' ') {
73
- result += ' ';
74
- }
75
- else {
76
- result += characters[Math.floor(Math.random() * characters.length)];
77
- }
78
- }
79
- setDisplay(result);
75
+ setDisplay(scrambleFrame(targetText, characters, resolvedCount));
80
76
  }
81
77
  rafRef.current = requestAnimationFrame(animate);
82
78
  };
package/dist/index.d.ts CHANGED
@@ -127,8 +127,9 @@ export type { LegalPageProps, AddressBlockProps, DatenschutzControllerProps, Dat
127
127
  export { useTextScramble } from './hooks/useTextScramble';
128
128
  export type { UseTextScrambleOptions } from './hooks/useTextScramble';
129
129
  export { isSafeUrl, isSafeHref } from './utils';
130
+ export type { Tone, ValueStatus, Provenance, Band, HealthLabels } from './utils/health';
130
131
  export type { SafeHrefOptions } from './utils';
131
132
  export { ZOOM_LEVELS } from './components/TimelineContainer';
132
133
  export { componentRegistry, projects, getComponentMeta, getComponentsByOrigin, getComponentsByConsumer, getVariantsUsedBy, getVariantConsumerMap, } from './components/registry';
133
134
  export type { ComponentMeta, ProjectId, ProjectInfo, VariantMeta, PlatformId, PlatformMeta, ChangelogEntry, } from './components/registry';
134
- export declare const version = "0.41.0";
135
+ export declare const version = "0.41.2";