@refraction-ui/astro 0.5.0 → 0.6.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.
Files changed (38) hide show
  1. package/dist/analytics/analytics-manager.ts +361 -0
  2. package/dist/analytics/consent.ts +40 -0
  3. package/dist/analytics/console-sink.ts +37 -0
  4. package/dist/analytics/http-sink.ts +213 -0
  5. package/dist/analytics/identity.ts +64 -0
  6. package/dist/analytics/index.ts +60 -0
  7. package/dist/analytics/mock-sink.ts +62 -0
  8. package/dist/analytics/noop.ts +48 -0
  9. package/dist/analytics/redaction.ts +93 -0
  10. package/dist/analytics/session.ts +162 -0
  11. package/dist/analytics/storage.ts +119 -0
  12. package/dist/analytics/types.ts +273 -0
  13. package/dist/analytics/uuid.ts +63 -0
  14. package/dist/astro-analytics/AnalyticsScript.astro +130 -0
  15. package/dist/astro-analytics/index.ts +49 -0
  16. package/dist/astro-logger/TelemetryScript.astro +108 -0
  17. package/dist/astro-logger/index.ts +48 -0
  18. package/dist/astro-logger/library-error-capture.ts +121 -0
  19. package/dist/astro-logger/telemetry-middleware.ts +92 -0
  20. package/dist/astro-waveform/Waveform.astro +8 -2
  21. package/dist/index.ts +2 -0
  22. package/dist/logger/console-sink.ts +64 -0
  23. package/dist/logger/faro-engine.ts +130 -0
  24. package/dist/logger/index.ts +39 -0
  25. package/dist/logger/mock-sink.ts +38 -0
  26. package/dist/logger/noop.ts +52 -0
  27. package/dist/logger/presets.ts +51 -0
  28. package/dist/logger/redact.ts +33 -0
  29. package/dist/logger/telemetry-manager.ts +229 -0
  30. package/dist/logger/types.ts +121 -0
  31. package/dist/shared/dev-feedback.ts +154 -0
  32. package/dist/shared/index.ts +25 -0
  33. package/dist/shared/library-origin-error.ts +240 -0
  34. package/dist/voice-pill/voice-pill.styles.ts +14 -13
  35. package/dist/voice-pill/voice-pill.ts +3 -2
  36. package/dist/waveform/waveform-renderer.ts +13 -6
  37. package/dist/waveform/waveform.ts +15 -2
  38. package/package.json +4 -2
@@ -0,0 +1,240 @@
1
+ /**
2
+ * library-origin-error — builds the **library-origin error envelope** for
3
+ * errors that originate inside a `@refraction-ui/*` package.
4
+ *
5
+ * The envelope is expressed THROUGH the existing telemetry record contract
6
+ * (`@refraction-ui/logger`'s `LogRecord`) — the contract is reused, NOT
7
+ * redefined. To keep `@refraction-ui/shared` zero-dependency, the record is
8
+ * mirrored structurally (see {@link DevFeedbackRecord}) rather than imported;
9
+ * a consumer wiring the real logger sink satisfies it structurally.
10
+ *
11
+ * Aggressive redaction (epic #247 guardrails): the payload is package,
12
+ * componentName, version, a normalized stack **fingerprint hash**, and
13
+ * framework only. Never app state, props values, user data, PII, or full app
14
+ * stack frames.
15
+ */
16
+
17
+ import type { DevFeedbackRecord, DevFeedbackSink } from './dev-feedback.js'
18
+
19
+ /** Matches any stack frame that references a `@refraction-ui/*` package. */
20
+ const REFRACTION_FRAME = /@refraction-ui\//
21
+
22
+ /** Frameworks a refraction-ui adapter can run under. */
23
+ export type LibraryFramework =
24
+ | 'react'
25
+ | 'angular'
26
+ | 'astro'
27
+ | 'vue'
28
+ | 'svelte'
29
+ | 'vanilla'
30
+ | 'unknown'
31
+
32
+ /** Inputs describing where a library-origin error came from. */
33
+ export interface LibraryOriginErrorInput {
34
+ /** Originating package, e.g. `@refraction-ui/react`. */
35
+ package: string
36
+ /** refraction-ui component the error originated in, e.g. `Dialog`. */
37
+ componentName: string
38
+ /** Originating package version, e.g. `0.1.5`. */
39
+ version: string
40
+ /** Host framework the component was running under. */
41
+ framework: LibraryFramework
42
+ /** The thrown error (or its stack string). Used ONLY to derive a hash. */
43
+ error?: unknown
44
+ }
45
+
46
+ /**
47
+ * The redacted, library-origin payload carried in `LogRecord.context`. Contains
48
+ * NO app data — only the five identifying fields plus a stack fingerprint hash.
49
+ */
50
+ export interface LibraryOriginEnvelope {
51
+ /** Marks this record as a library-origin error (for downstream routing). */
52
+ origin: 'refraction-ui'
53
+ package: string
54
+ componentName: string
55
+ version: string
56
+ framework: LibraryFramework
57
+ /** Stable, app-data-free hash of the normalized stack. */
58
+ fingerprint: string
59
+ }
60
+
61
+ /**
62
+ * Normalize a stack trace into a deterministic, app-data-free string before
63
+ * hashing:
64
+ * - keep only frames that reference a `@refraction-ui/*` package (drop the
65
+ * app's own frames — library-origin only),
66
+ * - strip absolute paths, line/column numbers, query strings, and hashes,
67
+ * - sort-free (preserve call order) but whitespace-collapsed.
68
+ *
69
+ * If no refraction-ui frames are present the function returns an empty string,
70
+ * which yields a stable "no-frames" fingerprint rather than leaking app frames.
71
+ */
72
+ function normalizeStack(stack: string): string {
73
+ const frames = stack
74
+ .split('\n')
75
+ .map((line) => line.trim())
76
+ .filter((line) => line.startsWith('at ') || line.includes('@'))
77
+ .filter((line) => REFRACTION_FRAME.test(line))
78
+ .map((line) =>
79
+ line
80
+ // drop everything from the path/url onward, keep the symbol
81
+ .replace(/\((?:.*?)(@refraction-ui\/[^):]+)[^)]*\)/, '($1)')
82
+ .replace(/(@refraction-ui\/[^\s:?#]+)[^\s)]*/g, '$1')
83
+ // strip :line:col
84
+ .replace(/:\d+:\d+/g, '')
85
+ // strip line:col without colon prefix
86
+ .replace(/:\d+\b/g, '')
87
+ // strip query/hash
88
+ .replace(/[?#][^\s)]*/g, '')
89
+ .replace(/\s+/g, ' ')
90
+ .trim(),
91
+ )
92
+
93
+ return frames.join('\n')
94
+ }
95
+
96
+ /**
97
+ * Deterministic, dependency-free 53-bit string hash (cyrb53). Stable across
98
+ * runs and processes for the same input — required so the intake can dedupe by
99
+ * fingerprint. NOT cryptographic; it only needs to be collision-resistant
100
+ * enough to group identical normalized stacks.
101
+ */
102
+ function cyrb53(str: string, seed = 0): string {
103
+ let h1 = 0xdeadbeef ^ seed
104
+ let h2 = 0x41c6ce57 ^ seed
105
+ for (let i = 0; i < str.length; i++) {
106
+ const ch = str.charCodeAt(i)
107
+ h1 = Math.imul(h1 ^ ch, 2654435761)
108
+ h2 = Math.imul(h2 ^ ch, 1597334677)
109
+ }
110
+ h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507)
111
+ h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909)
112
+ h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507)
113
+ h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909)
114
+ const out = 4294967296 * (2097151 & h2) + (h1 >>> 0)
115
+ return out.toString(16).padStart(14, '0')
116
+ }
117
+
118
+ function stackOf(error: unknown): string {
119
+ if (typeof error === 'string') return error
120
+ if (
121
+ error &&
122
+ typeof error === 'object' &&
123
+ 'stack' in error &&
124
+ typeof (error as { stack?: unknown }).stack === 'string'
125
+ ) {
126
+ return (error as { stack: string }).stack
127
+ }
128
+ return ''
129
+ }
130
+
131
+ /**
132
+ * Compute the stable stack fingerprint for a library-origin error. Exposed
133
+ * separately so capture seams / tests can assert fingerprint stability without
134
+ * building the whole record.
135
+ */
136
+ export function stackFingerprint(error: unknown): string {
137
+ const normalized = normalizeStack(stackOf(error))
138
+ return cyrb53(normalized)
139
+ }
140
+
141
+ /**
142
+ * Build the redacted library-origin envelope for an error.
143
+ */
144
+ export function libraryOriginEnvelope(
145
+ input: LibraryOriginErrorInput,
146
+ ): LibraryOriginEnvelope {
147
+ return {
148
+ origin: 'refraction-ui',
149
+ package: input.package,
150
+ componentName: input.componentName,
151
+ version: input.version,
152
+ framework: input.framework,
153
+ fingerprint: stackFingerprint(input.error),
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Build the full library-origin error record, expressed THROUGH the existing
159
+ * telemetry record contract (`LogRecord`, mirrored as {@link DevFeedbackRecord}
160
+ * to avoid a hard logger dependency). The redacted envelope rides in
161
+ * `context`; no app data, props, PII, or app stack frames are included.
162
+ *
163
+ * This record is exactly what a consumer-wired telemetry sink (or the
164
+ * `devWarn`/`devError` injected sink) consumes.
165
+ */
166
+ export function libraryOriginError(
167
+ input: LibraryOriginErrorInput,
168
+ ): DevFeedbackRecord {
169
+ const envelope = libraryOriginEnvelope(input)
170
+ return {
171
+ level: 'error',
172
+ message: `${input.package}/${input.componentName}: library-origin error`,
173
+ timestamp: Date.now(),
174
+ context: { ...envelope },
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Library-origin predicate — `true` iff the error's stack contains at least
180
+ * one frame that references a `@refraction-ui/*` package. This is the gate
181
+ * every per-framework capture seam uses: only library-origin errors are
182
+ * eligible; the app's own errors return `false` and pass through untouched.
183
+ *
184
+ * Mirrors exactly the frame-detection rule {@link stackFingerprint} normalizes
185
+ * by — no new contract, just the companion predicate the seams need. Never
186
+ * throws for a missing/odd error (returns `false`).
187
+ */
188
+ export function isLibraryOriginError(error: unknown): boolean {
189
+ const stack = stackOf(error)
190
+ if (!stack) return false
191
+ return REFRACTION_FRAME.test(stack)
192
+ }
193
+
194
+ /**
195
+ * The fixed identity of the refraction-ui package/component a seam tags an
196
+ * error with. The seam supplies this; the stack itself supplies the
197
+ * fingerprint. Never carries app data.
198
+ */
199
+ export type LibraryOriginIdentity = Pick<
200
+ LibraryOriginErrorInput,
201
+ 'package' | 'componentName' | 'version' | 'framework'
202
+ >
203
+
204
+ /**
205
+ * The single capture primitive shared by every per-framework seam (React error
206
+ * boundary, Angular `ErrorHandler`, Astro middleware). It performs the entire
207
+ * guarded flow in one place so the seams stay thin and identical in behavior:
208
+ *
209
+ * 1. **Library-origin filter** — if the error's stack has no
210
+ * `@refraction-ui/*` frame it is the app's own error: return `null` and do
211
+ * NOT touch, capture, or forward it.
212
+ * 2. **Tag** — build the redacted {@link libraryOriginError} record
213
+ * (package/componentName/version/framework + app-data-free fingerprint).
214
+ * 3. **Route to the optional sink** — forward the record to the
215
+ * consumer-injected sink ONLY if one was wired; a broken sink can never
216
+ * break the consumer app. With no sink this is a no-op beyond returning
217
+ * the record (so a seam can still surface it locally).
218
+ *
219
+ * @returns the tagged record for a library-origin error, or `null` when the
220
+ * error is app-origin (the "pass through untouched" signal).
221
+ */
222
+ export function captureLibraryOriginError(
223
+ error: unknown,
224
+ identity: LibraryOriginIdentity,
225
+ sink: DevFeedbackSink | null | undefined,
226
+ ): DevFeedbackRecord | null {
227
+ if (!isLibraryOriginError(error)) return null
228
+
229
+ const record = libraryOriginError({ ...identity, error })
230
+
231
+ if (sink) {
232
+ try {
233
+ sink.log(record)
234
+ } catch {
235
+ // A broken sink must never break the consumer app.
236
+ }
237
+ }
238
+
239
+ return record
240
+ }
@@ -22,33 +22,34 @@ export const voicePillSpeakerStyles =
22
22
  '[--rfr-voice-pill-accent:hsl(var(--primary))] [--rfr-voice-pill-accent-foreground:hsl(var(--primary-foreground))] data-[speaker=user]:[--rfr-voice-pill-accent:hsl(var(--accent-foreground))] data-[speaker=user]:[--rfr-voice-pill-accent-foreground:hsl(var(--accent))] data-[muted=true]:[--rfr-voice-pill-accent:hsl(var(--muted-foreground))]'
23
23
 
24
24
  export const voicePillPositionVariants = cva({
25
- base: 'fixed z-50',
25
+ base: 'z-50',
26
26
  variants: {
27
27
  position: {
28
+ inline: 'relative',
28
29
  'top-start':
29
- 'top-[calc(env(safe-area-inset-top)+1rem)] left-[calc(env(safe-area-inset-left)+1rem)]',
30
+ 'fixed top-[calc(env(safe-area-inset-top)+1rem)] left-[calc(env(safe-area-inset-left)+1rem)]',
30
31
  'top-center':
31
- 'top-[calc(env(safe-area-inset-top)+1rem)] left-1/2 -translate-x-1/2',
32
+ 'fixed top-[calc(env(safe-area-inset-top)+1rem)] left-1/2 -translate-x-1/2',
32
33
  'top-end':
33
- 'top-[calc(env(safe-area-inset-top)+1rem)] right-[calc(env(safe-area-inset-right)+1rem)]',
34
+ 'fixed top-[calc(env(safe-area-inset-top)+1rem)] right-[calc(env(safe-area-inset-right)+1rem)]',
34
35
  'bottom-start':
35
- 'bottom-[calc(env(safe-area-inset-bottom)+1rem)] left-[calc(env(safe-area-inset-left)+1rem)]',
36
+ 'fixed bottom-[calc(env(safe-area-inset-bottom)+1rem)] left-[calc(env(safe-area-inset-left)+1rem)]',
36
37
  'bottom-center':
37
- 'bottom-[calc(env(safe-area-inset-bottom)+1rem)] left-1/2 -translate-x-1/2',
38
+ 'fixed bottom-[calc(env(safe-area-inset-bottom)+1rem)] left-1/2 -translate-x-1/2',
38
39
  'bottom-end':
39
- 'bottom-[calc(env(safe-area-inset-bottom)+1rem)] right-[calc(env(safe-area-inset-right)+1rem)]',
40
+ 'fixed bottom-[calc(env(safe-area-inset-bottom)+1rem)] right-[calc(env(safe-area-inset-right)+1rem)]',
40
41
  'left-start':
41
- 'left-[calc(env(safe-area-inset-left)+1rem)] top-[calc(env(safe-area-inset-top)+1rem)]',
42
+ 'fixed left-[calc(env(safe-area-inset-left)+1rem)] top-[calc(env(safe-area-inset-top)+1rem)]',
42
43
  'left-center':
43
- 'left-[calc(env(safe-area-inset-left)+1rem)] top-1/2 -translate-y-1/2',
44
+ 'fixed left-[calc(env(safe-area-inset-left)+1rem)] top-1/2 -translate-y-1/2',
44
45
  'left-end':
45
- 'left-[calc(env(safe-area-inset-left)+1rem)] bottom-[calc(env(safe-area-inset-bottom)+1rem)]',
46
+ 'fixed left-[calc(env(safe-area-inset-left)+1rem)] bottom-[calc(env(safe-area-inset-bottom)+1rem)]',
46
47
  'right-start':
47
- 'right-[calc(env(safe-area-inset-right)+1rem)] top-[calc(env(safe-area-inset-top)+1rem)]',
48
+ 'fixed right-[calc(env(safe-area-inset-right)+1rem)] top-[calc(env(safe-area-inset-top)+1rem)]',
48
49
  'right-center':
49
- 'right-[calc(env(safe-area-inset-right)+1rem)] top-1/2 -translate-y-1/2',
50
+ 'fixed right-[calc(env(safe-area-inset-right)+1rem)] top-1/2 -translate-y-1/2',
50
51
  'right-end':
51
- 'right-[calc(env(safe-area-inset-right)+1rem)] bottom-[calc(env(safe-area-inset-bottom)+1rem)]',
52
+ 'fixed right-[calc(env(safe-area-inset-right)+1rem)] bottom-[calc(env(safe-area-inset-bottom)+1rem)]',
52
53
  },
53
54
  },
54
55
  defaultVariants: {
@@ -3,6 +3,7 @@ import type { AccessibilityProps } from '../shared/index.ts'
3
3
  export type VoicePillSpeaker = 'ai' | 'user' | (string & {})
4
4
 
5
5
  export type VoicePillPosition =
6
+ | 'inline'
6
7
  | 'top-start'
7
8
  | 'top-center'
8
9
  | 'top-end'
@@ -29,7 +30,7 @@ export interface VoicePillProps {
29
30
  muted?: boolean
30
31
  /** Callback for the optional mute toggle */
31
32
  onToggleMute?: () => void
32
- /** Fixed viewport position */
33
+ /** Viewport position, or inline for embedding inside another layout */
33
34
  position?: VoicePillPosition
34
35
  }
35
36
 
@@ -52,7 +53,7 @@ export interface VoicePillAPI {
52
53
  visualIntensity: number
53
54
  /** Muted state */
54
55
  muted: boolean
55
- /** Fixed viewport position */
56
+ /** Viewport position, or inline for embedding inside another layout */
56
57
  position: VoicePillPosition
57
58
  /** Fallback initials for the speaker avatar */
58
59
  initials: string
@@ -1,7 +1,9 @@
1
1
  import {
2
2
  DEFAULT_WAVEFORM_COLOR,
3
+ DEFAULT_WAVEFORM_AMPLITUDE,
3
4
  DEFAULT_WAVEFORM_INTENSITY,
4
5
  getWaveformPeak,
6
+ normalizeAmplitude,
5
7
  normalizeBarCount,
6
8
  normalizeIntensity,
7
9
  resampleWaveformSamples,
@@ -20,6 +22,7 @@ export interface WaveformRenderOptions {
20
22
  variant?: WaveformVariant
21
23
  color?: string
22
24
  intensity?: number
25
+ amplitude?: number
23
26
  barCount?: number
24
27
  }
25
28
 
@@ -60,22 +63,23 @@ export function drawWaveform(
60
63
  const variant = options.variant ?? 'bars'
61
64
  const color = options.color ?? DEFAULT_WAVEFORM_COLOR
62
65
  const intensity = normalizeIntensity(options.intensity ?? DEFAULT_WAVEFORM_INTENSITY)
66
+ const amplitude = normalizeAmplitude(options.amplitude ?? DEFAULT_WAVEFORM_AMPLITUDE)
63
67
  const barCount = normalizeBarCount(options.barCount)
64
68
 
65
69
  context.clearRect(0, 0, width, height)
66
70
  applyCanvasColor(context, color)
67
71
 
68
72
  if (variant === 'line') {
69
- drawLine(context, samples, width, height, intensity)
73
+ drawLine(context, samples, width, height, intensity, amplitude)
70
74
  return
71
75
  }
72
76
 
73
77
  if (variant === 'rings') {
74
- drawRings(context, samples, width, height, intensity)
78
+ drawRings(context, samples, width, height, intensity, amplitude)
75
79
  return
76
80
  }
77
81
 
78
- drawBars(context, samples, width, height, intensity, barCount)
82
+ drawBars(context, samples, width, height, intensity, amplitude, barCount)
79
83
  }
80
84
 
81
85
  function drawBars(
@@ -84,6 +88,7 @@ function drawBars(
84
88
  width: number,
85
89
  height: number,
86
90
  intensity: number,
91
+ amplitude: number,
87
92
  barCount: number,
88
93
  ) {
89
94
  const bars = scaleWaveformSamples(resampleWaveformSamples(samples, barCount), intensity)
@@ -93,7 +98,7 @@ function drawBars(
93
98
 
94
99
  for (let i = 0; i < bars.length; i += 1) {
95
100
  const value = Math.abs(bars[i])
96
- const barHeight = Math.max(value * height, value > 0 ? 1 : 0)
101
+ const barHeight = Math.max(value * height * amplitude, value > 0 ? 1 : 0)
97
102
  const x = i * slotWidth + (slotWidth - barWidth) / 2
98
103
  const y = center - barHeight / 2
99
104
 
@@ -107,10 +112,11 @@ function drawLine(
107
112
  width: number,
108
113
  height: number,
109
114
  intensity: number,
115
+ amplitudeScale: number,
110
116
  ) {
111
117
  const lineSamples = scaleWaveformSamples(samples, intensity)
112
118
  const center = height / 2
113
- const amplitude = height * 0.45
119
+ const amplitude = height * 0.45 * amplitudeScale
114
120
 
115
121
  context.beginPath()
116
122
 
@@ -137,8 +143,9 @@ function drawRings(
137
143
  width: number,
138
144
  height: number,
139
145
  intensity: number,
146
+ amplitude: number,
140
147
  ) {
141
- const peak = getWaveformPeak(scaleWaveformSamples(samples, intensity))
148
+ const peak = getWaveformPeak(scaleWaveformSamples(samples, intensity)) * amplitude
142
149
  const minDimension = Math.min(width, height)
143
150
  const centerX = width / 2
144
151
  const centerY = height / 2
@@ -8,6 +8,7 @@ export interface WaveformProps {
8
8
  source?: WaveformSource
9
9
  samples?: WaveformSampleInput
10
10
  intensity?: number
11
+ amplitude?: number
11
12
  variant?: WaveformVariant
12
13
  height?: number | string
13
14
  width?: number | string
@@ -20,6 +21,7 @@ export interface WaveformProps {
20
21
  export interface NormalizedWaveformConfig {
21
22
  variant: WaveformVariant
22
23
  intensity: number
24
+ amplitude: number
23
25
  height: number | string
24
26
  width: number | string
25
27
  barCount: number
@@ -40,6 +42,7 @@ export const DEFAULT_WAVEFORM_BAR_COUNT = 48
40
42
  export const DEFAULT_WAVEFORM_COLOR = 'var(--accent-2)'
41
43
  export const DEFAULT_WAVEFORM_HEIGHT = 80
42
44
  export const DEFAULT_WAVEFORM_INTENSITY = 1
45
+ export const DEFAULT_WAVEFORM_AMPLITUDE = 1
43
46
  export const DEFAULT_WAVEFORM_SMOOTHING = 0.8
44
47
 
45
48
  const MAX_BAR_COUNT = 1024
@@ -63,10 +66,17 @@ export function normalizeIntensity(value: number | undefined): number {
63
66
  return Math.min(MAX_INTENSITY, Math.max(0, value))
64
67
  }
65
68
 
69
+ export function normalizeAmplitude(value: number | undefined): number {
70
+ if (value == null || !Number.isFinite(value)) return DEFAULT_WAVEFORM_AMPLITUDE
71
+
72
+ return Math.min(1, Math.max(0, value))
73
+ }
74
+
66
75
  export function normalizeWaveformConfig(props: WaveformProps = {}): NormalizedWaveformConfig {
67
76
  return {
68
77
  variant: props.variant ?? 'bars',
69
78
  intensity: normalizeIntensity(props.intensity),
79
+ amplitude: normalizeAmplitude(props.amplitude),
70
80
  height: props.height ?? DEFAULT_WAVEFORM_HEIGHT,
71
81
  width: props.width ?? '100%',
72
82
  barCount: normalizeBarCount(props.barCount),
@@ -87,6 +97,7 @@ export function createSilentSamples(count = DEFAULT_WAVEFORM_BAR_COUNT): Float32
87
97
  export function createIntensitySamples(
88
98
  intensity = DEFAULT_WAVEFORM_INTENSITY,
89
99
  count = DEFAULT_WAVEFORM_BAR_COUNT,
100
+ phase = 0,
90
101
  ): Float32Array {
91
102
  const normalizedIntensity = normalizeIntensity(intensity)
92
103
  const normalizedCount = normalizeBarCount(count)
@@ -94,11 +105,13 @@ export function createIntensitySamples(
94
105
 
95
106
  if (normalizedIntensity === 0) return samples
96
107
 
108
+ const pulse = 0.72 + Math.sin(phase * 1.8) * 0.18
109
+
97
110
  for (let i = 0; i < normalizedCount; i += 1) {
98
111
  const progress = normalizedCount <= 1 ? 0.5 : i / (normalizedCount - 1)
99
112
  const envelope = 0.35 + Math.sin(progress * Math.PI) * 0.65
100
- const carrier = Math.sin(progress * Math.PI * 6)
101
- samples[i] = carrier * envelope * normalizedIntensity
113
+ const carrier = Math.sin(progress * Math.PI * 6 + phase * 2.8)
114
+ samples[i] = carrier * envelope * normalizedIntensity * pulse
102
115
  }
103
116
 
104
117
  return samples
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@refraction-ui/astro",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "All Refraction UI Astro components in one package",
5
5
  "type": "module",
6
6
  "exports": {
@@ -93,7 +93,9 @@
93
93
  "@refraction-ui/astro-link-card": "workspace:*",
94
94
  "@refraction-ui/astro-card-grid": "workspace:*",
95
95
  "@refraction-ui/astro-payment": "workspace:*",
96
- "@refraction-ui/astro-command-input": "workspace:*"
96
+ "@refraction-ui/astro-command-input": "workspace:*",
97
+ "@refraction-ui/astro-logger": "workspace:*",
98
+ "@refraction-ui/astro-analytics": "workspace:*"
97
99
  },
98
100
  "publishConfig": {
99
101
  "access": "public"