@jjlmoya/utils-audiovisual 1.2.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 (120) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +198 -0
  3. package/src/category/i18n/es.ts +198 -0
  4. package/src/category/i18n/fr.ts +198 -0
  5. package/src/category/index.ts +17 -0
  6. package/src/category/seo.astro +15 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +4 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +32 -0
  12. package/src/layouts/PreviewLayout.astro +117 -0
  13. package/src/pages/[locale]/[slug].astro +146 -0
  14. package/src/pages/[locale].astro +251 -0
  15. package/src/pages/index.astro +4 -0
  16. package/src/tests/faq_count.test.ts +19 -0
  17. package/src/tests/locale_completeness.test.ts +42 -0
  18. package/src/tests/mocks/astro_mock.js +2 -0
  19. package/src/tests/no_h1_in_components.test.ts +48 -0
  20. package/src/tests/seo_length.test.ts +22 -0
  21. package/src/tests/tool_validation.test.ts +17 -0
  22. package/src/tool/chromaticLens/bibliography.astro +17 -0
  23. package/src/tool/chromaticLens/component.astro +178 -0
  24. package/src/tool/chromaticLens/i18n/en.ts +246 -0
  25. package/src/tool/chromaticLens/i18n/es.ts +244 -0
  26. package/src/tool/chromaticLens/i18n/fr.ts +244 -0
  27. package/src/tool/chromaticLens/index.ts +43 -0
  28. package/src/tool/chromaticLens/logic.ts +87 -0
  29. package/src/tool/chromaticLens/seo.astro +15 -0
  30. package/src/tool/chromaticLens/style.css +308 -0
  31. package/src/tool/chromaticLens/ui.ts +109 -0
  32. package/src/tool/collageMaker/bibliography.astro +17 -0
  33. package/src/tool/collageMaker/component.astro +302 -0
  34. package/src/tool/collageMaker/i18n/en.ts +233 -0
  35. package/src/tool/collageMaker/i18n/es.ts +231 -0
  36. package/src/tool/collageMaker/i18n/fr.ts +231 -0
  37. package/src/tool/collageMaker/index.ts +51 -0
  38. package/src/tool/collageMaker/logic.ts +134 -0
  39. package/src/tool/collageMaker/seo.astro +15 -0
  40. package/src/tool/collageMaker/style.css +386 -0
  41. package/src/tool/exifCleaner/bibliography.astro +18 -0
  42. package/src/tool/exifCleaner/component.astro +162 -0
  43. package/src/tool/exifCleaner/i18n/en.ts +277 -0
  44. package/src/tool/exifCleaner/i18n/es.ts +277 -0
  45. package/src/tool/exifCleaner/i18n/fr.ts +277 -0
  46. package/src/tool/exifCleaner/index.ts +57 -0
  47. package/src/tool/exifCleaner/logic.ts +135 -0
  48. package/src/tool/exifCleaner/seo.astro +18 -0
  49. package/src/tool/exifCleaner/style.css +289 -0
  50. package/src/tool/exifCleaner/ui.ts +117 -0
  51. package/src/tool/imageCompressor/bibliography.astro +17 -0
  52. package/src/tool/imageCompressor/component.astro +262 -0
  53. package/src/tool/imageCompressor/i18n/en.ts +232 -0
  54. package/src/tool/imageCompressor/i18n/es.ts +230 -0
  55. package/src/tool/imageCompressor/i18n/fr.ts +230 -0
  56. package/src/tool/imageCompressor/index.ts +50 -0
  57. package/src/tool/imageCompressor/logic.ts +79 -0
  58. package/src/tool/imageCompressor/seo.astro +15 -0
  59. package/src/tool/imageCompressor/style.css +503 -0
  60. package/src/tool/printQualityCalculator/bibliography.astro +18 -0
  61. package/src/tool/printQualityCalculator/component.astro +318 -0
  62. package/src/tool/printQualityCalculator/i18n/en.ts +247 -0
  63. package/src/tool/printQualityCalculator/i18n/es.ts +245 -0
  64. package/src/tool/printQualityCalculator/i18n/fr.ts +245 -0
  65. package/src/tool/printQualityCalculator/index.ts +56 -0
  66. package/src/tool/printQualityCalculator/logic.ts +53 -0
  67. package/src/tool/printQualityCalculator/seo.astro +18 -0
  68. package/src/tool/printQualityCalculator/style.css +491 -0
  69. package/src/tool/printQualityCalculator/ui.ts +122 -0
  70. package/src/tool/privacyBlur/bibliography.astro +17 -0
  71. package/src/tool/privacyBlur/component.astro +230 -0
  72. package/src/tool/privacyBlur/i18n/en.ts +238 -0
  73. package/src/tool/privacyBlur/i18n/es.ts +236 -0
  74. package/src/tool/privacyBlur/i18n/fr.ts +236 -0
  75. package/src/tool/privacyBlur/index.ts +49 -0
  76. package/src/tool/privacyBlur/logic.ts +249 -0
  77. package/src/tool/privacyBlur/seo.astro +15 -0
  78. package/src/tool/privacyBlur/style.css +332 -0
  79. package/src/tool/privacyBlur/ui.ts +124 -0
  80. package/src/tool/subtitleSync/bibliography.astro +17 -0
  81. package/src/tool/subtitleSync/component.astro +187 -0
  82. package/src/tool/subtitleSync/i18n/en.ts +241 -0
  83. package/src/tool/subtitleSync/i18n/es.ts +241 -0
  84. package/src/tool/subtitleSync/i18n/fr.ts +241 -0
  85. package/src/tool/subtitleSync/index.ts +49 -0
  86. package/src/tool/subtitleSync/logic.ts +91 -0
  87. package/src/tool/subtitleSync/seo.astro +15 -0
  88. package/src/tool/subtitleSync/style.css +325 -0
  89. package/src/tool/subtitleSync/ui.ts +152 -0
  90. package/src/tool/timelapseCalculator/bibliography.astro +15 -0
  91. package/src/tool/timelapseCalculator/component.astro +148 -0
  92. package/src/tool/timelapseCalculator/i18n/en.ts +169 -0
  93. package/src/tool/timelapseCalculator/i18n/es.ts +169 -0
  94. package/src/tool/timelapseCalculator/i18n/fr.ts +169 -0
  95. package/src/tool/timelapseCalculator/index.ts +52 -0
  96. package/src/tool/timelapseCalculator/logic.ts +46 -0
  97. package/src/tool/timelapseCalculator/seo.astro +18 -0
  98. package/src/tool/timelapseCalculator/style.css +285 -0
  99. package/src/tool/tvDistance/bibliography.astro +17 -0
  100. package/src/tool/tvDistance/component.astro +178 -0
  101. package/src/tool/tvDistance/i18n/en.ts +223 -0
  102. package/src/tool/tvDistance/i18n/es.ts +223 -0
  103. package/src/tool/tvDistance/i18n/fr.ts +223 -0
  104. package/src/tool/tvDistance/index.ts +49 -0
  105. package/src/tool/tvDistance/logic.ts +47 -0
  106. package/src/tool/tvDistance/seo.astro +15 -0
  107. package/src/tool/tvDistance/style.css +435 -0
  108. package/src/tool/tvDistance/ui.ts +66 -0
  109. package/src/tool/videoFrameExtractor/bibliography.astro +17 -0
  110. package/src/tool/videoFrameExtractor/component.astro +285 -0
  111. package/src/tool/videoFrameExtractor/i18n/en.ts +235 -0
  112. package/src/tool/videoFrameExtractor/i18n/es.ts +235 -0
  113. package/src/tool/videoFrameExtractor/i18n/fr.ts +235 -0
  114. package/src/tool/videoFrameExtractor/index.ts +53 -0
  115. package/src/tool/videoFrameExtractor/logic.ts +49 -0
  116. package/src/tool/videoFrameExtractor/seo.astro +15 -0
  117. package/src/tool/videoFrameExtractor/style.css +426 -0
  118. package/src/tool/videoFrameExtractor/ui.ts +179 -0
  119. package/src/tools.ts +25 -0
  120. package/src/types.ts +72 -0
@@ -0,0 +1,435 @@
1
+ .tvd-root {
2
+ --tvd-bg: #fff;
3
+ --tvd-bg-muted: #f8fafc;
4
+ --tvd-bg-sim: #f1f5f9;
5
+ --tvd-border: #e2e8f0;
6
+ --tvd-text: #0f172a;
7
+ --tvd-text-muted: #94a3b8;
8
+ --tvd-primary: #2563eb;
9
+ --tvd-primary-light: rgba(59,130,246,0.06);
10
+ --tvd-primary-border: rgba(59,130,246,0.12);
11
+ --tvd-shadow: rgba(0,0,0,0.06);
12
+ --tvd-radius: 1.5rem;
13
+
14
+ max-width: 1000px;
15
+ margin: 0 auto;
16
+ padding: 1rem;
17
+ }
18
+
19
+ .theme-dark .tvd-root {
20
+ --tvd-bg: #09090b;
21
+ --tvd-bg-muted: #18181b;
22
+ --tvd-bg-sim: #09090b;
23
+ --tvd-border: #27272a;
24
+ --tvd-text: #fafafa;
25
+ --tvd-text-muted: #71717a;
26
+ --tvd-primary: #60a5fa;
27
+ --tvd-primary-light: rgba(96,165,250,0.08);
28
+ --tvd-primary-border: rgba(96,165,250,0.15);
29
+ --tvd-shadow: rgba(0,0,0,0.4);
30
+ }
31
+
32
+ .tvd-card {
33
+ background: var(--tvd-bg);
34
+ border: 1px solid var(--tvd-border);
35
+ border-radius: var(--tvd-radius);
36
+ box-shadow: 0 4px 32px var(--tvd-shadow);
37
+ overflow: hidden;
38
+ }
39
+
40
+ .tvd-body {
41
+ display: grid;
42
+ grid-template-columns: 320px 1fr;
43
+ }
44
+
45
+ @media (max-width: 720px) {
46
+ .tvd-body {
47
+ grid-template-columns: 1fr;
48
+ }
49
+ }
50
+
51
+ .tvd-left {
52
+ padding: 1.75rem;
53
+ border-right: 1px solid var(--tvd-border);
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: 1.25rem;
57
+ }
58
+
59
+ @media (max-width: 720px) {
60
+ .tvd-left {
61
+ border-right: none;
62
+ border-bottom: 1px solid var(--tvd-border);
63
+ }
64
+ }
65
+
66
+ .tvd-specs-block {
67
+ background: var(--tvd-bg);
68
+ display: flex;
69
+ flex-direction: column;
70
+ gap: 1.5rem;
71
+ }
72
+
73
+ .tvd-specs-title {
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 0.5rem;
77
+ font-size: 1.05rem;
78
+ font-weight: 800;
79
+ color: var(--tvd-text);
80
+ margin: 0 0 0.25rem;
81
+ }
82
+
83
+ .tvd-tv-icon {
84
+ width: 1.25rem;
85
+ height: 1.25rem;
86
+ fill: var(--tvd-primary);
87
+ flex-shrink: 0;
88
+ }
89
+
90
+ .tvd-label {
91
+ font-size: 0.65rem;
92
+ font-weight: 800;
93
+ text-transform: uppercase;
94
+ letter-spacing: 0.12em;
95
+ color: var(--tvd-text-muted);
96
+ }
97
+
98
+ .tvd-field {
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: 0.625rem;
102
+ }
103
+
104
+ .tvd-field-row {
105
+ display: flex;
106
+ justify-content: space-between;
107
+ align-items: center;
108
+ }
109
+
110
+ .tvd-diagonal-val {
111
+ font-size: 0.85rem;
112
+ font-weight: 800;
113
+ color: var(--tvd-primary);
114
+ }
115
+
116
+ .tvd-slider {
117
+ width: 100%;
118
+ height: 6px;
119
+ accent-color: var(--tvd-primary);
120
+ cursor: pointer;
121
+ border-radius: 9999px;
122
+ }
123
+
124
+ .tvd-res-grid {
125
+ display: grid;
126
+ grid-template-columns: repeat(3, 1fr);
127
+ gap: 0.5rem;
128
+ }
129
+
130
+ .tvd-res-btn {
131
+ padding: 0.625rem 0.25rem;
132
+ border-radius: 0.75rem;
133
+ border: 1px solid var(--tvd-border);
134
+ background: var(--tvd-bg);
135
+ color: var(--tvd-text-muted);
136
+ font-size: 0.75rem;
137
+ font-weight: 700;
138
+ cursor: pointer;
139
+ transition: all 0.2s;
140
+ }
141
+
142
+ .tvd-res-btn:hover:not(.tvd-res-btn-active) {
143
+ border-color: var(--tvd-primary);
144
+ opacity: 0.7;
145
+ }
146
+
147
+ .tvd-res-btn-active {
148
+ background: var(--tvd-primary);
149
+ color: #fff;
150
+ border-color: var(--tvd-primary);
151
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.25);
152
+ }
153
+
154
+ .tvd-thx-block {
155
+ background: var(--tvd-primary-light);
156
+ border: 1px solid var(--tvd-primary-border);
157
+ border-radius: 1rem;
158
+ padding: 1rem;
159
+ display: flex;
160
+ flex-direction: column;
161
+ gap: 0.625rem;
162
+ }
163
+
164
+ .tvd-thx-header {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 0.5rem;
168
+ }
169
+
170
+ .tvd-thx-icon {
171
+ width: 1.125rem;
172
+ height: 1.125rem;
173
+ fill: var(--tvd-primary);
174
+ flex-shrink: 0;
175
+ }
176
+
177
+ .tvd-thx-title {
178
+ font-size: 0.8rem;
179
+ font-weight: 800;
180
+ color: var(--tvd-primary);
181
+ }
182
+
183
+ .tvd-thx-desc {
184
+ font-size: 0.75rem;
185
+ line-height: 1.6;
186
+ color: var(--tvd-primary);
187
+ opacity: 0.8;
188
+ margin: 0;
189
+ }
190
+
191
+ .tvd-right {
192
+ display: flex;
193
+ flex-direction: column;
194
+ background: var(--tvd-bg);
195
+ }
196
+
197
+ .tvd-sim-badge {
198
+ display: flex;
199
+ align-items: center;
200
+ gap: 0.5rem;
201
+ padding: 1.25rem 1.5rem 0;
202
+ }
203
+
204
+ .tvd-sim-dot {
205
+ width: 8px;
206
+ height: 8px;
207
+ border-radius: 50%;
208
+ background: var(--tvd-primary);
209
+ animation: tvd-pulse 2s infinite;
210
+ }
211
+
212
+ @keyframes tvd-pulse {
213
+ 0%, 100% { opacity: 1; }
214
+ 50% { opacity: 0.4; }
215
+ }
216
+
217
+ .tvd-sim-badge-text {
218
+ font-size: 0.6rem;
219
+ font-weight: 900;
220
+ text-transform: uppercase;
221
+ letter-spacing: 0.18em;
222
+ color: var(--tvd-text-muted);
223
+ }
224
+
225
+ .tvd-sim-area {
226
+ flex: 1;
227
+ background: var(--tvd-bg-sim);
228
+ display: flex;
229
+ flex-direction: column;
230
+ align-items: center;
231
+ justify-content: flex-start;
232
+ padding: 2rem 1rem 1.5rem;
233
+ position: relative;
234
+ min-height: 400px;
235
+ }
236
+
237
+ .tvd-tv-visual {
238
+ position: relative;
239
+ display: flex;
240
+ flex-direction: column;
241
+ align-items: center;
242
+ transition: width 0.5s ease;
243
+ }
244
+
245
+ .tvd-tv-screen {
246
+ background: #0a0a0f;
247
+ border: 3px solid #27272a;
248
+ border-radius: 0.5rem;
249
+ width: 100%;
250
+ aspect-ratio: 16/9;
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: center;
254
+ position: relative;
255
+ overflow: hidden;
256
+ box-shadow: 0 20px 50px rgba(0,0,0,0.3);
257
+ }
258
+
259
+ .tvd-screen-gradient {
260
+ position: absolute;
261
+ inset: 0;
262
+ background: linear-gradient(135deg, rgba(59,130,246,0.1) 0%, transparent 60%);
263
+ }
264
+
265
+ .tvd-screen-ghost {
266
+ width: 30%;
267
+ height: 30%;
268
+ fill: rgba(96,165,250,0.2);
269
+ position: relative;
270
+ z-index: 1;
271
+ }
272
+
273
+ .tvd-screen-inset {
274
+ position: absolute;
275
+ inset: 0;
276
+ box-shadow: inset 0 0 40px rgba(59,130,246,0.08);
277
+ }
278
+
279
+ .tvd-stand-neck {
280
+ width: 16px;
281
+ height: 16px;
282
+ background: #27272a;
283
+ margin-top: -1px;
284
+ }
285
+
286
+ .tvd-stand-base {
287
+ width: 70px;
288
+ height: 6px;
289
+ background: #27272a;
290
+ border-radius: 3px;
291
+ }
292
+
293
+ .tvd-tv-tooltip {
294
+ position: absolute;
295
+ top: -2.25rem;
296
+ left: 50%;
297
+ transform: translateX(-50%);
298
+ background: var(--tvd-text);
299
+ color: var(--tvd-bg);
300
+ font-size: 0.65rem;
301
+ font-weight: 800;
302
+ padding: 0.3rem 0.75rem;
303
+ border-radius: 9999px;
304
+ white-space: nowrap;
305
+ opacity: 0;
306
+ transition: opacity 0.25s;
307
+ pointer-events: none;
308
+ }
309
+
310
+ .tvd-tv-visual:hover .tvd-tv-tooltip {
311
+ opacity: 1;
312
+ }
313
+
314
+ .tvd-distance-line {
315
+ width: 1px;
316
+ position: relative;
317
+ transition: height 0.5s ease;
318
+ margin: 0.25rem 0;
319
+ }
320
+
321
+ .tvd-line-dashed {
322
+ width: 100%;
323
+ height: 100%;
324
+ border-left: 1px dashed rgba(59,130,246,0.4);
325
+ }
326
+
327
+ .tvd-person {
328
+ display: flex;
329
+ flex-direction: column;
330
+ align-items: center;
331
+ gap: 1rem;
332
+ }
333
+
334
+ .tvd-person-inner {
335
+ display: flex;
336
+ flex-direction: column;
337
+ align-items: center;
338
+ }
339
+
340
+ .tvd-person-head {
341
+ width: 2.5rem;
342
+ height: 2.5rem;
343
+ border-radius: 50%;
344
+ background: var(--tvd-border);
345
+ border: 2px solid var(--tvd-text-muted);
346
+ opacity: 0.7;
347
+ margin-bottom: 0.25rem;
348
+ }
349
+
350
+ .tvd-person-body {
351
+ width: 4rem;
352
+ height: 2rem;
353
+ background: var(--tvd-primary);
354
+ border-radius: 1.5rem 1.5rem 0 0;
355
+ box-shadow: 0 4px 16px rgba(37,99,235,0.3);
356
+ }
357
+
358
+ .tvd-location-card {
359
+ background: var(--tvd-bg);
360
+ border: 1px solid var(--tvd-border);
361
+ border-radius: 1rem;
362
+ padding: 0.625rem 1.25rem;
363
+ display: flex;
364
+ flex-direction: column;
365
+ align-items: center;
366
+ gap: 0.2rem;
367
+ box-shadow: 0 4px 16px var(--tvd-shadow);
368
+ }
369
+
370
+ .tvd-location-label {
371
+ font-size: 0.6rem;
372
+ font-weight: 900;
373
+ text-transform: uppercase;
374
+ letter-spacing: 0.16em;
375
+ color: var(--tvd-text-muted);
376
+ }
377
+
378
+ .tvd-location-val {
379
+ font-size: 1.25rem;
380
+ font-weight: 900;
381
+ color: var(--tvd-primary);
382
+ line-height: 1;
383
+ }
384
+
385
+ .tvd-stats {
386
+ display: grid;
387
+ grid-template-columns: 1fr 1fr 1fr;
388
+ gap: 0.5rem;
389
+ padding: 0.75rem 1rem;
390
+ background: var(--tvd-bg-muted);
391
+ border-top: 1px solid var(--tvd-border);
392
+ }
393
+
394
+ .tvd-stat {
395
+ display: flex;
396
+ flex-direction: column;
397
+ align-items: center;
398
+ gap: 0.375rem;
399
+ padding: 1rem 0.5rem;
400
+ border-radius: 1rem;
401
+ transition: background 0.2s;
402
+ }
403
+
404
+ .tvd-stat:hover {
405
+ background: var(--tvd-bg);
406
+ }
407
+
408
+ .tvd-stat-opt {
409
+ background: var(--tvd-bg);
410
+ border: 1px solid var(--tvd-border);
411
+ box-shadow: 0 2px 8px var(--tvd-shadow);
412
+ }
413
+
414
+ .tvd-stat-label {
415
+ font-size: 0.6rem;
416
+ font-weight: 900;
417
+ text-transform: uppercase;
418
+ letter-spacing: 0.12em;
419
+ color: var(--tvd-text-muted);
420
+ }
421
+
422
+ .tvd-stat-opt .tvd-stat-label {
423
+ color: var(--tvd-primary);
424
+ }
425
+
426
+ .tvd-stat-val {
427
+ font-size: 1.2rem;
428
+ font-weight: 900;
429
+ color: var(--tvd-text);
430
+ }
431
+
432
+ .tvd-stat-opt .tvd-stat-val {
433
+ font-size: 1.75rem;
434
+ color: var(--tvd-primary);
435
+ }
@@ -0,0 +1,66 @@
1
+ import { calculateViewingDistance, getDimensionsFromDiagonal, type TVSpecs } from './logic';
2
+ import type { TvDistanceUI } from './index';
3
+
4
+ export function initTvDistance() {
5
+ const root = document.getElementById('tv-distance-root');
6
+ if (!root) return;
7
+
8
+ const labels = JSON.parse(root.dataset.ui || '{}') as TvDistanceUI;
9
+
10
+ const diagonalSlider = root.querySelector('#diagonal-slider') as HTMLInputElement;
11
+ const diagonalValDisplay = root.querySelector('#diagonal-val-display') as HTMLElement;
12
+ const resButtons = root.querySelectorAll('.res-btn') as NodeListOf<HTMLButtonElement>;
13
+
14
+ const tvVisual = root.querySelector('#tv-visual') as HTMLElement;
15
+ const tvWidthDisplay = root.querySelector('#tv-width-display') as HTMLElement;
16
+ const distanceLine = root.querySelector('#distance-line') as HTMLElement;
17
+ const distanceBubbleVal = root.querySelector('#distance-bubble-val') as HTMLElement;
18
+
19
+ const minVal = root.querySelector('#min-val') as HTMLElement;
20
+ const optVal = root.querySelector('#opt-val') as HTMLElement;
21
+ const maxVal = root.querySelector('#max-val') as HTMLElement;
22
+
23
+ let currentRes: TVSpecs['resolution'] = "4k";
24
+
25
+ const updateUI = () => {
26
+ const diagonal = parseInt(diagonalSlider.value);
27
+ diagonalValDisplay.textContent = `${diagonal}"`;
28
+
29
+ const specs: TVSpecs = {
30
+ diagonalInches: diagonal,
31
+ resolution: currentRes,
32
+ aspectRatio: 16 / 9
33
+ };
34
+
35
+ const dist = calculateViewingDistance(specs);
36
+ const dims = getDimensionsFromDiagonal(diagonal);
37
+
38
+ minVal.textContent = `${dist.min.toFixed(2)}${labels.unitMeters}`;
39
+ optVal.textContent = `${dist.optimal.toFixed(2)}${labels.unitMeters}`;
40
+ maxVal.textContent = `${dist.max.toFixed(2)}${labels.unitMeters}`;
41
+ distanceBubbleVal.textContent = `${dist.optimal.toFixed(2)}${labels.unitMeters}`;
42
+ tvWidthDisplay.textContent = `${Math.round(dims.width)} ${labels.unitCm}`;
43
+
44
+ const scale = 1.8;
45
+ tvVisual.style.width = `${dims.width * scale}px`;
46
+ tvVisual.style.height = `${dims.height * scale}px`;
47
+
48
+ const gapHeight = dist.optimal * 80;
49
+ distanceLine.style.height = `${gapHeight}px`;
50
+
51
+ resButtons.forEach(btn => {
52
+ if (btn.dataset.res === currentRes) btn.classList.add('active');
53
+ else btn.classList.remove('active');
54
+ });
55
+ };
56
+
57
+ diagonalSlider.oninput = updateUI;
58
+ resButtons.forEach(btn => {
59
+ btn.onclick = () => {
60
+ currentRes = btn.dataset.res as TVSpecs['resolution'];
61
+ updateUI();
62
+ };
63
+ });
64
+
65
+ updateUI();
66
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { videoFrameExtractor } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await videoFrameExtractor.i18n[locale]?.();
12
+ if (!content || !content.bibliography || content.bibliography.length === 0) return null;
13
+
14
+ const { bibliography, bibliographyTitle = 'Bibliografía' } = content;
15
+ ---
16
+
17
+ <SharedBibliography title={bibliographyTitle} links={bibliography} />