@jjlmoya/utils-motor 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.
- package/package.json +68 -0
- package/scripts/postinstall.mjs +27 -0
- package/src/category/i18n/de.ts +17 -0
- package/src/category/i18n/en.ts +17 -0
- package/src/category/i18n/es.ts +17 -0
- package/src/category/i18n/fr.ts +17 -0
- package/src/category/i18n/id.ts +17 -0
- package/src/category/i18n/it.ts +17 -0
- package/src/category/i18n/ja.ts +17 -0
- package/src/category/i18n/ko.ts +17 -0
- package/src/category/i18n/nl.ts +17 -0
- package/src/category/i18n/pl.ts +17 -0
- package/src/category/i18n/pt.ts +17 -0
- package/src/category/i18n/ru.ts +17 -0
- package/src/category/i18n/sv.ts +17 -0
- package/src/category/i18n/tr.ts +17 -0
- package/src/category/i18n/zh.ts +17 -0
- package/src/category/index.ts +24 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +11 -0
- package/src/entries.ts +7 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +23 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +153 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/diacritics_density.test.ts +118 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/i18n_coverage.test.ts +36 -0
- package/src/tests/inverted_punctuation.test.ts +84 -0
- package/src/tests/locale_completeness.test.ts +29 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_en_dash.test.ts +70 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/pagespeed_best_practices.test.ts +198 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/script_density.test.ts +94 -0
- package/src/tests/seo_length.test.ts +23 -0
- package/src/tests/seo_parity.test.ts +60 -0
- package/src/tests/shared-test-helpers.ts +56 -0
- package/src/tests/slug_language_code_format.test.ts +23 -0
- package/src/tests/slug_uniqueness.test.ts +81 -0
- package/src/tests/spanish_leakage.test.ts +177 -0
- package/src/tests/title_quality.test.ts +55 -0
- package/src/tests/tool_exports.test.ts +34 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tests/translation_copy.test.ts +125 -0
- package/src/tool/brakingDistanceCalculator/bibliography.astro +6 -0
- package/src/tool/brakingDistanceCalculator/bibliography.ts +12 -0
- package/src/tool/brakingDistanceCalculator/braking-distance-calculator.css +601 -0
- package/src/tool/brakingDistanceCalculator/component.astro +117 -0
- package/src/tool/brakingDistanceCalculator/controller.ts +114 -0
- package/src/tool/brakingDistanceCalculator/dom-views.ts +58 -0
- package/src/tool/brakingDistanceCalculator/entry.ts +30 -0
- package/src/tool/brakingDistanceCalculator/evaluator.ts +45 -0
- package/src/tool/brakingDistanceCalculator/i18n/de.ts +45 -0
- package/src/tool/brakingDistanceCalculator/i18n/en.ts +208 -0
- package/src/tool/brakingDistanceCalculator/i18n/es.ts +46 -0
- package/src/tool/brakingDistanceCalculator/i18n/factory.ts +114 -0
- package/src/tool/brakingDistanceCalculator/i18n/fr.ts +40 -0
- package/src/tool/brakingDistanceCalculator/i18n/id.ts +38 -0
- package/src/tool/brakingDistanceCalculator/i18n/it.ts +39 -0
- package/src/tool/brakingDistanceCalculator/i18n/ja.ts +38 -0
- package/src/tool/brakingDistanceCalculator/i18n/ko.ts +38 -0
- package/src/tool/brakingDistanceCalculator/i18n/nl.ts +38 -0
- package/src/tool/brakingDistanceCalculator/i18n/pl.ts +38 -0
- package/src/tool/brakingDistanceCalculator/i18n/pt.ts +39 -0
- package/src/tool/brakingDistanceCalculator/i18n/ru.ts +38 -0
- package/src/tool/brakingDistanceCalculator/i18n/sv.ts +38 -0
- package/src/tool/brakingDistanceCalculator/i18n/tr.ts +38 -0
- package/src/tool/brakingDistanceCalculator/i18n/zh.ts +38 -0
- package/src/tool/brakingDistanceCalculator/index.ts +10 -0
- package/src/tool/brakingDistanceCalculator/logic.test.ts +84 -0
- package/src/tool/brakingDistanceCalculator/logic.ts +82 -0
- package/src/tool/brakingDistanceCalculator/seo.astro +15 -0
- package/src/tool/brakingDistanceCalculator/storage.ts +28 -0
- package/src/tool/brakingDistanceCalculator/ui.ts +42 -0
- package/src/tools.ts +4 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createLocalizedContent } from './factory';
|
|
2
|
+
|
|
3
|
+
export const content = createLocalizedContent({
|
|
4
|
+
locale: 'sv', slug: 'berakna-bromsstracka', title: 'Beräkna bromssträcka för bil och motorcykel',
|
|
5
|
+
description: 'Beräkna reaktionssträcka, bromssträcka och total stoppsträcka utifrån hastighet, reaktionstid, väggrepp och lutning.', faqTitle: 'Vanliga frågor', bibliographyTitle: 'Källor',
|
|
6
|
+
ui: {
|
|
7
|
+
unitLabel: 'Enheter', metricUnit: 'km/h', imperialUnit: 'mph', vehicleLabel: 'Fordon', carLabel: 'Bil', motorcycleLabel: 'Motorcykel', speedLabel: 'Starthastighet', reactionTimeLabel: 'Reaktionstid', gradeLabel: 'Vägens lutning',
|
|
8
|
+
roadConditionLabel: 'Väggrepp', dryRoadLabel: 'Torrt', wetRoadLabel: 'Vått', snowRoadLabel: 'Snö', iceRoadLabel: 'Is', decelerationLabel: 'Antagen retardation', reactionDistanceLabel: 'Reaktion', brakingDistanceLabel: 'Bromsning',
|
|
9
|
+
totalDistanceLabel: 'Total stoppsträcka', totalTimeLabel: 'Total stopptid', roadStartLabel: 'Faran upptäcks', brakePointLabel: 'Bromsning börjar', stopPointLabel: 'Helt stilla', safetyExperimentLabel: 'Test av säkerhetsavstånd',
|
|
10
|
+
safetyExperimentHint: 'Försiktigt scenario: trafiken framför blir ett fast hinder så snart du upptäcker den.', gapTimeLabel: 'Tidsavstånd', availableDistanceLabel: 'Tillgängligt utrymme', marginLabel: 'Säkerhetsmarginal', shortfallLabel: 'Saknad sträcka',
|
|
11
|
+
simulateLabel: 'Simulera stoppet', leadVehicleLabel: 'Fordon framför', enoughGapMessage: 'Det beräknade stoppet ryms och lämnar en viss marginal.', tightGapMessage: 'Stoppet ryms precis. Verkliga förhållanden kräver större marginal.',
|
|
12
|
+
insufficientGapMessage: 'Fordonet når trafiken framför innan det beräknade stoppet är klart.', advancedLabel: 'Avancerat bromsantagande', normalMessage: 'Den beräknade stoppsträckan är måttlig, men verkliga förhållanden kräver ändå säkerhetsmarginal.',
|
|
13
|
+
warningMessage: 'Vägsträckan som går åt är längre än den känns från förarplatsen.', criticalMessage: 'Mycket lågt grepp eller hög hastighet ger en extrem stoppsträcka.', carNote: 'Bilens beteende varierar med däck, bromsskick, last, ABS och vägyta.',
|
|
14
|
+
motorcycleNote: 'För motorcykeln påverkar även körteknik, bromsfördelning, lutning, däck och vägyta.', estimateNotice: 'Endast en pedagogisk uppskattning. Använd aldrig resultatet som garanterat säkerhetsavstånd eller ersättning för trafikregler.',
|
|
15
|
+
},
|
|
16
|
+
faq: [
|
|
17
|
+
{ question: 'Vad är skillnaden mellan reaktionssträcka och bromssträcka?', answer: 'Reaktionssträckan är den sträcka som körs innan bromsningen börjar. Bromssträckan börjar när bromsarna verkar och slutar när fordonet står stilla. Tillsammans bildar de den totala stoppsträckan.' },
|
|
18
|
+
{ question: 'Varför ökar stoppsträckan så snabbt med hastigheten?', answer: 'Reaktionssträckan ökar proportionellt med hastigheten, medan bromssträckan ökar ungefär med hastigheten i kvadrat. Under samma förhållanden kan en fördubblad hastighet göra bromsdelen cirka fyra gånger längre.' },
|
|
19
|
+
{ question: 'Hur påverkar vägens lutning beräkningen?', answer: 'Uppför ökar den effektiva retardationen och nedför minskar den. Kalkylatorn behandlar lutningen som en gravitationskomponent enligt uppbyggnaden i FHWA:s modell för stoppsikt.' },
|
|
20
|
+
{ question: 'Är resultatet ett garanterat säkerhetsavstånd?', answer: 'Nej. Däck, bromsar, uppmärksamhet, last, väder, föroreningar, ABS och mätfel kan ändra den verkliga sträckan. Följ trafikreglerna och lämna större marginal.' },
|
|
21
|
+
],
|
|
22
|
+
howTo: [
|
|
23
|
+
{ name: 'Välj enhet och fordon', text: 'Välj kilometer eller miles per timme och därefter bil eller motorcykel för rätt säkerhetsinformation.' },
|
|
24
|
+
{ name: 'Ställ in hastighet och reaktionstid', text: 'Anpassa reglagen till scenariot du vill undersöka.' },
|
|
25
|
+
{ name: 'Beskriv vägen', text: 'Välj grepp, finjustera retardationen vid behov och ange ett positivt värde uppför eller negativt nedför.' },
|
|
26
|
+
{ name: 'Läs stoppförloppet', text: 'Jämför den blå reaktionsdelen med den gula bromsdelen och granska total sträcka och tid.' },
|
|
27
|
+
{ name: 'Prova avståndet', text: 'Ange ett tidsavstånd och kör simuleringen för att se om fordonet stannar före trafiken framför i det försiktiga scenariot.' },
|
|
28
|
+
],
|
|
29
|
+
seo: {
|
|
30
|
+
constructionTitle: 'Så byggs den totala stoppsträckan upp', constructionText: 'Ett fullständigt stopp har två faser. Under <strong>upptäckt och reaktion</strong> fortsätter fordonet nästan i starthastigheten. När bromsarna verkar måste krafter mellan däck och väg ta bort rörelseenergin. Kalkylatorn visar sträckorna separat så att reaktionstid inte förväxlas med bromsförmåga.',
|
|
31
|
+
partsValue: '2 faser', partsLabel: 'Reaktion plus bromsning', speedValue: 'Hastighet²', speedLabel: 'Bromssträckans ökning', localValue: '100 % lokalt', localLabel: 'Inga färddata skickas', formulaTitle: 'Formeln bakom uppskattningen',
|
|
32
|
+
formulaText: 'Reaktionssträckan är hastigheten i meter per sekund multiplicerad med reaktionstiden. Bromssträckan är hastigheten i kvadrat dividerad med två gånger den effektiva retardationen. Den kombinerar bromsantagandet med lutningens gravitationseffekt.',
|
|
33
|
+
tableHeaders: ['Indata', 'Vad som ändras', 'Varför det spelar roll'], tableRows: [['Hastighet', 'Båda faserna', 'Bromssträckan ökar ungefär med hastigheten i kvadrat'], ['Reaktionstid', 'Reaktionsfasen', 'Distraktion och trötthet lägger till meter före bromsning'], ['Retardation', 'Bromsfasen', 'Grepp, däck, bromsar och vägyta avgör inbromsningen'], ['Lutning', 'Bromsfasen', 'Tyngdkraften hjälper nedför och motverkar uppför']],
|
|
34
|
+
presetsTitle: 'Väglägen är scenarier, inte certifieringar', tipTitle: 'Välj den försiktiga sidan', tipText: 'Torrt, vått, snö och is är tydliga modellantaganden. De undersöker inte däck eller väg. Prova lägre retardation och längre reaktionstid när förhållandena är osäkra och följ officiella avståndsregler.',
|
|
35
|
+
experimentTitle: 'Experimentera med utrymmet framför', experimentText: 'Testet omvandlar tidsavståndet till meter vid vald hastighet och jämför det med total stoppsträcka. Animationen behandlar avsiktligt trafiken framför som ett fast hinder. Det är en försiktig illustration, inte en prognos för två fordon i rörelse.',
|
|
36
|
+
marginTitle: 'Bilar och motorcyklar behöver verklig marginal', marginText: 'Samma fysikmodell illustrerar båda fordonstyperna men återskapar inget enskilt fordon. Bromstemperatur, däckblandning, lastförskjutning, ABS-kalibrering, körteknik, smuts och väder kan ändra det verkliga utfallet avsevärt.',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createLocalizedContent } from './factory';
|
|
2
|
+
|
|
3
|
+
export const content = createLocalizedContent({
|
|
4
|
+
locale: 'tr', slug: 'fren-mesafesi-hesaplama', title: 'Otomobil ve motosiklet fren mesafesi hesaplama',
|
|
5
|
+
description: 'Hız, tepki süresi, yol tutuşu ve eğime göre tepki, fren ve toplam durma mesafesini hesaplayın.', faqTitle: 'Sık sorulan sorular', bibliographyTitle: 'Kaynaklar',
|
|
6
|
+
ui: {
|
|
7
|
+
unitLabel: 'Birimler', metricUnit: 'km/sa', imperialUnit: 'mph', vehicleLabel: 'Araç', carLabel: 'Otomobil', motorcycleLabel: 'Motosiklet', speedLabel: 'Başlangıç hızı', reactionTimeLabel: 'Tepki süresi', gradeLabel: 'Yol eğimi',
|
|
8
|
+
roadConditionLabel: 'Yol tutuşu', dryRoadLabel: 'Kuru', wetRoadLabel: 'Islak', snowRoadLabel: 'Karlı', iceRoadLabel: 'Buzlu', decelerationLabel: 'Varsayılan yavaşlama', reactionDistanceLabel: 'Tepki', brakingDistanceLabel: 'Frenleme',
|
|
9
|
+
totalDistanceLabel: 'Toplam durma mesafesi', totalTimeLabel: 'Toplam durma süresi', roadStartLabel: 'Tehlike fark edildi', brakePointLabel: 'Fren uygulandı', stopPointLabel: 'Tam duruş', safetyExperimentLabel: 'Güvenli takip mesafesi deneyi',
|
|
10
|
+
safetyExperimentHint: 'Temkinli senaryo: öndeki trafik fark edildiği anda sabit bir engele dönüşür.', gapTimeLabel: 'Takip süresi', availableDistanceLabel: 'Öndeki boşluk', marginLabel: 'Güvenlik payı', shortfallLabel: 'Eksik mesafe',
|
|
11
|
+
simulateLabel: 'Durmayı canlandır', leadVehicleLabel: 'Öndeki araç', enoughGapMessage: 'Hesaplanan duruş mevcut alana sığıyor ve bir miktar pay bırakıyor.', tightGapMessage: 'Araç ancak durabiliyor. Gerçek koşullarda daha fazla pay gerekir.',
|
|
12
|
+
insufficientGapMessage: 'Araç, hesaplanan duruş tamamlanmadan öndeki trafiğe ulaşıyor.', advancedLabel: 'Gelişmiş fren varsayımı', normalMessage: 'Hesaplanan durma mesafesi sınırlı olsa da gerçek koşullar güvenlik payı gerektirir.',
|
|
13
|
+
warningMessage: 'Kullanılan yol, sürücü koltuğundan hissedilenden daha uzundur.', criticalMessage: 'Çok düşük yol tutuşu veya yüksek hız aşırı bir durma mesafesi oluşturur.', carNote: 'Otomobil davranışı lastiklere, fren durumuna, yüke, ABS\'ye ve yol yüzeyine göre değişir.',
|
|
14
|
+
motorcycleNote: 'Motosiklet frenlemesinde sürüş tekniği, fren dağılımı, yatış, lastikler ve zemin de etkilidir.', estimateNotice: 'Yalnızca eğitim amaçlı tahmindir. Sonucu garantili güvenli mesafe veya trafik kurallarının yerine kullanmayın.',
|
|
15
|
+
},
|
|
16
|
+
faq: [
|
|
17
|
+
{ question: 'Tepki mesafesi ile fren mesafesi arasındaki fark nedir?', answer: 'Tepki mesafesi, frenleme başlamadan önce alınan yoldur. Fren mesafesi frenlerin etkisinden aracın durmasına kadar sürer. İkisinin toplamı, toplam durma mesafesidir.' },
|
|
18
|
+
{ question: 'Durma mesafesi hızla neden bu kadar hızlı artar?', answer: 'Tepki mesafesi hızla doğru orantılı, fren mesafesi ise yaklaşık olarak hızın karesiyle artar. Aynı koşullarda hızı iki katına çıkarmak frenleme bölümünü yaklaşık dört katına çıkarabilir.' },
|
|
19
|
+
{ question: 'Yol eğimi hesabı nasıl etkiler?', answer: 'Yokuş yukarı eğim etkili yavaşlamayı artırır, yokuş aşağı eğim azaltır. Hesaplayıcı eğimi, FHWA duruş görüş mesafesi modelinin yapısına uygun bir yer çekimi bileşeni olarak uygular.' },
|
|
20
|
+
{ question: 'Sonuç garantili bir güvenli takip mesafesi midir?', answer: 'Hayır. Lastikler, frenler, dikkat, yük, hava, yüzey kirliliği, ABS ve ölçüm hataları gerçek mesafeyi değiştirebilir. Resmî kurallara uyun ve daha geniş pay bırakın.' },
|
|
21
|
+
],
|
|
22
|
+
howTo: [
|
|
23
|
+
{ name: 'Birimi ve aracı seçin', text: 'Saatte kilometre veya mili, ardından uygun güvenlik notu için otomobil ya da motosikleti seçin.' },
|
|
24
|
+
{ name: 'Hızı ve tepki süresini ayarlayın', text: 'İncelemek istediğiniz senaryoya göre iki kaydırıcıyı ayarlayın.' },
|
|
25
|
+
{ name: 'Yol koşullarını tanımlayın', text: 'Yol tutuşunu seçin, gerekirse yavaşlamayı düzenleyin; yokuş yukarı için pozitif, aşağı için negatif değer kullanın.' },
|
|
26
|
+
{ name: 'Durma sürecini okuyun', text: 'Mavi tepki bölümüyle sarı fren bölümünü karşılaştırın ve toplam mesafe ile süreyi inceleyin.' },
|
|
27
|
+
{ name: 'Takip mesafesini deneyin', text: 'Bir takip süresi belirleyip canlandırmayı çalıştırarak temkinli senaryoda aracın öndeki trafikten önce durup durmadığını görün.' },
|
|
28
|
+
],
|
|
29
|
+
seo: {
|
|
30
|
+
constructionTitle: 'Toplam durma mesafesi nasıl oluşur?', constructionText: 'Tam duruş iki aşamadan oluşur. <strong>Algılama ve tepki</strong> sırasında araç başlangıç hızına yakın biçimde ilerlemeye devam eder. Frenler çalıştığında lastik ile yol arasındaki kuvvetler hareket enerjisini azaltır. Hesaplayıcı, tepki süresiyle fren performansının karıştırılmaması için iki mesafeyi ayrı gösterir.',
|
|
31
|
+
partsValue: '2 aşama', partsLabel: 'Tepki ve frenleme', speedValue: 'Hız²', speedLabel: 'Fren mesafesinin artışı', localValue: '%100 yerel', localLabel: 'Sürüş verisi gönderilmez', formulaTitle: 'Tahminin arkasındaki formül',
|
|
32
|
+
formulaText: 'Tepki mesafesi, saniyede metre cinsinden hız ile tepki süresinin çarpımıdır. Fren mesafesi, hızın karesinin etkili yavaşlamanın iki katına bölünmesiyle bulunur. Etkili yavaşlama, fren varsayımını eğimin yer çekimi etkisiyle birleştirir.',
|
|
33
|
+
tableHeaders: ['Girdi', 'Neyi değiştirir?', 'Neden önemlidir?'], tableRows: [['Hız', 'İki aşamayı da', 'Fren mesafesi yaklaşık olarak hızın karesiyle artar'], ['Tepki süresi', 'Tepki aşamasını', 'Dikkat dağınıklığı ve yorgunluk fren öncesi mesafeyi uzatır'], ['Yavaşlama', 'Fren aşamasını', 'Tutuş, lastikler, frenler ve zemin yavaşlamayı belirler'], ['Eğim', 'Fren aşamasını', 'Yer çekimi inişi destekler, çıkışa karşı koyar']],
|
|
34
|
+
presetsTitle: 'Yol seçenekleri senaryodur, sertifika değildir', tipTitle: 'Temkinli tarafı seçin', tipText: 'Kuru, ıslak, karlı ve buzlu seçenekleri açık model varsayımlarıdır; lastiğinizi veya yolu incelemez. Belirsizlikte daha düşük yavaşlama ve daha uzun tepki süresi deneyin, resmî kurallara uyun.',
|
|
35
|
+
experimentTitle: 'Öndeki boşlukla deney yapın', experimentText: 'Deney, seçilen takip süresini mevcut hızda metreye dönüştürüp toplam durma mesafesiyle karşılaştırır. Canlandırma öndeki trafiği bilerek sabit engel sayar. Bu, iki hareketli aracın tahmini değil, temkinli bir gösterimdir.',
|
|
36
|
+
marginTitle: 'Otomobil ve motosikletler gerçek pay ister', marginText: 'Aynı fizik modeli iki araç türünü gösterebilir ancak belirli bir aracı taklit etmez. Fren sıcaklığı, lastik hamuru, yük aktarımı, ABS ayarı, sürüş tekniği, zemin kirliliği ve hava gerçek sonucu önemli ölçüde değiştirebilir.',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createLocalizedContent } from './factory';
|
|
2
|
+
|
|
3
|
+
export const content = createLocalizedContent({
|
|
4
|
+
locale: 'zh', slug: 'braking-distance-calculator', title: '汽车与摩托车刹车距离计算器',
|
|
5
|
+
description: '根据速度、反应时间、路面附着力和坡度,估算反应距离、制动距离与总停车距离。', faqTitle: '常见问题', bibliographyTitle: '参考资料',
|
|
6
|
+
ui: {
|
|
7
|
+
unitLabel: '单位', metricUnit: '公里/小时', imperialUnit: '英里/小时', vehicleLabel: '车辆', carLabel: '汽车', motorcycleLabel: '摩托车', speedLabel: '初始速度', reactionTimeLabel: '反应时间', gradeLabel: '道路坡度',
|
|
8
|
+
roadConditionLabel: '路面附着力', dryRoadLabel: '干燥', wetRoadLabel: '湿滑', snowRoadLabel: '积雪', iceRoadLabel: '结冰', decelerationLabel: '假定减速度', reactionDistanceLabel: '反应', brakingDistanceLabel: '制动',
|
|
9
|
+
totalDistanceLabel: '总停车距离', totalTimeLabel: '总停车时间', roadStartLabel: '发现危险', brakePointLabel: '开始制动', stopPointLabel: '完全停车', safetyExperimentLabel: '安全距离实验',
|
|
10
|
+
safetyExperimentHint: '保守场景:发现前方车辆的瞬间,将其视为固定障碍物。', gapTimeLabel: '跟车时间间隔', availableDistanceLabel: '前方空间', marginLabel: '安全余量', shortfallLabel: '不足距离',
|
|
11
|
+
simulateLabel: '运行停车模拟', leadVehicleLabel: '前方车辆', enoughGapMessage: '计算出的停车过程可在现有空间内完成,并留有一定余量。', tightGapMessage: '计算结果仅能勉强停住,实际驾驶需要更大余量。',
|
|
12
|
+
insufficientGapMessage: '车辆在完成计算出的停车过程之前就会到达前方车辆位置。', advancedLabel: '高级制动假设', normalMessage: '计算出的停车距离较短,但真实路况仍需要安全余量。',
|
|
13
|
+
warningMessage: '停车所需的道路长度比驾驶座上的感觉更长。', criticalMessage: '附着力极低或车速过高会产生极长的停车距离。', carNote: '汽车表现会随轮胎、制动器状态、载荷、ABS及路面质量而变化。',
|
|
14
|
+
motorcycleNote: '摩托车制动还受骑手技术、前后制动力分配、倾角、轮胎和路面状况影响。', estimateNotice: '仅供学习参考。请勿将结果视为有保证的安全距离,也不能用它替代交通法规。',
|
|
15
|
+
},
|
|
16
|
+
faq: [
|
|
17
|
+
{ question: '反应距离和制动距离有什么区别?', answer: '反应距离是发现危险后、制动开始前车辆驶过的距离。制动距离从制动器开始发挥作用算起,到车辆停止为止。两者相加就是总停车距离。' },
|
|
18
|
+
{ question: '为什么停车距离会随速度快速增加?', answer: '反应距离与速度成正比,而制动距离大致与速度的平方成正比。在其他条件相同时,速度加倍可能使制动部分增至约四倍。' },
|
|
19
|
+
{ question: '道路坡度如何影响计算结果?', answer: '上坡会提高有效减速度,下坡则会降低它。计算器依照FHWA停车视距模型的结构,将坡度作为重力分量计入。' },
|
|
20
|
+
{ question: '计算结果能作为有保证的安全跟车距离吗?', answer: '不能。轮胎、制动器、注意力、载荷、天气、路面污染、ABS和测量误差都会改变实际距离。请遵守当地交通法规,并预留更大余量。' },
|
|
21
|
+
],
|
|
22
|
+
howTo: [
|
|
23
|
+
{ name: '选择单位和车辆', text: '选择公里/小时或英里/小时,再选择汽车或摩托车,以显示相应的安全提示。' },
|
|
24
|
+
{ name: '设置速度和反应时间', text: '按照想要研究的场景调整初始速度和感知反应时间。' },
|
|
25
|
+
{ name: '设置道路条件', text: '选择路面附着力,必要时微调减速度;上坡使用正值,下坡使用负值。' },
|
|
26
|
+
{ name: '查看停车过程', text: '比较蓝色反应区段与黄色制动区段,然后查看总距离和总时间。' },
|
|
27
|
+
{ name: '尝试不同跟车距离', text: '设置跟车时间间隔并运行模拟,查看在保守场景下车辆能否在前车位置之前停下。' },
|
|
28
|
+
],
|
|
29
|
+
seo: {
|
|
30
|
+
constructionTitle: '总停车距离由哪些部分组成', constructionText: '完整停车包含两个阶段。在<strong>感知和反应</strong>期间,车辆仍以接近初始速度继续前进。制动器工作后,轮胎与路面之间的力开始消耗动能。计算器将两段距离分开显示,避免把反应延迟误认为制动性能。',
|
|
31
|
+
partsValue: '2个阶段', partsLabel: '反应加制动', speedValue: '速度²', speedLabel: '制动距离的增长', localValue: '100%本地', localLabel: '不会上传行程数据', formulaTitle: '估算所采用的公式',
|
|
32
|
+
formulaText: '反应距离等于以米/秒表示的速度乘以反应时间。制动距离等于速度平方除以两倍有效减速度。有效减速度结合了所选制动假设和道路坡度产生的重力影响。',
|
|
33
|
+
tableHeaders: ['输入项', '影响部分', '为什么重要'], tableRows: [['速度', '两个阶段', '制动距离大致随速度平方增长'], ['反应时间', '反应阶段', '分心和疲劳会增加制动前的行驶距离'], ['减速度', '制动阶段', '附着力、轮胎、制动器和路面决定减速能力'], ['坡度', '制动阶段', '重力在下坡时推动前进,在上坡时阻碍前进']],
|
|
34
|
+
presetsTitle: '路面选项是场景假设,并非性能认证', tipTitle: '优先采用保守条件', tipText: '干燥、湿滑、积雪和结冰是透明的模型假设,不会检测您的轮胎或道路。条件不确定时,请尝试较低减速度和较长反应时间,并遵守当地法规。',
|
|
35
|
+
experimentTitle: '尝试不同的前方空间', experimentText: '安全距离实验会按当前速度把所选时间间隔换算成米,并与总停车距离比较。动画有意把前方车辆当作固定障碍物。这是偏向安全的示意,不是对两辆运动车辆的预测。',
|
|
36
|
+
marginTitle: '汽车和摩托车都需要现实余量', marginText: '同一物理模型可以说明两类车辆,但无法复现某一辆具体车辆。制动器温度、轮胎配方、载荷转移、ABS标定、驾驶技术、路面污染和天气都可能让实际结果明显偏离理想计算。',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { brakingDistanceCalculator } from './entry';
|
|
2
|
+
|
|
3
|
+
export * from './entry';
|
|
4
|
+
|
|
5
|
+
export const BRAKING_DISTANCE_CALCULATOR_TOOL = {
|
|
6
|
+
entry: brakingDistanceCalculator,
|
|
7
|
+
Component: () => import('./component.astro'),
|
|
8
|
+
SEOComponent: () => import('./seo.astro'),
|
|
9
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
10
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
calculateBrakingDistance,
|
|
4
|
+
calculateEffectiveDeceleration,
|
|
5
|
+
calculateSafetyDistanceScenario,
|
|
6
|
+
speedToMetersPerSecond,
|
|
7
|
+
} from './logic';
|
|
8
|
+
|
|
9
|
+
describe('braking distance calculator logic', () => {
|
|
10
|
+
it('converts metric and imperial speeds', () => {
|
|
11
|
+
expect(speedToMetersPerSecond(36, 'kmh')).toBeCloseTo(10, 6);
|
|
12
|
+
expect(speedToMetersPerSecond(60, 'mph')).toBeCloseTo(26.8224, 6);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('separates reaction and braking distance', () => {
|
|
16
|
+
const result = calculateBrakingDistance({
|
|
17
|
+
speed: 80,
|
|
18
|
+
unit: 'kmh',
|
|
19
|
+
reactionTime: 2.5,
|
|
20
|
+
deceleration: 3.4,
|
|
21
|
+
gradePercent: 0,
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
expect(result.reactionDistance).toBeCloseTo(55.56, 2);
|
|
25
|
+
expect(result.brakingDistance).toBeCloseTo(72.62, 2);
|
|
26
|
+
expect(result.totalDistance).toBeCloseTo(128.18, 2);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('increases stopping distance downhill', () => {
|
|
30
|
+
const base = {
|
|
31
|
+
speed: 70,
|
|
32
|
+
unit: 'kmh' as const,
|
|
33
|
+
reactionTime: 2.5,
|
|
34
|
+
deceleration: 3.4,
|
|
35
|
+
};
|
|
36
|
+
const uphill = calculateBrakingDistance({ ...base, gradePercent: 6 });
|
|
37
|
+
const downhill = calculateBrakingDistance({ ...base, gradePercent: -6 });
|
|
38
|
+
|
|
39
|
+
expect(downhill.brakingDistance).toBeGreaterThan(uphill.brakingDistance);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('keeps the denominator positive for extreme inputs', () => {
|
|
43
|
+
expect(calculateEffectiveDeceleration(0.2, -20)).toBe(0.1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('handles invalid and negative values without NaN', () => {
|
|
47
|
+
const result = calculateBrakingDistance({
|
|
48
|
+
speed: Number.NaN,
|
|
49
|
+
unit: 'kmh',
|
|
50
|
+
reactionTime: -2,
|
|
51
|
+
deceleration: -1,
|
|
52
|
+
gradePercent: 0,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
expect(result.totalDistance).toBe(0);
|
|
56
|
+
expect(Number.isFinite(result.totalTime)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('turns a time gap into distance and exposes the stopping margin', () => {
|
|
60
|
+
const result = calculateBrakingDistance({
|
|
61
|
+
speed: 90,
|
|
62
|
+
unit: 'kmh',
|
|
63
|
+
reactionTime: 1,
|
|
64
|
+
deceleration: 5,
|
|
65
|
+
gradePercent: 0,
|
|
66
|
+
});
|
|
67
|
+
const scenario = calculateSafetyDistanceScenario(result, 3);
|
|
68
|
+
|
|
69
|
+
expect(scenario.gapDistance).toBeCloseTo(75, 6);
|
|
70
|
+
expect(scenario.marginDistance).toBeCloseTo(75 - result.totalDistance, 6);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('clamps an invalid time gap instead of returning NaN', () => {
|
|
74
|
+
const result = calculateBrakingDistance({
|
|
75
|
+
speed: 50,
|
|
76
|
+
unit: 'kmh',
|
|
77
|
+
reactionTime: 1,
|
|
78
|
+
deceleration: 4,
|
|
79
|
+
gradePercent: 0,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(calculateSafetyDistanceScenario(result, Number.NaN).gapDistance).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export type SpeedUnit = 'kmh' | 'mph';
|
|
2
|
+
export type VehicleType = 'car' | 'motorcycle';
|
|
3
|
+
|
|
4
|
+
export interface BrakingInput {
|
|
5
|
+
speed: number;
|
|
6
|
+
unit: SpeedUnit;
|
|
7
|
+
reactionTime: number;
|
|
8
|
+
deceleration: number;
|
|
9
|
+
gradePercent: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface BrakingResult {
|
|
13
|
+
speedMps: number;
|
|
14
|
+
reactionDistance: number;
|
|
15
|
+
brakingDistance: number;
|
|
16
|
+
totalDistance: number;
|
|
17
|
+
brakingTime: number;
|
|
18
|
+
totalTime: number;
|
|
19
|
+
effectiveDeceleration: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SafetyDistanceScenario {
|
|
23
|
+
gapSeconds: number;
|
|
24
|
+
gapDistance: number;
|
|
25
|
+
marginDistance: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const GRAVITY = 9.80665;
|
|
29
|
+
export const MPH_TO_MPS = 0.44704;
|
|
30
|
+
|
|
31
|
+
function nonNegative(value: number): number {
|
|
32
|
+
return Number.isFinite(value) ? Math.max(0, value) : 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function speedToMetersPerSecond(speed: number, unit: SpeedUnit): number {
|
|
36
|
+
const safeSpeed = nonNegative(speed);
|
|
37
|
+
return unit === 'mph' ? safeSpeed * MPH_TO_MPS : safeSpeed / 3.6;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function calculateEffectiveDeceleration(
|
|
41
|
+
deceleration: number,
|
|
42
|
+
gradePercent: number,
|
|
43
|
+
): number {
|
|
44
|
+
const gradeEffect = GRAVITY * (gradePercent / 100);
|
|
45
|
+
return Math.max(0.1, nonNegative(deceleration) + gradeEffect);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function calculateBrakingDistance(input: BrakingInput): BrakingResult {
|
|
49
|
+
const speedMps = speedToMetersPerSecond(input.speed, input.unit);
|
|
50
|
+
const reactionTime = nonNegative(input.reactionTime);
|
|
51
|
+
const effectiveDeceleration = calculateEffectiveDeceleration(
|
|
52
|
+
input.deceleration,
|
|
53
|
+
input.gradePercent,
|
|
54
|
+
);
|
|
55
|
+
const reactionDistance = speedMps * reactionTime;
|
|
56
|
+
const brakingDistance = (speedMps ** 2) / (2 * effectiveDeceleration);
|
|
57
|
+
const brakingTime = speedMps / effectiveDeceleration;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
speedMps,
|
|
61
|
+
reactionDistance,
|
|
62
|
+
brakingDistance,
|
|
63
|
+
totalDistance: reactionDistance + brakingDistance,
|
|
64
|
+
brakingTime,
|
|
65
|
+
totalTime: reactionTime + brakingTime,
|
|
66
|
+
effectiveDeceleration,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function calculateSafetyDistanceScenario(
|
|
71
|
+
result: BrakingResult,
|
|
72
|
+
gapSeconds: number,
|
|
73
|
+
): SafetyDistanceScenario {
|
|
74
|
+
const safeGapSeconds = nonNegative(gapSeconds);
|
|
75
|
+
const gapDistance = result.speedMps * safeGapSeconds;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
gapSeconds: safeGapSeconds,
|
|
79
|
+
gapDistance,
|
|
80
|
+
marginDistance: gapDistance - result.totalDistance,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { brakingDistanceCalculator } from './entry';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props;
|
|
11
|
+
const loader = brakingDistanceCalculator.i18n[locale] ?? brakingDistanceCalculator.i18n.en;
|
|
12
|
+
const content = await loader?.();
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SpeedUnit, VehicleType } from './logic';
|
|
2
|
+
|
|
3
|
+
export interface BrakingState {
|
|
4
|
+
speed: number;
|
|
5
|
+
unit: SpeedUnit;
|
|
6
|
+
reactionTime: number;
|
|
7
|
+
deceleration: number;
|
|
8
|
+
gradePercent: number;
|
|
9
|
+
gapSeconds: number;
|
|
10
|
+
vehicle: VehicleType;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const STORAGE_KEY = 'braking-distance-calculator-state';
|
|
14
|
+
|
|
15
|
+
export function loadBrakingState(): BrakingState | null {
|
|
16
|
+
try {
|
|
17
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
18
|
+
return raw ? JSON.parse(raw) as BrakingState : null;
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function saveBrakingState(state: BrakingState): void {
|
|
25
|
+
try {
|
|
26
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
27
|
+
} catch {}
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface BrakingDistanceCalculatorUI extends Record<string, string> {
|
|
2
|
+
unitLabel: string;
|
|
3
|
+
metricUnit: string;
|
|
4
|
+
imperialUnit: string;
|
|
5
|
+
vehicleLabel: string;
|
|
6
|
+
carLabel: string;
|
|
7
|
+
motorcycleLabel: string;
|
|
8
|
+
speedLabel: string;
|
|
9
|
+
reactionTimeLabel: string;
|
|
10
|
+
gradeLabel: string;
|
|
11
|
+
roadConditionLabel: string;
|
|
12
|
+
dryRoadLabel: string;
|
|
13
|
+
wetRoadLabel: string;
|
|
14
|
+
snowRoadLabel: string;
|
|
15
|
+
iceRoadLabel: string;
|
|
16
|
+
decelerationLabel: string;
|
|
17
|
+
reactionDistanceLabel: string;
|
|
18
|
+
brakingDistanceLabel: string;
|
|
19
|
+
totalDistanceLabel: string;
|
|
20
|
+
totalTimeLabel: string;
|
|
21
|
+
roadStartLabel: string;
|
|
22
|
+
brakePointLabel: string;
|
|
23
|
+
stopPointLabel: string;
|
|
24
|
+
safetyExperimentLabel: string;
|
|
25
|
+
safetyExperimentHint: string;
|
|
26
|
+
gapTimeLabel: string;
|
|
27
|
+
availableDistanceLabel: string;
|
|
28
|
+
marginLabel: string;
|
|
29
|
+
shortfallLabel: string;
|
|
30
|
+
simulateLabel: string;
|
|
31
|
+
leadVehicleLabel: string;
|
|
32
|
+
enoughGapMessage: string;
|
|
33
|
+
tightGapMessage: string;
|
|
34
|
+
insufficientGapMessage: string;
|
|
35
|
+
advancedLabel: string;
|
|
36
|
+
normalMessage: string;
|
|
37
|
+
warningMessage: string;
|
|
38
|
+
criticalMessage: string;
|
|
39
|
+
carNote: string;
|
|
40
|
+
motorcycleNote: string;
|
|
41
|
+
estimateNotice: string;
|
|
42
|
+
}
|
package/src/tools.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { SEOSection } from '@jjlmoya/utils-shared';
|
|
2
|
+
import type { WithContext, Thing } from 'schema-dts';
|
|
3
|
+
|
|
4
|
+
export type { SEOSection };
|
|
5
|
+
|
|
6
|
+
export type KnownLocale =
|
|
7
|
+
| 'ar' | 'da' | 'de' | 'en' | 'es' | 'fi'
|
|
8
|
+
| 'fr' | 'id' | 'it' | 'ja' | 'ko' | 'nb' | 'nl'
|
|
9
|
+
| 'pl' | 'pt' | 'ru' | 'sv' | 'tr' | 'zh';
|
|
10
|
+
|
|
11
|
+
export interface FAQItem {
|
|
12
|
+
question: string;
|
|
13
|
+
answer: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface BibliographyEntry {
|
|
17
|
+
name: string;
|
|
18
|
+
url: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface HowToStep {
|
|
22
|
+
name: string;
|
|
23
|
+
text: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ToolLocaleContent<TUI extends Record<string, string> = Record<string, string>> {
|
|
27
|
+
slug: string;
|
|
28
|
+
title: string;
|
|
29
|
+
description: string;
|
|
30
|
+
ui: TUI;
|
|
31
|
+
seo: SEOSection[];
|
|
32
|
+
faqTitle?: string;
|
|
33
|
+
faq: FAQItem[];
|
|
34
|
+
bibliographyTitle?: string;
|
|
35
|
+
bibliography: BibliographyEntry[];
|
|
36
|
+
howTo: HowToStep[];
|
|
37
|
+
schemas: WithContext<Thing>[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CategoryLocaleContent {
|
|
41
|
+
slug: string;
|
|
42
|
+
title: string;
|
|
43
|
+
description: string;
|
|
44
|
+
seo: SEOSection[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export type LocaleLoader<T> = () => Promise<T>;
|
|
48
|
+
|
|
49
|
+
export type LocaleMap<T> = Partial<Record<KnownLocale, LocaleLoader<T>>>;
|
|
50
|
+
|
|
51
|
+
export interface MotorToolEntry<TUI extends Record<string, string> = Record<string, string>> {
|
|
52
|
+
id: string;
|
|
53
|
+
icons: {
|
|
54
|
+
bg: string;
|
|
55
|
+
fg: string;
|
|
56
|
+
};
|
|
57
|
+
i18n: LocaleMap<ToolLocaleContent<TUI>>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface MotorCategoryEntry {
|
|
61
|
+
icon: string;
|
|
62
|
+
tools: MotorToolEntry<Record<string, string>>[];
|
|
63
|
+
i18n: LocaleMap<CategoryLocaleContent>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface ToolDefinition {
|
|
67
|
+
entry: MotorToolEntry<Record<string, string>>;
|
|
68
|
+
Component: unknown;
|
|
69
|
+
SEOComponent: unknown;
|
|
70
|
+
BibliographyComponent: unknown;
|
|
71
|
+
}
|
|
72
|
+
|