@jjlmoya/utils-games-development 1.65.0 → 1.67.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 (77) hide show
  1. package/package.json +67 -67
  2. package/scripts/postinstall.mjs +27 -27
  3. package/src/category/GamesCategorySEO.astro +19 -19
  4. package/src/category/i18n/de.ts +15 -15
  5. package/src/category/i18n/en.ts +15 -15
  6. package/src/category/i18n/es.ts +15 -15
  7. package/src/category/i18n/fr.ts +15 -15
  8. package/src/category/i18n/id.ts +10 -10
  9. package/src/category/i18n/it.ts +10 -10
  10. package/src/category/i18n/ja.ts +10 -10
  11. package/src/category/i18n/ko.ts +10 -10
  12. package/src/category/i18n/nl.ts +10 -10
  13. package/src/category/i18n/pl.ts +10 -10
  14. package/src/category/i18n/pt.ts +10 -10
  15. package/src/category/i18n/ru.ts +10 -10
  16. package/src/category/i18n/sv.ts +10 -10
  17. package/src/category/i18n/tr.ts +10 -10
  18. package/src/category/i18n/zh.ts +10 -10
  19. package/src/category/seo.astro +15 -15
  20. package/src/components/PreviewNavSidebar.astro +116 -116
  21. package/src/components/PreviewToolbar.astro +143 -143
  22. package/src/data.ts +11 -11
  23. package/src/env.d.ts +5 -5
  24. package/src/index.ts +20 -20
  25. package/src/layouts/PreviewLayout.astro +122 -122
  26. package/src/pages/[locale]/[slug].astro +165 -165
  27. package/src/pages/[locale].astro +250 -250
  28. package/src/pages/index.astro +4 -4
  29. package/src/tests/bibliography_wellformed_export.test.ts +46 -0
  30. package/src/tests/category_seo_quality.test.ts +74 -0
  31. package/src/tests/diacritics_density.test.ts +118 -118
  32. package/src/tests/faq_count.test.ts +19 -19
  33. package/src/tests/i18n_coverage.test.ts +36 -36
  34. package/src/tests/inverted_punctuation.test.ts +84 -84
  35. package/src/tests/mocks/astro_mock.js +2 -2
  36. package/src/tests/no_en_dash.test.ts +70 -70
  37. package/src/tests/no_h1_in_components.test.ts +48 -48
  38. package/src/tests/pagespeed_best_practices.test.ts +198 -198
  39. package/src/tests/qa-test-helpers.ts +32 -32
  40. package/src/tests/qa_bibliography_links.test.ts +41 -41
  41. package/src/tests/qa_claim_evidence.test.ts +69 -69
  42. package/src/tests/qa_logic_reference_coverage.test.ts +46 -46
  43. package/src/tests/qa_runtime_i18n.test.ts +100 -100
  44. package/src/tests/schemas_fulfillment.test.ts +23 -23
  45. package/src/tests/script_density.test.ts +94 -94
  46. package/src/tests/seo_length.test.ts +23 -23
  47. package/src/tests/seo_wellformed_export.test.ts +65 -0
  48. package/src/tests/slug_language_code_format.test.ts +23 -23
  49. package/src/tests/slug_uniqueness.test.ts +80 -80
  50. package/src/tests/title_quality.test.ts +56 -56
  51. package/src/tool/audioLoopPointFinder/audio-loop-point-finder.css +322 -322
  52. package/src/tool/audioLoopPointFinder/client.ts +262 -262
  53. package/src/tool/damageFormulaLab/bibliography.astro +3 -21
  54. package/src/tool/damageFormulaLab/seo.astro +7 -5
  55. package/src/tool/gameDeltaTimeFixedTimestepLab/bibliography.astro +3 -19
  56. package/src/tool/hitboxHurtboxAnimator/hitbox-hurtbox-animator.css +614 -614
  57. package/src/tool/hitboxHurtboxAnimator/seo.astro +7 -5
  58. package/src/tool/saveFileEditor/component.astro +351 -351
  59. package/src/tool/saveFileEditor/game-save-file-editor.css +250 -250
  60. package/src/tool/spriteSheetPacker/app-client.ts +248 -248
  61. package/src/tool/spriteSheetPacker/bibliography.ts +12 -12
  62. package/src/tool/spriteSheetPacker/component.astro +229 -229
  63. package/src/tool/spriteSheetPacker/dropzone-client.ts +55 -55
  64. package/src/tool/spriteSheetPacker/entry.ts +28 -28
  65. package/src/tool/spriteSheetPacker/exporter.ts +116 -116
  66. package/src/tool/spriteSheetPacker/extractor-client.ts +127 -127
  67. package/src/tool/spriteSheetPacker/flipbook-client.ts +60 -60
  68. package/src/tool/spriteSheetPacker/logic.test.ts +155 -155
  69. package/src/tool/spriteSheetPacker/logic.ts +32 -32
  70. package/src/tool/spriteSheetPacker/packer-core.ts +121 -121
  71. package/src/tool/spriteSheetPacker/packer-ui.ts +86 -86
  72. package/src/tool/spriteSheetPacker/sprite-sheet-packer.css +578 -578
  73. package/src/tool/spriteSheetPacker/types.ts +89 -89
  74. package/src/tool/spriteSheetPacker/ui.ts +42 -42
  75. package/src/tool/steamCapsuleGenerator/index.ts +9 -9
  76. package/src/tool/steamCapsuleGenerator/validation.ts +14 -14
  77. package/src/types.ts +72 -72
@@ -1,262 +1,262 @@
1
- import {
2
- findNearestZeroCrossing,
3
- samplesToSeconds,
4
- formatTime,
5
- parseAudioLoopMetadata,
6
- } from './logic';
7
- import {
8
- dropzone,
9
- browseBtn,
10
- fileInput,
11
- workspace,
12
- canvas,
13
- statDuration,
14
- statSampleRate,
15
- statChannels,
16
- statSamples,
17
- startInput,
18
- endInput,
19
- durationInput,
20
- snapBtn,
21
- playBtn,
22
- stopBtn,
23
- exportBtn,
24
- presetFull,
25
- presetIntro,
26
- presetMiddleLoop,
27
- statusText,
28
- measureCanvasParentWidth,
29
- measureCanvasBoundingRect,
30
- setElementText,
31
- getI18nText,
32
- spawnParticle,
33
- } from './dom-utils';
34
- import { renderWaveformBars, renderWaveformOverlay } from './waveform-renderer';
35
- import { initAudioContext, playLoop, stopLoop, exportWav } from './audio-player';
36
-
37
- let audioBuffer: AudioBuffer | null = null;
38
-
39
- let loopStartSample = 0;
40
- let loopEndSample = 0;
41
- let totalSamples = 0;
42
- let isDraggingStart = false;
43
- let isDraggingEnd = false;
44
-
45
- function updateStats() {
46
- if (!audioBuffer) return;
47
- setElementText(statDuration, formatTime(audioBuffer.duration));
48
- setElementText(statSampleRate, String(audioBuffer.sampleRate) + ' Hz');
49
- setElementText(statChannels, String(audioBuffer.numberOfChannels));
50
- setElementText(statSamples, audioBuffer.length.toLocaleString());
51
- }
52
-
53
- function updateInputs() {
54
- if (!audioBuffer) return;
55
- if (startInput) {
56
- startInput.max = String(totalSamples);
57
- startInput.value = String(loopStartSample);
58
- }
59
- if (endInput) {
60
- endInput.max = String(totalSamples);
61
- endInput.value = String(loopEndSample);
62
- }
63
- if (durationInput) {
64
- const durSec = samplesToSeconds(loopEndSample - loopStartSample, audioBuffer.sampleRate);
65
- durationInput.value = formatTime(durSec);
66
- }
67
- }
68
-
69
- function drawWaveform() {
70
- if (!canvas || !audioBuffer) return;
71
- const width = measureCanvasParentWidth();
72
- const height = 180;
73
- canvas.width = width;
74
- canvas.height = height;
75
-
76
- const ctx = canvas.getContext('2d');
77
- if (!ctx) return;
78
-
79
- ctx.fillStyle = '#020617';
80
- ctx.fillRect(0, 0, width, height);
81
- renderWaveformBars(ctx, audioBuffer, width, height);
82
- renderWaveformOverlay({
83
- ctx,
84
- loopStartSample,
85
- loopEndSample,
86
- totalSamples,
87
- width,
88
- height,
89
- });
90
- }
91
-
92
- function decodeAudioDataPromise(ctx: AudioContext, buffer: ArrayBuffer): Promise<AudioBuffer> {
93
- return new Promise((resolve, reject) => {
94
- ctx.decodeAudioData(
95
- buffer,
96
- (decoded) => resolve(decoded),
97
- (err) => reject(err)
98
- );
99
- });
100
- }
101
-
102
- function handleFile(file: File) {
103
- const ctx = initAudioContext();
104
- if (ctx.state === 'suspended') {
105
- ctx.resume();
106
- }
107
- const reader = new FileReader();
108
- reader.onload = async (e) => {
109
- try {
110
- const arrayBuf = e.target?.result as ArrayBuffer;
111
- if (!ctx || !arrayBuf) return;
112
- const rawBytes = new Uint8Array(arrayBuf);
113
- const parsedMeta = parseAudioLoopMetadata(rawBytes);
114
-
115
- const bufferCopy = arrayBuf.slice(0);
116
- audioBuffer = await decodeAudioDataPromise(ctx, bufferCopy);
117
- totalSamples = audioBuffer.length;
118
-
119
- loopStartSample = parsedMeta.loopStart ?? 0;
120
- loopEndSample = parsedMeta.loopEnd ?? totalSamples;
121
-
122
- updateStats();
123
- updateInputs();
124
- drawWaveform();
125
- if (workspace) workspace.style.display = 'flex';
126
- setElementText(statusText, getI18nText('loaded'));
127
- } catch {
128
- setElementText(statusText, getI18nText('decode-error'));
129
- }
130
- };
131
- reader.readAsArrayBuffer(file);
132
- }
133
-
134
- function snapToZeroCrossing() {
135
- if (!audioBuffer) return;
136
- const channelData = audioBuffer.getChannelData(0);
137
- loopStartSample = findNearestZeroCrossing(channelData, loopStartSample);
138
- loopEndSample = findNearestZeroCrossing(channelData, loopEndSample);
139
- updateInputs();
140
- drawWaveform();
141
- setElementText(statusText, getI18nText('snapped'));
142
- }
143
-
144
- dropzone?.addEventListener('click', () => fileInput?.click());
145
- browseBtn?.addEventListener('click', (e) => {
146
- e.stopPropagation();
147
- fileInput?.click();
148
- });
149
-
150
- fileInput?.addEventListener('change', (e) => {
151
- const files = (e.target as HTMLInputElement).files;
152
- if (files && files[0]) handleFile(files[0]);
153
- });
154
-
155
- dropzone?.addEventListener('dragover', (e) => {
156
- e.preventDefault();
157
- dropzone?.classList.add('drag-over');
158
- });
159
-
160
- dropzone?.addEventListener('dragleave', () => dropzone?.classList.remove('drag-over'));
161
- dropzone?.addEventListener('drop', (e) => {
162
- e.preventDefault();
163
- dropzone?.classList.remove('drag-over');
164
- const files = e.dataTransfer?.files;
165
- if (files && files[0]) handleFile(files[0]);
166
- });
167
-
168
- startInput?.addEventListener('change', () => {
169
- loopStartSample = Math.max(0, Math.min(totalSamples, parseInt(startInput.value) || 0));
170
- updateInputs();
171
- drawWaveform();
172
- });
173
-
174
- endInput?.addEventListener('change', () => {
175
- loopEndSample = Math.max(0, Math.min(totalSamples, parseInt(endInput.value) || 0));
176
- updateInputs();
177
- drawWaveform();
178
- });
179
-
180
- snapBtn?.addEventListener('click', (e) => {
181
- snapToZeroCrossing();
182
- spawnParticle('ZERO SNAP!', e.clientX, e.clientY);
183
- });
184
-
185
- playBtn?.addEventListener('click', () => playLoop(audioBuffer, loopStartSample, loopEndSample, totalSamples));
186
- stopBtn?.addEventListener('click', () => stopLoop());
187
- exportBtn?.addEventListener('click', (e) => {
188
- exportWav(audioBuffer, loopStartSample, loopEndSample);
189
- spawnParticle('EXPORTED!', e.clientX, e.clientY);
190
- });
191
-
192
- presetFull?.addEventListener('click', () => {
193
- loopStartSample = 0;
194
- loopEndSample = totalSamples;
195
- updateInputs();
196
- drawWaveform();
197
- });
198
-
199
- presetIntro?.addEventListener('click', () => {
200
- loopStartSample = Math.round(totalSamples * 0.1);
201
- loopEndSample = totalSamples;
202
- updateInputs();
203
- drawWaveform();
204
- });
205
-
206
- presetMiddleLoop?.addEventListener('click', () => {
207
- loopStartSample = Math.round(totalSamples * 0.25);
208
- loopEndSample = Math.round(totalSamples * 0.75);
209
- updateInputs();
210
- drawWaveform();
211
- });
212
-
213
- canvas?.addEventListener('mousedown', (e) => {
214
- if (!totalSamples) return;
215
- const rect = measureCanvasBoundingRect();
216
- if (!rect) return;
217
-
218
- const x = e.clientX - rect.left;
219
- const clickedSample = Math.round((x / rect.width) * totalSamples);
220
- const startX = (loopStartSample / totalSamples) * rect.width;
221
- const endX = (loopEndSample / totalSamples) * rect.width;
222
-
223
- if (Math.abs(x - startX) < 15) {
224
- isDraggingStart = true;
225
- } else if (Math.abs(x - endX) < 15) {
226
- isDraggingEnd = true;
227
- } else {
228
- if (Math.abs(clickedSample - loopStartSample) < Math.abs(clickedSample - loopEndSample)) {
229
- loopStartSample = clickedSample;
230
- } else {
231
- loopEndSample = clickedSample;
232
- }
233
- updateInputs();
234
- drawWaveform();
235
- }
236
- });
237
-
238
- window.addEventListener('mousemove', (e) => {
239
- if (!isDraggingStart && !isDraggingEnd) return;
240
- const rect = measureCanvasBoundingRect();
241
- if (!rect) return;
242
-
243
- const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));
244
- const sample = Math.round((x / rect.width) * totalSamples);
245
-
246
- if (isDraggingStart) {
247
- loopStartSample = Math.min(sample, loopEndSample - 100);
248
- } else if (isDraggingEnd) {
249
- loopEndSample = Math.max(sample, loopStartSample + 100);
250
- }
251
- updateInputs();
252
- drawWaveform();
253
- });
254
-
255
- window.addEventListener('mouseup', () => {
256
- isDraggingStart = false;
257
- isDraggingEnd = false;
258
- });
259
-
260
- window.addEventListener('resize', () => {
261
- drawWaveform();
262
- });
1
+ import {
2
+ findNearestZeroCrossing,
3
+ samplesToSeconds,
4
+ formatTime,
5
+ parseAudioLoopMetadata,
6
+ } from './logic';
7
+ import {
8
+ dropzone,
9
+ browseBtn,
10
+ fileInput,
11
+ workspace,
12
+ canvas,
13
+ statDuration,
14
+ statSampleRate,
15
+ statChannels,
16
+ statSamples,
17
+ startInput,
18
+ endInput,
19
+ durationInput,
20
+ snapBtn,
21
+ playBtn,
22
+ stopBtn,
23
+ exportBtn,
24
+ presetFull,
25
+ presetIntro,
26
+ presetMiddleLoop,
27
+ statusText,
28
+ measureCanvasParentWidth,
29
+ measureCanvasBoundingRect,
30
+ setElementText,
31
+ getI18nText,
32
+ spawnParticle,
33
+ } from './dom-utils';
34
+ import { renderWaveformBars, renderWaveformOverlay } from './waveform-renderer';
35
+ import { initAudioContext, playLoop, stopLoop, exportWav } from './audio-player';
36
+
37
+ let audioBuffer: AudioBuffer | null = null;
38
+
39
+ let loopStartSample = 0;
40
+ let loopEndSample = 0;
41
+ let totalSamples = 0;
42
+ let isDraggingStart = false;
43
+ let isDraggingEnd = false;
44
+
45
+ function updateStats() {
46
+ if (!audioBuffer) return;
47
+ setElementText(statDuration, formatTime(audioBuffer.duration));
48
+ setElementText(statSampleRate, String(audioBuffer.sampleRate) + ' Hz');
49
+ setElementText(statChannels, String(audioBuffer.numberOfChannels));
50
+ setElementText(statSamples, audioBuffer.length.toLocaleString());
51
+ }
52
+
53
+ function updateInputs() {
54
+ if (!audioBuffer) return;
55
+ if (startInput) {
56
+ startInput.max = String(totalSamples);
57
+ startInput.value = String(loopStartSample);
58
+ }
59
+ if (endInput) {
60
+ endInput.max = String(totalSamples);
61
+ endInput.value = String(loopEndSample);
62
+ }
63
+ if (durationInput) {
64
+ const durSec = samplesToSeconds(loopEndSample - loopStartSample, audioBuffer.sampleRate);
65
+ durationInput.value = formatTime(durSec);
66
+ }
67
+ }
68
+
69
+ function drawWaveform() {
70
+ if (!canvas || !audioBuffer) return;
71
+ const width = measureCanvasParentWidth();
72
+ const height = 180;
73
+ canvas.width = width;
74
+ canvas.height = height;
75
+
76
+ const ctx = canvas.getContext('2d');
77
+ if (!ctx) return;
78
+
79
+ ctx.fillStyle = '#020617';
80
+ ctx.fillRect(0, 0, width, height);
81
+ renderWaveformBars(ctx, audioBuffer, width, height);
82
+ renderWaveformOverlay({
83
+ ctx,
84
+ loopStartSample,
85
+ loopEndSample,
86
+ totalSamples,
87
+ width,
88
+ height,
89
+ });
90
+ }
91
+
92
+ function decodeAudioDataPromise(ctx: AudioContext, buffer: ArrayBuffer): Promise<AudioBuffer> {
93
+ return new Promise((resolve, reject) => {
94
+ ctx.decodeAudioData(
95
+ buffer,
96
+ (decoded) => resolve(decoded),
97
+ (err) => reject(err)
98
+ );
99
+ });
100
+ }
101
+
102
+ function handleFile(file: File) {
103
+ const ctx = initAudioContext();
104
+ if (ctx.state === 'suspended') {
105
+ ctx.resume();
106
+ }
107
+ const reader = new FileReader();
108
+ reader.onload = async (e) => {
109
+ try {
110
+ const arrayBuf = e.target?.result as ArrayBuffer;
111
+ if (!ctx || !arrayBuf) return;
112
+ const rawBytes = new Uint8Array(arrayBuf);
113
+ const parsedMeta = parseAudioLoopMetadata(rawBytes);
114
+
115
+ const bufferCopy = arrayBuf.slice(0);
116
+ audioBuffer = await decodeAudioDataPromise(ctx, bufferCopy);
117
+ totalSamples = audioBuffer.length;
118
+
119
+ loopStartSample = parsedMeta.loopStart ?? 0;
120
+ loopEndSample = parsedMeta.loopEnd ?? totalSamples;
121
+
122
+ updateStats();
123
+ updateInputs();
124
+ drawWaveform();
125
+ if (workspace) workspace.style.display = 'flex';
126
+ setElementText(statusText, getI18nText('loaded'));
127
+ } catch {
128
+ setElementText(statusText, getI18nText('decode-error'));
129
+ }
130
+ };
131
+ reader.readAsArrayBuffer(file);
132
+ }
133
+
134
+ function snapToZeroCrossing() {
135
+ if (!audioBuffer) return;
136
+ const channelData = audioBuffer.getChannelData(0);
137
+ loopStartSample = findNearestZeroCrossing(channelData, loopStartSample);
138
+ loopEndSample = findNearestZeroCrossing(channelData, loopEndSample);
139
+ updateInputs();
140
+ drawWaveform();
141
+ setElementText(statusText, getI18nText('snapped'));
142
+ }
143
+
144
+ dropzone?.addEventListener('click', () => fileInput?.click());
145
+ browseBtn?.addEventListener('click', (e) => {
146
+ e.stopPropagation();
147
+ fileInput?.click();
148
+ });
149
+
150
+ fileInput?.addEventListener('change', (e) => {
151
+ const files = (e.target as HTMLInputElement).files;
152
+ if (files && files[0]) handleFile(files[0]);
153
+ });
154
+
155
+ dropzone?.addEventListener('dragover', (e) => {
156
+ e.preventDefault();
157
+ dropzone?.classList.add('drag-over');
158
+ });
159
+
160
+ dropzone?.addEventListener('dragleave', () => dropzone?.classList.remove('drag-over'));
161
+ dropzone?.addEventListener('drop', (e) => {
162
+ e.preventDefault();
163
+ dropzone?.classList.remove('drag-over');
164
+ const files = e.dataTransfer?.files;
165
+ if (files && files[0]) handleFile(files[0]);
166
+ });
167
+
168
+ startInput?.addEventListener('change', () => {
169
+ loopStartSample = Math.max(0, Math.min(totalSamples, parseInt(startInput.value) || 0));
170
+ updateInputs();
171
+ drawWaveform();
172
+ });
173
+
174
+ endInput?.addEventListener('change', () => {
175
+ loopEndSample = Math.max(0, Math.min(totalSamples, parseInt(endInput.value) || 0));
176
+ updateInputs();
177
+ drawWaveform();
178
+ });
179
+
180
+ snapBtn?.addEventListener('click', (e) => {
181
+ snapToZeroCrossing();
182
+ spawnParticle('ZERO SNAP!', e.clientX, e.clientY);
183
+ });
184
+
185
+ playBtn?.addEventListener('click', () => playLoop(audioBuffer, loopStartSample, loopEndSample, totalSamples));
186
+ stopBtn?.addEventListener('click', () => stopLoop());
187
+ exportBtn?.addEventListener('click', (e) => {
188
+ exportWav(audioBuffer, loopStartSample, loopEndSample);
189
+ spawnParticle('EXPORTED!', e.clientX, e.clientY);
190
+ });
191
+
192
+ presetFull?.addEventListener('click', () => {
193
+ loopStartSample = 0;
194
+ loopEndSample = totalSamples;
195
+ updateInputs();
196
+ drawWaveform();
197
+ });
198
+
199
+ presetIntro?.addEventListener('click', () => {
200
+ loopStartSample = Math.round(totalSamples * 0.1);
201
+ loopEndSample = totalSamples;
202
+ updateInputs();
203
+ drawWaveform();
204
+ });
205
+
206
+ presetMiddleLoop?.addEventListener('click', () => {
207
+ loopStartSample = Math.round(totalSamples * 0.25);
208
+ loopEndSample = Math.round(totalSamples * 0.75);
209
+ updateInputs();
210
+ drawWaveform();
211
+ });
212
+
213
+ canvas?.addEventListener('mousedown', (e) => {
214
+ if (!totalSamples) return;
215
+ const rect = measureCanvasBoundingRect();
216
+ if (!rect) return;
217
+
218
+ const x = e.clientX - rect.left;
219
+ const clickedSample = Math.round((x / rect.width) * totalSamples);
220
+ const startX = (loopStartSample / totalSamples) * rect.width;
221
+ const endX = (loopEndSample / totalSamples) * rect.width;
222
+
223
+ if (Math.abs(x - startX) < 15) {
224
+ isDraggingStart = true;
225
+ } else if (Math.abs(x - endX) < 15) {
226
+ isDraggingEnd = true;
227
+ } else {
228
+ if (Math.abs(clickedSample - loopStartSample) < Math.abs(clickedSample - loopEndSample)) {
229
+ loopStartSample = clickedSample;
230
+ } else {
231
+ loopEndSample = clickedSample;
232
+ }
233
+ updateInputs();
234
+ drawWaveform();
235
+ }
236
+ });
237
+
238
+ window.addEventListener('mousemove', (e) => {
239
+ if (!isDraggingStart && !isDraggingEnd) return;
240
+ const rect = measureCanvasBoundingRect();
241
+ if (!rect) return;
242
+
243
+ const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));
244
+ const sample = Math.round((x / rect.width) * totalSamples);
245
+
246
+ if (isDraggingStart) {
247
+ loopStartSample = Math.min(sample, loopEndSample - 100);
248
+ } else if (isDraggingEnd) {
249
+ loopEndSample = Math.max(sample, loopStartSample + 100);
250
+ }
251
+ updateInputs();
252
+ drawWaveform();
253
+ });
254
+
255
+ window.addEventListener('mouseup', () => {
256
+ isDraggingStart = false;
257
+ isDraggingEnd = false;
258
+ });
259
+
260
+ window.addEventListener('resize', () => {
261
+ drawWaveform();
262
+ });
@@ -1,24 +1,6 @@
1
1
  ---
2
- import type { BibliographyEntry } from '../../types';
3
-
4
- interface Props {
5
- title?: string;
6
- entries?: BibliographyEntry[];
7
- }
8
-
9
- const { title = 'References', entries = [] } = Astro.props;
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { bibliographyEntries } from './bibliography';
10
4
  ---
11
5
 
12
- {entries.length > 0 && (
13
- <section class="damage-lab-references" aria-labelledby="damage-lab-reference-title">
14
- <h2 id="damage-lab-reference-title">{title}</h2>
15
- <ul>
16
- {entries.map((entry) => <li><a href={entry.url} rel="noopener noreferrer" target="_blank">{entry.name}</a></li>)}
17
- </ul>
18
- </section>
19
- )}
20
-
21
- <style>
22
- .damage-lab-references { margin-block: 2rem; }
23
- .damage-lab-references a { color: var(--n-primary); }
24
- </style>
6
+ <SharedBibliography links={bibliographyEntries} />
@@ -1,13 +1,15 @@
1
1
  ---
2
2
  import { SEORenderer } from '@jjlmoya/utils-shared';
3
- import type { SEOSection } from '../../types';
3
+ import { damageFormulaLab } from './entry';
4
+ import type { KnownLocale } from '../../types';
4
5
 
5
6
  interface Props {
6
- locale?: string;
7
- sections?: SEOSection[];
7
+ locale?: KnownLocale;
8
8
  }
9
9
 
10
- const { locale = 'en', sections = [] } = Astro.props;
10
+ const { locale = 'es' } = Astro.props;
11
+ const loader = damageFormulaLab.i18n[locale as KnownLocale] ?? damageFormulaLab.i18n.es ?? damageFormulaLab.i18n.en;
12
+ const content = loader ? await loader() : null;
11
13
  ---
12
14
 
13
- <SEORenderer content={{ locale, sections }} />
15
+ {content?.seo && content.seo.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -1,22 +1,6 @@
1
1
  ---
2
- import type { BibliographyEntry } from '../../types';
3
-
4
- interface Props {
5
- title?: string;
6
- entries?: BibliographyEntry[];
7
- }
8
-
9
- const { title = 'References', entries = [] } = Astro.props;
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { bibliographyEntries } from './bibliography';
10
4
  ---
11
5
 
12
- {entries.length > 0 && <section class="gll-references" aria-labelledby="gll-reference-title"><h2 id="gll-reference-title">{title}</h2><ul>{entries.map((entry) => <li><a href={entry.url} rel="noopener noreferrer" target="_blank">{entry.name}</a></li>)}</ul></section>}
13
-
14
- <style>
15
- .gll-references {
16
- margin-block: 2rem;
17
- }
18
-
19
- .gll-references a {
20
- color: var(--n-primary);
21
- }
22
- </style>
6
+ <SharedBibliography links={bibliographyEntries} />