@jjlmoya/utils-alcohol 1.25.0 → 1.26.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 (139) hide show
  1. package/package.json +1 -1
  2. package/src/entries.ts +7 -1
  3. package/src/index.ts +1 -0
  4. package/src/pages/[locale]/[slug].astro +7 -3
  5. package/src/tests/locale_completeness.test.ts +4 -9
  6. package/src/tests/tool_validation.test.ts +2 -2
  7. package/src/tool/alcoholClearance/bibliography.ts +6 -0
  8. package/src/tool/alcoholClearance/entry.ts +1 -0
  9. package/src/tool/alcoholClearance/i18n/de.ts +1 -12
  10. package/src/tool/alcoholClearance/i18n/en.ts +1 -12
  11. package/src/tool/alcoholClearance/i18n/es.ts +1 -12
  12. package/src/tool/alcoholClearance/i18n/fr.ts +1 -12
  13. package/src/tool/alcoholClearance/i18n/id.ts +1 -12
  14. package/src/tool/alcoholClearance/i18n/it.ts +1 -12
  15. package/src/tool/alcoholClearance/i18n/ja.ts +1 -12
  16. package/src/tool/alcoholClearance/i18n/ko.ts +1 -12
  17. package/src/tool/alcoholClearance/i18n/nl.ts +1 -12
  18. package/src/tool/alcoholClearance/i18n/pl.ts +1 -12
  19. package/src/tool/alcoholClearance/i18n/pt.ts +1 -12
  20. package/src/tool/alcoholClearance/i18n/ru.ts +1 -12
  21. package/src/tool/alcoholClearance/i18n/sv.ts +1 -12
  22. package/src/tool/alcoholClearance/i18n/tr.ts +1 -12
  23. package/src/tool/alcoholClearance/i18n/zh.ts +1 -12
  24. package/src/tool/beerCooler/bibliography.ts +6 -0
  25. package/src/tool/beerCooler/entry.ts +1 -0
  26. package/src/tool/beerCooler/i18n/de.ts +1 -12
  27. package/src/tool/beerCooler/i18n/en.ts +1 -12
  28. package/src/tool/beerCooler/i18n/es.ts +1 -12
  29. package/src/tool/beerCooler/i18n/fr.ts +1 -12
  30. package/src/tool/beerCooler/i18n/id.ts +1 -12
  31. package/src/tool/beerCooler/i18n/it.ts +1 -12
  32. package/src/tool/beerCooler/i18n/ja.ts +1 -12
  33. package/src/tool/beerCooler/i18n/ko.ts +1 -12
  34. package/src/tool/beerCooler/i18n/nl.ts +1 -12
  35. package/src/tool/beerCooler/i18n/pl.ts +1 -12
  36. package/src/tool/beerCooler/i18n/pt.ts +1 -12
  37. package/src/tool/beerCooler/i18n/ru.ts +1 -12
  38. package/src/tool/beerCooler/i18n/sv.ts +1 -12
  39. package/src/tool/beerCooler/i18n/tr.ts +1 -12
  40. package/src/tool/beerCooler/i18n/zh.ts +1 -12
  41. package/src/tool/carbonationCalculator/bibliography.ts +6 -0
  42. package/src/tool/carbonationCalculator/entry.ts +1 -0
  43. package/src/tool/carbonationCalculator/i18n/de.ts +1 -12
  44. package/src/tool/carbonationCalculator/i18n/en.ts +1 -12
  45. package/src/tool/carbonationCalculator/i18n/es.ts +1 -12
  46. package/src/tool/carbonationCalculator/i18n/fr.ts +1 -12
  47. package/src/tool/carbonationCalculator/i18n/id.ts +1 -12
  48. package/src/tool/carbonationCalculator/i18n/it.ts +1 -12
  49. package/src/tool/carbonationCalculator/i18n/ja.ts +1 -12
  50. package/src/tool/carbonationCalculator/i18n/ko.ts +1 -12
  51. package/src/tool/carbonationCalculator/i18n/nl.ts +1 -12
  52. package/src/tool/carbonationCalculator/i18n/pl.ts +1 -12
  53. package/src/tool/carbonationCalculator/i18n/pt.ts +1 -12
  54. package/src/tool/carbonationCalculator/i18n/ru.ts +1 -12
  55. package/src/tool/carbonationCalculator/i18n/sv.ts +1 -12
  56. package/src/tool/carbonationCalculator/i18n/tr.ts +1 -12
  57. package/src/tool/carbonationCalculator/i18n/zh.ts +1 -12
  58. package/src/tool/cocktailBalancer/bibliography.ts +7 -0
  59. package/src/tool/cocktailBalancer/entry.ts +1 -0
  60. package/src/tool/cocktailBalancer/i18n/de.ts +1 -16
  61. package/src/tool/cocktailBalancer/i18n/en.ts +1 -16
  62. package/src/tool/cocktailBalancer/i18n/es.ts +1 -16
  63. package/src/tool/cocktailBalancer/i18n/fr.ts +1 -16
  64. package/src/tool/cocktailBalancer/i18n/id.ts +1 -16
  65. package/src/tool/cocktailBalancer/i18n/it.ts +1 -16
  66. package/src/tool/cocktailBalancer/i18n/ja.ts +1 -16
  67. package/src/tool/cocktailBalancer/i18n/ko.ts +1 -16
  68. package/src/tool/cocktailBalancer/i18n/nl.ts +1 -16
  69. package/src/tool/cocktailBalancer/i18n/pl.ts +1 -16
  70. package/src/tool/cocktailBalancer/i18n/pt.ts +1 -16
  71. package/src/tool/cocktailBalancer/i18n/ru.ts +1 -16
  72. package/src/tool/cocktailBalancer/i18n/sv.ts +1 -16
  73. package/src/tool/cocktailBalancer/i18n/tr.ts +1 -16
  74. package/src/tool/cocktailBalancer/i18n/zh.ts +1 -16
  75. package/src/tool/fortifiedWine/bibliography.astro +14 -0
  76. package/src/tool/fortifiedWine/bibliography.ts +7 -0
  77. package/src/tool/fortifiedWine/component.astro +331 -0
  78. package/src/tool/fortifiedWine/entry.ts +62 -0
  79. package/src/tool/fortifiedWine/fortified-wine-builder.css +534 -0
  80. package/src/tool/fortifiedWine/i18n/de.ts +66 -0
  81. package/src/tool/fortifiedWine/i18n/en.ts +140 -0
  82. package/src/tool/fortifiedWine/i18n/es.ts +140 -0
  83. package/src/tool/fortifiedWine/i18n/fr.ts +91 -0
  84. package/src/tool/fortifiedWine/i18n/id.ts +91 -0
  85. package/src/tool/fortifiedWine/i18n/it.ts +91 -0
  86. package/src/tool/fortifiedWine/i18n/ja.ts +91 -0
  87. package/src/tool/fortifiedWine/i18n/ko.ts +91 -0
  88. package/src/tool/fortifiedWine/i18n/nl.ts +91 -0
  89. package/src/tool/fortifiedWine/i18n/pl.ts +91 -0
  90. package/src/tool/fortifiedWine/i18n/pt.ts +91 -0
  91. package/src/tool/fortifiedWine/i18n/ru.ts +91 -0
  92. package/src/tool/fortifiedWine/i18n/sv.ts +91 -0
  93. package/src/tool/fortifiedWine/i18n/tr.ts +91 -0
  94. package/src/tool/fortifiedWine/i18n/zh.ts +91 -0
  95. package/src/tool/fortifiedWine/index.ts +8 -0
  96. package/src/tool/fortifiedWine/logic.ts +46 -0
  97. package/src/tool/fortifiedWine/seo.astro +41 -0
  98. package/src/tool/jelloShotLab/bibliography.astro +14 -0
  99. package/src/tool/jelloShotLab/bibliography.ts +8 -0
  100. package/src/tool/jelloShotLab/component.astro +183 -0
  101. package/src/tool/jelloShotLab/entry.ts +62 -0
  102. package/src/tool/jelloShotLab/i18n/de.ts +156 -0
  103. package/src/tool/jelloShotLab/i18n/en.ts +156 -0
  104. package/src/tool/jelloShotLab/i18n/es.ts +156 -0
  105. package/src/tool/jelloShotLab/i18n/fr.ts +156 -0
  106. package/src/tool/jelloShotLab/i18n/id.ts +156 -0
  107. package/src/tool/jelloShotLab/i18n/it.ts +156 -0
  108. package/src/tool/jelloShotLab/i18n/ja.ts +156 -0
  109. package/src/tool/jelloShotLab/i18n/ko.ts +156 -0
  110. package/src/tool/jelloShotLab/i18n/nl.ts +156 -0
  111. package/src/tool/jelloShotLab/i18n/pl.ts +156 -0
  112. package/src/tool/jelloShotLab/i18n/pt.ts +156 -0
  113. package/src/tool/jelloShotLab/i18n/ru.ts +156 -0
  114. package/src/tool/jelloShotLab/i18n/sv.ts +156 -0
  115. package/src/tool/jelloShotLab/i18n/tr.ts +156 -0
  116. package/src/tool/jelloShotLab/i18n/zh.ts +156 -0
  117. package/src/tool/jelloShotLab/index.ts +11 -0
  118. package/src/tool/jelloShotLab/jello-shot-lab.css +229 -0
  119. package/src/tool/jelloShotLab/logic.ts +29 -0
  120. package/src/tool/jelloShotLab/seo.astro +53 -0
  121. package/src/tool/partyKeg/bibliography.ts +6 -0
  122. package/src/tool/partyKeg/entry.ts +1 -0
  123. package/src/tool/partyKeg/i18n/de.ts +1 -12
  124. package/src/tool/partyKeg/i18n/en.ts +1 -12
  125. package/src/tool/partyKeg/i18n/es.ts +1 -12
  126. package/src/tool/partyKeg/i18n/fr.ts +1 -12
  127. package/src/tool/partyKeg/i18n/id.ts +1 -12
  128. package/src/tool/partyKeg/i18n/it.ts +1 -12
  129. package/src/tool/partyKeg/i18n/ja.ts +1 -12
  130. package/src/tool/partyKeg/i18n/ko.ts +1 -12
  131. package/src/tool/partyKeg/i18n/nl.ts +1 -12
  132. package/src/tool/partyKeg/i18n/pl.ts +1 -12
  133. package/src/tool/partyKeg/i18n/pt.ts +1 -12
  134. package/src/tool/partyKeg/i18n/ru.ts +1 -12
  135. package/src/tool/partyKeg/i18n/sv.ts +1 -12
  136. package/src/tool/partyKeg/i18n/tr.ts +1 -12
  137. package/src/tool/partyKeg/i18n/zh.ts +1 -12
  138. package/src/tools.ts +5 -0
  139. package/src/types.ts +1 -1
@@ -0,0 +1,91 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { WithContext, SoftwareApplication, FAQPage, HowTo } from 'schema-dts';
3
+ import type { FortifiedWineBuilderUI, FortifiedWineBuilderLocaleContent } from '../index';
4
+
5
+ const slug = 'berikad-vin-kalkylator';
6
+ const title = 'Berikad Vin & Vermouth Byggare: Pearson Kvadrat Kalkylator';
7
+ const description = 'Beräkna exakt hur mycket sprit du behöver tillsätta till ditt vin för att nå önskad alkoholhalt. Skapa perfekt vermouth, portvin och sherry med Pearson Kvadrat-metoden.';
8
+
9
+ const ui: FortifiedWineBuilderUI = {
10
+ intentionTitle: 'Vad skapar du?',
11
+ intentionVermouth: 'Vermouth',
12
+ intentionPort: 'Portvin',
13
+ intentionSherry: 'Sherry',
14
+ intentionCustom: 'Anpassad',
15
+ modeALabel: 'Från vin jag har',
16
+ modeBLabel: 'Mål för slutvolym',
17
+ wineSection: 'Basvin',
18
+ wineVolumeLabel: 'Vinvolym (L)',
19
+ wineAbvLabel: 'Vinets alkoholhalt (%)',
20
+ spiritSection: 'Berikningssprit',
21
+ spiritAbvLabel: 'Spritens alkoholhalt (%)',
22
+ brandyPreset: 'Brandy (38°)',
23
+ neutralPreset: 'Neutral (96°)',
24
+ aguardientePreset: 'Aguardiente (42°)',
25
+ targetAbvLabel: 'Målalkoholhalt (%)',
26
+ targetVolumeLabel: 'Mål totalvolym (L)',
27
+ resultsTitle: 'Ditt Recept',
28
+ addLabel: 'Tillsätt',
29
+ finalVolumeLabel: 'Slutvolym',
30
+ bottlesSection: 'Flaskor som behövs',
31
+ copyBtn: 'Kopiera Recept',
32
+ copiedBtn: 'Kopierat!',
33
+ pearsonTitle: 'Pearson Kvadrat',
34
+ wineCornerLabel: 'Vin',
35
+ spiritCornerLabel: 'Sprit',
36
+ emptyState: 'Ange dina värden för att se Pearson Kvadraten',
37
+ errorAbv: 'Spritens alkoholhalt måste vara högre än målhalten, och målet måste vara högre än vinets alkoholhalt.',
38
+ errorMode: 'Ange en giltig volym för att beräkna.',
39
+ };
40
+
41
+ const faqTitle = 'Vanliga Frågor';
42
+
43
+ const faq: FortifiedWineBuilderLocaleContent['faq'] = [
44
+ {
45
+ question: 'Vad är Pearson Kvadrat-metoden?',
46
+ answer: 'Pearson Kvadraten är en enkel grafisk metod som används inom vinframställning för att beräkna blandningsförhållanden. Målalkoholhalten placeras i mitten av kvadraten, vinets alkoholhalt uppe till vänster och spritens alkoholhalt nere till vänster. De diagonala skillnaderna ger de proportionella delarna av varje vätska som behövs.',
47
+ },
48
+ {
49
+ question: 'Vilken är den typiska alkoholhalten för vermouth?',
50
+ answer: 'Traditionell vermouth har en alkoholhalt på 15 % till 18 %. Torr vermouth (franskstil) ligger vanligtvis i den lägre änden (15–16 %), medan söt vermouth (italienskstil) ofta är 16–18 %. Rosé-vermouth befinner sig däremellan.',
51
+ },
52
+ {
53
+ question: 'Vilket basvin bör jag använda till vermouth?',
54
+ answer: 'Traditionellt fungerar ett neutralt, torrt vitt vin med 10–12 % alkohol bäst. Vinet ger ryggraden, men eftersom det kommer att aromatiseras med örter och botanicals behöver du inget dyrt vin — en ren, syrarik bas är idealisk.',
55
+ },
56
+ {
57
+ question: 'Kan jag använda neutral alkohol istället för brandy?',
58
+ answer: 'Ja. Neutral alkohol (96 % vol.) ger maximal kontroll och en renare smakprofil. Brandy tillför sin egen karaktär (ek, torkad frukt, vanilj), vilket kan vara önskvärt eller oönskat beroende på stilen. Portvin använder traditionellt druvbrandy, medan vissa vermouths använder neutral sprit.',
59
+ },
60
+ {
61
+ question: 'Hur bevarar berikningsprocessen vinet?',
62
+ answer: 'När vinets alkoholhalt överstiger ungefär 15–16 % hämmas jästens fermentering — jästen kan inte överleva i miljöer med hög alkoholhalt. Det är därför berikade viner har mycket längre hållbarhet än vanligt vin. Alkoholen fungerar som ett naturligt konserveringsmedel mot både jäst- och bakterieförstöring.',
63
+ },
64
+ ];
65
+
66
+ const howTo: FortifiedWineBuilderLocaleContent['howTo'] = [
67
+ { name: 'Välj stil', text: 'Välj Vermouth, Portvin, Sherry eller Anpassad för att automatiskt fylla i det rekommenderade målintervallet för alkoholhalt.' },
68
+ { name: 'Ange vindata', text: 'Ange volymen på ditt basvin (eller målvolymen i läge B) och dess nuvarande alkoholhalt.' },
69
+ { name: 'Ställ in sprit', text: 'Välj ett spritpreset eller ange en anpassad alkoholhalt. Pearson Kvadraten uppdateras i realtid.' },
70
+ { name: 'Läs ditt recept', text: 'Kalkylatorn visar exakt hur många milliliter sprit som ska tillsättas och slutvolymen.' },
71
+ ];
72
+
73
+
74
+ const seo: FortifiedWineBuilderLocaleContent['seo'] = [
75
+ { type: 'title', text: 'Pearson Kvadraten: Antik matematik, perfekt vin', level: 2 },
76
+ { type: 'paragraph', html: '<strong>Pearson Kvadraten</strong> är ett av de äldsta och mest eleganta verktygen inom vinmakarens matematik. Utvecklad på 1800-talet låter den vilken vinmakare som helst — professionell eller amatör — beräkna blandningsförhållanden med ingenting mer än subtraktion. Vårt verktyg digitaliserar denna visuella metod och lägger till realtidsfeedback, så att du spenderar mindre tid på beräkningar och mer tid på att skapa.' },
77
+ { type: 'stats', items: [{ label: 'Vermouth', value: '15–18 % vol.', icon: 'mdi:glass-cocktail' }, { label: 'Portvin', value: '18–20 % vol.', icon: 'mdi:bottle-wine' }, { label: 'Sherry', value: '15–17 % vol.', icon: 'mdi:cup-water' }], columns: 3 },
78
+ { type: 'card', title: 'Varför berika till 18 %?', icon: 'mdi:shield-check', html: 'Ovanför ungefär 15 % vol. hämmas <em>Saccharomyces cerevisiae</em> — den primära vinjästen. Vid 18 % är jäsningen fullständigt stoppad. Det är därför portvin behåller restsöta: spriten tillsätts mitt under jäsningen och dödar jästen innan allt socker har förbrukats.' },
79
+ { type: 'tip', title: 'Proffstips: Mät vid 20 °C', html: 'Alkoholens densitet förändras med temperaturen. Officiella alkoholmätningar är kalibrerade vid 20 °C. Om din sprit eller vin är avsevärt varmare eller kallare, tillämpa en korrektionsfaktor: ungefär +0,04 % vol. per °C under 20 °C och −0,04 % per °C över.' },
80
+ { type: 'title', text: 'Hantverksvermouth-renässansen', level: 2 },
81
+ { type: 'paragraph', html: 'Södra Europa upplever en hantverksvermouth-renässans. Barcelona, Valencia och San Sebastián har återtagit <em>la hora del vermut</em> som en kulturell institution, och små producenter i Spanien, Italien och Frankrike buteljerar remarkabla uttryck. Detta har skapat en ny generation hemmaproducenter som vill ha tekniska verktyg som matchar deras ambitioner.' },
82
+ { type: 'summary', title: 'Vem är det här verktyget för?', items: ['Hemmavinmakare: Berika din skörd med precision istället för gissningar.', 'Hantverksvermouthproducenter: Prototypa nya alkoholmål innan du skalar upp till fulla batcher.', 'Spritutbildare: Demonstrera Pearson Kvadrat-metoden visuellt i workshops.'] },
83
+ ];
84
+
85
+ const schemas: FortifiedWineBuilderLocaleContent['schemas'] = [
86
+ { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) } as WithContext<FAQPage>,
87
+ { '@context': 'https://schema.org', '@type': 'HowTo', name: title, description, step: howTo.map((step, i) => ({ '@type': 'HowToStep', position: i + 1, name: step.name, text: step.text })) } as WithContext<HowTo>,
88
+ { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description, applicationCategory: 'UtilityApplication', operatingSystem: 'Web', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' } } as WithContext<SoftwareApplication>,
89
+ ];
90
+
91
+ export const content: FortifiedWineBuilderLocaleContent = { slug, title, description, ui, seo, faqTitle, faq, bibliography, howTo, schemas };
@@ -0,0 +1,91 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { WithContext, SoftwareApplication, FAQPage, HowTo } from 'schema-dts';
3
+ import type { FortifiedWineBuilderUI, FortifiedWineBuilderLocaleContent } from '../index';
4
+
5
+ const slug = 'fortifiye-sarap-hesaplayici';
6
+ const title = 'Fortifiye Şarap & Vermut Oluşturucu: Pearson Karesi Hesaplayıcı';
7
+ const description = 'Hedef alkol oranına ulaşmak için şarabınıza ne kadar ispirto eklemeniz gerektiğini tam olarak hesaplayın. Pearson Karesi yöntemiyle mükemmel vermut, porto ve sherry yapın.';
8
+
9
+ const ui: FortifiedWineBuilderUI = {
10
+ intentionTitle: 'Ne yapıyorsunuz?',
11
+ intentionVermouth: 'Vermut',
12
+ intentionPort: 'Porto',
13
+ intentionSherry: 'Sherry',
14
+ intentionCustom: 'Özel',
15
+ modeALabel: 'Elimdeki şaraptan',
16
+ modeBLabel: 'Hedef son hacim',
17
+ wineSection: 'Baz Şarap',
18
+ wineVolumeLabel: 'Şarap hacmi (L)',
19
+ wineAbvLabel: 'Şarap alkol oranı (%)',
20
+ spiritSection: 'Fortifikasyon İspirti',
21
+ spiritAbvLabel: 'İspirto alkol oranı (%)',
22
+ brandyPreset: 'Brendi (38°)',
23
+ neutralPreset: 'Nötr (96°)',
24
+ aguardientePreset: 'Aguardiente (42°)',
25
+ targetAbvLabel: 'Hedef alkol oranı (%)',
26
+ targetVolumeLabel: 'Hedef toplam hacim (L)',
27
+ resultsTitle: 'Tarifiniz',
28
+ addLabel: 'Ekle',
29
+ finalVolumeLabel: 'Son hacim',
30
+ bottlesSection: 'Gereken şişeler',
31
+ copyBtn: 'Tarifi Kopyala',
32
+ copiedBtn: 'Kopyalandı!',
33
+ pearsonTitle: 'Pearson Karesi',
34
+ wineCornerLabel: 'Şarap',
35
+ spiritCornerLabel: 'İspirto',
36
+ emptyState: 'Pearson Karesini görmek için değerlerinizi girin',
37
+ errorAbv: 'İspirito alkol oranı hedef orandan yüksek, hedef oran da şarap alkol oranından yüksek olmalıdır.',
38
+ errorMode: 'Hesaplamak için geçerli bir hacim girin.',
39
+ };
40
+
41
+ const faqTitle = 'Sıkça Sorulan Sorular';
42
+
43
+ const faq: FortifiedWineBuilderLocaleContent['faq'] = [
44
+ {
45
+ question: 'Pearson Karesi yöntemi nedir?',
46
+ answer: 'Pearson Karesi, şarap yapımında harmanlama oranlarını hesaplamak için kullanılan basit bir grafik yöntemdir. Hedef alkol oranı karenin ortasına, şarap alkol oranı sol üst köşeye, ispirto alkol oranı ise sol alt köşeye yerleştirilir. Çapraz farklılıklar, her sıvının gerekli orantılı miktarlarını verir.',
47
+ },
48
+ {
49
+ question: 'Vermutun tipik alkol oranı nedir?',
50
+ answer: 'Geleneksel vermutun alkol oranı %15 ile %18 arasındadır. Kuru (Fransız tarzı) vermut genellikle alt aralıkta (%15–16) yer alırken, tatlı (İtalyan tarzı) vermut çoğunlukla %16–18 civarındadır. Rosé vermut ise ikisi arasında yer alır.',
51
+ },
52
+ {
53
+ question: 'Vermut için hangi baz şarabı kullanmalıyım?',
54
+ answer: 'Geleneksel olarak %10–12 alkollü nötr, kuru bir beyaz şarap en iyi sonucu verir. Şarap temel yapıyı oluşturur, ancak bitkisel malzemeler ve otlarla aromalandırılacağından pahalı bir şaraba gerek yoktur — temiz ve asitli bir baz idealdir.',
55
+ },
56
+ {
57
+ question: 'Brendi yerine nötr alkol kullanabilir miyim?',
58
+ answer: 'Evet. Nötr alkol (%96) maksimum kontrol ve daha temiz bir lezzet profili sağlar. Brendi kendi karakterini (meşe, kuru meyve, vanilya) katar; bu da stile bağlı olarak istenebilir ya da istenilmeyebilir. Porto geleneksel olarak üzüm brendisi kullanırken bazı vermutlar nötr alkol kullanır.',
59
+ },
60
+ {
61
+ question: 'Fortifikasyon şarabı nasıl korur?',
62
+ answer: 'Şarabın alkol oranı yaklaşık %15–16\'yı geçtiğinde maya fermantasyonu engellenir — mayalar yüksek alkollü ortamlarda yaşayamaz. Bu nedenle fortifiye şarapların normal şaraplara kıyasla çok daha uzun raf ömrü vardır. Alkol hem maya hem de bakteri bozulmasına karşı doğal bir koruyucu olarak işlev görür.',
63
+ },
64
+ ];
65
+
66
+ const howTo: FortifiedWineBuilderLocaleContent['howTo'] = [
67
+ { name: 'Stili seçin', text: 'Önerilen hedef alkol oranı aralığını otomatik doldurmak için Vermut, Porto, Sherry veya Özel\'i seçin.' },
68
+ { name: 'Şarap verilerini girin', text: 'Baz şarabınızın hacmini (veya Mod B\'de hedef son hacmi) ve mevcut alkol oranını girin.' },
69
+ { name: 'İspirti ayarlayın', text: 'Bir ispirto ön ayarı seçin veya özel bir alkol oranı girin. Pearson Karesi gerçek zamanlı olarak güncellenir.' },
70
+ { name: 'Tarifinizi okuyun', text: 'Hesaplayıcı, eklenecek ispirtonun tam mililitre miktarını ve son hacmi gösterir.' },
71
+ ];
72
+
73
+
74
+ const seo: FortifiedWineBuilderLocaleContent['seo'] = [
75
+ { type: 'title', text: 'Pearson Karesi: Kadim Matematik, Mükemmel Şarap', level: 2 },
76
+ { type: 'paragraph', html: '<strong>Pearson Karesi</strong>, şarap matematiğinin en eski ve en zarif araçlarından biridir. 19. yüzyılda geliştirilen bu yöntem, profesyonel ya da amatör her şarap üreticisinin yalnızca çıkarma işlemiyle harmanlama oranlarını hesaplamasını sağlar. Aracımız bu görsel yöntemi dijitalleştirerek gerçek zamanlı geri bildirim ekler; böylece hesaplama için daha az, üretim için daha fazla zaman harcarsınız.' },
77
+ { type: 'stats', items: [{ label: 'Vermut', value: '%15–18', icon: 'mdi:glass-cocktail' }, { label: 'Porto', value: '%18–20', icon: 'mdi:bottle-wine' }, { label: 'Sherry', value: '%15–17', icon: 'mdi:cup-water' }], columns: 3 },
78
+ { type: 'card', title: 'Neden %18\'de fortifiye edilir?', icon: 'mdi:shield-check', html: 'Yaklaşık %15\'in üzerinde, <em>Saccharomyces cerevisiae</em> — birincil şarap mayası — inhibe olmaya başlar. %18\'e ulaşıldığında fermantasyon tamamen durur. Porto\'nun artık şeker içermesinin nedeni budur: ispirto fermantasyonun ortasında eklenerek tüm şeker tüketilmeden maya öldürülür.' },
79
+ { type: 'tip', title: 'Pro İpucu: 20 °C\'de Ölçün', html: 'Alkolün yoğunluğu sıcaklıkla değişir. Resmi alkol ölçümleri 20 °C\'de kalibre edilmiştir. İspiritiniz veya şarabınız önemli ölçüde daha sıcak veya daha soğuksa bir düzeltme faktörü uygulayın: 20 °C\'nin altında her derece için yaklaşık +0,04 % ve üzerinde her derece için −0,04 %.' },
80
+ { type: 'title', text: 'Zanaatkâr Vermut Rönesansı', level: 2 },
81
+ { type: 'paragraph', html: 'Güney Avrupa bir zanaatkâr vermut rönesansı yaşıyor. Barselona, Valensiya ve San Sebastián, <em>la hora del vermut\'u</em> kültürel bir kurum olarak yeniden benimsedi; İspanya, İtalya ve Fransa\'daki küçük üreticiler olağanüstü ifadeler şişeliyor. Bu durum, hırslarına uygun teknik araçlar isteyen yeni nesil ev üreticilerini ortaya çıkardı.' },
82
+ { type: 'summary', title: 'Bu araç kime yönelik?', items: ['Ev şarap üreticileri: Hasatınızı tahmin yerine hassasiyetle fortifiye edin.', 'Zanaatkâr vermut üreticileri: Tam partilere ölçeklendirmeden önce yeni alkol hedeflerini prototiplandırın.', 'Spirits eğitimcileri: Pearson Karesi yöntemini atölyelerde görsel olarak gösterin.'] },
83
+ ];
84
+
85
+ const schemas: FortifiedWineBuilderLocaleContent['schemas'] = [
86
+ { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) } as WithContext<FAQPage>,
87
+ { '@context': 'https://schema.org', '@type': 'HowTo', name: title, description, step: howTo.map((step, i) => ({ '@type': 'HowToStep', position: i + 1, name: step.name, text: step.text })) } as WithContext<HowTo>,
88
+ { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description, applicationCategory: 'UtilityApplication', operatingSystem: 'Web', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' } } as WithContext<SoftwareApplication>,
89
+ ];
90
+
91
+ export const content: FortifiedWineBuilderLocaleContent = { slug, title, description, ui, seo, faqTitle, faq, bibliography, howTo, schemas };
@@ -0,0 +1,91 @@
1
+ import { bibliography } from '../bibliography';
2
+ import type { WithContext, SoftwareApplication, FAQPage, HowTo } from 'schema-dts';
3
+ import type { FortifiedWineBuilderUI, FortifiedWineBuilderLocaleContent } from '../index';
4
+
5
+ const slug = 'fortified-wine-builder';
6
+ const title = '加强葡萄酒与苦艾酒酿造器:皮尔逊方法计算器';
7
+ const description = '精确计算需要向葡萄酒中添加多少烈酒才能达到目标酒精度。使用皮尔逊方法酿造完美的苦艾酒、波特酒和雪利酒。';
8
+
9
+ const ui: FortifiedWineBuilderUI = {
10
+ intentionTitle: '您在制作什么?',
11
+ intentionVermouth: '苦艾酒',
12
+ intentionPort: '波特酒',
13
+ intentionSherry: '雪利酒',
14
+ intentionCustom: '自定义',
15
+ modeALabel: '基于现有葡萄酒',
16
+ modeBLabel: '目标最终容量',
17
+ wineSection: '基础葡萄酒',
18
+ wineVolumeLabel: '葡萄酒容量(升)',
19
+ wineAbvLabel: '葡萄酒酒精度(%)',
20
+ spiritSection: '加强烈酒',
21
+ spiritAbvLabel: '烈酒酒精度(%)',
22
+ brandyPreset: '白兰地(38°)',
23
+ neutralPreset: '中性酒精(96°)',
24
+ aguardientePreset: '阿瓜尔迪恩特(42°)',
25
+ targetAbvLabel: '目标酒精度(%)',
26
+ targetVolumeLabel: '目标总容量(升)',
27
+ resultsTitle: '您的配方',
28
+ addLabel: '添加',
29
+ finalVolumeLabel: '最终容量',
30
+ bottlesSection: '所需瓶数',
31
+ copyBtn: '复制配方',
32
+ copiedBtn: '已复制!',
33
+ pearsonTitle: '皮尔逊方法',
34
+ wineCornerLabel: '葡萄酒',
35
+ spiritCornerLabel: '烈酒',
36
+ emptyState: '请输入数值以查看皮尔逊方法图示',
37
+ errorAbv: '烈酒酒精度必须高于目标酒精度,目标酒精度必须高于葡萄酒酒精度。',
38
+ errorMode: '请输入有效容量进行计算。',
39
+ };
40
+
41
+ const faqTitle = '常见问题';
42
+
43
+ const faq: FortifiedWineBuilderLocaleContent['faq'] = [
44
+ {
45
+ question: '什么是皮尔逊方法?',
46
+ answer: '皮尔逊方法是酿酒中用于计算调配比例的简单图解法。将目标酒精度放在方格中央,葡萄酒酒精度放在左上角,烈酒酒精度放在左下角。对角线差值即为所需各液体的比例份数。',
47
+ },
48
+ {
49
+ question: '苦艾酒的典型酒精度是多少?',
50
+ answer: '传统苦艾酒的酒精度在15%至18%之间。干型(法式风格)苦艾酒通常处于较低端(15-16%),而甜型(意式风格)苦艾酒通常为16-18%。玫瑰苦艾酒居于两者之间。',
51
+ },
52
+ {
53
+ question: '制作苦艾酒应使用哪种基础葡萄酒?',
54
+ answer: '传统上,酒精度在10-12%之间的中性干白葡萄酒效果最佳。葡萄酒提供骨架,但由于将用草药和植物原料调香,不需要昂贵的葡萄酒——干净、酸度适中的基底最为理想。',
55
+ },
56
+ {
57
+ question: '可以用中性酒精代替白兰地吗?',
58
+ answer: '可以。中性酒精(96%)提供最大的控制力和更纯净的风味特征。白兰地带有其自身特色(橡木、干果、香草),这对不同风格可能是有益或无益的。波特酒传统上使用葡萄白兰地,而一些苦艾酒使用中性烈酒。',
59
+ },
60
+ {
61
+ question: '加强过程如何保存葡萄酒?',
62
+ answer: '当葡萄酒的酒精度超过约15-16%时,酵母发酵受到抑制——酵母无法在高酒精环境中存活。这就是加强葡萄酒比普通葡萄酒保质期更长的原因。酒精既能防止酵母又能防止细菌腐败,起到天然防腐剂的作用。',
63
+ },
64
+ ];
65
+
66
+ const howTo: FortifiedWineBuilderLocaleContent['howTo'] = [
67
+ { name: '选择风格', text: '选择苦艾酒、波特酒、雪利酒或自定义,自动填入推荐的目标酒精度范围。' },
68
+ { name: '输入葡萄酒数据', text: '输入基础葡萄酒的容量(或模式B中的目标最终容量)及其当前酒精度。' },
69
+ { name: '设置烈酒', text: '选择烈酒预设或输入自定义酒精度。皮尔逊方法图示实时更新。' },
70
+ { name: '查看配方', text: '计算器精确显示需要添加的烈酒毫升数和最终容量。' },
71
+ ];
72
+
73
+
74
+ const seo: FortifiedWineBuilderLocaleContent['seo'] = [
75
+ { type: 'title', text: '皮尔逊方法:古老的数学,完美的葡萄酒', level: 2 },
76
+ { type: 'paragraph', html: '<strong>皮尔逊方法</strong>是酿酒数学中最古老、最优雅的工具之一。它诞生于19世纪,使任何酿酒师——无论专业人士还是爱好者——都能仅凭减法计算调配比例。我们的工具将这种可视化方法数字化,并添加实时反馈,让您花更少的时间计算,花更多的时间酿造。' },
77
+ { type: 'stats', items: [{ label: '苦艾酒', value: '15–18% vol.', icon: 'mdi:glass-cocktail' }, { label: '波特酒', value: '18–20% vol.', icon: 'mdi:bottle-wine' }, { label: '雪利酒', value: '15–17% vol.', icon: 'mdi:cup-water' }], columns: 3 },
78
+ { type: 'card', title: '为什么在18%时加强?', icon: 'mdi:shield-check', html: '当酒精度超过约15%时,<em>Saccharomyces cerevisiae</em>(主要葡萄酒酵母)受到抑制。达到18%时,发酵完全停止。这就是为什么波特酒保留残余糖分:烈酒在发酵中途加入,在所有糖分被消耗之前杀死酵母。' },
79
+ { type: 'tip', title: '专业提示:在20°C时测量', html: '酒精密度随温度变化。官方酒精度测量值以20°C为标准校准。如果您的烈酒或葡萄酒温度明显偏高或偏低,请应用修正系数:低于20°C每降1°C约+0.04%,高于20°C每升1°C约−0.04%。' },
80
+ { type: 'title', text: '精酿苦艾酒的复兴', level: 2 },
81
+ { type: 'paragraph', html: '南欧正在经历精酿苦艾酒的复兴。巴塞罗那、瓦伦西亚和圣塞巴斯蒂安将<em>la hora del vermut</em>重新确立为文化传统,西班牙、意大利和法国的小型生产商正在装瓶出品令人惊叹的产品。这催生了新一代家庭生产者,他们希望获得与自身抱负相匹配的技术工具。' },
82
+ { type: 'summary', title: '这个工具适合谁?', items: ['家庭酿酒师:以精准度而非猜测来加强您的收成。', '精酿苦艾酒生产商:在扩大到完整批次之前对新的酒精度目标进行原型测试。', '烈酒教育者:在工作坊中直观地演示皮尔逊方法。'] },
83
+ ];
84
+
85
+ const schemas: FortifiedWineBuilderLocaleContent['schemas'] = [
86
+ { '@context': 'https://schema.org', '@type': 'FAQPage', mainEntity: faq.map((item) => ({ '@type': 'Question', name: item.question, acceptedAnswer: { '@type': 'Answer', text: item.answer } })) } as WithContext<FAQPage>,
87
+ { '@context': 'https://schema.org', '@type': 'HowTo', name: title, description, step: howTo.map((step, i) => ({ '@type': 'HowToStep', position: i + 1, name: step.name, text: step.text })) } as WithContext<HowTo>,
88
+ { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: title, description, applicationCategory: 'UtilityApplication', operatingSystem: 'Web', offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' } } as WithContext<SoftwareApplication>,
89
+ ];
90
+
91
+ export const content: FortifiedWineBuilderLocaleContent = { slug, title, description, ui, seo, faqTitle, faq, bibliography, howTo, schemas };
@@ -0,0 +1,8 @@
1
+ import { fortifiedWine } from './entry';
2
+ export * from './entry';
3
+ export const FORTIFIED_WINE_TOOL: ToolDefinition = {
4
+ entry: fortifiedWine as AlcoholToolEntry<Record<string, string>>,
5
+ Component: () => import('./component.astro'),
6
+ SEOComponent: () => import('./seo.astro'),
7
+ BibliographyComponent: () => import('./bibliography.astro'),
8
+ };
@@ -0,0 +1,46 @@
1
+ export interface PearsonResult {
2
+ partsWine: number;
3
+ partsSpirit: number;
4
+ totalParts: number;
5
+ volumeWine: number;
6
+ volumeSpirit: number;
7
+ volumeTotal: number;
8
+ bottles75: number;
9
+ bottles50: number;
10
+ }
11
+
12
+ export function calculateModeA(wineAbv: number, spiritAbv: number, targetAbv: number, wineVolume: number): PearsonResult {
13
+ const partsWine = spiritAbv - targetAbv;
14
+ const partsSpirit = targetAbv - wineAbv;
15
+ const totalParts = partsWine + partsSpirit;
16
+ const volumeSpirit = wineVolume * (partsSpirit / partsWine);
17
+ const volumeTotal = wineVolume + volumeSpirit;
18
+ return {
19
+ partsWine,
20
+ partsSpirit,
21
+ totalParts,
22
+ volumeWine: wineVolume,
23
+ volumeSpirit,
24
+ volumeTotal,
25
+ bottles75: Math.ceil(volumeTotal / 0.75),
26
+ bottles50: Math.ceil(volumeTotal / 0.5),
27
+ };
28
+ }
29
+
30
+ export function calculateModeB(wineAbv: number, spiritAbv: number, targetAbv: number, targetVolume: number): PearsonResult {
31
+ const partsWine = spiritAbv - targetAbv;
32
+ const partsSpirit = targetAbv - wineAbv;
33
+ const totalParts = partsWine + partsSpirit;
34
+ const volumeWine = targetVolume * (partsWine / totalParts);
35
+ const volumeSpirit = targetVolume * (partsSpirit / totalParts);
36
+ return {
37
+ partsWine,
38
+ partsSpirit,
39
+ totalParts,
40
+ volumeWine,
41
+ volumeSpirit,
42
+ volumeTotal: targetVolume,
43
+ bottles75: Math.ceil(targetVolume / 0.75),
44
+ bottles50: Math.ceil(targetVolume / 0.5),
45
+ };
46
+ }
@@ -0,0 +1,41 @@
1
+ ---
2
+ import {
3
+ SEOTitle, SEOTip, SEOCard, SEOStats,
4
+ SEOSummary, SEODiagnostic, SEOArticle
5
+ } from '@jjlmoya/utils-shared';
6
+ import { fortifiedWine } from './index';
7
+ import type { KnownLocale } from '../../types';
8
+
9
+ interface Props {
10
+ locale?: KnownLocale;
11
+ }
12
+
13
+ const { locale = 'es' } = Astro.props;
14
+ const content = await fortifiedWine.i18n[locale]?.();
15
+ if (!content) return null;
16
+
17
+ const { seo } = content;
18
+ ---
19
+
20
+ <SEOArticle>
21
+ {seo.map((section: any) => {
22
+ switch (section.type) {
23
+ case 'summary':
24
+ return <SEOSummary title={section.title} items={section.items} />;
25
+ case 'title':
26
+ return <SEOTitle title={section.text} level={section.level || 2} />;
27
+ case 'paragraph':
28
+ return <p set:html={section.html} />;
29
+ case 'stats':
30
+ return <SEOStats stats={section.items} columns={section.columns} />;
31
+ case 'card':
32
+ return <SEOCard title={section.title} icon={section.icon}><Fragment set:html={section.html} /></SEOCard>;
33
+ case 'tip':
34
+ return <SEOTip title={section.title}><Fragment set:html={section.html} /></SEOTip>;
35
+ case 'diagnostic':
36
+ return <SEODiagnostic title={section.title} icon={section.icon} type={section.variant} badge={section.badge}><Fragment set:html={section.html} /></SEODiagnostic>;
37
+ default:
38
+ return null;
39
+ }
40
+ })}
41
+ </SEOArticle>
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { jelloShotLab } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await jelloShotLab.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SharedBibliography links={content.bibliography} />}
@@ -0,0 +1,8 @@
1
+ import type { BibliographyEntry } from '../../types';
2
+
3
+ export const bibliography: BibliographyEntry[] = [
4
+ { name: 'Gelatin — Wikipedia', url: 'https://en.wikipedia.org/wiki/Gelatin' },
5
+ { name: 'Jello Shot — Wikipedia', url: 'https://en.wikipedia.org/wiki/Jello_shot' },
6
+ { name: 'McGee, H. — On Food and Cooking: The Science and Lore of the Kitchen (Scribner, 2004)', url: 'https://www.simonandschuster.com/books/On-Food-and-Cooking/Harold-McGee/9780684800011' },
7
+ { name: 'The Food Lab: Better Home Cooking Through Science — J. Kenji López-Alt (Norton, 2015)', url: 'https://www.seriouseats.com/the-food-lab-better-home-cooking-through-science' },
8
+ ];
@@ -0,0 +1,183 @@
1
+ ---
2
+ import { Icon } from 'astro-icon/components';
3
+ import { jelloShotLab } from './entry';
4
+ import './jello-shot-lab.css';
5
+ import type { JelloShotLabUI } from './entry';
6
+ import type { KnownLocale } from '../../types';
7
+
8
+ interface Props {
9
+ ui?: JelloShotLabUI;
10
+ locale?: KnownLocale;
11
+ }
12
+
13
+ let { ui, locale } = Astro.props;
14
+
15
+ if (!locale) {
16
+ const url = Astro.url.pathname;
17
+ const parts = url.split('/').filter(Boolean);
18
+ const possibleLang = parts[0] as KnownLocale;
19
+ const supportedLangs: KnownLocale[] = [
20
+ 'ar', 'da', 'de', 'en', 'es', 'fi', 'fr', 'id', 'it', 'ja', 'ko', 'nb', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'zh'
21
+ ];
22
+ locale = supportedLangs.includes(possibleLang) ? possibleLang : 'en';
23
+ }
24
+
25
+ if (!ui) {
26
+ const contentLoader = jelloShotLab.i18n[locale as KnownLocale];
27
+ if (contentLoader) {
28
+ const content = await contentLoader();
29
+ ui = content?.ui;
30
+ }
31
+ }
32
+
33
+ if (!ui) {
34
+ const fallbackLoader = jelloShotLab.i18n['en'];
35
+ if (fallbackLoader) {
36
+ const fallbackContent = await fallbackLoader();
37
+ ui = fallbackContent?.ui;
38
+ }
39
+ }
40
+ ---
41
+
42
+ {ui ? (
43
+ <div id="jsl-app" class="jsl-container theme-dark">
44
+ <div class="jsl-card">
45
+ <div class="jsl-card-header">
46
+ <div class="jsl-mini-toggle">
47
+ <button class="jsl-mini-btn active" data-mode="dark" id="toggle-party">
48
+ <Icon name="mdi:party-popper" /> {ui.modeParty}
49
+ </button>
50
+ <button class="jsl-mini-btn" data-mode="light" id="toggle-recipe">
51
+ <Icon name="mdi:silverware-fork-knife" /> {ui.modeRecipe}
52
+ </button>
53
+ </div>
54
+ </div>
55
+
56
+ <div class="jsl-content-panel">
57
+ <div class="jsl-grid-inputs">
58
+ <div class="jsl-field">
59
+ <label class="jsl-label">{ui.liquorAbvLabel}</label>
60
+ <input type="number" id="jsl-abv" class="jsl-input" value="40" min="0" max="100" step="0.5" />
61
+ </div>
62
+
63
+ <div class="jsl-field">
64
+ <label class="jsl-label">{ui.totalVolumeLabel} ({ui.unitMm})</label>
65
+ <input type="number" id="jsl-volume" class="jsl-input" value="500" min="50" max="5000" step="50" />
66
+ </div>
67
+
68
+ <div class="jsl-field">
69
+ <label class="jsl-label">{ui.modeIntensity}</label>
70
+ <select id="jsl-intensity" class="jsl-select">
71
+ <option value="light">{ui.modeIntensityLight}</option>
72
+ <option value="balanced" selected>{ui.modeIntensityBalanced}</option>
73
+ <option value="limit">{ui.modeIntensityLimit}</option>
74
+ </select>
75
+ </div>
76
+
77
+ <div class="jsl-field">
78
+ <label class="jsl-label">{ui.partyPlannerGuestLabel}</label>
79
+ <input type="number" id="jsl-guests" class="jsl-input" value="10" min="1" max="200" />
80
+ </div>
81
+ </div>
82
+
83
+ <p class="jsl-subtitle" style="font-size: 0.85rem; color: var(--jsl-text-muted); line-height: 1.5;">
84
+ {ui.description}
85
+ </p>
86
+ </div>
87
+
88
+ <div class="jsl-visual-panel">
89
+ <div class="jsl-tank">
90
+ <div id="liquid-water" class="jsl-liquid jsl-liquid-water"></div>
91
+ <div id="liquid-alcohol" class="jsl-liquid jsl-liquid-alcohol"></div>
92
+ </div>
93
+
94
+ <div class="jsl-stats-compact">
95
+ <div class="jsl-stat-row">
96
+ <span class="jsl-stat-l">{ui.boilingWaterLabel}</span>
97
+ <span id="res-water" class="jsl-stat-v">250ml</span>
98
+ </div>
99
+ <div class="jsl-stat-row">
100
+ <span class="jsl-stat-l">{ui.alcoholVolumeLabel}</span>
101
+ <span id="res-alcohol" class="jsl-stat-v">250ml</span>
102
+ </div>
103
+ <div class="jsl-stat-row">
104
+ <span class="jsl-stat-l">{ui.packetsNeededLabel}</span>
105
+ <span id="res-packets" class="jsl-stat-v">2</span>
106
+ </div>
107
+ <div class="jsl-stat-row">
108
+ <span class="jsl-stat-l">{ui.chillingTimeLabel}</span>
109
+ <span id="res-time" class="jsl-stat-v">4h</span>
110
+ </div>
111
+ </div>
112
+ </div>
113
+ </div>
114
+ </div>
115
+ ) : (
116
+ <div class="jsl-container theme-dark">
117
+ <div class="jsl-card" style="padding: 3rem; text-align: center;">
118
+ <Icon name="mdi:alert-circle" style="font-size: 3rem; color: var(--jsl-accent); margin-bottom: 1rem;" />
119
+ <p>Error: UI Not Found</p>
120
+ </div>
121
+ </div>
122
+ )}
123
+
124
+ <script>
125
+ import { calculateJelloShot } from './logic';
126
+
127
+ const abvInput = document.getElementById('jsl-abv') as HTMLInputElement;
128
+ const volInput = document.getElementById('jsl-volume') as HTMLInputElement;
129
+ const intensityInput = document.getElementById('jsl-intensity') as HTMLSelectElement;
130
+ const guestsInput = document.getElementById('jsl-guests') as HTMLInputElement;
131
+
132
+ const resWater = document.getElementById('res-water');
133
+ const resAlcohol = document.getElementById('res-alcohol');
134
+ const resPackets = document.getElementById('res-packets');
135
+ const resTime = document.getElementById('res-time');
136
+ const liquidWater = document.getElementById('liquid-water');
137
+ const liquidAlcohol = document.getElementById('liquid-alcohol');
138
+ const container = document.getElementById('jsl-app');
139
+ const btnParty = document.getElementById('toggle-party');
140
+ const btnRecipe = document.getElementById('toggle-recipe');
141
+
142
+ function updateStats(results: ReturnType<typeof calculateJelloShot>, volume: number) {
143
+ if (resWater) resWater.innerText = `${Math.round(results.boilingWaterVolume)}ml`;
144
+ if (resAlcohol) resAlcohol.innerText = `${Math.round(results.alcoholVolume)}ml`;
145
+ if (resPackets) resPackets.innerText = `${results.packetsNeeded.toFixed(1)}`;
146
+ if (resTime) resTime.innerText = `${results.chillingTimeHours}h`;
147
+ const waterPct = (results.boilingWaterVolume / volume) * 90;
148
+ const alcoholPct = (results.alcoholVolume / volume) * 90;
149
+ if (liquidWater) liquidWater.style.height = `${waterPct}%`;
150
+ if (liquidAlcohol) {
151
+ liquidAlcohol.style.height = `${alcoholPct}%`;
152
+ liquidAlcohol.style.bottom = `${waterPct}%`;
153
+ }
154
+ }
155
+
156
+ function update() {
157
+ if (!abvInput || !volInput || !intensityInput) return;
158
+ const abv = parseFloat(abvInput.value);
159
+ const volume = parseFloat(volInput.value);
160
+ const intensity = intensityInput.value as 'light' | 'balanced' | 'limit';
161
+ updateStats(calculateJelloShot({ abv, totalVolume: volume, intensity }), volume);
162
+ }
163
+
164
+ btnParty?.addEventListener('click', () => {
165
+ container?.classList.add('theme-dark');
166
+ container?.classList.remove('theme-light');
167
+ btnParty.classList.add('active');
168
+ btnRecipe?.classList.remove('active');
169
+ });
170
+
171
+ btnRecipe?.addEventListener('click', () => {
172
+ container?.classList.add('theme-light');
173
+ container?.classList.remove('theme-dark');
174
+ btnRecipe.classList.add('active');
175
+ btnParty?.classList.remove('active');
176
+ });
177
+
178
+ [abvInput, volInput, intensityInput, guestsInput].forEach(el => {
179
+ el?.addEventListener('input', update);
180
+ });
181
+
182
+ update();
183
+ </script>
@@ -0,0 +1,62 @@
1
+ import type { AlcoholToolEntry, ToolLocaleContent } from '../../types';
2
+ export { bibliography } from './bibliography';
3
+
4
+ export interface JelloShotLabUI {
5
+ title: string;
6
+ description: string;
7
+ liquorBaseLabel: string;
8
+ liquorAbvLabel: string;
9
+ gelatinLabel: string;
10
+ diluentLabel: string;
11
+ calculateBtn: string;
12
+ modePrecision: string;
13
+ modeIntensity: string;
14
+ modeIntensityLight: string;
15
+ modeIntensityBalanced: string;
16
+ modeIntensityLimit: string;
17
+ partyPlannerTitle: string;
18
+ partyPlannerGuestLabel: string;
19
+ partyPlannerShotsPerGuest: string;
20
+ multiLayerTitle: string;
21
+ howToTitle: string;
22
+ proTipsTitle: string;
23
+ resultsTitle: string;
24
+ totalVolumeLabel: string;
25
+ boilingWaterLabel: string;
26
+ alcoholVolumeLabel: string;
27
+ packetsNeededLabel: string;
28
+ chillingTimeLabel: string;
29
+ unitMm: string;
30
+ unitOz: string;
31
+ unitGrams: string;
32
+ unitPackets: string;
33
+ modeParty: string;
34
+ modeRecipe: string;
35
+ }
36
+
37
+ export type JelloShotLabLocaleContent = ToolLocaleContent<JelloShotLabUI>;
38
+
39
+ export const jelloShotLab: AlcoholToolEntry<JelloShotLabUI> = {
40
+ id: 'jello-shot-lab',
41
+ icons: {
42
+ bg: 'mdi:flask',
43
+ fg: 'mdi:cup',
44
+ },
45
+ i18n: {
46
+ de: () => import('./i18n/de').then((m) => m.content),
47
+ en: () => import('./i18n/en').then((m) => m.content),
48
+ es: () => import('./i18n/es').then((m) => m.content),
49
+ fr: () => import('./i18n/fr').then((m) => m.content),
50
+ id: () => import('./i18n/id').then((m) => m.content),
51
+ it: () => import('./i18n/it').then((m) => m.content),
52
+ ja: () => import('./i18n/ja').then((m) => m.content),
53
+ ko: () => import('./i18n/ko').then((m) => m.content),
54
+ nl: () => import('./i18n/nl').then((m) => m.content),
55
+ pl: () => import('./i18n/pl').then((m) => m.content),
56
+ pt: () => import('./i18n/pt').then((m) => m.content),
57
+ ru: () => import('./i18n/ru').then((m) => m.content),
58
+ sv: () => import('./i18n/sv').then((m) => m.content),
59
+ tr: () => import('./i18n/tr').then((m) => m.content),
60
+ zh: () => import('./i18n/zh').then((m) => m.content),
61
+ },
62
+ };