@jjlmoya/utils-hardware 1.15.0 → 1.16.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 (152) hide show
  1. package/package.json +1 -1
  2. package/src/category/index.ts +2 -1
  3. package/src/entries.ts +4 -1
  4. package/src/index.ts +1 -0
  5. package/src/pages/[locale]/[slug].astro +28 -12
  6. package/src/tests/locale_completeness.test.ts +6 -20
  7. package/src/tests/shared-test-helpers.ts +56 -0
  8. package/src/tests/tool_exports.test.ts +34 -0
  9. package/src/tests/tool_validation.test.ts +2 -2
  10. package/src/tool/batteryHealthEstimator/bibliography.ts +13 -0
  11. package/src/tool/batteryHealthEstimator/i18n/de.ts +2 -12
  12. package/src/tool/batteryHealthEstimator/i18n/en.ts +2 -12
  13. package/src/tool/batteryHealthEstimator/i18n/es.ts +2 -12
  14. package/src/tool/batteryHealthEstimator/i18n/fr.ts +2 -12
  15. package/src/tool/batteryHealthEstimator/i18n/id.ts +2 -12
  16. package/src/tool/batteryHealthEstimator/i18n/it.ts +2 -12
  17. package/src/tool/batteryHealthEstimator/i18n/ja.ts +2 -12
  18. package/src/tool/batteryHealthEstimator/i18n/ko.ts +2 -12
  19. package/src/tool/batteryHealthEstimator/i18n/nl.ts +2 -12
  20. package/src/tool/batteryHealthEstimator/i18n/pl.ts +2 -12
  21. package/src/tool/batteryHealthEstimator/i18n/pt.ts +2 -12
  22. package/src/tool/batteryHealthEstimator/i18n/ru.ts +2 -12
  23. package/src/tool/batteryHealthEstimator/i18n/sv.ts +2 -12
  24. package/src/tool/batteryHealthEstimator/i18n/tr.ts +2 -12
  25. package/src/tool/batteryHealthEstimator/i18n/zh.ts +2 -12
  26. package/src/tool/batteryHealthEstimator/seo.astro +3 -2
  27. package/src/tool/deadPixelTest/bibliography.ts +13 -0
  28. package/src/tool/deadPixelTest/i18n/de.ts +2 -12
  29. package/src/tool/deadPixelTest/i18n/en.ts +2 -12
  30. package/src/tool/deadPixelTest/i18n/es.ts +2 -12
  31. package/src/tool/deadPixelTest/i18n/fr.ts +2 -12
  32. package/src/tool/deadPixelTest/i18n/id.ts +2 -12
  33. package/src/tool/deadPixelTest/i18n/it.ts +2 -12
  34. package/src/tool/deadPixelTest/i18n/ja.ts +2 -12
  35. package/src/tool/deadPixelTest/i18n/ko.ts +2 -12
  36. package/src/tool/deadPixelTest/i18n/nl.ts +2 -12
  37. package/src/tool/deadPixelTest/i18n/pl.ts +2 -12
  38. package/src/tool/deadPixelTest/i18n/pt.ts +2 -12
  39. package/src/tool/deadPixelTest/i18n/ru.ts +2 -12
  40. package/src/tool/deadPixelTest/i18n/sv.ts +2 -12
  41. package/src/tool/deadPixelTest/i18n/tr.ts +2 -12
  42. package/src/tool/deadPixelTest/i18n/zh.ts +2 -12
  43. package/src/tool/deadPixelTest/seo.astro +3 -2
  44. package/src/tool/gamepadTest/bibliography.ts +12 -0
  45. package/src/tool/gamepadTest/i18n/de.ts +2 -12
  46. package/src/tool/gamepadTest/i18n/en.ts +2 -12
  47. package/src/tool/gamepadTest/i18n/es.ts +2 -12
  48. package/src/tool/gamepadTest/i18n/fr.ts +2 -12
  49. package/src/tool/gamepadTest/i18n/id.ts +2 -12
  50. package/src/tool/gamepadTest/i18n/it.ts +2 -12
  51. package/src/tool/gamepadTest/i18n/ja.ts +2 -12
  52. package/src/tool/gamepadTest/i18n/ko.ts +2 -12
  53. package/src/tool/gamepadTest/i18n/nl.ts +2 -12
  54. package/src/tool/gamepadTest/i18n/pl.ts +2 -12
  55. package/src/tool/gamepadTest/i18n/pt.ts +2 -12
  56. package/src/tool/gamepadTest/i18n/ru.ts +2 -12
  57. package/src/tool/gamepadTest/i18n/sv.ts +2 -12
  58. package/src/tool/gamepadTest/i18n/tr.ts +2 -12
  59. package/src/tool/gamepadTest/i18n/zh.ts +2 -12
  60. package/src/tool/gamepadTest/seo.astro +3 -2
  61. package/src/tool/gamepadVibrationTester/bibliography.ts +13 -0
  62. package/src/tool/gamepadVibrationTester/i18n/de.ts +2 -12
  63. package/src/tool/gamepadVibrationTester/i18n/en.ts +2 -12
  64. package/src/tool/gamepadVibrationTester/i18n/es.ts +2 -12
  65. package/src/tool/gamepadVibrationTester/i18n/fr.ts +2 -12
  66. package/src/tool/gamepadVibrationTester/i18n/id.ts +2 -12
  67. package/src/tool/gamepadVibrationTester/i18n/it.ts +2 -12
  68. package/src/tool/gamepadVibrationTester/i18n/ja.ts +2 -12
  69. package/src/tool/gamepadVibrationTester/i18n/ko.ts +2 -12
  70. package/src/tool/gamepadVibrationTester/i18n/nl.ts +2 -12
  71. package/src/tool/gamepadVibrationTester/i18n/pl.ts +2 -12
  72. package/src/tool/gamepadVibrationTester/i18n/pt.ts +2 -12
  73. package/src/tool/gamepadVibrationTester/i18n/ru.ts +2 -12
  74. package/src/tool/gamepadVibrationTester/i18n/sv.ts +2 -12
  75. package/src/tool/gamepadVibrationTester/i18n/tr.ts +2 -12
  76. package/src/tool/gamepadVibrationTester/i18n/zh.ts +2 -12
  77. package/src/tool/gamepadVibrationTester/seo.astro +3 -2
  78. package/src/tool/keyboardTest/bibliography.ts +13 -0
  79. package/src/tool/keyboardTest/i18n/de.ts +2 -12
  80. package/src/tool/keyboardTest/i18n/en.ts +2 -12
  81. package/src/tool/keyboardTest/i18n/es.ts +2 -12
  82. package/src/tool/keyboardTest/i18n/fr.ts +2 -12
  83. package/src/tool/keyboardTest/i18n/id.ts +2 -12
  84. package/src/tool/keyboardTest/i18n/it.ts +2 -12
  85. package/src/tool/keyboardTest/i18n/ja.ts +2 -12
  86. package/src/tool/keyboardTest/i18n/ko.ts +2 -12
  87. package/src/tool/keyboardTest/i18n/nl.ts +2 -12
  88. package/src/tool/keyboardTest/i18n/pl.ts +2 -12
  89. package/src/tool/keyboardTest/i18n/pt.ts +2 -12
  90. package/src/tool/keyboardTest/i18n/ru.ts +2 -12
  91. package/src/tool/keyboardTest/i18n/sv.ts +2 -12
  92. package/src/tool/keyboardTest/i18n/tr.ts +2 -12
  93. package/src/tool/keyboardTest/i18n/zh.ts +2 -12
  94. package/src/tool/keyboardTest/seo.astro +3 -2
  95. package/src/tool/mousePollingTest/bibliography.ts +13 -0
  96. package/src/tool/mousePollingTest/i18n/de.ts +2 -12
  97. package/src/tool/mousePollingTest/i18n/en.ts +2 -12
  98. package/src/tool/mousePollingTest/i18n/es.ts +2 -12
  99. package/src/tool/mousePollingTest/i18n/fr.ts +2 -12
  100. package/src/tool/mousePollingTest/i18n/id.ts +2 -12
  101. package/src/tool/mousePollingTest/i18n/it.ts +2 -12
  102. package/src/tool/mousePollingTest/i18n/ja.ts +2 -12
  103. package/src/tool/mousePollingTest/i18n/ko.ts +2 -12
  104. package/src/tool/mousePollingTest/i18n/nl.ts +2 -12
  105. package/src/tool/mousePollingTest/i18n/pl.ts +2 -12
  106. package/src/tool/mousePollingTest/i18n/pt.ts +2 -12
  107. package/src/tool/mousePollingTest/i18n/ru.ts +2 -12
  108. package/src/tool/mousePollingTest/i18n/sv.ts +2 -12
  109. package/src/tool/mousePollingTest/i18n/tr.ts +2 -12
  110. package/src/tool/mousePollingTest/i18n/zh.ts +2 -12
  111. package/src/tool/mousePollingTest/seo.astro +3 -2
  112. package/src/tool/refreshRateDetector/bibliography.astro +14 -0
  113. package/src/tool/refreshRateDetector/bibliography.ts +20 -0
  114. package/src/tool/refreshRateDetector/component.astro +206 -0
  115. package/src/tool/refreshRateDetector/entry.ts +29 -0
  116. package/src/tool/refreshRateDetector/i18n/de.ts +196 -0
  117. package/src/tool/refreshRateDetector/i18n/en.ts +196 -0
  118. package/src/tool/refreshRateDetector/i18n/es.ts +196 -0
  119. package/src/tool/refreshRateDetector/i18n/fr.ts +196 -0
  120. package/src/tool/refreshRateDetector/i18n/id.ts +196 -0
  121. package/src/tool/refreshRateDetector/i18n/it.ts +196 -0
  122. package/src/tool/refreshRateDetector/i18n/ja.ts +196 -0
  123. package/src/tool/refreshRateDetector/i18n/ko.ts +196 -0
  124. package/src/tool/refreshRateDetector/i18n/nl.ts +196 -0
  125. package/src/tool/refreshRateDetector/i18n/pl.ts +196 -0
  126. package/src/tool/refreshRateDetector/i18n/pt.ts +196 -0
  127. package/src/tool/refreshRateDetector/i18n/ru.ts +196 -0
  128. package/src/tool/refreshRateDetector/i18n/sv.ts +196 -0
  129. package/src/tool/refreshRateDetector/i18n/tr.ts +196 -0
  130. package/src/tool/refreshRateDetector/i18n/zh.ts +196 -0
  131. package/src/tool/refreshRateDetector/index.ts +11 -0
  132. package/src/tool/refreshRateDetector/monitor-refresh-rate-detector.css +342 -0
  133. package/src/tool/refreshRateDetector/seo.astro +15 -0
  134. package/src/tool/refreshRateDetector/ui.ts +24 -0
  135. package/src/tool/toneGenerator/bibliography.ts +13 -0
  136. package/src/tool/toneGenerator/i18n/de.ts +2 -12
  137. package/src/tool/toneGenerator/i18n/en.ts +2 -12
  138. package/src/tool/toneGenerator/i18n/es.ts +2 -12
  139. package/src/tool/toneGenerator/i18n/fr.ts +2 -12
  140. package/src/tool/toneGenerator/i18n/id.ts +2 -12
  141. package/src/tool/toneGenerator/i18n/it.ts +2 -12
  142. package/src/tool/toneGenerator/i18n/ja.ts +2 -12
  143. package/src/tool/toneGenerator/i18n/ko.ts +2 -12
  144. package/src/tool/toneGenerator/i18n/nl.ts +2 -12
  145. package/src/tool/toneGenerator/i18n/pl.ts +2 -12
  146. package/src/tool/toneGenerator/i18n/pt.ts +2 -12
  147. package/src/tool/toneGenerator/i18n/ru.ts +2 -12
  148. package/src/tool/toneGenerator/i18n/sv.ts +2 -12
  149. package/src/tool/toneGenerator/i18n/tr.ts +2 -12
  150. package/src/tool/toneGenerator/i18n/zh.ts +2 -12
  151. package/src/tool/toneGenerator/seo.astro +3 -2
  152. package/src/tools.ts +2 -1
@@ -0,0 +1,196 @@
1
+ import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
+ import type { ToolLocaleContent } from '../../../types';
3
+ import type { RefreshRateDetectorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
5
+
6
+ const slug = 'monitor-refresh-rate-detector';
7
+ const title = '显示器刷新率在线测试仪';
8
+ const description = '使用 requestAnimationFrame 毫秒级精准检测显示器的刷新率。测试帧稳定性,并与行业标准进行对比。';
9
+
10
+ const faqData = [
11
+ {
12
+ question: '什么是刷新率 (Hz)?',
13
+ answer: '刷新率是指显示器每秒更新图像的次数。60Hz 的显示器每秒更新 60 次,而 144Hz 则更新 144 次。刷新率越高,画面动态效果越流畅。',
14
+ },
15
+ {
16
+ question: '这个检测器有多准?',
17
+ answer: '本工具利用与显示器刷新周期同步的 requestAnimationFrame API。准确度取决于系统负载。稳定模式通过延长测量时间来提供更高的精度。',
18
+ },
19
+ {
20
+ question: '稳定模式和快速模式有什么区别?',
21
+ answer: '快速模式测量时间较短(约 3 秒),可快速反馈。稳定模式时间较长(约 10 秒),旨在滤除系统噪点,提供更可靠的结果。',
22
+ },
23
+ {
24
+ question: '为什么检测到的 Hz 与显示器规格不符?',
25
+ answer: '可能的原因包括:电缆连接松动、驱动程序过时或操作系统缩放设置干扰。请尝试重新插拔显示电缆或更新显卡驱动程序。',
26
+ },
27
+ {
28
+ question: '现代显示器支持哪些刷新率?',
29
+ answer: '常见的标准包括 60Hz(基础)、75Hz、120Hz、144Hz(游戏级)、240Hz(竞技级)和 360Hz(专业电竞)。',
30
+ },
31
+ ];
32
+
33
+ const howToData = [
34
+ {
35
+ name: '加载工具',
36
+ text: '只需打开此页面,检测器便会立即开始测量。',
37
+ },
38
+ {
39
+ name: '等待稳定',
40
+ text: '选择稳定或快速模式。请勿移动窗口,等待测量完成。',
41
+ },
42
+ {
43
+ name: '检查仪表盘',
44
+ text: '显示器的刷新率将显示在平滑的仪表盘上,并附带基准统计数据(最小/最大/平均)。',
45
+ },
46
+ {
47
+ name: '对比标准',
48
+ text: '工具会显示您的显示器符合哪项标准(60, 75, 120, 144, 240, 360Hz)。',
49
+ },
50
+ {
51
+ name: '可选:跳帧测试',
52
+ text: '观察屏幕上的动画方块,通过视觉确认画面的流畅度。',
53
+ },
54
+ ];
55
+
56
+ const faqSchema: WithContext<FAQPage> = {
57
+ '@context': 'https://schema.org',
58
+ '@type': 'FAQPage',
59
+ mainEntity: faqData.map((item) => ({
60
+ '@type': 'Question',
61
+ name: item.question,
62
+ acceptedAnswer: { '@type': 'Answer', text: item.answer },
63
+ })),
64
+ };
65
+
66
+ const howToSchema: WithContext<HowTo> = {
67
+ '@context': 'https://schema.org',
68
+ '@type': 'HowTo',
69
+ name: title,
70
+ description,
71
+ step: howToData.map((step, i) => ({
72
+ '@type': 'HowToStep',
73
+ position: i + 1,
74
+ name: step.name,
75
+ text: step.text,
76
+ })),
77
+ };
78
+
79
+ const appSchema: WithContext<SoftwareApplication> = {
80
+ '@context': 'https://schema.org',
81
+ '@type': 'SoftwareApplication',
82
+ name: title,
83
+ description,
84
+ applicationCategory: 'UtilityApplication',
85
+ operatingSystem: 'All',
86
+ offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
87
+ inLanguage: 'zh',
88
+ };
89
+
90
+ export const content: ToolLocaleContent<RefreshRateDetectorUI> = {
91
+ slug,
92
+ title,
93
+ description,
94
+ faq: faqData,
95
+ bibliography,
96
+ howTo: howToData,
97
+ schemas: [faqSchema, howToSchema, appSchema],
98
+ seo: [
99
+ {
100
+ type: 'title',
101
+ text: '显示器刷新率测试仪:在线检测屏幕 Hz 频率',
102
+ level: 2,
103
+ },
104
+ {
105
+ type: 'paragraph',
106
+ html: '毫秒级精准检测显示器的刷新率(60Hz, 144Hz, 240Hz 等)。测试帧稳定性,验证显示器是否在额定规格下正常运行。',
107
+ },
108
+ {
109
+ type: 'title',
110
+ text: '为什么显示器刷新率很重要',
111
+ level: 3,
112
+ },
113
+ {
114
+ type: 'paragraph',
115
+ html: '刷新率决定了屏幕上动作的流畅程度。游戏玩家能从 144Hz+ 的显示器中获益,而普通用户通常觉得 60Hz 已足够。此工具可帮助确认您的显示器是否真正达到了宣传的刷新率。',
116
+ },
117
+ {
118
+ type: 'title',
119
+ text: '如何检测您的刷新率',
120
+ level: 3,
121
+ },
122
+ {
123
+ type: 'list',
124
+ items: [
125
+ '加载此检测器——测量立即开始',
126
+ '选择快速(3秒)或稳定(10秒)测量模式',
127
+ '从仪表盘读取显示器的 Hz 数值',
128
+ '对照行业标准(60, 75, 120, 144, 240, 360Hz)',
129
+ ],
130
+ },
131
+ {
132
+ type: 'title',
133
+ text: '常见的刷新率标准',
134
+ level: 3,
135
+ },
136
+ {
137
+ type: 'table',
138
+ headers: ['标准', '应用场景', '典型用户'],
139
+ rows: [
140
+ ['60Hz', '普通办公', '办公、网页浏览'],
141
+ ['75Hz', '轻度游戏', '休闲玩家'],
142
+ ['120Hz', '多媒体', '主机游戏、流媒体'],
143
+ ['144Hz', '竞技游戏', 'FPS、快节奏游戏'],
144
+ ['240Hz+', '专业电竞', '职业选手、主播'],
145
+ ],
146
+ },
147
+ {
148
+ type: 'title',
149
+ text: '故障排除:显示刷新率低于预期',
150
+ level: 3,
151
+ },
152
+ {
153
+ type: 'list',
154
+ items: [
155
+ '检查 HDMI/DisplayPort 线缆连接——接触不良会降低带宽',
156
+ '更新显卡驱动程序(NVIDIA, AMD, Intel)',
157
+ '检查系统的显示设置,确保已启用高刷新率',
158
+ '尝试更换显示器上的线缆或接口',
159
+ '重启电脑并重新测试',
160
+ ],
161
+ },
162
+ {
163
+ type: 'title',
164
+ text: '检测背后的技术',
165
+ level: 3,
166
+ },
167
+ {
168
+ type: 'paragraph',
169
+ html: '本工具使用浏览器的 requestAnimationFrame API,它直接与显示器的刷新周期同步。通过测量动画帧之间的时间,我们能以高精度计算出准确的刷新率,无需特殊硬件支持。',
170
+ },
171
+ ],
172
+ ui: {
173
+ badge: '显示测试',
174
+ title: '刷新率检测仪',
175
+ description: '立即检测您的显示器刷新率',
176
+ modeStable: '稳定(10秒,更精准)',
177
+ modeFast: '快速(3秒,更迅速)',
178
+ measurementStatus: '正在测量...',
179
+ currentHz: '当前',
180
+ averageHz: '平均',
181
+ maxHz: '最大',
182
+ minHz: '最小',
183
+ standardDetected: '检测到标准',
184
+ frameSkippingTest: '跳帧测试',
185
+ startMeasurement: '开始测量',
186
+ resetMeasurement: '重置',
187
+ copyResult: '复制结果',
188
+ copiedFeedback: '已复制到剪贴板!',
189
+ optimalConfiguration: '[OK] 最佳配置',
190
+ suboptimalConfiguration: '[警告] 低于最佳水平',
191
+ unstableRefreshRate: '[警告] 刷新率不稳定',
192
+ measurementNotStarted: '准备就绪',
193
+ switchMonitorHint: '将窗口拖动到另一个显示器以进行测试',
194
+ incompatibleBrowserMsg: '您的浏览器不支持 requestAnimationFrame',
195
+ },
196
+ };
@@ -0,0 +1,11 @@
1
+ import { refreshRateDetector } from './entry';
2
+ import type { ToolDefinition } from '../../types';
3
+
4
+ export * from './entry';
5
+
6
+ export const REFRESH_RATE_DETECTOR_TOOL: ToolDefinition = {
7
+ entry: refreshRateDetector,
8
+ Component: () => import('./component.astro'),
9
+ SEOComponent: () => import('./seo.astro'),
10
+ BibliographyComponent: () => import('./bibliography.astro'),
11
+ };
@@ -0,0 +1,342 @@
1
+ /* Variables for light mode */
2
+ :root {
3
+ --rrd-bg: #fff;
4
+ --rrd-text: #000;
5
+ --rrd-text-muted: #555;
6
+ --rrd-border: #d0d0d0;
7
+ --rrd-metric-bg: #f8f8f8;
8
+ --rrd-button-bg: #f0f0f0;
9
+ --rrd-button-border: #aaa;
10
+ --rrd-button-text: #222;
11
+ --rrd-monitor-bezel: #222;
12
+ --rrd-monitor-screen: #050505;
13
+ --rrd-hz-text: #fff;
14
+ }
15
+
16
+ /* Variables for dark mode */
17
+ .theme-dark {
18
+ --rrd-bg: #1a1a1a;
19
+ --rrd-text: #f0f0f0;
20
+ --rrd-text-muted: #999;
21
+ --rrd-border: #333;
22
+ --rrd-metric-bg: #2a2a2a;
23
+ --rrd-button-bg: #2a2a2a;
24
+ --rrd-button-border: #444;
25
+ --rrd-button-text: #e0e0e0;
26
+ --rrd-monitor-bezel: #f5f5f5;
27
+ --rrd-monitor-screen: #0a0a0a;
28
+ --rrd-hz-text: #fff;
29
+ }
30
+
31
+ .rrd-wrapper {
32
+ width: 100%;
33
+ max-width: 100%;
34
+ background: transparent;
35
+ color: var(--rrd-text);
36
+ }
37
+
38
+ .rrd-container {
39
+ width: 100%;
40
+ max-width: 100%;
41
+ display: flex;
42
+ justify-content: center;
43
+ }
44
+
45
+ .rrd-card {
46
+ width: 100%;
47
+ max-width: 800px;
48
+ background: var(--rrd-bg);
49
+ border: 1px solid var(--rrd-border);
50
+ border-radius: 12px;
51
+ padding: 2rem;
52
+ }
53
+
54
+ .rrd-layout {
55
+ display: grid;
56
+ grid-template-columns: 1fr 1fr;
57
+ gap: 2rem;
58
+ align-items: stretch;
59
+ }
60
+
61
+ /* LEFT COLUMN: METRICS */
62
+ .rrd-left {
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: 2rem;
66
+ }
67
+
68
+ .rrd-metrics {
69
+ display: grid;
70
+ grid-template-columns: 1fr 1fr;
71
+ gap: 1.5rem;
72
+ }
73
+
74
+ .rrd-metric-item {
75
+ display: flex;
76
+ flex-direction: column;
77
+ gap: 0.5rem;
78
+ padding: 1rem;
79
+ background: var(--rrd-metric-bg);
80
+ border: 1px solid var(--rrd-border);
81
+ border-radius: 8px;
82
+ }
83
+
84
+ .rrd-metric-label {
85
+ font-size: 0.75rem;
86
+ font-weight: 600;
87
+ text-transform: uppercase;
88
+ letter-spacing: 0.05em;
89
+ color: var(--rrd-text-muted);
90
+ }
91
+
92
+ .rrd-metric-value {
93
+ font-size: 1.5rem;
94
+ font-weight: 700;
95
+ color: var(--rrd-text);
96
+ }
97
+
98
+ .rrd-metric-unit {
99
+ font-size: 0.75rem;
100
+ color: var(--rrd-text-muted);
101
+ }
102
+
103
+ .rrd-mode-selector {
104
+ display: grid;
105
+ grid-template-columns: 1fr 1fr;
106
+ gap: 0.75rem;
107
+ }
108
+
109
+ .rrd-mode-btn {
110
+ padding: 0.75rem 1rem;
111
+ background: var(--rrd-button-bg);
112
+ border: 2px solid var(--rrd-button-border);
113
+ border-radius: 6px;
114
+ color: var(--rrd-button-text);
115
+ font-size: 0.875rem;
116
+ font-weight: 600;
117
+ cursor: pointer;
118
+ transition: all 200ms ease;
119
+ }
120
+
121
+ .rrd-mode-btn:hover {
122
+ background: var(--rrd-border);
123
+ }
124
+
125
+ .rrd-mode-btn.selected {
126
+ background: var(--rrd-text);
127
+ color: var(--rrd-bg);
128
+ border-color: var(--rrd-text);
129
+ }
130
+
131
+ /* RIGHT COLUMN: MONITOR */
132
+ .rrd-right {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: center;
136
+ min-height: 300px;
137
+ }
138
+
139
+ .rrd-monitor {
140
+ display: flex;
141
+ flex-direction: column;
142
+ align-items: center;
143
+ gap: 1rem;
144
+ }
145
+
146
+ .rrd-monitor-bezel {
147
+ width: 280px;
148
+ aspect-ratio: 16 / 10;
149
+ background: var(--rrd-monitor-bezel);
150
+ border: 8px solid var(--rrd-monitor-bezel);
151
+ border-radius: 12px;
152
+ padding: 12px;
153
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
154
+ }
155
+
156
+ .rrd-monitor-screen {
157
+ width: 100%;
158
+ height: 100%;
159
+ background: var(--rrd-monitor-screen);
160
+ border-radius: 4px;
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ overflow: hidden;
165
+ }
166
+
167
+ .rrd-monitor-content {
168
+ width: 100%;
169
+ height: 100%;
170
+ display: flex;
171
+ align-items: center;
172
+ justify-content: center;
173
+ position: relative;
174
+ overflow: hidden;
175
+ }
176
+
177
+ .rrd-monitor-content::before {
178
+ content: '';
179
+ position: absolute;
180
+ top: 0;
181
+ left: 0;
182
+ width: 100%;
183
+ height: 100%;
184
+ background: repeating-linear-gradient(
185
+ 0deg,
186
+ rgba(255, 255, 255, 0.15) 0,
187
+ rgba(255, 255, 255, 0.15) 1px,
188
+ transparent 1px,
189
+ transparent 2px
190
+ );
191
+ pointer-events: none;
192
+ animation: rrd-scanlines 8ms linear infinite;
193
+ z-index: 1;
194
+ }
195
+
196
+ .rrd-hz-display {
197
+ font-size: 4rem;
198
+ font-weight: 900;
199
+ color: var(--rrd-hz-text);
200
+ line-height: 1;
201
+ text-shadow:
202
+ 0 0 10px rgba(255, 255, 255, 0.8),
203
+ 0 0 20px rgba(255, 255, 255, 0.6),
204
+ 0 0 40px rgba(255, 255, 255, 0.4);
205
+ position: relative;
206
+ z-index: 2;
207
+ animation: rrd-glitch 2s ease-in-out infinite;
208
+ letter-spacing: 0.1em;
209
+ }
210
+
211
+ .rrd-monitor-stand {
212
+ width: 100px;
213
+ height: 12px;
214
+ background: var(--rrd-monitor-bezel);
215
+ border-radius: 6px;
216
+ position: relative;
217
+ }
218
+
219
+ .rrd-monitor-stand::after {
220
+ content: '';
221
+ position: absolute;
222
+ bottom: -8px;
223
+ left: 50%;
224
+ transform: translateX(-50%);
225
+ width: 60px;
226
+ height: 8px;
227
+ background: var(--rrd-monitor-bezel);
228
+ border-radius: 4px;
229
+ }
230
+
231
+ @keyframes rrd-scanlines {
232
+ 0% {
233
+ transform: translateY(0);
234
+ }
235
+ 100% {
236
+ transform: translateY(20px);
237
+ }
238
+ }
239
+
240
+ @keyframes rrd-glitch {
241
+ 0%, 100% {
242
+ opacity: 1;
243
+ transform: translate(0, 0) skewX(0deg);
244
+ text-shadow:
245
+ 0 0 10px rgba(255, 255, 255, 0.8),
246
+ 0 0 20px rgba(255, 255, 255, 0.6),
247
+ 0 0 40px rgba(255, 255, 255, 0.4);
248
+ filter: contrast(1);
249
+ }
250
+ 10% {
251
+ opacity: 0.8;
252
+ transform: translate(-3px, 2px) skewX(-5deg);
253
+ text-shadow:
254
+ -3px 0 15px rgba(255, 255, 255, 0.9),
255
+ 3px 0 15px rgba(255, 255, 255, 0.5);
256
+ filter: contrast(1.2);
257
+ }
258
+ 20% {
259
+ opacity: 1;
260
+ transform: translate(3px, -2px) skewX(5deg);
261
+ text-shadow:
262
+ 3px 0 15px rgba(255, 255, 255, 0.9),
263
+ -3px 0 15px rgba(255, 255, 255, 0.5);
264
+ filter: contrast(1.3);
265
+ }
266
+ 30% {
267
+ opacity: 0.85;
268
+ transform: translate(-2px, 1px) skewX(-3deg);
269
+ text-shadow:
270
+ 0 0 20px rgba(255, 255, 255, 0.7),
271
+ 0 0 40px rgba(255, 255, 255, 0.5);
272
+ filter: contrast(1.1);
273
+ }
274
+ 40% {
275
+ opacity: 1;
276
+ transform: translate(2px, -1px) skewX(3deg);
277
+ text-shadow:
278
+ 0 0 30px rgba(255, 255, 255, 0.8),
279
+ 0 0 50px rgba(255, 255, 255, 0.6);
280
+ filter: contrast(1.4);
281
+ }
282
+ 50% {
283
+ opacity: 0.9;
284
+ transform: translate(-1px, 0) skewX(-2deg);
285
+ text-shadow:
286
+ -2px 0 10px rgba(255, 255, 255, 0.8),
287
+ 2px 0 10px rgba(255, 255, 255, 0.6);
288
+ filter: contrast(1.2);
289
+ }
290
+ 60% {
291
+ opacity: 1;
292
+ transform: translate(1px, 2px) skewX(2deg);
293
+ text-shadow:
294
+ 0 0 25px rgba(255, 255, 255, 0.9),
295
+ 0 0 45px rgba(255, 255, 255, 0.7);
296
+ filter: contrast(1.3);
297
+ }
298
+ 70% {
299
+ opacity: 0.8;
300
+ transform: translate(-2px, -2px) skewX(-4deg);
301
+ text-shadow:
302
+ -3px 0 20px rgba(255, 255, 255, 0.85),
303
+ 3px 0 20px rgba(255, 255, 255, 0.5);
304
+ filter: contrast(1.25);
305
+ }
306
+ 80% {
307
+ opacity: 1;
308
+ transform: translate(2px, 1px) skewX(4deg);
309
+ text-shadow:
310
+ 3px 0 20px rgba(255, 255, 255, 0.85),
311
+ -3px 0 20px rgba(255, 255, 255, 0.5);
312
+ filter: contrast(1.35);
313
+ }
314
+ 90% {
315
+ opacity: 0.9;
316
+ transform: translate(-1px, -1px) skewX(-2deg);
317
+ text-shadow:
318
+ 0 0 15px rgba(255, 255, 255, 0.8),
319
+ 0 0 30px rgba(255, 255, 255, 0.6);
320
+ filter: contrast(1.15);
321
+ }
322
+ }
323
+
324
+ /* Responsive */
325
+ @media (max-width: 768px) {
326
+ .rrd-layout {
327
+ grid-template-columns: 1fr;
328
+ gap: 1.5rem;
329
+ }
330
+
331
+ .rrd-metrics {
332
+ grid-template-columns: 1fr 1fr;
333
+ }
334
+
335
+ .rrd-monitor-bezel {
336
+ width: 240px;
337
+ }
338
+
339
+ .rrd-hz-display {
340
+ font-size: 3rem;
341
+ }
342
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { refreshRateDetector } 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 refreshRateDetector.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,24 @@
1
+ export interface RefreshRateDetectorUI extends Record<string, string> {
2
+ badge: string;
3
+ title: string;
4
+ description: string;
5
+ modeStable: string;
6
+ modeFast: string;
7
+ measurementStatus: string;
8
+ currentHz: string;
9
+ averageHz: string;
10
+ maxHz: string;
11
+ minHz: string;
12
+ standardDetected: string;
13
+ frameSkippingTest: string;
14
+ startMeasurement: string;
15
+ resetMeasurement: string;
16
+ copyResult: string;
17
+ copiedFeedback: string;
18
+ optimalConfiguration: string;
19
+ suboptimalConfiguration: string;
20
+ unstableRefreshRate: string;
21
+ measurementNotStarted: string;
22
+ switchMonitorHint: string;
23
+ incompatibleBrowserMsg: string;
24
+ }
@@ -0,0 +1,13 @@
1
+ import type { BibliographyEntry } from '../../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+
5
+ {
6
+ name: 'MDN Web Docs — Web Audio API',
7
+ url: 'https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API',
8
+ },
9
+ {
10
+ name: 'ISO 226:2023 — Equal-loudness contours',
11
+ url: 'https://www.iso.org/standard/83117.html',
12
+ },
13
+ ];
@@ -1,6 +1,7 @@
1
1
  import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
2
  import type { ToolLocaleContent } from '../../../types';
3
3
  import type { ToneGeneratorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
4
5
 
5
6
  const slug = 'ton-frequenz-generator-online';
6
7
  const title = 'Online Ton und Frequenzgenerator';
@@ -87,21 +88,10 @@ export const content: ToolLocaleContent<ToneGeneratorUI> = {
87
88
  slug,
88
89
  title,
89
90
  description,
90
- faqTitle: 'Häufig gestellte Fragen',
91
91
  faq: faqData,
92
- bibliographyTitle: 'Referenzen',
93
- bibliography: [
94
- {
95
- name: 'MDN Web Docs — Web Audio API',
96
- url: 'https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API',
97
- },
98
- {
99
- name: 'ISO 226:2023 — Equal-loudness contours',
100
- url: 'https://www.iso.org/standard/83117.html',
101
- },
102
- ],
103
92
  howTo: howToData,
104
93
  schemas: [faqSchema, howToSchema, appSchema],
94
+ bibliography,
105
95
  seo: [
106
96
  { type: 'title', text: 'Alles über Frequenzen und Schallwellen', level: 2 },
107
97
  {
@@ -1,6 +1,7 @@
1
1
  import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
2
  import type { ToolLocaleContent } from '../../../types';
3
3
  import type { ToneGeneratorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
4
5
 
5
6
  const slug = 'tone-frequency-generator';
6
7
  const title = 'Online Tone and Frequency Generator';
@@ -87,21 +88,10 @@ export const content: ToolLocaleContent<ToneGeneratorUI> = {
87
88
  slug,
88
89
  title,
89
90
  description,
90
- faqTitle: 'Frequently Asked Questions',
91
91
  faq: faqData,
92
- bibliographyTitle: 'References',
93
- bibliography: [
94
- {
95
- name: 'MDN Web Docs — Web Audio API',
96
- url: 'https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API',
97
- },
98
- {
99
- name: 'ISO 226:2023 — Equal-loudness contours',
100
- url: 'https://www.iso.org/standard/83117.html',
101
- },
102
- ],
103
92
  howTo: howToData,
104
93
  schemas: [faqSchema, howToSchema, appSchema],
94
+ bibliography,
105
95
  seo: [
106
96
  { type: 'title', text: 'Everything About Frequencies and Sound Waves', level: 2 },
107
97
  {
@@ -1,6 +1,7 @@
1
1
  import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
2
2
  import type { ToolLocaleContent } from '../../../types';
3
3
  import type { ToneGeneratorUI } from '../ui';
4
+ import { bibliography } from '../bibliography';
4
5
 
5
6
  const slug = 'generador-tonos';
6
7
  const title = 'Generador de Tonos y Frecuencias Online';
@@ -87,21 +88,10 @@ export const content: ToolLocaleContent<ToneGeneratorUI> = {
87
88
  slug,
88
89
  title,
89
90
  description,
90
- faqTitle: 'Preguntas Frecuentes',
91
91
  faq: faqData,
92
- bibliographyTitle: 'Referencias',
93
- bibliography: [
94
- {
95
- name: 'MDN Web Docs — Web Audio API',
96
- url: 'https://developer.mozilla.org/es/docs/Web/API/Web_Audio_API',
97
- },
98
- {
99
- name: 'ISO 226:2023 — Curvas de igual sonoridad',
100
- url: 'https://www.iso.org/standard/83117.html',
101
- },
102
- ],
103
92
  howTo: howToData,
104
93
  schemas: [faqSchema, howToSchema, appSchema],
94
+ bibliography,
105
95
  seo: [
106
96
  { type: 'title', text: 'Todo sobre Frecuencias y Ondas de Sonido', level: 2 },
107
97
  {