@jjlmoya/utils-motor 1.11.0 → 1.13.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 +1 -1
- package/src/category/index.ts +2 -1
- package/src/entries.ts +4 -1
- package/src/index.ts +5 -0
- package/src/tests/locale_completeness.test.ts +1 -1
- package/src/tests/mfe_assets_contract.test.ts +48 -0
- package/src/tests/tool_validation.test.ts +1 -1
- package/src/tool/trailerTongueWeightCalculator/bibliography.astro +6 -0
- package/src/tool/trailerTongueWeightCalculator/bibliography.ts +16 -0
- package/src/tool/trailerTongueWeightCalculator/component.astro +116 -0
- package/src/tool/trailerTongueWeightCalculator/controller.ts +143 -0
- package/src/tool/trailerTongueWeightCalculator/dom-views.ts +79 -0
- package/src/tool/trailerTongueWeightCalculator/entry.ts +30 -0
- package/src/tool/trailerTongueWeightCalculator/evaluator.ts +10 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/de.ts +39 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/en.ts +63 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/es.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/fr.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/id.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/it.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/ja.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/ko.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/nl.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/pl.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/pt.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/ru.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/sv.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/tr.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/i18n/zh.ts +38 -0
- package/src/tool/trailerTongueWeightCalculator/index.ts +10 -0
- package/src/tool/trailerTongueWeightCalculator/logic.test.ts +44 -0
- package/src/tool/trailerTongueWeightCalculator/logic.ts +91 -0
- package/src/tool/trailerTongueWeightCalculator/seo.astro +15 -0
- package/src/tool/trailerTongueWeightCalculator/storage.ts +32 -0
- package/src/tool/trailerTongueWeightCalculator/trailer-tongue-weight-calculator.css +429 -0
- package/src/tool/trailerTongueWeightCalculator/ui.ts +49 -0
- package/src/tools.ts +4 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliographyEntries } from '../bibliography';
|
|
4
|
+
import type { TrailerTongueWeightCalculatorUI } from '../ui';
|
|
5
|
+
|
|
6
|
+
const ui: TrailerTongueWeightCalculatorUI = {
|
|
7
|
+
metricLabel: 'Метрическая кг / м', imperialLabel: 'Имперская фунт / фут', resetLabel: 'Сбросить расчёт', presetLabel: 'Проверить положение груза', balancedPreset: 'Сбалансировано', frontHeavyPreset: 'Тяжёлый перед', rearHeavyPreset: 'Тяжёлый зад', presetMetaTemplate: '{mass} загружено, центр {position}', geometryLabel: 'Геометрия прицепа', geometryHint: 'Разместите общий центр груза между сцепкой и осью', totalMassLabel: 'Масса загруженного прицепа', totalMassHint: 'Полная масса прицепа и груза перед поездкой', hitchDistanceLabel: 'Расстояние от сцепки до оси', hitchDistanceHint: 'Измерьте от точки соединения до осевой линии', centerPositionLabel: 'Центр груза от оси', centerPositionHint: 'Положительное значение направлено к сцепке, отрицательное за ось', tongueLimitsLabel: 'Ограничения сцепки', tongueLimitsHint: 'Используйте ограничения автомобиля и сцепного устройства', minimumTongueLabel: 'Минимальная нагрузка на сцепку', minimumTongueHint: 'Желаемая нагрузка вниз на соединение', maximumTongueLabel: 'Максимальная нагрузка на сцепку', maximumTongueHint: 'Меньшее из ограничений сцепки и автомобиля', resultKicker: 'Равновесие у соединения', resultTitle: 'Посмотрите, куда действует груз', sceneLabel: 'Вид сбоку на прицеп со сцепкой, осью, центром масс и рассчитанной нагрузкой', hitchLabel: 'Сцепка', axleLabel: 'Ось', centerOfMassLabel: 'Центр груза', loadedMassLabel: 'Загруженная масса', tongueWeightLabel: 'Нагрузка на сцепку', tonguePercentLabel: 'Доля на сцепке', axleLoadLabel: 'Остаток на оси', allowedRangeLabel: 'Допустимый диапазон', statusSafe: 'В диапазоне', statusLight: 'Слишком мало', statusHeavy: 'Слишком много', statusInvalid: 'Проверьте ограничения', shiftTitle: 'Практическая корректировка груза', shiftForwardTemplate: 'Переместите общий центр масс примерно на {distance} к сцепке, чтобы попасть в середину диапазона.', shiftRearwardTemplate: 'Переместите общий центр масс примерно на {distance} к оси, чтобы попасть в середину диапазона.', shiftNone: 'Текущий центр уже близок к середине выбранного диапазона.', summaryTemplate: 'При положении {position} от оси на сцепку приходится {tongue}, то есть {percent} загруженной массы прицепа.', formulaLabel: 'Модель', formulaText: 'Нагрузка на сцепку = загруженная масса × расстояние до центра груза ÷ расстояние от сцепки до оси. Модель рассматривает неподвижный прицеп и введённый общий центр масс.', inputUnitMass: 'кг', inputUnitDistance: 'м', outputUnitMass: 'кг',
|
|
8
|
+
};
|
|
9
|
+
const faq = [
|
|
10
|
+
{ question: 'Что оценивает калькулятор нагрузки на сцепку прицепа?', answer: 'Он оценивает направленную вниз нагрузку на соединение по массе прицепа, расстоянию от сцепки до оси и положению общего центра груза от оси. Затем результат сравнивается с введёнными пределами.' },
|
|
11
|
+
{ question: 'Почему перемещение груза назад уменьшает нагрузку на сцепку?', answer: 'Когда центр смещается к оси или за неё, плечо до сцепки становится короче. В статической модели нагрузка на соединение уменьшается, и прицеп может стать менее устойчивым.' },
|
|
12
|
+
{ question: 'Какие ограничения сцепки нужно ввести?', answer: 'Введите ограничения для конкретной комбинации. Максимумом должно быть наименьшее значение для автомобиля, фаркопа, соединения и прицепа. Калькулятор не проверяет данные производителя или законность.' },
|
|
13
|
+
{ question: 'Заменяет ли этот инструмент взвешивание прицепа?', answer: 'Нет. Это модель для планирования загрузки. Проверьте данные производителя и измерьте реальную нагрузку на соединение подходящими весами перед поездкой.' },
|
|
14
|
+
{ question: 'Что означает предложенная корректировка?', answer: 'Она показывает изменение общего центра масс, необходимое для достижения середины выбранного диапазона. Это не указание переместить конкретный предмет на такое расстояние.' },
|
|
15
|
+
];
|
|
16
|
+
const howTo = [
|
|
17
|
+
{ name: 'Выберите систему единиц', text: 'Перед вводом размеров выберите метрическую кг / м или имперскую фунт / фут.' },
|
|
18
|
+
{ name: 'Введите загруженную геометрию', text: 'Укажите полную массу, расстояние от сцепки до оси и положение центра груза от оси.' },
|
|
19
|
+
{ name: 'Добавьте ограничения соединения', text: 'Введите желаемую минимальную нагрузку и наименьший применимый максимальный предел.' },
|
|
20
|
+
{ name: 'Прочитайте и исправьте результат', text: 'Проверьте схему равновесия, диапазон нагрузки и направление смещения центра груза.' },
|
|
21
|
+
];
|
|
22
|
+
const schemas: ToolLocaleContent<TrailerTongueWeightCalculatorUI>['schemas'] = [
|
|
23
|
+
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) } as WithContext<FAQPage>,
|
|
24
|
+
{ '@context': 'https://schema.org', '@type': 'HowTo', name: 'Оценить нагрузку на сцепку перед буксировкой', description: 'Рассчитайте статическую нагрузку на сцепку по массе и положению центра груза.', step: howTo.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name, text: step.text })) } as WithContext<HowTo>,
|
|
25
|
+
{ '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Калькулятор нагрузки на сцепку прицепа', description: 'Оцените нагрузку на сцепку по массе, расстоянию до оси и центру груза в браузере.', applicationCategory: 'UtilitiesApplication', operatingSystem: 'All', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, inLanguage: 'ru' } as WithContext<SoftwareApplication>,
|
|
26
|
+
];
|
|
27
|
+
export const content: ToolLocaleContent<TrailerTongueWeightCalculatorUI> = { slug: 'kalkulyator-nagruzki-na-farkop-pricepa', title: 'Калькулятор нагрузки на сцепку прицепа', description: 'Оцените нагрузку вниз на сцепку по массе, расстоянию до оси и центру груза перед буксировкой.', ui, faqTitle: 'Частые вопросы', faq, howTo, bibliographyTitle: 'Библиографические источники', bibliography: bibliographyEntries, schemas, seo: [
|
|
28
|
+
{ type: 'title', text: 'Рассчитайте нагрузку на сцепку прицепа', level: 2 },
|
|
29
|
+
{ type: 'paragraph', html: 'Этот калькулятор превращает положение груза в статическую оценку нагрузки вниз на соединение прицепа. Введите массу в дорожном состоянии, расстояние от оси до сцепки и общий центр масс.' },
|
|
30
|
+
{ type: 'paragraph', html: 'Так можно проверить план загрузки до перемещения коробок, инструментов, велосипедов и другого груза. Результат показывает нагрузку на сцепку, её долю, остаток на оси и соответствие введённым пределам.' },
|
|
31
|
+
{ type: 'title', text: 'Как положение груза меняет нагрузку на сцепку', level: 2 },
|
|
32
|
+
{ type: 'paragraph', html: 'Модель использует статическое вращательное равновесие вокруг оси. Центр ближе к сцепке увеличивает плечо и передаёт больше нагрузки на соединение. Центр на оси или за ней уменьшает нагрузку.' },
|
|
33
|
+
{ type: 'table', headers: ['Ввод', 'Роль в модели', 'Поддерживаемое решение'], rows: [['Загруженная масса', 'Общая нагрузка вниз', 'Учесть весь прицеп и груз'], ['Расстояние от сцепки до оси', 'Плечо рычага', 'Использовать реальную геометрию'], ['Центр груза от оси', 'Положение общего центра масс', 'Проверить переднюю и заднюю загрузку'], ['Пределы сцепки', 'Диапазон сравнения', 'Найти необходимость перераспределения']] },
|
|
34
|
+
{ type: 'title', text: 'Используйте результат до начала движения', level: 2 },
|
|
35
|
+
{ type: 'paragraph', html: 'Начните с массы, которую действительно будете буксировать, и сравните рассчитанную нагрузку с самым низким пределом автомобиля, фаркопа, соединения и прицепа. Подходящую спецификацию нужно определить самостоятельно.' },
|
|
36
|
+
{ type: 'list', items: ['Размещайте тяжёлые предметы низко и ближе к оси, если это разрешено инструкцией.', 'Используйте предложенное направление для проверки нового общего центра масс.', 'После перераспределения закрепите всё и измерьте реальную нагрузку подходящими весами.'] },
|
|
37
|
+
{ type: 'tip', title: 'Считайте результат проверкой загрузки, а не юридическим пределом', html: 'Калькулятор предполагает неподвижный жёсткий прицеп и один общий центр масс. Динамические силы, торможение, уклон, подвеска, нагрузка на шины, рейтинги производителя и измерение соединения не учитываются.' },
|
|
38
|
+
] };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliographyEntries } from '../bibliography';
|
|
4
|
+
import type { TrailerTongueWeightCalculatorUI } from '../ui';
|
|
5
|
+
|
|
6
|
+
const ui: TrailerTongueWeightCalculatorUI = {
|
|
7
|
+
metricLabel: 'Metriskt kg / m', imperialLabel: 'Imperial lb / ft', resetLabel: 'Återställ kalkylatorn', presetLabel: 'Prova lastposition', balancedPreset: 'Balanserad', frontHeavyPreset: 'Tung fram', rearHeavyPreset: 'Tung bak', presetMetaTemplate: '{mass} lastad, centrum {position}', geometryLabel: 'Släpvagnens geometri', geometryHint: 'Placera lastens tyngdpunkt mellan kopplingen och axeln', totalMassLabel: 'Lastad släpvagnsmassa', totalMassHint: 'Hela släpvagnen och lasten klar för färd', hitchDistanceLabel: 'Avstånd från koppling till axel', hitchDistanceHint: 'Mät från kopplingspunkten till axelns referens', centerPositionLabel: 'Lastcentrum från axeln', centerPositionHint: 'Positivt är mot kopplingen, negativt bakom axeln', tongueLimitsLabel: 'Kopplingsgränser', tongueLimitsHint: 'Använd gränserna för fordonet och kopplingen', minimumTongueLabel: 'Minsta kultryck', minimumTongueHint: 'Önskad nedåtriktad belastning på kopplingen', maximumTongueLabel: 'Högsta kultryck', maximumTongueHint: 'Det lägre av kopplingens och fordonets gränser', resultKicker: 'Balans vid kopplingen', resultTitle: 'Se vart lasten verkar', sceneLabel: 'Sidovy av släpvagn med koppling, axel, tyngdpunkt och beräknat kultryck', hitchLabel: 'Koppling', axleLabel: 'Axel', centerOfMassLabel: 'Lastcentrum', loadedMassLabel: 'Lastad massa', tongueWeightLabel: 'Kultryck', tonguePercentLabel: 'Andel på kopplingen', axleLoadLabel: 'Återstående axellast', allowedRangeLabel: 'Tillåtet intervall', statusSafe: 'Inom intervallet', statusLight: 'För lätt', statusHeavy: 'För tung', statusInvalid: 'Kontrollera gränserna', shiftTitle: 'Praktisk lastjustering', shiftForwardTemplate: 'Flytta den totala tyngdpunkten ungefär {distance} mot kopplingen för att nå mitten av intervallet.', shiftRearwardTemplate: 'Flytta den totala tyngdpunkten ungefär {distance} mot axeln för att nå mitten av intervallet.', shiftNone: 'Det aktuella centrumet ligger redan nära mitten av det valda intervallet.', summaryTemplate: 'Vid {position} från axeln verkar {tongue} på kopplingen, alltså {percent} av den lastade släpvagnsmassan.', formulaLabel: 'Modell', formulaText: 'Kultryck = lastad massa × lastcentrumets avstånd ÷ avstånd från koppling till axel. Modellen behandlar släpvagnen som stillastående och använder den angivna totala tyngdpunkten.', inputUnitMass: 'kg', inputUnitDistance: 'm', outputUnitMass: 'kg',
|
|
8
|
+
};
|
|
9
|
+
const faq = [
|
|
10
|
+
{ question: 'Vad uppskattar kalkylatorn för släpvagnens kultryck?', answer: 'Den uppskattar den nedåtriktade belastningen på kopplingen från lastad massa, avståndet mellan koppling och axel samt lastcentrum mätt från axeln. Resultatet jämförs med dina minimi- och maxgränser.' },
|
|
11
|
+
{ question: 'Varför minskar kultrycket när lasten flyttas bakåt?', answer: 'Hävstångsarmen till kopplingen blir kortare när tyngdpunkten flyttas mot axeln eller bakom den. I den statiska modellen minskar belastningen och släpvagnen kan bli mindre stabil.' },
|
|
12
|
+
{ question: 'Vilken kultrycksgräns ska jag ange?', answer: 'Ange gränserna som gäller för din kombination. Maxvärdet ska vara det lägsta av fordonets, dragkrokens, kopplingens och släpvagnens gränser. Kalkylatorn kontrollerar inte tillverkaruppgifter eller laglighet.' },
|
|
13
|
+
{ question: 'Kan verktyget ersätta vägning av släpvagnen?', answer: 'Nej. Det är en planeringsmodell innan du flyttar lasten. Kontrollera tillverkarens uppgifter och mät den faktiska kopplingsbelastningen med en lämplig våg.' },
|
|
14
|
+
{ question: 'Vad betyder den föreslagna justeringen?', answer: 'Den visar hur mycket den totala tyngdpunkten behöver ändras för att nå mitten av valt intervall. Det är inte en instruktion att flytta ett visst föremål exakt så långt.' },
|
|
15
|
+
];
|
|
16
|
+
const howTo = [
|
|
17
|
+
{ name: 'Välj enhetssystem', text: 'Välj metriskt kg / m eller imperial lb / ft innan du anger mått.' },
|
|
18
|
+
{ name: 'Ange lastad geometri', text: 'Lägg till full lastad massa, avståndet från koppling till axel och lastcentrumets avstånd från axeln.' },
|
|
19
|
+
{ name: 'Lägg till kopplingsgränser', text: 'Ange önskad minsta belastning och den lägsta tillämpliga maxgränsen.' },
|
|
20
|
+
{ name: 'Läs och justera resultatet', text: 'Kontrollera balansbilden, belastningsintervallet och föreslagen riktning för att flytta lastcentrum.' },
|
|
21
|
+
];
|
|
22
|
+
const schemas: ToolLocaleContent<TrailerTongueWeightCalculatorUI>['schemas'] = [
|
|
23
|
+
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) } as WithContext<FAQPage>,
|
|
24
|
+
{ '@context': 'https://schema.org', '@type': 'HowTo', name: 'Uppskatta kultryck före körning', description: 'Beräkna statiskt kultryck från massa och lastcentrumets position.', step: howTo.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name, text: step.text })) } as WithContext<HowTo>,
|
|
25
|
+
{ '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Kalkylator för släpvagnens kultryck', description: 'Uppskatta kultryck från lastad massa, axelavstånd och lastcentrum i webbläsaren.', applicationCategory: 'UtilitiesApplication', operatingSystem: 'All', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, inLanguage: 'sv' } as WithContext<SoftwareApplication>,
|
|
26
|
+
];
|
|
27
|
+
export const content: ToolLocaleContent<TrailerTongueWeightCalculatorUI> = { slug: 'berakna-kultryck-slapvagn', title: 'Kalkylator för släpvagnens kultryck', description: 'Uppskatta släpvagnens nedåtriktade kultryck från massa, axelavstånd och lastcentrum före körning.', ui, faqTitle: 'Vanliga frågor', faq, howTo, bibliographyTitle: 'Bibliografiska källor', bibliography: bibliographyEntries, schemas, seo: [
|
|
28
|
+
{ type: 'title', text: 'Beräkna belastningen på släpvagnens koppling', level: 2 },
|
|
29
|
+
{ type: 'paragraph', html: 'Den här kalkylatorn omvandlar en lastposition till en statisk uppskattning av den nedåtriktade belastningen på släpvagnens koppling. Ange massan som ska färdas, avståndet från axeln till kopplingen och den totala tyngdpunkten.' },
|
|
30
|
+
{ type: 'paragraph', html: 'Resultatet hjälper dig att testa en lastplan innan du flyttar lådor, verktyg, cyklar eller annan last. Du får kultryck, procentandel, återstående axellast och jämförelse med dina gränser.' },
|
|
31
|
+
{ type: 'title', text: 'Så ändrar lastens placering kultrycket', level: 2 },
|
|
32
|
+
{ type: 'paragraph', html: 'Modellen använder statisk rotationsjämvikt runt axeln. En tyngdpunkt närmare kopplingen ökar hävarmen och överför mer last till kopplingen. En tyngdpunkt vid eller bakom axeln minskar belastningen.' },
|
|
33
|
+
{ type: 'table', headers: ['Indata', 'Roll i modellen', 'Beslut som stöds'], rows: [['Lastad massa', 'Total nedåtriktad belastning', 'Ta med hela släpvagnen och lasten'], ['Avstånd koppling till axel', 'Hävarm', 'Använd den verkliga geometrin'], ['Lastcentrum från axeln', 'Det kombinerade masscentrumets läge', 'Testa främre och bakre last'], ['Kultrycksgränser', 'Jämförelseintervall', 'Upptäck behov av omfördelning']] },
|
|
34
|
+
{ type: 'title', text: 'Använd resultatet innan släpvagnen flyttas', level: 2 },
|
|
35
|
+
{ type: 'paragraph', html: 'Börja med massan du faktiskt ska dra och jämför det beräknade kultrycket med den lägsta tillämpliga gränsen för fordon, dragkrok, koppling och släpvagn. Verktyget kan inte avgöra vilken uppgift som gäller.' },
|
|
36
|
+
{ type: 'list', items: ['Placera tunga saker lågt och nära axeln om släpvagnens instruktioner tillåter det.', 'Använd den föreslagna riktningen för att testa en ny total tyngdpunkt.', 'Säkra allt efter omfördelning och kontrollera den faktiska kopplingsbelastningen med lämplig våg.'] },
|
|
37
|
+
{ type: 'tip', title: 'Se uppskattningen som en lastkontroll, inte en laglig gräns', html: 'Kalkylatorn antar en stillastående styv släpvagn och en enda total tyngdpunkt. Dynamiska krafter, bromsning, lutning, fjädring, däcklaster, tillverkargränser och verklig mätning ingår inte.' },
|
|
38
|
+
] };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliographyEntries } from '../bibliography';
|
|
4
|
+
import type { TrailerTongueWeightCalculatorUI } from '../ui';
|
|
5
|
+
|
|
6
|
+
const ui: TrailerTongueWeightCalculatorUI = {
|
|
7
|
+
metricLabel: 'Metrik kg / m', imperialLabel: 'İngiliz lb / ft', resetLabel: 'Hesaplayıcıyı sıfırla', presetLabel: 'Yük konumunu dene', balancedPreset: 'Dengeli', frontHeavyPreset: 'Ön ağır', rearHeavyPreset: 'Arka ağır', presetMetaTemplate: '{mass} yüklü, merkez {position}', geometryLabel: 'Römork geometrisi', geometryHint: 'Yüklü ağırlık merkezini çeki demiri ile aks arasına yerleştirin', totalMassLabel: 'Yüklü römork kütlesi', totalMassHint: 'Çekmeye hazır römork ve yükün tamamı', hitchDistanceLabel: 'Çeki demiri ile aks arasındaki mesafe', hitchDistanceHint: 'Bağlantı noktasından aks referansına kadar ölçün', centerPositionLabel: 'Aksın yük merkezine mesafesi', centerPositionHint: 'Pozitif değer çeki demirine doğru, negatif değer aksın arkasındadır', tongueLimitsLabel: 'Bağlantı sınırları', tongueLimitsHint: 'Aracınız ve çeki donanımınız için belirtilen sınırları kullanın', minimumTongueLabel: 'Minimum çeki yükü', minimumTongueHint: 'Bağlantıda istediğiniz aşağı yönlü yük', maximumTongueLabel: 'Maksimum çeki yükü', maximumTongueHint: 'Çeki demiri ve araç sınırlarından düşük olanı', resultKicker: 'Bağlantıdaki denge', resultTitle: 'Yükün nereye etki ettiğini görün', sceneLabel: 'Çeki demiri, aks, ağırlık merkezi ve hesaplanan çeki yükünü gösteren römork yan görünümü', hitchLabel: 'Çeki demiri', axleLabel: 'Aks', centerOfMassLabel: 'Yük merkezi', loadedMassLabel: 'Yüklü kütle', tongueWeightLabel: 'Çeki yükü', tonguePercentLabel: 'Çeki demirindeki pay', axleLoadLabel: 'Aksta kalan yük', allowedRangeLabel: 'İzin verilen aralık', statusSafe: 'Aralık içinde', statusLight: 'Çok hafif', statusHeavy: 'Çok ağır', statusInvalid: 'Sınırları kontrol edin', shiftTitle: 'Pratik yük ayarı', shiftForwardTemplate: 'Aralığın ortasına ulaşmak için toplam ağırlık merkezini çeki demirine doğru yaklaşık {distance} taşıyın.', shiftRearwardTemplate: 'Aralığın ortasına ulaşmak için toplam ağırlık merkezini aksa doğru yaklaşık {distance} taşıyın.', shiftNone: 'Mevcut merkez seçtiğiniz aralığın ortasına zaten yakın.', summaryTemplate: 'Akstan {position} konumunda, çeki demirine {tongue} etki eder; bu, yüklü römork kütlesinin {percent} değeridir.', formulaLabel: 'Model', formulaText: 'Çeki yükü = yüklü kütle × yük merkezi mesafesi ÷ çeki demiri ile aks arasındaki mesafe. Model römorku sabit kabul eder ve girdiğiniz toplam ağırlık merkezini kullanır.', inputUnitMass: 'kg', inputUnitDistance: 'm', outputUnitMass: 'kg',
|
|
8
|
+
};
|
|
9
|
+
const faq = [
|
|
10
|
+
{ question: 'Römork çeki yükü hesaplayıcı neyi tahmin eder?', answer: 'Yüklü römork kütlesi, çeki demiri ile aks arasındaki mesafe ve akstan ölçülen toplam yük merkeziyle bağlantıdaki aşağı yönlü yükü tahmin eder. Sonucu girdiğiniz alt ve üst sınırlarla karşılaştırır.' },
|
|
11
|
+
{ question: 'Yükü arkaya taşımak çeki yükünü neden azaltır?', answer: 'Merkez aksa veya aksın arkasına taşındığında çeki demirine giden kaldıraç kolu kısalır. Statik denge modelinde bağlantı yükü azalır ve römork daha dengesiz olabilir.' },
|
|
12
|
+
{ question: 'Hangi çeki sınırını girmeliyim?', answer: 'Gerçek kombinasyonunuz için geçerli sınırları girin. Maksimum değer araç, çeki donanımı, bağlantı ve römork sınırlarının en düşüğüdür. Hesaplayıcı üretici bilgisini veya yasal uygunluğu doğrulamaz.' },
|
|
13
|
+
{ question: 'Bu araç römorku tartmanın yerini tutar mı?', answer: 'Hayır. Yükü taşımadan önce planlama yapmak için kullanılan bir modeldir. Üretici bilgilerini kontrol edin ve yola çıkmadan önce gerçek bağlantı yükünü uygun bir kantarla ölçün.' },
|
|
14
|
+
{ question: 'Önerilen ayar ne anlama gelir?', answer: 'Seçtiğiniz yük aralığının ortasına ulaşmak için toplam ağırlık merkezinin ne kadar değişmesi gerektiğini gösterir. Belirli bir nesneyi tam bu mesafe kadar taşıma talimatı değildir.' },
|
|
15
|
+
];
|
|
16
|
+
const howTo = [
|
|
17
|
+
{ name: 'Birim sistemini seçin', text: 'Ölçüleri girmeden önce metrik kg / m veya İngiliz lb / ft seçin.' },
|
|
18
|
+
{ name: 'Yüklü geometriyi girin', text: 'Toplam yüklü kütleyi, çeki demiri ile aks arasındaki mesafeyi ve akstan yük merkezine mesafeyi ekleyin.' },
|
|
19
|
+
{ name: 'Bağlantı sınırlarını ekleyin', text: 'İstediğiniz minimum aşağı yönlü yükü ve geçerli en düşük maksimum sınırı girin.' },
|
|
20
|
+
{ name: 'Sonucu okuyup ayarlayın', text: 'Denge görüntüsünü, yük aralığını ve yük merkezini taşıma yönü önerisini kontrol edin.' },
|
|
21
|
+
];
|
|
22
|
+
const schemas: ToolLocaleContent<TrailerTongueWeightCalculatorUI>['schemas'] = [
|
|
23
|
+
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) } as WithContext<FAQPage>,
|
|
24
|
+
{ '@context': 'https://schema.org', '@type': 'HowTo', name: 'Çekmeden önce römork çeki yükünü tahmin edin', description: 'Kütle ve yük merkezi konumundan statik çeki yükünü hesaplayın.', step: howTo.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name, text: step.text })) } as WithContext<HowTo>,
|
|
25
|
+
{ '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: 'Römork çeki yükü hesaplayıcı', description: 'Yüklü kütle, aks mesafesi ve yük merkezi konumundan tarayıcıda çeki yükünü tahmin edin.', applicationCategory: 'UtilitiesApplication', operatingSystem: 'All', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, inLanguage: 'tr' } as WithContext<SoftwareApplication>,
|
|
26
|
+
];
|
|
27
|
+
export const content: ToolLocaleContent<TrailerTongueWeightCalculatorUI> = { slug: 'romork-dil-yuku-hesaplama', title: 'Römork Çeki Yükü Hesaplayıcı', description: 'Çekmeden önce yüklü kütle, aks mesafesi ve yük merkezi konumundan römorkun aşağı yönlü çeki yükünü tahmin edin.', ui, faqTitle: 'Sık sorulan sorular', faq, howTo, bibliographyTitle: 'Kaynakça', bibliography: bibliographyEntries, schemas, seo: [
|
|
28
|
+
{ type: 'title', text: 'Römork Çeki Demirindeki Yükü Hesaplayın', level: 2 },
|
|
29
|
+
{ type: 'paragraph', html: 'Bu hesaplayıcı, yük konumunu römork bağlantısındaki aşağı yönlü yükün statik tahminine dönüştürür. Seyir halindeki kütleyi, aks ile çeki demiri arasındaki mesafeyi ve toplam ağırlık merkezini girin.' },
|
|
30
|
+
{ type: 'paragraph', html: 'Kutuları, aletleri, bisikletleri veya başka bir yükü taşımadan önce yükleme planını test edebilirsiniz. Sonuç çeki yükünü, oranını, aksta kalan yükü ve sınırlarınıza göre durumu gösterir.' },
|
|
31
|
+
{ type: 'title', text: 'Yük Konumu Çeki Yükünü Nasıl Değiştirir', level: 2 },
|
|
32
|
+
{ type: 'paragraph', html: 'Model aks çevresinde statik dönme dengesini kullanır. Merkez çeki demirine yaklaştıkça kaldıraç kolu büyür ve bağlantıya aktarılan yük artar. Merkez aksta veya arkasındaysa çeki yükü azalır.' },
|
|
33
|
+
{ type: 'table', headers: ['Girdi', 'Modeldeki rolü', 'Desteklediği karar'], rows: [['Yüklü kütle', 'Toplam aşağı yönlü yük', 'Römork ve yükün tamamını dahil etmek'], ['Çeki demiri ile aks mesafesi', 'Kaldıraç kolu', 'Gerçek geometriyi kullanmak'], ['Akstan yük merkezi', 'Birleşik ağırlık merkezi konumu', 'Ön ve arka yüklemeyi denemek'], ['Çeki sınırları', 'Karşılaştırma aralığı', 'Yeniden dağıtım ihtiyacını görmek']] },
|
|
34
|
+
{ type: 'title', text: 'Römorku Hareket Ettirmeden Önce Sonucu Kullanın', level: 2 },
|
|
35
|
+
{ type: 'paragraph', html: 'Gerçekte çekeceğiniz yüklü kütleyle başlayın ve hesaplanan çeki yükünü araç, çeki donanımı, bağlantı ve römork için geçerli en düşük sınırla karşılaştırın. Hangi teknik değerin geçerli olduğunu siz doğrulamalısınız.' },
|
|
36
|
+
{ type: 'list', items: ['Römork talimatları izin veriyorsa ağır eşyaları alçakta ve aksa yakın yerleştirin.', 'Yeni bir toplam ağırlık merkezi denemek için önerilen yönü kullanın.', 'Yükü yeniden dağıttıktan sonra her şeyi sabitleyin ve gerçek bağlantı yükünü uygun bir kantarla kontrol edin.'] },
|
|
37
|
+
{ type: 'tip', title: 'Tahmini bir yükleme kontrolü olarak görün, yasal sınır olarak değil', html: 'Hesaplayıcı sabit ve rijit bir römorkla tek bir toplam ağırlık merkezi varsayar. Dinamik kuvvetler, frenleme, yol eğimi, süspansiyon, lastik yükleri, üretici değerleri ve gerçek ölçüm hesaba katılmaz.' },
|
|
38
|
+
] };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FAQPage, HowTo, SoftwareApplication, WithContext } from 'schema-dts';
|
|
2
|
+
import type { ToolLocaleContent } from '../../../types';
|
|
3
|
+
import { bibliographyEntries } from '../bibliography';
|
|
4
|
+
import type { TrailerTongueWeightCalculatorUI } from '../ui';
|
|
5
|
+
|
|
6
|
+
const ui: TrailerTongueWeightCalculatorUI = {
|
|
7
|
+
metricLabel: '公制 kg / m', imperialLabel: '英制 lb / ft', resetLabel: '重置计算器', presetLabel: '试用装载位置', balancedPreset: '均衡', frontHeavyPreset: '前部偏重', rearHeavyPreset: '后部偏重', presetMetaTemplate: '{mass} 已装载,中心 {position}', geometryLabel: '拖车几何信息', geometryHint: '将装载后的重心放在连接器与车轴之间', totalMassLabel: '装载后的拖车质量', totalMassHint: '准备牵引时拖车和货物的完整质量', hitchDistanceLabel: '连接器到车轴的距离', hitchDistanceHint: '从连接点测量到车轴基准线', centerPositionLabel: '重心到车轴的距离', centerPositionHint: '正值表示朝向连接器,负值表示在车轴后方', tongueLimitsLabel: '连接限制', tongueLimitsHint: '使用车辆和连接器标注的限制值', minimumTongueLabel: '最小连接器载荷', minimumTongueHint: '希望施加在连接处的向下载荷', maximumTongueLabel: '最大连接器载荷', maximumTongueHint: '连接器和车辆限制中较低的数值', resultKicker: '连接处的平衡', resultTitle: '查看载荷作用的方向', sceneLabel: '显示连接器、车轴、重心和计算连接器载荷的拖车侧视图', hitchLabel: '连接器', axleLabel: '车轴', centerOfMassLabel: '装载重心', loadedMassLabel: '装载质量', tongueWeightLabel: '连接器载荷', tonguePercentLabel: '连接器分担比例', axleLoadLabel: '车轴剩余载荷', allowedRangeLabel: '允许范围', statusSafe: '在范围内', statusLight: '过轻', statusHeavy: '过重', statusInvalid: '检查限制', shiftTitle: '实用的装载调整', shiftForwardTemplate: '将整体重心向连接器移动约 {distance},以达到范围中点。', shiftRearwardTemplate: '将整体重心向车轴移动约 {distance},以达到范围中点。', shiftNone: '当前重心已经接近所选范围的中点。', summaryTemplate: '重心位于车轴的 {position} 处时,连接器承受 {tongue},即装载拖车质量的 {percent}。', formulaLabel: '模型', formulaText: '连接器载荷 = 装载质量 × 重心到车轴的距离 ÷ 连接器到车轴的距离。模型将拖车视为静止,并使用输入的整体重心。', inputUnitMass: 'kg', inputUnitDistance: 'm', outputUnitMass: 'kg',
|
|
8
|
+
};
|
|
9
|
+
const faq = [
|
|
10
|
+
{ question: '拖车连接器载荷计算器估算什么?', answer: '它根据装载后的拖车质量、连接器到车轴的距离以及从车轴测量的整体重心,估算连接处的向下载荷,并与输入的最小和最大限制进行比较。' },
|
|
11
|
+
{ question: '为什么把货物向后移动会降低连接器载荷?', answer: '当重心向车轴或车轴后方移动时,到连接器的力臂变短。在静态平衡模型中,连接处载荷会降低,拖车可能变得不稳定。' },
|
|
12
|
+
{ question: '应该输入哪个连接器限制?', answer: '输入适用于实际组合的限制。最大值应取车辆、牵引装置、连接器和拖车限制中的最小值。计算器不会查询制造商规格,也不会认证组合是否合法。' },
|
|
13
|
+
{ question: '这个工具能代替称量拖车吗?', answer: '不能。它是移动货物前的规划模型。请查看制造商资料,并在出发前用合适的秤确认实际连接载荷。' },
|
|
14
|
+
{ question: '建议的调整是什么意思?', answer: '它表示让整体重心到达所选载荷范围中点所需的移动量,并不表示要把某一件物品准确移动这么远。' },
|
|
15
|
+
];
|
|
16
|
+
const howTo = [
|
|
17
|
+
{ name: '选择单位系统', text: '输入尺寸前,选择公制 kg / m 或英制 lb / ft。' },
|
|
18
|
+
{ name: '输入装载几何信息', text: '输入完整装载质量、连接器到车轴的距离以及重心到车轴的距离。' },
|
|
19
|
+
{ name: '添加连接限制', text: '输入希望的最小向下载荷以及适用的最大限制中的较小值。' },
|
|
20
|
+
{ name: '阅读并调整结果', text: '查看平衡图、载荷范围以及移动整体重心的建议方向。' },
|
|
21
|
+
];
|
|
22
|
+
const schemas: ToolLocaleContent<TrailerTongueWeightCalculatorUI>['schemas'] = [
|
|
23
|
+
{ '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) } as WithContext<FAQPage>,
|
|
24
|
+
{ '@context': 'https://schema.org', '@type': 'HowTo', name: '牵引前估算拖车连接器载荷', description: '根据质量和重心位置计算静态连接器载荷。', step: howTo.map((step, index) => ({ '@type': 'HowToStep', position: index + 1, name: step.name, text: step.text })) } as WithContext<HowTo>,
|
|
25
|
+
{ '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: '拖车连接器载荷计算器', description: '在浏览器中根据装载质量、车轴距离和重心位置估算连接器载荷。', applicationCategory: 'UtilitiesApplication', operatingSystem: 'All', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' }, inLanguage: 'zh' } as WithContext<SoftwareApplication>,
|
|
26
|
+
];
|
|
27
|
+
export const content: ToolLocaleContent<TrailerTongueWeightCalculatorUI> = { slug: 'trailer-tongue-weight-calculator', title: '拖车连接器载荷计算器', description: '在牵引前,根据装载质量、车轴距离和重心位置估算拖车连接器的向下载荷。', ui, faqTitle: '常见问题', faq, howTo, bibliographyTitle: '参考资料', bibliography: bibliographyEntries, schemas, seo: [
|
|
28
|
+
{ type: 'title', text: '计算拖车连接处的载荷', level: 2 },
|
|
29
|
+
{ type: 'paragraph', html: '这个计算器会把装载位置转换为拖车连接处向下载荷的静态估算。输入行驶时的质量、车轴到连接器的距离,以及整体重心的位置。' },
|
|
30
|
+
{ type: 'paragraph', html: '在移动箱子、工具、自行车或其他货物之前,可以先测试装载方案。结果会显示连接器载荷、占拖车质量的比例、车轴剩余载荷,以及是否处于输入的限制范围内。' },
|
|
31
|
+
{ type: 'title', text: '装载位置如何改变连接器载荷', level: 2 },
|
|
32
|
+
{ type: 'paragraph', html: '模型使用绕车轴的静态转动平衡。重心靠近连接器时,力臂变长,传递到连接处的载荷增加。重心位于车轴或车轴后方时,连接器载荷会降低。' },
|
|
33
|
+
{ type: 'table', headers: ['输入', '在模型中的作用', '支持的判断'], rows: [['装载质量', '静止状态的总向下载荷', '包含完整拖车和货物'], ['连接器到车轴距离', '连接处与车轴之间的力臂', '使用实际几何尺寸'], ['重心到车轴距离', '合成重心的位置', '测试前部和后部装载'], ['连接器限制', '用于比较的范围', '发现是否需要重新分配']] },
|
|
34
|
+
{ type: 'title', text: '在移动拖车前使用结果', level: 2 },
|
|
35
|
+
{ type: 'paragraph', html: '从实际要牵引的装载质量开始,然后将计算出的连接器载荷与车辆、牵引装置、连接器和拖车中最严格的适用限制比较。计算器无法替你判断哪个规格适用。' },
|
|
36
|
+
{ type: 'list', items: ['如果拖车说明允许,将重物低放并靠近车轴。', '按照建议方向调整整体重心并重新测试。', '重新分配后固定所有物品,并在出发前用合适的秤确认实际连接载荷。'] },
|
|
37
|
+
{ type: 'tip', title: '把估算当作装载检查,而不是法律限制', html: '计算器假设拖车是静止的刚体,并且只有一个整体重心。它不包含动态力、制动、道路坡度、悬挂、轮胎载荷、制造商额定值或实际连接处测量。' },
|
|
38
|
+
] };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { trailerTongueWeightCalculator } from './entry';
|
|
2
|
+
|
|
3
|
+
export * from './entry';
|
|
4
|
+
|
|
5
|
+
export const TRAILER_TONGUE_WEIGHT_CALCULATOR_TOOL = {
|
|
6
|
+
entry: trailerTongueWeightCalculator,
|
|
7
|
+
Component: () => import('./component.astro'),
|
|
8
|
+
SEOComponent: () => import('./seo.astro'),
|
|
9
|
+
BibliographyComponent: () => import('./bibliography.astro'),
|
|
10
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { evaluateTrailerStatus } from './evaluator';
|
|
3
|
+
import { calculateTrailerPlan, convertDistance, convertMass, getPreset, normalizeTrailerInputs, parseDistance, parseMass } from './logic';
|
|
4
|
+
|
|
5
|
+
describe('trailer tongue weight calculator', () => {
|
|
6
|
+
it('calculates the coupling load from the lever arms', () => {
|
|
7
|
+
const plan = calculateTrailerPlan({ totalMassKg: 1200, hitchDistanceM: 3, centerPositionM: 0.5, minimumTongueKg: 90, maximumTongueKg: 180 });
|
|
8
|
+
expect(plan.tongueWeightKg).toBe(200);
|
|
9
|
+
expect(plan.tonguePercent).toBeCloseTo(16.6667, 3);
|
|
10
|
+
expect(plan.axleLoadKg).toBe(1000);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('identifies light, safe, and heavy positions', () => {
|
|
14
|
+
const base = { totalMassKg: 1200, hitchDistanceM: 3, minimumTongueKg: 90, maximumTongueKg: 180 };
|
|
15
|
+
const light = calculateTrailerPlan({ ...base, centerPositionM: 0.1 });
|
|
16
|
+
const safe = calculateTrailerPlan({ ...base, centerPositionM: 0.3 });
|
|
17
|
+
const heavy = calculateTrailerPlan({ ...base, centerPositionM: 0.6 });
|
|
18
|
+
expect(evaluateTrailerStatus(base, light)).toBe('light');
|
|
19
|
+
expect(evaluateTrailerStatus(base, safe)).toBe('safe');
|
|
20
|
+
expect(evaluateTrailerStatus(base, heavy)).toBe('heavy');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns a center shift toward the target range', () => {
|
|
24
|
+
const plan = calculateTrailerPlan({ totalMassKg: 1200, hitchDistanceM: 3, centerPositionM: 0.6, minimumTongueKg: 90, maximumTongueKg: 180 });
|
|
25
|
+
expect(plan.targetCenterPositionM).toBeCloseTo(0.3375, 4);
|
|
26
|
+
expect(plan.shiftDistanceM).toBeCloseTo(0.2625, 4);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('keeps limits ordered after unsafe input', () => {
|
|
30
|
+
const normalized = normalizeTrailerInputs({ totalMassKg: 100, hitchDistanceM: 2, centerPositionM: 0, minimumTongueKg: 300, maximumTongueKg: 100 });
|
|
31
|
+
expect(normalized.maximumTongueKg).toBe(301);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('round trips metric and imperial values', () => {
|
|
35
|
+
expect(convertMass(parseMass(100, 'imperial'), 'imperial')).toBeCloseTo(100, 5);
|
|
36
|
+
expect(convertDistance(parseDistance(12, 'imperial'), 'imperial')).toBeCloseTo(12, 5);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('provides the three named loading presets', () => {
|
|
40
|
+
expect(getPreset('balanced').inputs.centerPositionM).toBe(0.34);
|
|
41
|
+
expect(getPreset('front-heavy').inputs.centerPositionM).toBeGreaterThan(getPreset('balanced').inputs.centerPositionM);
|
|
42
|
+
expect(getPreset('rear-heavy').inputs.centerPositionM).toBeLessThan(getPreset('balanced').inputs.centerPositionM);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export type UnitSystem = 'metric' | 'imperial';
|
|
2
|
+
|
|
3
|
+
export interface TrailerInputs {
|
|
4
|
+
totalMassKg: number;
|
|
5
|
+
hitchDistanceM: number;
|
|
6
|
+
centerPositionM: number;
|
|
7
|
+
minimumTongueKg: number;
|
|
8
|
+
maximumTongueKg: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface TrailerPlan {
|
|
12
|
+
tongueWeightKg: number;
|
|
13
|
+
tonguePercent: number;
|
|
14
|
+
axleLoadKg: number;
|
|
15
|
+
targetTongueKg: number;
|
|
16
|
+
targetCenterPositionM: number;
|
|
17
|
+
shiftDistanceM: number;
|
|
18
|
+
valid: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface TrailerPreset {
|
|
22
|
+
id: 'balanced' | 'front-heavy' | 'rear-heavy';
|
|
23
|
+
inputs: TrailerInputs;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const TRAILER_PRESETS: TrailerPreset[] = [
|
|
27
|
+
{
|
|
28
|
+
id: 'balanced',
|
|
29
|
+
inputs: { totalMassKg: 1200, hitchDistanceM: 3, centerPositionM: 0.34, minimumTongueKg: 90, maximumTongueKg: 180 },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'front-heavy',
|
|
33
|
+
inputs: { totalMassKg: 1200, hitchDistanceM: 3, centerPositionM: 0.56, minimumTongueKg: 90, maximumTongueKg: 180 },
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'rear-heavy',
|
|
37
|
+
inputs: { totalMassKg: 1200, hitchDistanceM: 3, centerPositionM: 0.16, minimumTongueKg: 90, maximumTongueKg: 180 },
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const clamp = (value: number, minimum: number, maximum: number): number => Math.min(maximum, Math.max(minimum, value));
|
|
42
|
+
|
|
43
|
+
const finiteOr = (value: number, fallback: number): number => Number.isFinite(value) ? value : fallback;
|
|
44
|
+
|
|
45
|
+
export function normalizeTrailerInputs(inputs: TrailerInputs): TrailerInputs {
|
|
46
|
+
const minimumTongueKg = clamp(finiteOr(inputs.minimumTongueKg, 90), 0, 5000);
|
|
47
|
+
const maximumTongueKg = clamp(finiteOr(inputs.maximumTongueKg, 180), 1, 5000);
|
|
48
|
+
return {
|
|
49
|
+
totalMassKg: clamp(finiteOr(inputs.totalMassKg, 1200), 1, 20000),
|
|
50
|
+
hitchDistanceM: clamp(finiteOr(inputs.hitchDistanceM, 3), 0.5, 30),
|
|
51
|
+
centerPositionM: clamp(finiteOr(inputs.centerPositionM, 0.34), -15, 30),
|
|
52
|
+
minimumTongueKg,
|
|
53
|
+
maximumTongueKg: Math.max(minimumTongueKg + 1, maximumTongueKg),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function calculateTrailerPlan(rawInputs: TrailerInputs): TrailerPlan {
|
|
58
|
+
const inputs = normalizeTrailerInputs(rawInputs);
|
|
59
|
+
const tongueWeightKg = inputs.totalMassKg * inputs.centerPositionM / inputs.hitchDistanceM;
|
|
60
|
+
const targetTongueKg = (inputs.minimumTongueKg + inputs.maximumTongueKg) / 2;
|
|
61
|
+
const targetCenterPositionM = targetTongueKg * inputs.hitchDistanceM / inputs.totalMassKg;
|
|
62
|
+
return {
|
|
63
|
+
tongueWeightKg,
|
|
64
|
+
tonguePercent: (tongueWeightKg / inputs.totalMassKg) * 100,
|
|
65
|
+
axleLoadKg: inputs.totalMassKg - tongueWeightKg,
|
|
66
|
+
targetTongueKg,
|
|
67
|
+
targetCenterPositionM,
|
|
68
|
+
shiftDistanceM: Math.abs(targetCenterPositionM - inputs.centerPositionM),
|
|
69
|
+
valid: inputs.maximumTongueKg > inputs.minimumTongueKg && inputs.hitchDistanceM > 0 && inputs.totalMassKg > 0,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function convertMass(valueKg: number, unit: UnitSystem): number {
|
|
74
|
+
return unit === 'metric' ? valueKg : valueKg / 0.45359237;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function convertDistance(valueM: number, unit: UnitSystem): number {
|
|
78
|
+
return unit === 'metric' ? valueM : valueM / 0.3048;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function parseMass(value: number, unit: UnitSystem): number {
|
|
82
|
+
return unit === 'metric' ? value : value * 0.45359237;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function parseDistance(value: number, unit: UnitSystem): number {
|
|
86
|
+
return unit === 'metric' ? value : value * 0.3048;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getPreset(id: TrailerPreset['id']): TrailerPreset {
|
|
90
|
+
return TRAILER_PRESETS.find((preset) => preset.id === id) ?? TRAILER_PRESETS[0]!;
|
|
91
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { trailerTongueWeightCalculator } 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 = trailerTongueWeightCalculator.i18n[locale] ?? trailerTongueWeightCalculator.i18n.en;
|
|
12
|
+
const content = await loader?.();
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content && <SEORenderer content={{ locale, sections: content.seo }} />}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { TrailerInputs, UnitSystem } from './logic';
|
|
2
|
+
|
|
3
|
+
interface StoredTrailerState {
|
|
4
|
+
inputs: TrailerInputs;
|
|
5
|
+
unit: UnitSystem;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const STORAGE_KEY = 'jjlmoya-trailer-tongue-weight-calculator';
|
|
9
|
+
|
|
10
|
+
export function loadTrailerState(): Partial<StoredTrailerState> | null {
|
|
11
|
+
try {
|
|
12
|
+
const raw = localStorage.getItem(STORAGE_KEY);
|
|
13
|
+
if (!raw) return null;
|
|
14
|
+
const value: unknown = JSON.parse(raw);
|
|
15
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
16
|
+
return value as Partial<StoredTrailerState>;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function saveTrailerState(state: StoredTrailerState): void {
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function clearTrailerState(): void {
|
|
29
|
+
try {
|
|
30
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|