@jjlmoya/utils-drones 1.1.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 (56) hide show
  1. package/package.json +61 -0
  2. package/src/category/i18n/en.ts +107 -0
  3. package/src/category/i18n/es.ts +106 -0
  4. package/src/category/i18n/fr.ts +107 -0
  5. package/src/category/index.ts +15 -0
  6. package/src/category/seo.astro +92 -0
  7. package/src/components/PreviewNavSidebar.astro +116 -0
  8. package/src/components/PreviewToolbar.astro +143 -0
  9. package/src/data.ts +11 -0
  10. package/src/env.d.ts +5 -0
  11. package/src/index.ts +22 -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/seo_section_types.test.ts +75 -0
  22. package/src/tests/tool_validation.test.ts +17 -0
  23. package/src/tool/antenna-length-calculator/AntennaLengthCalculator.css +684 -0
  24. package/src/tool/antenna-length-calculator/bibliography.astro +14 -0
  25. package/src/tool/antenna-length-calculator/component.astro +360 -0
  26. package/src/tool/antenna-length-calculator/i18n/en.ts +204 -0
  27. package/src/tool/antenna-length-calculator/i18n/es.ts +204 -0
  28. package/src/tool/antenna-length-calculator/i18n/fr.ts +204 -0
  29. package/src/tool/antenna-length-calculator/index.ts +27 -0
  30. package/src/tool/antenna-length-calculator/seo.astro +39 -0
  31. package/src/tool/drone-flight-time/FlightTimeCalculator.css +363 -0
  32. package/src/tool/drone-flight-time/bibliography.astro +14 -0
  33. package/src/tool/drone-flight-time/component.astro +262 -0
  34. package/src/tool/drone-flight-time/components/AutonomyChart.astro +13 -0
  35. package/src/tool/drone-flight-time/components/BatterySpecs.astro +46 -0
  36. package/src/tool/drone-flight-time/components/ConsumptionStats.astro +33 -0
  37. package/src/tool/drone-flight-time/components/FlightDashboard.astro +33 -0
  38. package/src/tool/drone-flight-time/i18n/en.ts +200 -0
  39. package/src/tool/drone-flight-time/i18n/es.ts +200 -0
  40. package/src/tool/drone-flight-time/i18n/fr.ts +200 -0
  41. package/src/tool/drone-flight-time/index.ts +27 -0
  42. package/src/tool/drone-flight-time/seo.astro +31 -0
  43. package/src/tool/gps-coordinates-converter/GpsCoordinatesConverter.css +310 -0
  44. package/src/tool/gps-coordinates-converter/bibliography.astro +14 -0
  45. package/src/tool/gps-coordinates-converter/component.astro +355 -0
  46. package/src/tool/gps-coordinates-converter/components/GpsHistory.astro +36 -0
  47. package/src/tool/gps-coordinates-converter/components/GpsInputs.astro +92 -0
  48. package/src/tool/gps-coordinates-converter/components/GpsMap.astro +18 -0
  49. package/src/tool/gps-coordinates-converter/components/GpsResults.astro +50 -0
  50. package/src/tool/gps-coordinates-converter/i18n/en.ts +201 -0
  51. package/src/tool/gps-coordinates-converter/i18n/es.ts +201 -0
  52. package/src/tool/gps-coordinates-converter/i18n/fr.ts +201 -0
  53. package/src/tool/gps-coordinates-converter/index.ts +27 -0
  54. package/src/tool/gps-coordinates-converter/seo.astro +39 -0
  55. package/src/tools.ts +16 -0
  56. package/src/types.ts +72 -0
@@ -0,0 +1,360 @@
1
+ ---
2
+ const { ui } = Astro.props;
3
+ import { Icon } from "astro-icon/components";
4
+ import "./AntennaLengthCalculator.css";
5
+
6
+ const presets = [
7
+ { label: ui.presetFpv, val: 5800 },
8
+ { label: ui.presetWifi, val: 2400 },
9
+ { label: ui.presetElrs, val: 915 },
10
+ { label: ui.presetLrs, val: 868 },
11
+ { label: ui.presetUhf, val: 433 },
12
+ { label: ui.presetMbus, val: 169 },
13
+ ];
14
+
15
+ const materials = [
16
+ { label: ui.materialBareCopper, vf: 0.95 },
17
+ { label: ui.materialPvcInsulated, vf: 0.92 },
18
+ { label: ui.materialSolidRod, vf: 0.97 },
19
+ { label: ui.materialCoaxial, vf: 0.66 },
20
+ ];
21
+ ---
22
+
23
+ <div class="antenna-calculator-ui">
24
+ <div class="tech-mega-card animate-in fade-in zoom-in duration-1000">
25
+ <div class="card-grid">
26
+ <aside class="config-sidebar">
27
+ <section>
28
+ <div class="section-title">
29
+ <Icon name="mdi:sine-wave" />
30
+ <h2>{ui.signalParameters}</h2>
31
+ </div>
32
+
33
+ <div class="input-group">
34
+ <div class="input-with-unit">
35
+ <input type="number" id="freqInput" value="868" step="1" min="1" max="10000" />
36
+ <span class="unit">MHz</span>
37
+ </div>
38
+ </div>
39
+
40
+ <div class="preset-grid mt-6">
41
+ {presets.map(p => (
42
+ <button class="preset-btn" data-val={p.val}>
43
+ {p.label}
44
+ </button>
45
+ ))}
46
+ </div>
47
+ </section>
48
+
49
+ <div class="divider"></div>
50
+
51
+ <section>
52
+ <div class="section-title">
53
+ <Icon name="mdi:antenna" />
54
+ <h2>{ui.antennaType}</h2>
55
+ </div>
56
+ <div class="type-selector">
57
+ <div class="radio-card">
58
+ <input type="radio" name="waveType" value="half" checked id="waveHalf" />
59
+ <label for="waveHalf" class="radio-content">
60
+ <Icon name="mdi:arrow-left-right" />
61
+ <span>{ui.dipole}</span>
62
+ </label>
63
+ </div>
64
+ <div class="radio-card">
65
+ <input type="radio" name="waveType" value="quarter" id="waveQuarter" />
66
+ <label for="waveQuarter" class="radio-content">
67
+ <Icon name="mdi:arrow-up" />
68
+ <span>{ui.whip}</span>
69
+ </label>
70
+ </div>
71
+ </div>
72
+ </section>
73
+
74
+ <section>
75
+ <div class="section-title">
76
+ <Icon name="mdi:layers-triple" />
77
+ <h2>{ui.conductorMedium}</h2>
78
+ </div>
79
+ <div class="input-group">
80
+ <select id="vfInput" class="custom-select-premium">
81
+ {materials.map(m => (
82
+ <option value={m.vf}>{m.label}</option>
83
+ ))}
84
+ </select>
85
+ </div>
86
+ </section>
87
+ </aside>
88
+
89
+ <main class="main-display">
90
+ <div class="results-hero">
91
+ <div class="mega-result gold">
92
+ <span class="result-label">{ui.totalLength}</span>
93
+ <div class="result-value">
94
+ <span class="number" id="totalLength">0</span>
95
+ <span class="unit">mm</span>
96
+ </div>
97
+ </div>
98
+ <div class="mega-result glass" id="branchCard">
99
+ <span class="result-label">{ui.branchLength}</span>
100
+ <div class="result-value">
101
+ <span class="number" id="branchLength">0</span>
102
+ <span class="unit">mm</span>
103
+ </div>
104
+ </div>
105
+ </div>
106
+
107
+ <div class="visualizer-stage">
108
+ <svg id="antenna-svg" viewBox="0 0 400 300" preserveAspectRatio="xMidYMid meet">
109
+ <defs>
110
+ <linearGradient id="wireGrad" x1="0%" y1="0%" x2="100%" y2="0%">
111
+ <stop offset="0%" style="stop-color:#f59e0b;stop-opacity:1" />
112
+ <stop offset="100%" style="stop-color:#fbbf24;stop-opacity:1" />
113
+ </linearGradient>
114
+ </defs>
115
+
116
+ <g id="dipoleLayer">
117
+ <line x1="205" y1="150" x2="350" y2="150" class="antenna-wire active" id="rightArm" />
118
+ <line x1="195" y1="150" x2="50" y2="150" class="antenna-wire active" id="leftArm" />
119
+ <circle cx="200" cy="150" r="4" class="feed-core" />
120
+ <path d="M195,150 Q200,160 205,150" fill="none" stroke="#ef4444" stroke-width="2" class="feed-point-gap" />
121
+
122
+ <line x1="50" y1="170" x2="50" y2="180" class="measuring-line" id="leftTick" />
123
+ <line x1="350" y1="170" x2="350" y2="180" class="measuring-line" id="rightTick" />
124
+ <line x1="50" y1="175" x2="350" y2="175" class="measuring-line" id="measureLine" />
125
+ </g>
126
+
127
+ <g id="whipLayer" style="display: none;">
128
+ <line x1="200" y1="250" x2="200" y2="50" class="antenna-wire active" id="verticalWhip" />
129
+ <rect x="100" y="250" width="200" height="6" rx="3" class="ground-plate" />
130
+ <circle cx="200" cy="250" r="6" class="feed-core" />
131
+
132
+ <line x1="220" y1="50" x2="230" y2="50" class="measuring-line" id="topTick" />
133
+ <line x1="220" y1="250" x2="230" y2="250" class="measuring-line" id="bottomTick" />
134
+ <line x1="225" y1="50" x2="225" y2="250" class="measuring-line" id="verticalMeasure" />
135
+ </g>
136
+
137
+ <text x="200" y="280" class="svg-label-text" id="svgInfoText" text-anchor="middle">{ui.dynamicScheme}</text>
138
+ </svg>
139
+ </div>
140
+
141
+ <div class="harmonics-container">
142
+ <div class="section-title">
143
+ <Icon name="mdi:chart-timeline-variant" />
144
+ <h2>{ui.secondaryResonance}</h2>
145
+ </div>
146
+ <div class="harmonics-list" id="harmonicsList" data-label={ui.harmonicLabel}>
147
+ </div>
148
+ </div>
149
+
150
+ <div class="expert-reference-grid">
151
+ <div class="ref-card">
152
+ <Icon name="mdi:swap-vertical" />
153
+ <div>
154
+ <h4>{ui.swrIdeal}</h4>
155
+ <p>&lt; 1.5:1 (ROE)</p>
156
+ </div>
157
+ </div>
158
+ <div class="ref-card">
159
+ <Icon name="mdi:omega" />
160
+ <div>
161
+ <h4>{ui.impedance}</h4>
162
+ <p>50 Ω System</p>
163
+ </div>
164
+ </div>
165
+ </div>
166
+
167
+ <div class="security-warning">
168
+ <Icon name="mdi:alert-decagram" />
169
+ <p><strong>{ui.criticalNotice}:</strong> {ui.criticalNoticeText}</p>
170
+ </div>
171
+ </main>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <script lang="ts">
177
+ const freqInput = document.getElementById('freqInput') as HTMLInputElement;
178
+ const vfInput = document.getElementById('vfInput') as HTMLSelectElement;
179
+ const waveRadios = document.getElementsByName('waveType') as NodeListOf<HTMLInputElement>;
180
+
181
+ const elements = {
182
+ total: document.getElementById('totalLength'),
183
+ branch: document.getElementById('branchLength'),
184
+ branchCard: document.getElementById('branchCard'),
185
+ dipoleLayer: document.getElementById('dipoleLayer'),
186
+ whipLayer: document.getElementById('whipLayer'),
187
+ harmonics: document.getElementById('harmonicsList'),
188
+ leftArm: document.getElementById('leftArm') as SVGLineElement | null,
189
+ rightArm: document.getElementById('rightArm') as SVGLineElement | null,
190
+ verticalWhip: document.getElementById('verticalWhip') as SVGLineElement | null,
191
+ measureLine: document.getElementById('measureLine') as SVGLineElement | null,
192
+ leftTick: document.getElementById('leftTick') as SVGLineElement | null,
193
+ rightTick: document.getElementById('rightTick') as SVGLineElement | null,
194
+ topTick: document.getElementById('topTick') as SVGLineElement | null,
195
+ bottomTick: document.getElementById('bottomTick') as SVGLineElement | null,
196
+ verticalMeasure: document.getElementById('verticalMeasure') as SVGLineElement | null
197
+ };
198
+
199
+ function getWaveType(): string {
200
+ let waveType = 'half';
201
+ waveRadios.forEach(r => { if(r.checked) waveType = r.value; });
202
+ return waveType;
203
+ }
204
+
205
+ function updateLayerVisibility(waveType: string): void {
206
+ if (waveType === 'half') {
207
+ if (elements.branchCard) elements.branchCard.style.display = 'flex';
208
+ if (elements.dipoleLayer) elements.dipoleLayer.style.display = 'block';
209
+ if (elements.whipLayer) elements.whipLayer.style.display = 'none';
210
+ } else {
211
+ if (elements.branchCard) elements.branchCard.style.display = 'none';
212
+ if (elements.dipoleLayer) elements.dipoleLayer.style.display = 'none';
213
+ if (elements.whipLayer) elements.whipLayer.style.display = 'block';
214
+ }
215
+ }
216
+
217
+ function calculate() {
218
+ const freq = parseFloat(freqInput.value);
219
+ const vf = parseFloat(vfInput.value);
220
+ const waveType = getWaveType();
221
+
222
+ if (!freq || freq <= 0) return;
223
+
224
+ const lambda = 300000 / freq;
225
+ const totalPhysicalLambda = lambda * vf;
226
+
227
+ const totalLen = waveType === 'half' ? totalPhysicalLambda / 2 : totalPhysicalLambda / 4;
228
+ const branchLen = waveType === 'half' ? totalLen / 2 : totalLen;
229
+
230
+ updateLayerVisibility(waveType);
231
+ updateVisuals(waveType, waveType === 'half' ? branchLen : totalLen);
232
+
233
+ if (elements.total) elements.total.textContent = Math.round(totalLen).toString();
234
+ if (elements.branch) elements.branch.textContent = Math.round(branchLen).toString();
235
+
236
+ updateHarmonics(freq);
237
+ }
238
+
239
+ function setArmAttributes(x: number, isLeft: boolean): void {
240
+ const arm = isLeft ? elements.leftArm : elements.rightArm;
241
+ const startX = isLeft ? '195' : '205';
242
+ arm?.setAttribute('x1', startX);
243
+ arm?.setAttribute('x2', x.toString());
244
+ const tick = isLeft ? elements.leftTick : elements.rightTick;
245
+ tick?.setAttribute('x1', x.toString());
246
+ tick?.setAttribute('x2', x.toString());
247
+ }
248
+
249
+ function updateDipoleVisuals(visualLen: number): void {
250
+ const x1 = 200 - visualLen;
251
+ const x2 = 200 + visualLen;
252
+ setArmAttributes(x1, true);
253
+ setArmAttributes(x2, false);
254
+ elements.measureLine?.setAttribute('x1', x1.toString());
255
+ elements.measureLine?.setAttribute('x2', x2.toString());
256
+ }
257
+
258
+ function updateWhipVisuals(visualLen: number): void {
259
+ const y2 = 250 - visualLen;
260
+ elements.verticalWhip?.setAttribute('y2', y2.toString());
261
+ elements.verticalMeasure?.setAttribute('y1', y2.toString());
262
+ elements.topTick?.setAttribute('y1', y2.toString());
263
+ elements.topTick?.setAttribute('y2', y2.toString());
264
+ }
265
+
266
+ function updateVisuals(type: string, len: number) {
267
+ const visualLen = Math.min(Math.max(len / 4, 30), 160);
268
+ if (type === 'half') {
269
+ updateDipoleVisuals(visualLen);
270
+ } else {
271
+ updateWhipVisuals(visualLen);
272
+ }
273
+ }
274
+
275
+ function updateHarmonics(baseFreq: number) {
276
+ if (!elements.harmonics) return;
277
+
278
+ const harmonicLabel = elements.harmonics.dataset.label || "Armónica";
279
+
280
+ const orders = [
281
+ { label: `3ª ${harmonicLabel}`, val: baseFreq * 3 },
282
+ { label: `5ª ${harmonicLabel}`, val: baseFreq * 5 },
283
+ { label: `7ª ${harmonicLabel}`, val: baseFreq * 7 }
284
+ ];
285
+
286
+ elements.harmonics.innerHTML = orders.map(o => `
287
+ <div class="harmonic-card animate-in fade-in slide-in-from-bottom-2">
288
+ <span class="h-order">${o.label}</span>
289
+ <span class="h-freq">${Math.round(o.val)} MHz</span>
290
+ </div>
291
+ `).join('');
292
+ }
293
+
294
+ freqInput.addEventListener('input', calculate);
295
+
296
+ vfInput.addEventListener('change', calculate);
297
+ waveRadios.forEach(r => {
298
+ r.addEventListener('change', () => {
299
+ calculate();
300
+ document.querySelectorAll('.radio-card').forEach(card => card.classList.remove('active'));
301
+ const checkedRadio = document.querySelector('.radio-card input:checked');
302
+ if (checkedRadio?.parentElement?.classList) {
303
+ checkedRadio.parentElement.classList.add('active');
304
+ }
305
+ });
306
+ });
307
+
308
+ document.querySelectorAll('.preset-btn').forEach(btn => {
309
+ btn.addEventListener('click', () => {
310
+ const val = btn.getAttribute('data-val');
311
+ if (val) {
312
+ freqInput.value = val;
313
+ calculate();
314
+ }
315
+ });
316
+ });
317
+
318
+ calculate();
319
+ </script>
320
+
321
+ <style>
322
+ .custom-select-premium {
323
+ width: 100%;
324
+ padding: 1.1rem 3rem 1.1rem 1.25rem;
325
+ background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='%23f59e0b' d='M7.41 8.58L12 13.17l4.59-4.59L18 10l-6 6l-6-6l1.41-1.42Z'/%3E%3C/svg%3E") no-repeat right 1rem center;
326
+ border: 2px solid rgba(0,0,0,0.05);
327
+ border-radius: 18px;
328
+ font-weight: 700;
329
+ font-size: 0.95rem;
330
+ cursor: pointer;
331
+ outline: none;
332
+ appearance: none;
333
+ -webkit-appearance: none;
334
+ transition: all 0.3s ease;
335
+ box-shadow: 0 2px 5px rgba(0,0,0,0.02);
336
+ }
337
+
338
+ :global(.dark) .custom-select-premium {
339
+ background-color: rgba(15, 23, 42, 0.4);
340
+ border-color: rgba(255,255,255,0.1);
341
+ color: white;
342
+ }
343
+
344
+ .custom-select-premium:focus {
345
+ border-color: var(--antenna-primary);
346
+ box-shadow: 0 5px 15px rgba(var(--antenna-primary-rgb), 0.1);
347
+ }
348
+
349
+ .svg-label-text {
350
+ font-size: 12px;
351
+ font-weight: 800;
352
+ fill: #999;
353
+ text-transform: uppercase;
354
+ letter-spacing: 0.1em;
355
+ }
356
+
357
+ :global(.dark) .svg-label-text {
358
+ fill: #666;
359
+ }
360
+ </style>
@@ -0,0 +1,204 @@
1
+ import type { AntennaLengthCalculatorLocaleContent } from '../index';
2
+
3
+ const slug = 'antenna-length-calculator';
4
+ const title = 'RF Antenna Length Calculator | FPV Dipoles and Whips';
5
+ const description = 'Calculate the exact measurement for your 868MHz, 2.4GHz, and 5.8GHz antennas. Improve your drone\'s range and avoid burning your transmitter with optimized SWR.';
6
+
7
+ export const content: AntennaLengthCalculatorLocaleContent = {
8
+ slug,
9
+ title,
10
+ description,
11
+ faqTitle: 'Frequently Asked Questions',
12
+ bibliographyTitle: 'Bibliographic References',
13
+ ui: {
14
+ faqTitle: 'Frequently Asked Questions',
15
+ bibliographyTitle: 'Bibliographic References',
16
+ signalParameters: 'Signal Parameters',
17
+ antennaType: 'Antenna Type',
18
+ dipole: 'Dipole (1/2 λ)',
19
+ whip: 'Whip (1/4 λ)',
20
+ conductorMedium: 'Conductor Medium',
21
+ totalLength: 'Total Length',
22
+ branchLength: 'Branch Length',
23
+ secondaryResonance: 'Secondary Resonance Points',
24
+ swrIdeal: 'Ideal SWR',
25
+ impedance: 'Impedance',
26
+ criticalNotice: 'Critical Notice',
27
+ criticalNoticeText: 'A poorly cut antenna generates a high SWR (ROE) that can overheat and destroy your transmitter\'s power output stages in seconds.',
28
+ dynamicScheme: 'Dynamic Scheme (mm)',
29
+ harmonicLabel: 'Harmonic',
30
+ presetFpv: '5.8 GHz (FPV)',
31
+ presetWifi: '2.4 GHz (Wi-Fi)',
32
+ presetElrs: '915 MHz (ELRS)',
33
+ presetLrs: '868 MHz (LRS)',
34
+ presetUhf: '433 MHz (UHF)',
35
+ presetMbus: '169 MHz (M-Bus)',
36
+ materialBareCopper: 'Bare Copper (0.95)',
37
+ materialPvcInsulated: 'PVC Insulated Cable (0.92)',
38
+ materialSolidRod: 'Solid Rod (0.97)',
39
+ materialCoaxial: 'Coaxial Cable (0.66)',
40
+ },
41
+ seo: [
42
+ {
43
+ type: 'title',
44
+ text: 'Why Is Your Radio Frequency Antenna Length Critical?',
45
+ level: 2,
46
+ },
47
+ {
48
+ type: 'paragraph',
49
+ html: 'If you have ever wondered why racing drone (FPV) antennas, long-range controllers (ELRS/Crossfire), or even your Wi-Fi router have such specific lengths, the answer lies in the physics of resonance. An antenna is not just a piece of conductive wire; it is a component that must be "in tune" with the frequency of the electromagnetic wave it is handling.',
50
+ },
51
+ {
52
+ type: 'paragraph',
53
+ html: 'When building your own antenna, whether it is a <strong>dipole</strong> for 868MHz or a <strong>whip antenna</strong> for 5.8GHz, precision is measured in millimeters. An error of just 2 or 3 mm can make the antenna inefficient, causing what is known as high SWR (Standing Wave Ratio) or ROE.',
54
+ },
55
+ {
56
+ type: 'title',
57
+ text: 'Fundamental Concepts: Wavelength and Resonance',
58
+ level: 3,
59
+ },
60
+ {
61
+ type: 'paragraph',
62
+ html: 'Radio frequency (RF) travels at the speed of light (approximately 300,000 kilometers per second). For an antenna to emit or receive energy optimally, its physical size must be directly related to the distance a complete cycle of the wave travels, called the <strong>wavelength (λ)</strong>.',
63
+ },
64
+ {
65
+ type: 'paragraph',
66
+ html: 'The basic formula for calculating wavelength is λ = v / f, where \'v\' is the velocity of propagation and \'f\' is the frequency. However, in the real world, electricity travels slightly slower through metals than through a vacuum. This is where the <strong>Velocity Factor (Vf)</strong> comes into play.',
67
+ },
68
+ {
69
+ type: 'list',
70
+ items: [
71
+ 'Bare copper: Has a Vf of approximately 0.95.',
72
+ 'Insulated cables (PVC): The insulation slows the wave, lowering the factor to 0.92 or less.',
73
+ 'Solid copper rods: Since they are thicker and highly conductive, the factor rises slightly to 0.97.',
74
+ ],
75
+ },
76
+ {
77
+ type: 'title',
78
+ text: 'Common Antenna Types in Drones and Maker Projects',
79
+ level: 3,
80
+ },
81
+ {
82
+ type: 'paragraph',
83
+ html: '<strong>1. Half-Wave Dipole Antenna (1/2 λ):</strong> This is the gold standard for many applications. It consists of two arms (radiating elements) that together form half the wavelength of the operating frequency. It is a balanced antenna offering a "donut" shaped radiation pattern and is very easy to make with coaxial cable.',
84
+ },
85
+ {
86
+ type: 'paragraph',
87
+ html: '<strong>2. Quarter-Wave Whip or Monopole Antenna (1/4 λ):</strong> This is what we typically see on radio receivers or small drones. It has only one radiating element and uses the device\'s chassis or a ground plane to "reflect" the other half of the wave. Its length is exactly half that of a dipole, hence the name quarter-wave.',
88
+ },
89
+ {
90
+ type: 'title',
91
+ text: 'Critical Frequencies and Applications',
92
+ level: 3,
93
+ },
94
+ {
95
+ type: 'paragraph',
96
+ html: 'Each frequency band has its peculiarities. With our calculator, you can adjust the measurements for the most commonly used bands in the hobby:',
97
+ },
98
+ {
99
+ type: 'list',
100
+ items: [
101
+ '5.8 GHz (FPV Video): Lengths are tiny (around 12-13 mm for the radiator). Any excess solder can ruin performance.',
102
+ '2.4 GHz (Control and Wi-Fi): A saturated band where antenna efficiency is key to avoiding link loss (failsafe).',
103
+ '868 MHz / 915 MHz (Long Range): Used by systems like Team BlackSheep Crossfire or ExpressLRS. Antennas here are larger (about 8cm per arm) and allow for easier obstacle penetration.',
104
+ '433 MHz (UHF): The old long-range standard, with large antennas ideal for multi-kilometer flights.',
105
+ ],
106
+ },
107
+ {
108
+ type: 'title',
109
+ text: 'Technical Reference: SWR and Loss Table',
110
+ level: 3,
111
+ },
112
+ {
113
+ type: 'paragraph',
114
+ html: 'For optimal performance, SWR should be as close to 1.0 as possible. Here is a reference of how SWR affects your transmission power:',
115
+ },
116
+ {
117
+ type: 'table',
118
+ headers: ['SWR (ROE)', 'Return Loss', 'Reflected Power', 'Status'],
119
+ rows: [
120
+ ['1.0:1', '-∞ dB', '0%', '<strong>Perfect</strong>'],
121
+ ['1.2:1', '-21 dB', '0.8%', 'Excellent'],
122
+ ['1.5:1', '-14 dB', '4.0%', 'Good'],
123
+ ['2.0:1', '-9.5 dB', '11.1%', 'Acceptable Limit'],
124
+ ['3.0:1', '-6.0 dB', '25.0%', '<strong>Dangerous</strong>'],
125
+ ],
126
+ },
127
+ {
128
+ type: 'title',
129
+ text: 'The Importance of 50 Ohms',
130
+ level: 3,
131
+ },
132
+ {
133
+ type: 'paragraph',
134
+ html: 'Almost all radio systems used in drones and RC (VTx, receivers, controllers) are designed for a <strong>characteristic impedance of 50 Ohms</strong>. A perfectly resonant dipole antenna typically has an impedance close to 73 Ohms in free space, but when installed on a drone or by adjusting the arm angle (Inverted-V), it approaches the ideal 50 Ohms. Using 75 Ohm coaxial cables (like old TV cables) will cause a mismatch that degrades the signal regardless of how well you cut the antenna.',
135
+ },
136
+ {
137
+ type: 'title',
138
+ text: 'The Danger of High SWR: Protect Your VTx',
139
+ level: 3,
140
+ },
141
+ {
142
+ type: 'paragraph',
143
+ html: 'Why do we insist so much on accuracy? If the antenna is not the correct length, it cannot radiate all the energy sent by the video transmitter (VTx). That energy "bounces" back to the transmitter as heat.',
144
+ },
145
+ {
146
+ type: 'paragraph',
147
+ html: 'High SWR is the number one cause of burnt transmitters. If you fly without an antenna or with a poorly cut one, internal components will overheat in seconds, leaving your equipment useless. Using this tool to verify your cuts is the best security investment for your drone.',
148
+ },
149
+ {
150
+ type: 'title',
151
+ text: 'Harmonics: Understanding Interference',
152
+ level: 3,
153
+ },
154
+ {
155
+ type: 'paragraph',
156
+ html: 'An antenna cut for 868MHz does not only resonate at that frequency. Due to the nature of sine waves, it will also have resonance points at its odd multiples (3rd, 5th, 7th harmonics).',
157
+ },
158
+ {
159
+ type: 'paragraph',
160
+ html: 'This is vital to know because even if your antenna is emitting at 868MHz, you could be generating "noise" or interference at higher frequencies if the transmitter is not well-filtered. The harmonics calculator helps you predict where these ghost signals might appear.',
161
+ },
162
+ ],
163
+ faq: [
164
+ {
165
+ question: 'Why must my antenna wire have a specific length?',
166
+ answer: 'Radio waves resonate at multiples of their wavelength. If the wire doesn\'t match this resonance, energy reflects back to the transmitter instead of radiating, which can burn the equipment.',
167
+ },
168
+ {
169
+ question: 'What is the Velocity Factor (Vf)?',
170
+ answer: 'It is the ratio between the speed at which a signal travels through a conductor and the speed of light. In copper, it is typically 0.95, meaning the wave travels 5% slower and the antenna must be 5% shorter.',
171
+ },
172
+ {
173
+ question: 'Is a dipole or a whip antenna better?',
174
+ answer: 'A dipole (1/2 wave) is more efficient and predictable but bulkier. A whip (1/4 wave) is compact and ideal for small receivers, though it requires a ground plane to function well.',
175
+ },
176
+ {
177
+ question: 'How does wire thickness affect the antenna?',
178
+ answer: 'Thicker wires have a wider bandwidth (they are less critical regarding exact frequency), but their velocity factor changes slightly. For most FPV drones, standard 20-22AWG wire is ideal.',
179
+ },
180
+ ],
181
+ bibliography: [
182
+ { name: 'The Quarter-Wave Monopole', url: 'https://www.antenna-theory.com/antennas/monopole.php' },
183
+ { name: 'Velocity Factor of Transmission Lines', url: 'https://en.wikipedia.org/wiki/Velocity_factor' },
184
+ ],
185
+ howTo: [
186
+ {
187
+ name: 'Select Frequency',
188
+ text: 'Enter the exact frequency in MHz or use one of the quick buttons for 5.8GHz, 2.4GHz, or 868MHz.',
189
+ },
190
+ {
191
+ name: 'Choose Antenna Type',
192
+ text: 'Decide whether you will make a full dipole (1/2 wave) or a vertical whip antenna (1/4 wave).',
193
+ },
194
+ {
195
+ name: 'Adjust Material',
196
+ text: 'Choose the type of wire you will use so the calculator applies the correct velocity factor.',
197
+ },
198
+ {
199
+ name: 'Cut with Precision',
200
+ text: 'Use the "Length per arm" measurement to cut each element. Remember to measure from the solder point.',
201
+ },
202
+ ],
203
+ schemas: [],
204
+ };