@hapticjs/core 0.1.0 → 0.2.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
@@ -34,6 +34,66 @@ var NoopAdapter = class {
34
34
  }
35
35
  };
36
36
 
37
+ // src/adapters/web-vibration.adapter.ts
38
+ var WebVibrationAdapter = class {
39
+ constructor() {
40
+ this.name = "web-vibration";
41
+ this.supported = typeof navigator !== "undefined" && "vibrate" in navigator;
42
+ }
43
+ capabilities() {
44
+ return {
45
+ maxIntensityLevels: 1,
46
+ minDuration: 20,
47
+ maxDuration: 1e4,
48
+ supportsPattern: true,
49
+ supportsIntensity: false,
50
+ dualMotor: false
51
+ };
52
+ }
53
+ async pulse(_intensity, duration) {
54
+ if (!this.supported) return;
55
+ navigator.vibrate(Math.max(duration, 20));
56
+ }
57
+ async playSequence(steps) {
58
+ if (!this.supported || steps.length === 0) return;
59
+ const pattern = [];
60
+ let lastType = null;
61
+ for (const step of steps) {
62
+ if (step.type === "vibrate" && step.intensity > 0.05) {
63
+ const dur = Math.max(step.duration, 20);
64
+ if (lastType === "vibrate") {
65
+ pattern[pattern.length - 1] += dur;
66
+ } else {
67
+ pattern.push(dur);
68
+ }
69
+ lastType = "vibrate";
70
+ } else {
71
+ const dur = Math.max(step.duration, 10);
72
+ if (lastType === "pause") {
73
+ pattern[pattern.length - 1] += dur;
74
+ } else {
75
+ pattern.push(dur);
76
+ }
77
+ lastType = "pause";
78
+ }
79
+ }
80
+ if (pattern.length > 0) {
81
+ if (steps[0]?.type === "pause" || steps[0]?.type === "vibrate" && steps[0]?.intensity <= 0.05) {
82
+ pattern.unshift(0);
83
+ }
84
+ navigator.vibrate(pattern);
85
+ }
86
+ }
87
+ cancel() {
88
+ if (this.supported) {
89
+ navigator.vibrate(0);
90
+ }
91
+ }
92
+ dispose() {
93
+ this.cancel();
94
+ }
95
+ };
96
+
37
97
  // src/utils/scheduling.ts
38
98
  function delay(ms) {
39
99
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -45,49 +105,37 @@ function normalizeIntensity(intensity) {
45
105
  return clamp(intensity, 0, 1);
46
106
  }
47
107
 
48
- // src/adapters/web-vibration.adapter.ts
49
- var WebVibrationAdapter = class {
108
+ // src/adapters/ios-audio.adapter.ts
109
+ var IoSAudioAdapter = class {
50
110
  constructor() {
51
- this.name = "web-vibration";
111
+ this.name = "ios-audio";
112
+ this._audioCtx = null;
113
+ this._activeOscillator = null;
114
+ this._activeGain = null;
52
115
  this._cancelled = false;
53
- this.supported = typeof navigator !== "undefined" && "vibrate" in navigator;
116
+ this.supported = this._detectSupport();
54
117
  }
55
118
  capabilities() {
56
119
  return {
57
- maxIntensityLevels: 1,
58
- // on/off only
59
- minDuration: 10,
60
- maxDuration: 1e4,
120
+ maxIntensityLevels: 100,
121
+ minDuration: 5,
122
+ maxDuration: 5e3,
61
123
  supportsPattern: true,
62
- supportsIntensity: false,
124
+ supportsIntensity: true,
63
125
  dualMotor: false
64
126
  };
65
127
  }
66
- async pulse(_intensity, duration) {
128
+ async pulse(intensity, duration) {
67
129
  if (!this.supported) return;
68
- navigator.vibrate(duration);
130
+ await this._playTone(intensity, duration);
69
131
  }
70
132
  async playSequence(steps) {
71
133
  if (!this.supported || steps.length === 0) return;
72
134
  this._cancelled = false;
73
- const pattern = this._toVibrationPattern(steps);
74
- if (this._canUseNativePattern(steps)) {
75
- navigator.vibrate(pattern);
76
- return;
77
- }
78
135
  for (const step of steps) {
79
136
  if (this._cancelled) break;
80
- if (step.type === "vibrate") {
81
- if (step.intensity > 0.1) {
82
- if (step.intensity < 0.5) {
83
- await this._pwmVibrate(step.duration, step.intensity);
84
- } else {
85
- navigator.vibrate(step.duration);
86
- await delay(step.duration);
87
- }
88
- } else {
89
- await delay(step.duration);
90
- }
137
+ if (step.type === "vibrate" && step.intensity > 0) {
138
+ await this._playTone(step.intensity, step.duration);
91
139
  } else {
92
140
  await delay(step.duration);
93
141
  }
@@ -95,40 +143,82 @@ var WebVibrationAdapter = class {
95
143
  }
96
144
  cancel() {
97
145
  this._cancelled = true;
98
- if (this.supported) {
99
- navigator.vibrate(0);
100
- }
146
+ this._stopOscillator();
101
147
  }
102
148
  dispose() {
103
149
  this.cancel();
104
- }
105
- /** Convert steps to Vibration API pattern array */
106
- _toVibrationPattern(steps) {
107
- const pattern = [];
108
- for (const step of steps) {
109
- pattern.push(step.duration);
150
+ if (this._audioCtx) {
151
+ void this._audioCtx.close();
152
+ this._audioCtx = null;
110
153
  }
111
- return pattern;
112
154
  }
113
- /** Check if all steps can be played with native pattern (no intensity variation) */
114
- _canUseNativePattern(steps) {
115
- return steps.every(
116
- (s) => s.type === "pause" || s.type === "vibrate" && s.intensity >= 0.5
155
+ // ─── Internal ──────────────────────────────────────────────
156
+ _detectSupport() {
157
+ if (typeof window === "undefined") return false;
158
+ const hasAudioContext = typeof AudioContext !== "undefined" || typeof window.webkitAudioContext !== "undefined";
159
+ if (!hasAudioContext) return false;
160
+ const ua = navigator.userAgent;
161
+ const isIOS = /iPad|iPhone|iPod/.test(ua) || navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
162
+ return isIOS;
163
+ }
164
+ /** Get or create the AudioContext, resuming if suspended */
165
+ async _getAudioContext() {
166
+ if (!this._audioCtx) {
167
+ const Ctor = typeof AudioContext !== "undefined" ? AudioContext : window.webkitAudioContext;
168
+ this._audioCtx = new Ctor();
169
+ }
170
+ if (this._audioCtx.state === "suspended") {
171
+ await this._audioCtx.resume();
172
+ }
173
+ return this._audioCtx;
174
+ }
175
+ /** Map intensity (0-1) to frequency in the 20-60 Hz sub-bass range */
176
+ _intensityToFrequency(intensity) {
177
+ return 20 + intensity * 40;
178
+ }
179
+ /** Play a single oscillator tone for the given duration */
180
+ async _playTone(intensity, duration) {
181
+ const ctx = await this._getAudioContext();
182
+ const oscillator = ctx.createOscillator();
183
+ const gainNode = ctx.createGain();
184
+ oscillator.type = "sine";
185
+ oscillator.frequency.setValueAtTime(
186
+ this._intensityToFrequency(intensity),
187
+ ctx.currentTime
117
188
  );
118
- }
119
- /** Simulate lower intensity via pulse-width modulation */
120
- async _pwmVibrate(duration, intensity) {
121
- const cycleTime = 20;
122
- const onTime = Math.round(cycleTime * intensity);
123
- const offTime = cycleTime - onTime;
124
- const cycles = Math.floor(duration / cycleTime);
125
- const pattern = [];
126
- for (let i = 0; i < cycles; i++) {
127
- pattern.push(onTime, offTime);
189
+ gainNode.gain.setValueAtTime(intensity, ctx.currentTime);
190
+ oscillator.connect(gainNode);
191
+ gainNode.connect(ctx.destination);
192
+ this._activeOscillator = oscillator;
193
+ this._activeGain = gainNode;
194
+ oscillator.start(ctx.currentTime);
195
+ oscillator.stop(ctx.currentTime + duration / 1e3);
196
+ await new Promise((resolve) => {
197
+ oscillator.onended = () => {
198
+ oscillator.disconnect();
199
+ gainNode.disconnect();
200
+ this._activeOscillator = null;
201
+ this._activeGain = null;
202
+ resolve();
203
+ };
204
+ });
205
+ }
206
+ /** Immediately stop the active oscillator if any */
207
+ _stopOscillator() {
208
+ if (this._activeOscillator) {
209
+ try {
210
+ this._activeOscillator.stop();
211
+ this._activeOscillator.disconnect();
212
+ } catch {
213
+ }
214
+ this._activeOscillator = null;
128
215
  }
129
- if (pattern.length > 0) {
130
- navigator.vibrate(pattern);
131
- await delay(duration);
216
+ if (this._activeGain) {
217
+ try {
218
+ this._activeGain.disconnect();
219
+ } catch {
220
+ }
221
+ this._activeGain = null;
132
222
  }
133
223
  }
134
224
  };
@@ -162,6 +252,12 @@ function detectAdapter() {
162
252
  if (platform.hasVibrationAPI) {
163
253
  return new WebVibrationAdapter();
164
254
  }
255
+ if (platform.isIOS && platform.isWeb) {
256
+ const iosAdapter = new IoSAudioAdapter();
257
+ if (iosAdapter.supported) {
258
+ return iosAdapter;
259
+ }
260
+ }
165
261
  return new NoopAdapter();
166
262
  }
167
263
 
@@ -196,13 +292,13 @@ var FallbackManager = class {
196
292
  /** Execute fallback feedback for the given steps */
197
293
  async execute(steps) {
198
294
  if (this.config.type === "none") return;
199
- const totalDuration = steps.reduce((sum, s) => sum + s.duration, 0);
295
+ const totalDuration2 = steps.reduce((sum, s) => sum + s.duration, 0);
200
296
  const maxIntensity = Math.max(...steps.filter((s) => s.type === "vibrate").map((s) => s.intensity), 0);
201
297
  if (this.config.type === "visual" || this.config.type === "both") {
202
- await this._visualFallback(totalDuration, maxIntensity);
298
+ await this._visualFallback(totalDuration2, maxIntensity);
203
299
  }
204
300
  if (this.config.type === "audio" || this.config.type === "both") {
205
- await this._audioFallback(totalDuration, maxIntensity);
301
+ await this._audioFallback(totalDuration2, maxIntensity);
206
302
  }
207
303
  }
208
304
  async _visualFallback(duration, intensity) {
@@ -607,14 +703,14 @@ var HapticEngine = class _HapticEngine {
607
703
  // ─── Semantic API ──────────────────────────────────────────
608
704
  /** Light tap feedback */
609
705
  async tap(intensity = 0.6) {
610
- await this._playSteps([{ type: "vibrate", duration: 10, intensity }]);
706
+ await this._playSteps([{ type: "vibrate", duration: 30, intensity }]);
611
707
  }
612
708
  /** Double tap */
613
709
  async doubleTap(intensity = 0.6) {
614
710
  await this._playSteps([
615
- { type: "vibrate", duration: 10, intensity },
711
+ { type: "vibrate", duration: 25, intensity },
616
712
  { type: "pause", duration: 80, intensity: 0 },
617
- { type: "vibrate", duration: 10, intensity }
713
+ { type: "vibrate", duration: 25, intensity }
618
714
  ]);
619
715
  }
620
716
  /** Long press feedback */
@@ -649,24 +745,24 @@ var HapticEngine = class _HapticEngine {
649
745
  }
650
746
  /** Selection change feedback */
651
747
  async selection() {
652
- await this._playSteps([{ type: "vibrate", duration: 8, intensity: 0.4 }]);
748
+ await this._playSteps([{ type: "vibrate", duration: 25, intensity: 0.5 }]);
653
749
  }
654
750
  /** Toggle feedback */
655
751
  async toggle(on) {
656
752
  if (on) {
657
- await this._playSteps([{ type: "vibrate", duration: 15, intensity: 0.6 }]);
753
+ await this._playSteps([{ type: "vibrate", duration: 30, intensity: 0.6 }]);
658
754
  } else {
659
- await this._playSteps([{ type: "vibrate", duration: 10, intensity: 0.3 }]);
755
+ await this._playSteps([{ type: "vibrate", duration: 25, intensity: 0.4 }]);
660
756
  }
661
757
  }
662
758
  /** Impact with style (matches iOS UIImpactFeedbackGenerator) */
663
759
  async impact(style = "medium") {
664
760
  const presets2 = {
665
- light: [{ type: "vibrate", duration: 10, intensity: 0.3 }],
666
- medium: [{ type: "vibrate", duration: 15, intensity: 0.6 }],
667
- heavy: [{ type: "vibrate", duration: 25, intensity: 1 }],
668
- rigid: [{ type: "vibrate", duration: 8, intensity: 0.9 }],
669
- soft: [{ type: "vibrate", duration: 30, intensity: 0.4 }]
761
+ light: [{ type: "vibrate", duration: 25, intensity: 0.4 }],
762
+ medium: [{ type: "vibrate", duration: 35, intensity: 0.7 }],
763
+ heavy: [{ type: "vibrate", duration: 50, intensity: 1 }],
764
+ rigid: [{ type: "vibrate", duration: 30, intensity: 0.9 }],
765
+ soft: [{ type: "vibrate", duration: 35, intensity: 0.5 }]
670
766
  };
671
767
  await this._playSteps(presets2[style]);
672
768
  }
@@ -787,20 +883,135 @@ function validateHPL(input) {
787
883
  return { valid: errors.length === 0, errors };
788
884
  }
789
885
 
886
+ // src/patterns/sharing.ts
887
+ function totalDuration(steps) {
888
+ return steps.reduce((sum, s) => sum + s.duration, 0);
889
+ }
890
+ function resolveInput(input) {
891
+ if (typeof input === "string") {
892
+ const ast = parseHPL(input);
893
+ return { steps: compile(ast), hpl: input };
894
+ }
895
+ if (Array.isArray(input)) {
896
+ return { steps: input };
897
+ }
898
+ return { steps: input.steps, name: input.name };
899
+ }
900
+ function exportPattern(input, options = {}) {
901
+ const resolved = resolveInput(input);
902
+ const steps = resolved.steps;
903
+ const name = options.name ?? resolved.name ?? "Untitled Pattern";
904
+ const result = {
905
+ version: 1,
906
+ name,
907
+ steps
908
+ };
909
+ if (options.description) {
910
+ result.description = options.description;
911
+ }
912
+ if (resolved.hpl) {
913
+ result.hpl = resolved.hpl;
914
+ }
915
+ const hasMetadataFields = options.author || options.tags;
916
+ if (hasMetadataFields || steps.length > 0) {
917
+ result.metadata = {
918
+ duration: totalDuration(steps),
919
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
920
+ };
921
+ if (options.author) {
922
+ result.metadata.author = options.author;
923
+ }
924
+ if (options.tags) {
925
+ result.metadata.tags = options.tags;
926
+ }
927
+ }
928
+ return result;
929
+ }
930
+ function validateExport(data) {
931
+ if (data === null || typeof data !== "object") {
932
+ throw new Error("Invalid pattern data: expected an object");
933
+ }
934
+ const obj = data;
935
+ if (obj.version !== 1) {
936
+ throw new Error(
937
+ `Unsupported pattern version: ${String(obj.version)}. Expected version 1`
938
+ );
939
+ }
940
+ if (typeof obj.name !== "string" || obj.name.length === 0) {
941
+ throw new Error('Invalid pattern data: "name" must be a non-empty string');
942
+ }
943
+ if (!Array.isArray(obj.steps)) {
944
+ throw new Error('Invalid pattern data: "steps" must be an array');
945
+ }
946
+ for (let i = 0; i < obj.steps.length; i++) {
947
+ const step = obj.steps[i];
948
+ if (step.type !== "vibrate" && step.type !== "pause") {
949
+ throw new Error(
950
+ `Invalid step at index ${i}: "type" must be "vibrate" or "pause"`
951
+ );
952
+ }
953
+ if (typeof step.duration !== "number" || step.duration < 0) {
954
+ throw new Error(
955
+ `Invalid step at index ${i}: "duration" must be a non-negative number`
956
+ );
957
+ }
958
+ if (typeof step.intensity !== "number" || step.intensity < 0 || step.intensity > 1) {
959
+ throw new Error(
960
+ `Invalid step at index ${i}: "intensity" must be a number between 0 and 1`
961
+ );
962
+ }
963
+ }
964
+ }
965
+ function importPattern(data) {
966
+ const parsed = typeof data === "string" ? JSON.parse(data) : data;
967
+ validateExport(parsed);
968
+ const pattern = {
969
+ name: parsed.name,
970
+ steps: parsed.steps.map((s) => ({ ...s }))
971
+ };
972
+ if (parsed.metadata) {
973
+ pattern.metadata = { ...parsed.metadata };
974
+ }
975
+ return pattern;
976
+ }
977
+ function patternToJSON(input, options = {}) {
978
+ const exported = exportPattern(input, options);
979
+ return JSON.stringify(exported, null, 2);
980
+ }
981
+ function patternFromJSON(json) {
982
+ return importPattern(json);
983
+ }
984
+ function patternToDataURL(input, options = {}) {
985
+ const json = patternToJSON(input, options);
986
+ const encoded = btoa(json);
987
+ return `data:application/haptic+json;base64,${encoded}`;
988
+ }
989
+ function patternFromDataURL(url) {
990
+ const prefix = "data:application/haptic+json;base64,";
991
+ if (!url.startsWith(prefix)) {
992
+ throw new Error(
993
+ 'Invalid haptic data URL: expected "data:application/haptic+json;base64," prefix'
994
+ );
995
+ }
996
+ const encoded = url.slice(prefix.length);
997
+ const json = atob(encoded);
998
+ return patternFromJSON(json);
999
+ }
1000
+
790
1001
  // src/presets/ui.ts
791
1002
  var ui = {
792
1003
  /** Light button tap */
793
1004
  tap: {
794
1005
  name: "ui.tap",
795
- steps: [{ type: "vibrate", duration: 10, intensity: 0.6 }]
1006
+ steps: [{ type: "vibrate", duration: 30, intensity: 0.6 }]
796
1007
  },
797
1008
  /** Double tap */
798
1009
  doubleTap: {
799
1010
  name: "ui.doubleTap",
800
1011
  steps: [
801
- { type: "vibrate", duration: 10, intensity: 0.6 },
1012
+ { type: "vibrate", duration: 25, intensity: 0.6 },
802
1013
  { type: "pause", duration: 80, intensity: 0 },
803
- { type: "vibrate", duration: 10, intensity: 0.6 }
1014
+ { type: "vibrate", duration: 25, intensity: 0.6 }
804
1015
  ]
805
1016
  },
806
1017
  /** Long press acknowledgment */
@@ -811,58 +1022,58 @@ var ui = {
811
1022
  /** Toggle switch on */
812
1023
  toggleOn: {
813
1024
  name: "ui.toggleOn",
814
- steps: [{ type: "vibrate", duration: 15, intensity: 0.6 }]
1025
+ steps: [{ type: "vibrate", duration: 30, intensity: 0.6 }]
815
1026
  },
816
1027
  /** Toggle switch off */
817
1028
  toggleOff: {
818
1029
  name: "ui.toggleOff",
819
- steps: [{ type: "vibrate", duration: 10, intensity: 0.3 }]
1030
+ steps: [{ type: "vibrate", duration: 25, intensity: 0.4 }]
820
1031
  },
821
1032
  /** Slider snap to value */
822
1033
  sliderSnap: {
823
1034
  name: "ui.sliderSnap",
824
- steps: [{ type: "vibrate", duration: 5, intensity: 0.4 }]
1035
+ steps: [{ type: "vibrate", duration: 25, intensity: 0.5 }]
825
1036
  },
826
1037
  /** Selection changed */
827
1038
  selection: {
828
1039
  name: "ui.selection",
829
- steps: [{ type: "vibrate", duration: 8, intensity: 0.4 }]
1040
+ steps: [{ type: "vibrate", duration: 25, intensity: 0.5 }]
830
1041
  },
831
1042
  /** Pull to refresh threshold reached */
832
1043
  pullToRefresh: {
833
1044
  name: "ui.pullToRefresh",
834
1045
  steps: [
835
- { type: "vibrate", duration: 20, intensity: 0.5 },
1046
+ { type: "vibrate", duration: 30, intensity: 0.5 },
836
1047
  { type: "pause", duration: 40, intensity: 0 },
837
- { type: "vibrate", duration: 30, intensity: 0.7 }
1048
+ { type: "vibrate", duration: 40, intensity: 0.7 }
838
1049
  ]
839
1050
  },
840
1051
  /** Swipe action triggered */
841
1052
  swipe: {
842
1053
  name: "ui.swipe",
843
1054
  steps: [
844
- { type: "vibrate", duration: 12, intensity: 0.4 },
1055
+ { type: "vibrate", duration: 30, intensity: 0.5 },
845
1056
  { type: "pause", duration: 30, intensity: 0 },
846
- { type: "vibrate", duration: 8, intensity: 0.3 }
1057
+ { type: "vibrate", duration: 25, intensity: 0.4 }
847
1058
  ]
848
1059
  },
849
1060
  /** Context menu appearance */
850
1061
  contextMenu: {
851
1062
  name: "ui.contextMenu",
852
- steps: [{ type: "vibrate", duration: 20, intensity: 0.7 }]
1063
+ steps: [{ type: "vibrate", duration: 35, intensity: 0.7 }]
853
1064
  },
854
1065
  /** Drag start */
855
1066
  dragStart: {
856
1067
  name: "ui.dragStart",
857
- steps: [{ type: "vibrate", duration: 12, intensity: 0.5 }]
1068
+ steps: [{ type: "vibrate", duration: 30, intensity: 0.5 }]
858
1069
  },
859
1070
  /** Drag drop */
860
1071
  drop: {
861
1072
  name: "ui.drop",
862
1073
  steps: [
863
- { type: "vibrate", duration: 20, intensity: 0.8 },
1074
+ { type: "vibrate", duration: 30, intensity: 0.8 },
864
1075
  { type: "pause", duration: 30, intensity: 0 },
865
- { type: "vibrate", duration: 10, intensity: 0.4 }
1076
+ { type: "vibrate", duration: 25, intensity: 0.5 }
866
1077
  ]
867
1078
  }
868
1079
  };
@@ -873,9 +1084,9 @@ var notifications = {
873
1084
  success: {
874
1085
  name: "notifications.success",
875
1086
  steps: [
876
- { type: "vibrate", duration: 30, intensity: 0.5 },
1087
+ { type: "vibrate", duration: 35, intensity: 0.5 },
877
1088
  { type: "pause", duration: 60, intensity: 0 },
878
- { type: "vibrate", duration: 40, intensity: 0.8 }
1089
+ { type: "vibrate", duration: 45, intensity: 0.8 }
879
1090
  ]
880
1091
  },
881
1092
  /** Warning — three even pulses */
@@ -902,16 +1113,16 @@ var notifications = {
902
1113
  info: {
903
1114
  name: "notifications.info",
904
1115
  steps: [
905
- { type: "vibrate", duration: 20, intensity: 0.4 }
1116
+ { type: "vibrate", duration: 35, intensity: 0.5 }
906
1117
  ]
907
1118
  },
908
1119
  /** Message received */
909
1120
  messageReceived: {
910
1121
  name: "notifications.messageReceived",
911
1122
  steps: [
912
- { type: "vibrate", duration: 15, intensity: 0.5 },
1123
+ { type: "vibrate", duration: 30, intensity: 0.5 },
913
1124
  { type: "pause", duration: 100, intensity: 0 },
914
- { type: "vibrate", duration: 15, intensity: 0.5 }
1125
+ { type: "vibrate", duration: 30, intensity: 0.5 }
915
1126
  ]
916
1127
  },
917
1128
  /** Alarm — urgent repeating pattern */
@@ -935,9 +1146,9 @@ var notifications = {
935
1146
  reminder: {
936
1147
  name: "notifications.reminder",
937
1148
  steps: [
938
- { type: "vibrate", duration: 25, intensity: 0.5 },
1149
+ { type: "vibrate", duration: 30, intensity: 0.5 },
939
1150
  { type: "pause", duration: 150, intensity: 0 },
940
- { type: "vibrate", duration: 25, intensity: 0.5 }
1151
+ { type: "vibrate", duration: 30, intensity: 0.5 }
941
1152
  ]
942
1153
  }
943
1154
  };
@@ -951,26 +1162,25 @@ var gaming = {
951
1162
  { type: "vibrate", duration: 100, intensity: 1 },
952
1163
  { type: "vibrate", duration: 80, intensity: 0.8 },
953
1164
  { type: "vibrate", duration: 60, intensity: 0.5 },
954
- { type: "vibrate", duration: 40, intensity: 0.3 },
955
- { type: "vibrate", duration: 30, intensity: 0.1 }
1165
+ { type: "vibrate", duration: 40, intensity: 0.3 }
956
1166
  ]
957
1167
  },
958
1168
  /** Collision — sharp impact */
959
1169
  collision: {
960
1170
  name: "gaming.collision",
961
1171
  steps: [
962
- { type: "vibrate", duration: 30, intensity: 1 },
963
- { type: "pause", duration: 20, intensity: 0 },
964
- { type: "vibrate", duration: 15, intensity: 0.5 }
1172
+ { type: "vibrate", duration: 40, intensity: 1 },
1173
+ { type: "pause", duration: 30, intensity: 0 },
1174
+ { type: "vibrate", duration: 25, intensity: 0.5 }
965
1175
  ]
966
1176
  },
967
1177
  /** Heartbeat — rhythmic pulse */
968
1178
  heartbeat: {
969
1179
  name: "gaming.heartbeat",
970
1180
  steps: [
971
- { type: "vibrate", duration: 20, intensity: 0.8 },
1181
+ { type: "vibrate", duration: 30, intensity: 0.8 },
972
1182
  { type: "pause", duration: 80, intensity: 0 },
973
- { type: "vibrate", duration: 30, intensity: 1 },
1183
+ { type: "vibrate", duration: 40, intensity: 1 },
974
1184
  { type: "pause", duration: 400, intensity: 0 }
975
1185
  ]
976
1186
  },
@@ -978,17 +1188,17 @@ var gaming = {
978
1188
  gunshot: {
979
1189
  name: "gaming.gunshot",
980
1190
  steps: [
981
- { type: "vibrate", duration: 15, intensity: 1 },
982
- { type: "vibrate", duration: 30, intensity: 0.4 }
1191
+ { type: "vibrate", duration: 30, intensity: 1 },
1192
+ { type: "vibrate", duration: 40, intensity: 0.4 }
983
1193
  ]
984
1194
  },
985
1195
  /** Sword clash — metallic ring */
986
1196
  swordClash: {
987
1197
  name: "gaming.swordClash",
988
1198
  steps: [
989
- { type: "vibrate", duration: 10, intensity: 1 },
990
- { type: "pause", duration: 10, intensity: 0 },
991
- { type: "vibrate", duration: 30, intensity: 0.6 },
1199
+ { type: "vibrate", duration: 25, intensity: 1 },
1200
+ { type: "pause", duration: 20, intensity: 0 },
1201
+ { type: "vibrate", duration: 40, intensity: 0.6 },
992
1202
  { type: "vibrate", duration: 50, intensity: 0.3 }
993
1203
  ]
994
1204
  },
@@ -996,10 +1206,10 @@ var gaming = {
996
1206
  powerUp: {
997
1207
  name: "gaming.powerUp",
998
1208
  steps: [
999
- { type: "vibrate", duration: 40, intensity: 0.2 },
1000
- { type: "vibrate", duration: 40, intensity: 0.4 },
1001
- { type: "vibrate", duration: 40, intensity: 0.6 },
1002
- { type: "vibrate", duration: 40, intensity: 0.8 },
1209
+ { type: "vibrate", duration: 40, intensity: 0.3 },
1210
+ { type: "vibrate", duration: 40, intensity: 0.5 },
1211
+ { type: "vibrate", duration: 40, intensity: 0.7 },
1212
+ { type: "vibrate", duration: 40, intensity: 0.9 },
1003
1213
  { type: "vibrate", duration: 60, intensity: 1 }
1004
1214
  ]
1005
1215
  },
@@ -1007,46 +1217,46 @@ var gaming = {
1007
1217
  damage: {
1008
1218
  name: "gaming.damage",
1009
1219
  steps: [
1010
- { type: "vibrate", duration: 40, intensity: 0.9 },
1011
- { type: "pause", duration: 20, intensity: 0 },
1012
- { type: "vibrate", duration: 30, intensity: 0.6 },
1013
- { type: "pause", duration: 20, intensity: 0 },
1014
- { type: "vibrate", duration: 20, intensity: 0.3 }
1220
+ { type: "vibrate", duration: 50, intensity: 0.9 },
1221
+ { type: "pause", duration: 25, intensity: 0 },
1222
+ { type: "vibrate", duration: 40, intensity: 0.6 },
1223
+ { type: "pause", duration: 25, intensity: 0 },
1224
+ { type: "vibrate", duration: 30, intensity: 0.4 }
1015
1225
  ]
1016
1226
  },
1017
1227
  /** Item pickup — light cheerful */
1018
1228
  pickup: {
1019
1229
  name: "gaming.pickup",
1020
1230
  steps: [
1021
- { type: "vibrate", duration: 10, intensity: 0.3 },
1231
+ { type: "vibrate", duration: 25, intensity: 0.4 },
1022
1232
  { type: "pause", duration: 40, intensity: 0 },
1023
- { type: "vibrate", duration: 15, intensity: 0.6 }
1233
+ { type: "vibrate", duration: 30, intensity: 0.7 }
1024
1234
  ]
1025
1235
  },
1026
1236
  /** Level complete — celebratory */
1027
1237
  levelComplete: {
1028
1238
  name: "gaming.levelComplete",
1029
1239
  steps: [
1030
- { type: "vibrate", duration: 20, intensity: 0.5 },
1240
+ { type: "vibrate", duration: 30, intensity: 0.5 },
1031
1241
  { type: "pause", duration: 60, intensity: 0 },
1032
- { type: "vibrate", duration: 20, intensity: 0.5 },
1242
+ { type: "vibrate", duration: 30, intensity: 0.5 },
1033
1243
  { type: "pause", duration: 60, intensity: 0 },
1034
- { type: "vibrate", duration: 30, intensity: 0.7 },
1244
+ { type: "vibrate", duration: 40, intensity: 0.7 },
1035
1245
  { type: "pause", duration: 60, intensity: 0 },
1036
- { type: "vibrate", duration: 50, intensity: 1 }
1246
+ { type: "vibrate", duration: 60, intensity: 1 }
1037
1247
  ]
1038
1248
  },
1039
1249
  /** Engine rumble — continuous vibration */
1040
1250
  engineRumble: {
1041
1251
  name: "gaming.engineRumble",
1042
1252
  steps: [
1043
- { type: "vibrate", duration: 30, intensity: 0.4 },
1044
- { type: "pause", duration: 10, intensity: 0 },
1045
- { type: "vibrate", duration: 30, intensity: 0.5 },
1046
- { type: "pause", duration: 10, intensity: 0 },
1047
- { type: "vibrate", duration: 30, intensity: 0.4 },
1048
- { type: "pause", duration: 10, intensity: 0 },
1049
- { type: "vibrate", duration: 30, intensity: 0.5 }
1253
+ { type: "vibrate", duration: 40, intensity: 0.5 },
1254
+ { type: "pause", duration: 15, intensity: 0 },
1255
+ { type: "vibrate", duration: 40, intensity: 0.6 },
1256
+ { type: "pause", duration: 15, intensity: 0 },
1257
+ { type: "vibrate", duration: 40, intensity: 0.5 },
1258
+ { type: "pause", duration: 15, intensity: 0 },
1259
+ { type: "vibrate", duration: 40, intensity: 0.6 }
1050
1260
  ]
1051
1261
  }
1052
1262
  };
@@ -1057,9 +1267,9 @@ var accessibility = {
1057
1267
  confirm: {
1058
1268
  name: "accessibility.confirm",
1059
1269
  steps: [
1060
- { type: "vibrate", duration: 30, intensity: 0.7 },
1270
+ { type: "vibrate", duration: 35, intensity: 0.7 },
1061
1271
  { type: "pause", duration: 100, intensity: 0 },
1062
- { type: "vibrate", duration: 30, intensity: 0.7 }
1272
+ { type: "vibrate", duration: 35, intensity: 0.7 }
1063
1273
  ]
1064
1274
  },
1065
1275
  /** Deny/reject — long single buzz */
@@ -1073,41 +1283,41 @@ var accessibility = {
1073
1283
  boundary: {
1074
1284
  name: "accessibility.boundary",
1075
1285
  steps: [
1076
- { type: "vibrate", duration: 15, intensity: 1 }
1286
+ { type: "vibrate", duration: 30, intensity: 1 }
1077
1287
  ]
1078
1288
  },
1079
1289
  /** Focus change — subtle tick */
1080
1290
  focusChange: {
1081
1291
  name: "accessibility.focusChange",
1082
1292
  steps: [
1083
- { type: "vibrate", duration: 5, intensity: 0.3 }
1293
+ { type: "vibrate", duration: 25, intensity: 0.5 }
1084
1294
  ]
1085
1295
  },
1086
1296
  /** Counting rhythm — one tick per count */
1087
1297
  countTick: {
1088
1298
  name: "accessibility.countTick",
1089
1299
  steps: [
1090
- { type: "vibrate", duration: 8, intensity: 0.5 }
1300
+ { type: "vibrate", duration: 25, intensity: 0.5 }
1091
1301
  ]
1092
1302
  },
1093
1303
  /** Navigation landmark reached */
1094
1304
  landmark: {
1095
1305
  name: "accessibility.landmark",
1096
1306
  steps: [
1097
- { type: "vibrate", duration: 15, intensity: 0.6 },
1307
+ { type: "vibrate", duration: 25, intensity: 0.6 },
1098
1308
  { type: "pause", duration: 40, intensity: 0 },
1099
- { type: "vibrate", duration: 15, intensity: 0.6 },
1309
+ { type: "vibrate", duration: 25, intensity: 0.6 },
1100
1310
  { type: "pause", duration: 40, intensity: 0 },
1101
- { type: "vibrate", duration: 15, intensity: 0.6 }
1311
+ { type: "vibrate", duration: 25, intensity: 0.6 }
1102
1312
  ]
1103
1313
  },
1104
1314
  /** Progress checkpoint — escalating feedback */
1105
1315
  progressCheckpoint: {
1106
1316
  name: "accessibility.progressCheckpoint",
1107
1317
  steps: [
1108
- { type: "vibrate", duration: 20, intensity: 0.4 },
1318
+ { type: "vibrate", duration: 30, intensity: 0.5 },
1109
1319
  { type: "pause", duration: 60, intensity: 0 },
1110
- { type: "vibrate", duration: 25, intensity: 0.7 }
1320
+ { type: "vibrate", duration: 35, intensity: 0.7 }
1111
1321
  ]
1112
1322
  }
1113
1323
  };
@@ -1117,51 +1327,51 @@ var system = {
1117
1327
  /** Keyboard key press */
1118
1328
  keyPress: {
1119
1329
  name: "system.keyPress",
1120
- steps: [{ type: "vibrate", duration: 5, intensity: 0.3 }]
1330
+ steps: [{ type: "vibrate", duration: 25, intensity: 0.5 }]
1121
1331
  },
1122
1332
  /** Scroll tick (detent-like) */
1123
1333
  scrollTick: {
1124
1334
  name: "system.scrollTick",
1125
- steps: [{ type: "vibrate", duration: 3, intensity: 0.2 }]
1335
+ steps: [{ type: "vibrate", duration: 20, intensity: 0.4 }]
1126
1336
  },
1127
1337
  /** Scroll boundary reached */
1128
1338
  scrollBounce: {
1129
1339
  name: "system.scrollBounce",
1130
1340
  steps: [
1131
- { type: "vibrate", duration: 10, intensity: 0.5 },
1132
- { type: "vibrate", duration: 20, intensity: 0.3 }
1341
+ { type: "vibrate", duration: 25, intensity: 0.6 },
1342
+ { type: "vibrate", duration: 30, intensity: 0.4 }
1133
1343
  ]
1134
1344
  },
1135
1345
  /** Delete action */
1136
1346
  delete: {
1137
1347
  name: "system.delete",
1138
1348
  steps: [
1139
- { type: "vibrate", duration: 15, intensity: 0.5 },
1349
+ { type: "vibrate", duration: 30, intensity: 0.5 },
1140
1350
  { type: "pause", duration: 50, intensity: 0 },
1141
- { type: "vibrate", duration: 25, intensity: 0.8 }
1351
+ { type: "vibrate", duration: 40, intensity: 0.8 }
1142
1352
  ]
1143
1353
  },
1144
1354
  /** Undo action */
1145
1355
  undo: {
1146
1356
  name: "system.undo",
1147
1357
  steps: [
1148
- { type: "vibrate", duration: 20, intensity: 0.5 },
1358
+ { type: "vibrate", duration: 30, intensity: 0.5 },
1149
1359
  { type: "pause", duration: 80, intensity: 0 },
1150
- { type: "vibrate", duration: 10, intensity: 0.3 }
1360
+ { type: "vibrate", duration: 25, intensity: 0.4 }
1151
1361
  ]
1152
1362
  },
1153
1363
  /** Copy to clipboard */
1154
1364
  copy: {
1155
1365
  name: "system.copy",
1156
- steps: [{ type: "vibrate", duration: 12, intensity: 0.4 }]
1366
+ steps: [{ type: "vibrate", duration: 30, intensity: 0.5 }]
1157
1367
  },
1158
1368
  /** Paste from clipboard */
1159
1369
  paste: {
1160
1370
  name: "system.paste",
1161
1371
  steps: [
1162
- { type: "vibrate", duration: 8, intensity: 0.3 },
1372
+ { type: "vibrate", duration: 25, intensity: 0.4 },
1163
1373
  { type: "pause", duration: 30, intensity: 0 },
1164
- { type: "vibrate", duration: 12, intensity: 0.5 }
1374
+ { type: "vibrate", duration: 30, intensity: 0.6 }
1165
1375
  ]
1166
1376
  }
1167
1377
  };
@@ -1184,6 +1394,7 @@ exports.HPLParser = HPLParser;
1184
1394
  exports.HPLParserError = HPLParserError;
1185
1395
  exports.HPLTokenizerError = HPLTokenizerError;
1186
1396
  exports.HapticEngine = HapticEngine;
1397
+ exports.IoSAudioAdapter = IoSAudioAdapter;
1187
1398
  exports.NoopAdapter = NoopAdapter;
1188
1399
  exports.PatternComposer = PatternComposer;
1189
1400
  exports.WebVibrationAdapter = WebVibrationAdapter;
@@ -1191,11 +1402,17 @@ exports.accessibility = accessibility;
1191
1402
  exports.compile = compile;
1192
1403
  exports.detectAdapter = detectAdapter;
1193
1404
  exports.detectPlatform = detectPlatform;
1405
+ exports.exportPattern = exportPattern;
1194
1406
  exports.gaming = gaming;
1195
1407
  exports.haptic = haptic;
1408
+ exports.importPattern = importPattern;
1196
1409
  exports.notifications = notifications;
1197
1410
  exports.optimizeSteps = optimizeSteps;
1198
1411
  exports.parseHPL = parseHPL;
1412
+ exports.patternFromDataURL = patternFromDataURL;
1413
+ exports.patternFromJSON = patternFromJSON;
1414
+ exports.patternToDataURL = patternToDataURL;
1415
+ exports.patternToJSON = patternToJSON;
1199
1416
  exports.presets = presets;
1200
1417
  exports.system = system;
1201
1418
  exports.tokenize = tokenize;