├── .github
└── workflows
│ └── elm.yml
├── .gitignore
├── LICENSE
├── README.md
├── front
├── .gitignore
├── elm.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo.svg
│ └── robots.txt
└── src
│ ├── Data.elm
│ ├── Main.elm
│ ├── Quantity.elm
│ └── index.js
├── package-lock.json
└── package.json
/.github/workflows/elm.yml:
--------------------------------------------------------------------------------
1 | name: Elm
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 |
8 | runs-on: ubuntu-latest
9 |
10 | strategy:
11 | matrix:
12 | node-version: [12.x]
13 |
14 | steps:
15 | - uses: actions/checkout@v1
16 | - name: Use Node.js ${{ matrix.node-version }}
17 | uses: actions/setup-node@v1
18 | with:
19 | node-version: ${{ matrix.node-version }}
20 | - name: install elm
21 | run: |
22 | npm install -g elm elm-analyse
23 | - name: install dependencies
24 | working-directory: ./front
25 | run: |
26 | elm make src/Main.elm
27 | - name:
28 | working-directory: ./front
29 | run: |
30 | elm-analyse
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | elm-stuff
2 | node_modules
3 | ./public
4 | .DS_Store
5 |
6 | .now
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Tom MacWright
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Old Fashioned
2 |
3 | This is the software behind oldfashioned.tech. Given the, um, fast pace of development, there's
4 | a little sawdust on the shop floor, so to speak. So, the application is in `/front`.
5 |
6 | It's written in [Elm](https://elm-lang.org/), a lovely new language, and importantly uses [elm-ui](https://github.com/mdgriffith/elm-ui),
7 | a very futuristic UI library. So it's a little different than most web apps, but **on the bright side**,
8 | this _really_ fits into the Elm model super well, and also the data representation is super approachable.
9 |
10 | ## Set up
11 |
12 | To get set up:
13 |
14 | - You need [Node.js](https://nodejs.org/en/) installe:
15 |
16 | Then install create-elm-app globally:
17 |
18 | ```
19 | npm install create-elm-app -g
20 | ```
21 |
22 | Then, once you've cloned this git repository, go into the `front` directory, and run
23 |
24 | ```
25 | elm-app start
26 | ```
27 |
28 | ## The lay of the land
29 |
30 | There's a smidge of 'modularization', in this case, basically moving things into files. The files are:
31 |
32 | - `Main.elm`: the interface
33 | - `Quantity.elm`: a system for representing quantities
34 | - `Icons.elm`: um… icons
35 | - `Data.elm`: all the recipes and stuff
36 |
37 | A few concepts to get out of the way:
38 |
39 | I refer to substances, like "Vodka", as Materials. This is because just "Vodka" is not an ingredient:
40 | 6 Cl of Vodka is an ingredient.
41 |
42 | Quantities are kind of fancy, they use Elm types! Some example quantities:
43 |
44 | ```
45 | Dashes 2
46 | FewDashes
47 | CL 2
48 | ```
49 |
50 | Same with glasses - there's a type for each kind of glass.
51 |
52 | Note that all of the 'liquid' quantities are measured in CL, or Centiliters, because that's what
53 | Wikipedia uses. We convert to all the other measures on the frontend.
54 |
55 | So a recipe looks like this, and goes in a long list in Data.elm
56 |
57 | ```elm
58 | -- https://en.wikipedia.org/wiki/Sour_(cocktail)#White_Lady
59 | { name = "White lady"
60 | , ingredients =
61 | [ { material = gin, quantity = CL 4 }
62 | , { material = tripleSec, quantity = CL 3 }
63 | , { material = lemonJuice, quantity = CL 2 }
64 | ]
65 | , description = """Add all ingredients into cocktail shaker filled with ice. Shake well and strain into large cocktail glass."""
66 | , glass = Cocktail
67 | }
68 | ```
69 |
70 | Really, making recipes, Elm will usually tell you exactly what's wrong, which is really neat.
71 |
--------------------------------------------------------------------------------
/front/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | build
3 |
--------------------------------------------------------------------------------
/front/elm.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "application",
3 | "source-directories": [
4 | "src"
5 | ],
6 | "elm-version": "0.19.1",
7 | "dependencies": {
8 | "direct": {
9 | "elm/browser": "1.0.2",
10 | "elm/core": "1.0.5",
11 | "elm/html": "1.0.0",
12 | "elm/http": "2.0.0",
13 | "elm/json": "1.1.3",
14 | "elm/svg": "1.0.1",
15 | "elm/url": "1.0.0",
16 | "hecrj/elm-slug": "1.0.2",
17 | "mdgriffith/elm-ui": "1.1.8",
18 | "turboMaCk/any-set": "1.4.0"
19 | },
20 | "indirect": {
21 | "elm/bytes": "1.0.8",
22 | "elm/file": "1.0.5",
23 | "elm/regex": "1.0.0",
24 | "elm/time": "1.0.0",
25 | "elm/virtual-dom": "1.0.2",
26 | "turboMaCk/any-dict": "2.4.0"
27 | }
28 | },
29 | "test-dependencies": {
30 | "direct": {},
31 | "indirect": {}
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/front/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tmcw/flair/0a2fd9a5b70e76bc2a1a0164db85a064eec69372/front/public/favicon.ico
--------------------------------------------------------------------------------
/front/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
17 | Old Fashioned
18 |
19 |
20 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/front/public/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
40 |
--------------------------------------------------------------------------------
/front/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 |
--------------------------------------------------------------------------------
/front/src/Data.elm:
--------------------------------------------------------------------------------
1 | module Data exposing (Glass(..), Ingredient, Material, MaterialType(..), Recipe, SuperMaterial(..), Video(..), recipes)
2 |
3 | import Quantity exposing (Quantity(..))
4 |
5 |
6 | type
7 | MaterialType
8 | -- Main alcohol types:
9 | = Base -- Fermented base: Sparkling wine, beer, cider, etc.
10 | | Fortified -- Fermented base with extra alcohol: Port, sherry, vermouth, americano, etc.
11 | | Liqueur -- Spirit is infused / macerated with herb, spice, fruit; Amari, crème, etc.
12 | | Spirit -- Fermented base is distilled: Whiskey, rum, vodka, gin, tequila, brandy, etc.
13 | -- Main non-alcoholic types:
14 | | Bitters -- Technically often high alcohol but used sparingly.
15 | | Fruit
16 | | Juice
17 | | Seasoning -- Salt, herbs, spices.
18 | | Soda -- Cola, ginger beer, etc.
19 | | Syrup
20 | | Other
21 |
22 |
23 | type Glass
24 | = -- Tumblers:
25 | Collins
26 | | Highball
27 | | OldFashioned
28 | -- | Shot
29 | -- | Table
30 | -- Stemware:
31 | -- | Absinthe
32 | -- | Chalice
33 | | ChampagneCoupe
34 | | ChampagneFlute
35 | | Cocktail
36 | | Hurricane
37 | | Margarita
38 | -- | Sherry
39 | -- | Snifter
40 | | Wine
41 | -- Other:
42 | | CopperMug
43 | | IrishCoffeeMug
44 | | SteelCup
45 | | ZombieGlass
46 |
47 |
48 | type alias Material =
49 | { name : String
50 | , t : MaterialType
51 | , super : SuperMaterial
52 | }
53 |
54 |
55 | type SuperMaterial
56 | = SuperMaterial (Maybe Material)
57 |
58 |
59 | type alias Ingredient =
60 | { material : Material
61 | , quantity : Quantity
62 | , optional : Bool
63 | }
64 |
65 |
66 | ingredient : Material -> Quantity -> Ingredient
67 | ingredient mat quantity =
68 | { material = mat
69 | , quantity = quantity
70 | , optional = False
71 | }
72 |
73 |
74 | optionalIngredient : Material -> Quantity -> Ingredient
75 | optionalIngredient mat quantity =
76 | { material = mat
77 | , quantity = quantity
78 | , optional = True
79 | }
80 |
81 |
82 | material : String -> MaterialType -> Material
83 | material name t =
84 | { name = name, t = t, super = SuperMaterial Nothing }
85 |
86 |
87 | material3 : String -> MaterialType -> Material -> Material
88 | material3 name t super =
89 | { name = name, t = t, super = SuperMaterial (Just super) }
90 |
91 |
92 |
93 | -- Whiskey
94 |
95 |
96 | whiskey : Material
97 | whiskey =
98 | material "Whiskey" Spirit
99 |
100 |
101 | scotchWhiskey : Material
102 | scotchWhiskey =
103 | material3 "Scotch whiskey" Spirit whiskey
104 |
105 |
106 | bourbonWhiskey : Material
107 | bourbonWhiskey =
108 | material3 "Bourbon whiskey" Spirit whiskey
109 |
110 |
111 | canadianWhiskey : Material
112 | canadianWhiskey =
113 | material3 "Canadian whiskey" Spirit whiskey
114 |
115 |
116 | ryeWhiskey : Material
117 | ryeWhiskey =
118 | material3 "Rye whiskey" Spirit whiskey
119 |
120 |
121 | irishWhiskey : Material
122 | irishWhiskey =
123 | material3 "Irish whiskey" Spirit whiskey
124 |
125 |
126 | drambuie : Material
127 | drambuie =
128 | material "Drambuie" Liqueur
129 |
130 |
131 | water : Material
132 | water =
133 | material "Water" Other
134 |
135 |
136 | sodaWater : Material
137 | sodaWater =
138 | material "Soda water" Soda
139 |
140 |
141 | gin : Material
142 | gin =
143 | material "Gin" Spirit
144 |
145 |
146 | oldTomGin : Material
147 | oldTomGin =
148 | material3 "Old tom gin" Spirit gin
149 |
150 |
151 | londonDryGin : Material
152 | londonDryGin =
153 | material3 "London dry gin" Spirit gin
154 |
155 |
156 | whiteCremeDeMenthe : Material
157 | whiteCremeDeMenthe =
158 | material "White crème de menthe" Liqueur
159 |
160 |
161 | cremeDeMure : Material
162 | cremeDeMure =
163 | material "Crème de mure" Liqueur
164 |
165 |
166 | maraschino : Material
167 | maraschino =
168 | material "Maraschino" Liqueur
169 |
170 |
171 | brandy : Material
172 | brandy =
173 | material "Brandy" Spirit
174 |
175 |
176 | apricotBrandy : Material
177 | apricotBrandy =
178 | material "Apricot brandy" Liqueur
179 |
180 |
181 |
182 | -- `port` is a reserved word.
183 |
184 |
185 | pport : Material
186 | pport =
187 | material "Port" Fortified
188 |
189 |
190 | calvados : Material
191 | calvados =
192 | material "Calvados" Spirit
193 |
194 |
195 | bitters : Material
196 | bitters =
197 | material "Bitters" Bitters
198 |
199 |
200 | peachBitters : Material
201 | peachBitters =
202 | material3 "Peach bitters" Bitters bitters
203 |
204 |
205 | orangeBitters : Material
206 | orangeBitters =
207 | material3 "Orange bitters" Bitters bitters
208 |
209 |
210 | angosturaBitters : Material
211 | angosturaBitters =
212 | material3 "Angostura bitters" Bitters bitters
213 |
214 |
215 | peychaudsBitters : Material
216 | peychaudsBitters =
217 | material3 "Peychaud’s bitters" Bitters bitters
218 |
219 |
220 | aromaticBitters : Material
221 | aromaticBitters =
222 | material3 "Aromatic bitters" Bitters bitters
223 |
224 |
225 | lemon : Material
226 | lemon =
227 | material "Lemon" Fruit
228 |
229 |
230 | blackberry : Material
231 | blackberry =
232 | material "Blackberry" Fruit
233 |
234 |
235 | raspberry : Material
236 | raspberry =
237 | material "Raspberry" Fruit
238 |
239 |
240 | cherry : Material
241 | cherry =
242 | material "Cherry" Fruit
243 |
244 |
245 | pineapple : Material
246 | pineapple =
247 | material "Pineapple" Fruit
248 |
249 |
250 | sweetRedVermouth : Material
251 | sweetRedVermouth =
252 | material "Sweet red vermouth" Fortified
253 |
254 |
255 | dryVermouth : Material
256 | dryVermouth =
257 | material "Dry vermouth" Fortified
258 |
259 |
260 | cognac : Material
261 | cognac =
262 | material3 "Cognac" Spirit brandy
263 |
264 |
265 | tripleSec : Material
266 | tripleSec =
267 | material "Triple sec" Liqueur
268 |
269 |
270 | grandMarnier : Material
271 | grandMarnier =
272 | material3 "Grand Marnier" Liqueur tripleSec
273 |
274 |
275 | cointreau : Material
276 | cointreau =
277 | material3 "Cointreau" Liqueur tripleSec
278 |
279 |
280 | curacao : Material
281 | curacao =
282 | material3 "Curaçao" Liqueur tripleSec
283 |
284 |
285 | grenadine : Material
286 | grenadine =
287 | material "Grenadine" Syrup
288 |
289 |
290 | oj : Material
291 | oj =
292 | material "Orange juice" Juice
293 |
294 |
295 | pineappleJuice : Material
296 | pineappleJuice =
297 | material "Pineapple juice" Juice
298 |
299 |
300 | lime : Material
301 | lime =
302 | material "Lime" Fruit
303 |
304 |
305 | cachaca : Material
306 | cachaca =
307 | material3 "Cachaça" Spirit rum
308 |
309 |
310 | absinthe : Material
311 | absinthe =
312 | material "Absinthe" Liqueur
313 |
314 |
315 | campari : Material
316 | campari =
317 | material "Campari" Liqueur
318 |
319 |
320 | fernetBranca : Material
321 | fernetBranca =
322 | material "Fernet Branca" Liqueur
323 |
324 |
325 | raspberrySyrup : Material
326 | raspberrySyrup =
327 | material "Raspberry syrup" Syrup
328 |
329 |
330 | raspberryLiqueur : Material
331 | raspberryLiqueur =
332 | material "Raspberry liqueur" Liqueur
333 |
334 |
335 | orange : Material
336 | orange =
337 | material "Orange" Fruit
338 |
339 |
340 | eggYolk : Material
341 | eggYolk =
342 | material "Egg yolk" Other
343 |
344 |
345 | eggWhite : Material
346 | eggWhite =
347 | material "Egg white" Other
348 |
349 |
350 | champagne : Material
351 | champagne =
352 | material3 "Champagne" Base sparklingWine
353 |
354 |
355 | tequila : Material
356 | tequila =
357 | material "Tequila" Spirit
358 |
359 |
360 | rum : Material
361 | rum =
362 | material "Rum" Spirit
363 |
364 |
365 | whiteRum : Material
366 | whiteRum =
367 | material3 "White rum" Spirit rum
368 |
369 |
370 | goldRum : Material
371 | goldRum =
372 | material3 "Gold rum" Spirit rum
373 |
374 |
375 | demeraraRum : Material
376 | demeraraRum =
377 | material3 "Demerara rum" Spirit rum
378 |
379 |
380 | darkRum : Material
381 | darkRum =
382 | material3 "Dark rum" Spirit rum
383 |
384 |
385 | limeJuice : Material
386 | limeJuice =
387 | material "Lime juice" Juice
388 |
389 |
390 | cream : Material
391 | cream =
392 | material "Cream" Other
393 |
394 |
395 | brownCremeDeCacao : Material
396 | brownCremeDeCacao =
397 | material "Brown crème de cacao" Liqueur
398 |
399 |
400 | whiteCremeDeCacao : Material
401 | whiteCremeDeCacao =
402 | material "White crème de cacao" Liqueur
403 |
404 |
405 | lightCream : Material
406 | lightCream =
407 | material "Light cream" Other
408 |
409 |
410 | orangeFlowerWater : Material
411 | orangeFlowerWater =
412 | material "Orange flower water" Other
413 |
414 |
415 | vanillaExtract : Material
416 | vanillaExtract =
417 | material "Vanilla extract" Bitters
418 |
419 |
420 | cola : Material
421 | cola =
422 | material "Cola" Soda
423 |
424 |
425 | nutmeg : Material
426 | nutmeg =
427 | material "Nutmeg" Seasoning
428 |
429 |
430 | lemonJuice : Material
431 | lemonJuice =
432 | material "Lemon juice" Juice
433 |
434 |
435 | vodka : Material
436 | vodka =
437 | material "Vodka" Spirit
438 |
439 |
440 | gingerBeer : Material
441 | gingerBeer =
442 | material "Ginger beer" Soda
443 |
444 |
445 | gingerAle : Material
446 | gingerAle =
447 | material "Ginger ale" Soda
448 |
449 |
450 | prosecco : Material
451 | prosecco =
452 | material3 "Prosecco" Base sparklingWine
453 |
454 |
455 | mint : Material
456 | mint =
457 | material "Mint" Seasoning
458 |
459 |
460 | peachPuree : Material
461 | peachPuree =
462 | material "Peach purée" Other
463 |
464 |
465 | coffeeLiqueur : Material
466 | coffeeLiqueur =
467 | material "Coffee liqueur" Liqueur
468 |
469 |
470 | lilletBlanc : Material
471 | lilletBlanc =
472 | material "Lillet blanc" Fortified
473 |
474 |
475 | kinaLillet : Material
476 | kinaLillet =
477 | material "Kina lillet" Fortified
478 |
479 |
480 | greenCremeDeMenthe : Material
481 | greenCremeDeMenthe =
482 | material "Green crème de menthe" Liqueur
483 |
484 |
485 | cremeDeCassis : Material
486 | cremeDeCassis =
487 | material "Crème de cassis" Liqueur
488 |
489 |
490 | amaretto : Material
491 | amaretto =
492 | material "Amaretto" Liqueur
493 |
494 |
495 | olive : Material
496 | olive =
497 | material "Olive" Fruit
498 |
499 |
500 | wine : Material
501 | wine =
502 | material "Wine" Base
503 |
504 |
505 | dryWhiteWine : Material
506 | dryWhiteWine =
507 | material3 "Dry white wine" Base wine
508 |
509 |
510 | sparklingWine : Material
511 | sparklingWine =
512 | material "Sparkling wine" Base
513 |
514 |
515 | peachSchnapps : Material
516 | peachSchnapps =
517 | material "Peach schnapps" Liqueur
518 |
519 |
520 | cherryLiqueur : Material
521 | cherryLiqueur =
522 | material "Cherry liqueur" Liqueur
523 |
524 |
525 | domBenedictine : Material
526 | domBenedictine =
527 | material "DOM Bénédictine" Liqueur
528 |
529 |
530 | oliveJuice : Material
531 | oliveJuice =
532 | material "Olive juice" Juice
533 |
534 |
535 | cranberryJuice : Material
536 | cranberryJuice =
537 | material "Cranberry juice" Juice
538 |
539 |
540 | grapefruitJuice : Material
541 | grapefruitJuice =
542 | material "Grapefruit juice" Juice
543 |
544 |
545 | tomatoJuice : Material
546 | tomatoJuice =
547 | material "Tomato juice" Juice
548 |
549 |
550 | pepper : Material
551 | pepper =
552 | material "Pepper" Seasoning
553 |
554 |
555 | salt : Material
556 | salt =
557 | material "Salt" Seasoning
558 |
559 |
560 | celerySalt : Material
561 | celerySalt =
562 | material "Celery salt" Seasoning
563 |
564 |
565 | sugar : Material
566 | sugar =
567 | material "Sugar" Seasoning
568 |
569 |
570 | simpleSyrup : Material
571 | simpleSyrup =
572 | material "Simple syrup" Syrup
573 |
574 |
575 | caneSugar : Material
576 | caneSugar =
577 | material3 "Cane sugar" Seasoning sugar
578 |
579 |
580 | powderedSugar : Material
581 | powderedSugar =
582 | material3 "Powdered sugar" Seasoning sugar
583 |
584 |
585 | aperol : Material
586 | aperol =
587 | material "Aperol" Liqueur
588 |
589 |
590 | galliano : Material
591 | galliano =
592 | material "Galliano" Liqueur
593 |
594 |
595 | pisco : Material
596 | pisco =
597 | material3 "Pisco" Spirit brandy
598 |
599 |
600 | orgeatSyrup : Material
601 | orgeatSyrup =
602 | material "Orgeat (almond) syrup" Syrup
603 |
604 |
605 | cinnamonSyrup : Material
606 | cinnamonSyrup =
607 | material "Cinnamon syrup" Syrup
608 |
609 |
610 | agaveNectar : Material
611 | agaveNectar =
612 | material "Agave nectar" Syrup
613 |
614 |
615 | coconutCream : Material
616 | coconutCream =
617 | material "Coconut cream" Other
618 |
619 |
620 | espresso : Material
621 | espresso =
622 | material "Espresso" Other
623 |
624 |
625 | coffee : Material
626 | coffee =
627 | material "Coffee" Other
628 |
629 |
630 | worcestershireSauce : Material
631 | worcestershireSauce =
632 | material "Worcestershire sauce" Other
633 |
634 |
635 | irishCream : Material
636 | irishCream =
637 | material "Irish cream liqueur" Liqueur
638 |
639 |
640 | falernum : Material
641 | falernum =
642 | material "Falernum" Liqueur
643 |
644 |
645 | tabasco : Material
646 | tabasco =
647 | material "Tabasco" Other
648 |
649 |
650 | celery : Material
651 | celery =
652 | material "Celery" Other
653 |
654 |
655 | greenChartreuse : Material
656 | greenChartreuse =
657 | material "Green Chartreuse" Liqueur
658 |
659 |
660 | cremeDeViolette : Material
661 | cremeDeViolette =
662 | material "Crème de violette" Liqueur
663 |
664 |
665 | cubanAguardiente : Material
666 | cubanAguardiente =
667 | material3 "Cuban aguardiente" Spirit rum
668 |
669 |
670 | honeySyrup : Material
671 | honeySyrup =
672 | material "Honey syrup" Syrup
673 |
674 |
675 | honey : Material
676 | honey =
677 | material "Honey" Other
678 |
679 |
680 | grapefruitSoda : Material
681 | grapefruitSoda =
682 | material "Grapefruit soda" Soda
683 |
684 |
685 | amaroNonino : Material
686 | amaroNonino =
687 | material "Amaro Nonino" Liqueur
688 |
689 |
690 | blendedScotchWhiskey : Material
691 | blendedScotchWhiskey =
692 | material3 "Blended Scotch whiskey" Spirit whiskey
693 |
694 |
695 | islaySingleMaltScotch : Material
696 | islaySingleMaltScotch =
697 | material3 "Islay Single Malt Scotch whiskey" Spirit whiskey
698 |
699 |
700 | ginger : Material
701 | ginger =
702 | material "Ginger" Other
703 |
704 |
705 | candiedGinger : Material
706 | candiedGinger =
707 | material "Candied ginger" Other
708 |
709 |
710 | elderflowerSyrup : Material
711 | elderflowerSyrup =
712 | material "Elderflower syrup" Syrup
713 |
714 |
715 | grappa : Material
716 | grappa =
717 | material3 "Grappa" Spirit brandy
718 |
719 |
720 | whiteGrape : Material
721 | whiteGrape =
722 | material "White grape" Fruit
723 |
724 |
725 | mezcal : Material
726 | mezcal =
727 | material "Mezcal" Spirit
728 |
729 |
730 | overproofWhiteRum : Material
731 | overproofWhiteRum =
732 | material3 "Overproof white rum" Spirit rum
733 |
734 |
735 | yellowChartreuse : Material
736 | yellowChartreuse =
737 | material "Yellow Chartreuse" Liqueur
738 |
739 |
740 | redWine : Material
741 | redWine =
742 | material3 "Red wine" Base wine
743 |
744 |
745 | redChiliPepper : Material
746 | redChiliPepper =
747 | material "Red chili pepper" Fruit
748 |
749 |
750 | chamomileSyrup : Material
751 | chamomileSyrup =
752 | material "Chamomile syrup" Syrup
753 |
754 |
755 | type Video
756 | = Epicurious String
757 |
758 |
759 | type alias Recipe =
760 | { name : String
761 | , ingredients : List Ingredient
762 | , description : String
763 | , glass : Glass
764 | , video : Maybe Video
765 | }
766 |
767 |
768 | recipes : List Recipe
769 | recipes =
770 | [ -- https://en.wikipedia.org/wiki/Toronto_(cocktail)
771 | { name = "Toronto"
772 | , ingredients =
773 | [ ingredient canadianWhiskey (CL 5.5)
774 | , ingredient fernetBranca (CL 0.75)
775 | , ingredient sugar (Tsp 0.25)
776 | , ingredient angosturaBitters (Dash 1)
777 | , ingredient orange (Slice 1)
778 | ]
779 | , description = """Stir in mixing glass with ice & strain. Garnish with orange slice."""
780 | , glass = Cocktail
781 | , video = Nothing
782 | }
783 |
784 | -- https://en.wikipedia.org/wiki/French_75_(cocktail)
785 | -- https://iba-world.com/cocktails/french-75/
786 | , { name = "French 75"
787 | , ingredients =
788 | [ ingredient gin (CL 3)
789 | , ingredient simpleSyrup (CL 1.5)
790 | , ingredient lemonJuice (CL 1.5)
791 | , ingredient champagne (CL 6)
792 | ]
793 | , description = """Pour all the ingredients, except Champagne, into a shaker. Shake well and strain into a Champagne flute. Top up with Champagne. Stir gently."""
794 | , glass = ChampagneFlute
795 | , video = Just (Epicurious "29:55")
796 | }
797 |
798 | -- https://en.wikipedia.org/wiki/Rum_and_Coke
799 | -- https://iba-world.com/cocktails/cuba-libre/
800 | , { name = "Cuba Libre"
801 | , ingredients =
802 | [ ingredient cola (CL 12)
803 | , ingredient whiteRum (CL 5)
804 | , ingredient limeJuice (CL 1)
805 | , ingredient lime (Wedge 1)
806 | ]
807 | , description = """Build all ingredients in a highball glass filled with ice. Garnish with lime wedge."""
808 | , glass = Highball
809 | , video = Nothing
810 | }
811 |
812 | -- https://en.wikipedia.org/wiki/Moscow_mule
813 | -- https://iba-world.com/cocktails/moscow-mule/
814 | , { name = "Moscow mule"
815 | , ingredients =
816 | [ ingredient vodka (CL 4.5)
817 | , ingredient gingerBeer (CL 12)
818 | , ingredient limeJuice (CL 1)
819 | , ingredient lime (Slice 1)
820 | ]
821 | , description = """Combine vodka and ginger beer in a highball glass filled with ice. Add lime juice. Stir gently. Garnish with a lime slice."""
822 | , glass = CopperMug
823 | , video = Just (Epicurious "22:42")
824 | }
825 |
826 | -- https://en.wikipedia.org/wiki/Mimosa_(cocktail)
827 | -- https://iba-world.com/cocktails/mimosa/
828 | , { name = "Mimosa"
829 | , ingredients =
830 | [ ingredient champagne (CL 7.5)
831 | , ingredient oj (CL 7.5)
832 | , optionalIngredient orange (Custom "twist")
833 | ]
834 | , description = """Ensure both ingredients are well chilled, then mix into the glass. Garnish with orange twist (optional)."""
835 | , glass = ChampagneFlute
836 | , video = Nothing
837 | }
838 |
839 | -- https://en.wikipedia.org/wiki/Bellini_(cocktail)
840 | -- https://iba-world.com/cocktails/bellini/
841 | , { name = "Bellini"
842 | , ingredients =
843 | [ ingredient prosecco (CL 10)
844 | , ingredient peachPuree (CL 5)
845 | ]
846 | , description = """Pour peach purée into chilled glass, add sparkling wine. Stir gently."""
847 | , glass = ChampagneFlute
848 | , video = Nothing
849 | }
850 |
851 | -- https://en.wikipedia.org/wiki/Black_Russian
852 | -- https://iba-world.com/cocktails/black-russian/
853 | , { name = "Black russian"
854 | , ingredients =
855 | [ ingredient vodka (CL 5)
856 | , ingredient coffeeLiqueur (CL 2)
857 | ]
858 | , description = """Pour the ingredients into an old fashioned glass filled with ice cubes. Stir gently."""
859 | , glass = OldFashioned
860 | , video = Nothing
861 | }
862 |
863 | -- https://en.wikipedia.org/wiki/Caipirinha
864 | -- https://iba-world.com/cocktails/caipirinha/
865 | , { name = "Caipirinha"
866 | , ingredients =
867 | [ ingredient cachaca (CL 6)
868 | , ingredient lime (Whole 1)
869 | , ingredient caneSugar (Tsp 4)
870 | ]
871 | , description = """Place small lime wedges from one lime and sugar into old fashioned glass and muddle (mash the two ingredients together using a muddler or a wooden spoon). Fill the glass with ice and add the Cachaça. Use vodka instead of cachaça for a caipiroska; rum instead of cachaça for a caipirissima;"""
872 | , glass = OldFashioned
873 | , video = Just (Epicurious "35:50")
874 | }
875 |
876 | -- https://iba-world.com/cocktails/mojito/
877 | , { name = "Mojito"
878 | , ingredients =
879 | [ ingredient whiteRum (CL 4.5)
880 | , ingredient limeJuice (CL 2)
881 | , ingredient mint (Sprig 6)
882 | , ingredient caneSugar (Tsp 2)
883 | , ingredient lemon (Slice 1)
884 | , ingredient sodaWater (Splash 1)
885 | ]
886 | , description = """Muddle mint springs with sugar and lime juice. Add splash of soda water and fill glass with cracked ice. Pour rum and top with soda water. Garnish with sprig of mint leaves and lemon slice. Serve with straw."""
887 | , glass = Collins
888 | , video = Just (Epicurious "28:10")
889 | }
890 |
891 | -- https://iba-world.com/new-era-drinks/dark-n-stormy/
892 | , { name = "Dark ’n’ Stormy"
893 | , ingredients =
894 | [ ingredient darkRum (CL 6)
895 | , ingredient gingerBeer (CL 10)
896 | , ingredient lime (Wedge 1)
897 | ]
898 | , description = """Fill glass with ice, add rum and top with ginger beer. Garnish with lime wedge."""
899 | , glass = Highball
900 | , video = Just (Epicurious "26:53")
901 | }
902 |
903 | -- 'NEW ERA DRINKS' -----------------------------
904 | -- https://iba-world.com/new-era-drinks/bramble-2/
905 | , { name = "Bramble"
906 | , ingredients =
907 | [ ingredient gin (CL 4)
908 | , ingredient lemonJuice (CL 1.5)
909 | , ingredient simpleSyrup (CL 1)
910 | , ingredient cremeDeMure (CL 1.5)
911 | , ingredient lemon (Slice 1)
912 | , ingredient blackberry (Whole 2)
913 | ]
914 | , description = """Fill glass with crushed ice. Build gin, lemon juice and simple syrup over. Stir, and then pour blackberry liqueur over in a circular fashion to create marbling effect. Garnish with two blackberries and lemon slice."""
915 | , glass = OldFashioned
916 | , video = Just (Epicurious "19:10")
917 | }
918 | , { name = "French martini"
919 | , ingredients =
920 | [ ingredient vodka (CL 4)
921 | , ingredient raspberryLiqueur (CL 1.5)
922 | , ingredient pineappleJuice (CL 1)
923 | , ingredient lemon (Custom "peel")
924 | ]
925 | , description = """Pour all ingredients into shaker with ice cubes. Shake well and strain into a chilled cocktail glass. Squeeze oil from lemon peel onto the drink."""
926 | , glass = Cocktail
927 | , video = Nothing
928 | }
929 |
930 | -- https://iba-world.com/new-era-drinks/kamikaze-2/
931 | , { name = "Kamikaze"
932 | , ingredients =
933 | [ ingredient vodka (CL 3)
934 | , ingredient tripleSec (CL 3)
935 | , ingredient limeJuice (CL 3)
936 | , ingredient lime (Slice 1)
937 | ]
938 | , description = """Shake all ingredients together with ice. Strain into glass. Garnish with lime slice."""
939 | , glass = Cocktail
940 | , video = Nothing
941 | }
942 |
943 | -- https://iba-world.com/new-era-drinks/lemon-drop-martini/
944 | , { name = "Lemon drop martini"
945 | , ingredients =
946 | [ ingredient vodka (CL 2.5)
947 | , ingredient tripleSec (CL 2)
948 | , ingredient lemonJuice (CL 1.5)
949 | , ingredient sugar None
950 | , ingredient lime (Slice 1)
951 | ]
952 | , description = """Shake and strain into a chilled cocktail glass rimmed with sugar, garnish with a slice of lemon."""
953 | , glass = Cocktail
954 | , video = Nothing
955 | }
956 |
957 | -- https://iba-world.com/cocktails/vesper-2/
958 | , { name = "Vesper"
959 | , ingredients =
960 | [ ingredient gin (CL 4.5)
961 | , ingredient vodka (CL 1.5)
962 | , ingredient lilletBlanc (CL 0.75)
963 | , ingredient lemon (Custom "zest")
964 | ]
965 | , description = """Shake and strain into a chilled cocktail glass. Add the garnish."""
966 | , glass = Cocktail
967 | , video = Just (Epicurious "23:30")
968 | }
969 |
970 | -- https://en.wikipedia.org/wiki/Boulevardier_(cocktail)
971 | -- https://iba-world.com/iba-official-cocktails/boulevardier/
972 | , { name = "Boulevardier"
973 | , ingredients =
974 | [ ingredient bourbonWhiskey (CL 4.5)
975 | , ingredient sweetRedVermouth (CL 3)
976 | , ingredient campari (CL 3)
977 | , ingredient orange (Custom "peel")
978 | , optionalIngredient lemon (Custom "zest")
979 | ]
980 | , description = """Pour all ingredients into mixing glass with ice cubes. Stir well. Strain into chilled cocktail glass. Garnish with a orange zest, optionally a lemon zest."""
981 | , glass = OldFashioned
982 | , video = Just (Epicurious "8:15")
983 | }
984 |
985 | -- 'THE UNFORGETTABLES' --------------------------------------------
986 | -- https://en.wikipedia.org/wiki/Alexander_(cocktail)
987 | -- https://iba-world.com/iba-official-cocktails/alexander/
988 | , { name = "Alexander"
989 | , ingredients =
990 | [ ingredient cognac (CL 3)
991 | , ingredient brownCremeDeCacao (CL 3)
992 | , ingredient lightCream (CL 3)
993 | , ingredient nutmeg None
994 | ]
995 | , description = """Shake all ingredients with ice and strain contents into a cocktail glass. Sprinkle ground nutmeg on top and serve."""
996 | , glass = Cocktail
997 | , video = Nothing
998 | }
999 |
1000 | -- https://en.wikipedia.org/wiki/Americano_(cocktail)
1001 | -- https://iba-world.com/iba-official-cocktails/americano/
1002 | , { name = "Americano"
1003 | , ingredients =
1004 | [ ingredient campari (CL 3)
1005 | , ingredient sweetRedVermouth (CL 3)
1006 | , ingredient sodaWater (Splash 1)
1007 | , ingredient orange (Slice 0.5)
1008 | , ingredient lemon (Custom "twist")
1009 | ]
1010 | , description = """Pour the Campari and vermouth over ice into a highball glass, add a splash of soda water and garnish with half orange slice and a lemon twist."""
1011 | , glass = Highball
1012 | , video = Just (Epicurious "33:41")
1013 | }
1014 |
1015 | -- https://en.wikipedia.org/wiki/Angel_Face_(cocktail)
1016 | -- https://iba-world.com/iba-official-cocktails/angel-face/
1017 | , { name = "Angel face"
1018 | , ingredients =
1019 | [ ingredient gin (CL 3)
1020 | , ingredient apricotBrandy (CL 3)
1021 | , ingredient calvados (CL 3)
1022 | ]
1023 | , description = """Shake all ingredients with ice and strain contents into a cocktail glass."""
1024 | , glass = Cocktail
1025 | , video = Nothing
1026 | }
1027 |
1028 | -- https://en.wikipedia.org/wiki/Aviation_(cocktail)
1029 | -- https://iba-world.com/iba-official-cocktails/aviation/
1030 | , { name = "Aviation"
1031 | , ingredients =
1032 | [ ingredient gin (CL 4.5)
1033 | , ingredient lemonJuice (CL 1.5)
1034 | , ingredient maraschino (CL 1.5)
1035 | , ingredient cremeDeViolette (Tsp 1)
1036 | , ingredient cherry (Whole 1)
1037 | ]
1038 | , description = """Add all ingredients into cocktail shaker filled with ice. Shake well and strain into cocktail glass. Garnish with a cherry."""
1039 | , glass = Cocktail
1040 | , video = Nothing
1041 | }
1042 |
1043 | -- https://en.wikipedia.org/wiki/Bacardi_cocktail
1044 | -- https://iba-world.com/iba-official-cocktails/bacardi/
1045 | , { name = "Bacardi cocktail"
1046 | , ingredients =
1047 | [ ingredient whiteRum (CL 4.5)
1048 | , ingredient limeJuice (CL 2)
1049 | , ingredient grenadine (CL 1)
1050 | ]
1051 | , description = """Shake together with ice. Strain into glass and serve."""
1052 | , glass = Cocktail
1053 | , video = Nothing
1054 | }
1055 |
1056 | -- https://en.wikipedia.org/wiki/Between_the_Sheets_(cocktail)
1057 | -- https://iba-world.com/iba-official-cocktails/between-the-sheets/
1058 | , { name = "Between the sheets"
1059 | , ingredients =
1060 | [ ingredient whiteRum (CL 3)
1061 | , ingredient cognac (CL 3)
1062 | , ingredient tripleSec (CL 3)
1063 | , ingredient lemonJuice (CL 2)
1064 | ]
1065 | , description = """Pour all ingredients into shaker with ice cubes, shake, strain into chilled cocktail glass."""
1066 | , glass = Cocktail
1067 | , video = Nothing
1068 | }
1069 |
1070 | -- https://en.wikipedia.org/wiki/Casino_(cocktail)
1071 | -- https://iba-world.com/iba-official-cocktails/casino/
1072 | , { name = "Casino"
1073 | , ingredients =
1074 | [ ingredient oldTomGin (CL 4)
1075 | , ingredient maraschino (CL 1)
1076 | , ingredient orangeBitters (CL 1)
1077 | , ingredient lemonJuice (CL 1)
1078 | , ingredient lemon (Custom "twist")
1079 | , ingredient cherry (Whole 1)
1080 | ]
1081 | , description = """Pour all ingredients into shaker with ice cubes, shake well. Strain into chilled cocktail glass and garnish with a lemon twist and a marachino cherry."""
1082 | , glass = Cocktail
1083 | , video = Nothing
1084 | }
1085 |
1086 | -- https://en.wikipedia.org/wiki/Clover_Club_Cocktail
1087 | -- https://iba-world.com/iba-official-cocktails/clover-club/
1088 | , { name = "Clover club"
1089 | , ingredients =
1090 | [ ingredient gin (CL 4.5)
1091 | , ingredient lemonJuice (CL 1.5)
1092 | , ingredient raspberrySyrup (CL 1.5)
1093 | , ingredient eggWhite FewDrops
1094 | , ingredient raspberry (Whole 2)
1095 | ]
1096 | , description = """Pour all ingredients into cocktail shaker filled with ice. Shake well. Strain into cocktail glass. Garnish with fresh raspberries."""
1097 | , glass = Cocktail
1098 | , video = Nothing
1099 | }
1100 |
1101 | -- https://en.wikipedia.org/wiki/Daiquiri
1102 | -- https://iba-world.com/iba-official-cocktails/daiquiri/
1103 | , { name = "Daiquiri"
1104 | , ingredients =
1105 | [ ingredient whiteRum (CL 4.5)
1106 | , ingredient limeJuice (CL 2.5)
1107 | , ingredient simpleSyrup (CL 1.5)
1108 | ]
1109 | , description = """Pour all ingredients into shaker with ice cubes. Shake well. Double Strain in chilled cocktail glass."""
1110 | , glass = Cocktail
1111 | , video = Just (Epicurious "26:04")
1112 | }
1113 |
1114 | -- https://en.wikipedia.org/wiki/Derby_(cocktail)
1115 | -- https://iba-world.com/iba-official-cocktails/derby/
1116 | , { name = "Derby"
1117 | , ingredients =
1118 | [ ingredient gin (CL 6)
1119 | , ingredient peachBitters (Drop 2)
1120 | , ingredient mint (Custom "leaves")
1121 | ]
1122 | , description = """Pour all ingredients into a mixing glass with ice. Stir. Strain into a cocktail glass. Garnish with fresh mint leaves in the drink."""
1123 | , glass = Cocktail
1124 | , video = Nothing
1125 | }
1126 |
1127 | -- https://en.wikipedia.org/wiki/Martini_(cocktail)
1128 | -- https://iba-world.com/iba-official-cocktails/dry-martini/
1129 | , { name = "Dry martini"
1130 | , ingredients =
1131 | [ ingredient gin (CL 6)
1132 | , ingredient dryVermouth (CL 1)
1133 | , ingredient lemon (Custom "peel")
1134 | , ingredient olive (Whole 1)
1135 | ]
1136 | , description = """Straight: Pour all ingredients into mixing glass with ice cubes. Stir well. Strain into chilled martini cocktail glass. Squeeze oil from lemon peel onto the drink, or garnish with olive."""
1137 | , glass = Cocktail
1138 | , video = Just (Epicurious "13:39")
1139 | }
1140 |
1141 | -- https://en.wikipedia.org/wiki/Fizz_(cocktail)#Gin_fizz
1142 | -- https://iba-world.com/iba-official-cocktails/gin-fizz/
1143 | , { name = "Gin fizz"
1144 | , ingredients =
1145 | [ ingredient gin (CL 4.5)
1146 | , ingredient lemonJuice (CL 3)
1147 | , ingredient simpleSyrup (CL 1)
1148 | , ingredient sodaWater (CL 8)
1149 | , ingredient lemon (Slice 1)
1150 | ]
1151 | , description = """Shake all ingredients with ice cubes, except soda water. Pour into tumbler. Top with soda water. Garnish with lemon slice."""
1152 | , glass = OldFashioned
1153 | , video = Nothing
1154 | }
1155 |
1156 | -- https://en.wikipedia.org/wiki/John_Collins_(cocktail)
1157 | -- https://iba-world.com/iba-official-cocktails/john-collins/
1158 | , { name = "John collins"
1159 | , ingredients =
1160 | [ ingredient gin (CL 4.5)
1161 | , ingredient lemonJuice (CL 3)
1162 | , ingredient simpleSyrup (CL 1.5)
1163 | , ingredient sodaWater (CL 6)
1164 | , ingredient angosturaBitters (Dash 1)
1165 | , ingredient lemon (Slice 1)
1166 | , ingredient cherry (Whole 1)
1167 | ]
1168 | , description = """Pour all ingredients directly into highball glass filled with ice. Stir gently. Garnish with lemon slice and maraschino cherry. Add a dash of Angostura bitters."""
1169 | , glass = Collins
1170 | , video = Nothing
1171 | }
1172 |
1173 | -- https://en.wikipedia.org/wiki/Manhattan_(cocktail)
1174 | -- https://iba-world.com/iba-official-cocktails/manhattan/
1175 | , { name = "Manhattan"
1176 | , ingredients =
1177 | [ ingredient ryeWhiskey (CL 5)
1178 | , ingredient sweetRedVermouth (CL 2)
1179 | , ingredient angosturaBitters (Dash 1)
1180 | , ingredient cherry (Whole 1)
1181 | ]
1182 | , description = """Pour all ingredients into mixing glass with ice cubes. Stir well. Strain into chilled cocktail glass. Garnish with cocktail cherry."""
1183 | , glass = Cocktail
1184 | , video = Just (Epicurious "2:54")
1185 | }
1186 |
1187 | -- https://iba-world.com/iba-official-cocktails/mary-pickford/
1188 | -- https://en.wikipedia.org/wiki/Mary_Pickford_(cocktail)
1189 | , { name = "Mary pickford"
1190 | , ingredients =
1191 | [ ingredient whiteRum (CL 6)
1192 | , ingredient pineappleJuice (CL 6)
1193 | , ingredient grenadine (CL 1)
1194 | , ingredient maraschino (CL 1)
1195 | ]
1196 | , description = """Shake and strain into a chilled large cocktail glass"""
1197 | , glass = Cocktail
1198 | , video = Nothing
1199 | }
1200 |
1201 | -- https://en.wikipedia.org/wiki/Monkey_Gland
1202 | -- https://iba-world.com/iba-official-cocktails/monkey-gland/
1203 | , { name = "Monkey gland"
1204 | , ingredients =
1205 | [ ingredient gin (CL 5)
1206 | , ingredient oj (CL 3)
1207 | , ingredient absinthe (Drop 2)
1208 | , ingredient grenadine (Drop 2)
1209 | ]
1210 | , description = """Shake well over ice cubes in a shaker, strain into a chilled cocktail glass."""
1211 | , glass = Cocktail
1212 | , video = Nothing
1213 | }
1214 |
1215 | -- https://iba-world.com/iba-official-cocktails/negroni/
1216 | , { name = "Negroni"
1217 | , ingredients =
1218 | [ ingredient gin (CL 3)
1219 | , ingredient sweetRedVermouth (CL 3)
1220 | , ingredient campari (CL 3)
1221 | , ingredient orange (Slice 0.5)
1222 | ]
1223 | , description = """Pour all ingredients directly into old-fashioned glass filled with ice. Stir gently. Garnish with half orange slice."""
1224 | , glass = OldFashioned
1225 | , video = Just (Epicurious "15:52")
1226 | }
1227 |
1228 | -- https://en.wikipedia.org/wiki/Old_fashioned_(cocktail)
1229 | -- https://iba-world.com/iba-official-cocktails/old-fashioned/
1230 | , { name = "Old fashioned"
1231 | , ingredients =
1232 | [ ingredient whiskey (CL 4.5)
1233 | , ingredient angosturaBitters (Dash 2)
1234 | , ingredient sugar (Cube 1)
1235 | , ingredient water FewDashes
1236 | , ingredient orange (Slice 1)
1237 | , ingredient cherry (Whole 1)
1238 | ]
1239 | , description = """Place sugar cube in old-fashioned glass and saturate with bitters, add a dash of plain water. Muddle until dissolve. Fill the glass with ice cubes and add whiskey. Garnish with orange slice and a cocktail cherry."""
1240 | , glass = OldFashioned
1241 | , video = Just (Epicurious "1:45")
1242 | }
1243 |
1244 | -- https://en.wikipedia.org/wiki/Paradise_(cocktail)
1245 | -- https://iba-world.com/iba-official-cocktails/paradise/
1246 | , { name = "Paradise"
1247 | , ingredients =
1248 | [ ingredient gin (CL 3.5)
1249 | , ingredient apricotBrandy (CL 2)
1250 | , ingredient oj (CL 1.5)
1251 | ]
1252 | , description = """Shake together over ice. Strain into cocktail glass and serve chilled."""
1253 | , glass = Cocktail
1254 | , video = Nothing
1255 | }
1256 |
1257 | -- https://en.wikipedia.org/wiki/Planter%27s_punch
1258 | -- https://iba-world.com/iba-official-cocktails/planters-punch/
1259 | , { name = "Planter’s punch"
1260 | , ingredients =
1261 | [ ingredient darkRum (CL 4.5)
1262 | , ingredient oj (CL 3.5)
1263 | , ingredient pineappleJuice (CL 3.5)
1264 | , ingredient lemonJuice (CL 2)
1265 | , ingredient grenadine (CL 1)
1266 | , ingredient simpleSyrup (CL 1)
1267 | , ingredient angosturaBitters (Dash 3)
1268 | , ingredient cherry (Whole 1)
1269 | , ingredient pineapple (Slice 1)
1270 | ]
1271 | , description = """Pour all ingredients, except the bitters, into shaker filled with ice. Shake well. Pour into large glass, filled with ice. Add dash Angostura bitters. Garnish with cocktail cherry and pineapple."""
1272 | , glass = Cocktail
1273 | , video = Nothing
1274 | }
1275 |
1276 | -- https://en.wikipedia.org/wiki/Porto_flip
1277 | -- https://iba-world.com/iba-official-cocktails/porto-flip/
1278 | , { name = "Porto flip"
1279 | , ingredients =
1280 | [ ingredient brandy (CL 1.5)
1281 | , ingredient pport (CL 4.5)
1282 | , ingredient eggYolk (CL 1)
1283 | , ingredient nutmeg None
1284 | ]
1285 | , description = """Pour all ingredients into cocktail shaker filled with ice. Shake well. Strain into cocktail glass. Sprinkle with fresh ground nutmeg."""
1286 | , glass = Cocktail
1287 | , video = Nothing
1288 | }
1289 |
1290 | -- https://en.wikipedia.org/wiki/Fizz_(cocktail)#Ramos_gin_fizz
1291 | -- https://iba-world.com/iba-official-cocktails/ramos-fizz/
1292 | , { name = "Ramos fizz"
1293 | , ingredients =
1294 | [ ingredient gin (CL 4.5)
1295 | , ingredient cream (CL 6)
1296 | , ingredient simpleSyrup (CL 3)
1297 | , ingredient limeJuice (CL 1.5)
1298 | , ingredient lemonJuice (CL 1.5)
1299 | , ingredient eggWhite (Whole 1)
1300 | , ingredient orangeFlowerWater (Dash 3)
1301 | , ingredient vanillaExtract (Drop 2)
1302 | , ingredient sodaWater None
1303 | ]
1304 | , description = """Pour all ingredients (except soda) in a mixing glass, dry shake (no ice) for two minutes, add ice and hard shake for another minute. Strain into a highball glass without ice, top with soda."""
1305 | , glass = Highball
1306 | , video = Just (Epicurious "17:38")
1307 | }
1308 |
1309 | -- https://en.wikipedia.org/wiki/Rusty_Nail_(cocktail)
1310 | -- https://iba-world.com/iba-official-cocktails/rusty-nail/
1311 | , { name = "Rusty nail"
1312 | , ingredients =
1313 | [ ingredient scotchWhiskey (CL 4.5)
1314 | , ingredient drambuie (CL 2.5)
1315 | , ingredient lemon (Custom "twist")
1316 | ]
1317 | , description = """Pour all ingredients directly into old-fashioned glass filled with ice. Stir gently. Garnish with a lemon twist."""
1318 | , glass = OldFashioned
1319 | , video = Nothing
1320 | }
1321 |
1322 | -- https://en.wikipedia.org/wiki/Sazerac
1323 | -- https://iba-world.com/iba-official-cocktails/sazerac/
1324 | , { name = "Sazerac"
1325 | , ingredients =
1326 | [ ingredient cognac (CL 5)
1327 | , ingredient absinthe (CL 1)
1328 | , ingredient sugar (Cube 1)
1329 | , ingredient peychaudsBitters (Dash 2)
1330 | , ingredient lemon (Custom "peel")
1331 | ]
1332 | , description = """Rinse a chilled old-fashioned glass with the absinthe, add crushed ice and set it aside. Stir the remaining ingredients over ice and set it aside. Discard the ice and any excess absinthe from the prepared glass, and strain the drink into the glass. Add the Lemon peel for garnish. Note: The original recipe changed after the American Civil War, rye whiskey substituted cognac as it became hard to obtain."""
1333 | , glass = OldFashioned
1334 | , video = Just (Epicurious "5:45")
1335 | }
1336 |
1337 | -- https://en.wikipedia.org/wiki/Screwdriver_(cocktail)
1338 | -- https://iba-world.com/iba-official-cocktails/screwdriver/
1339 | , { name = "Screwdriver"
1340 | , ingredients =
1341 | [ ingredient vodka (CL 5)
1342 | , ingredient oj (CL 10)
1343 | , ingredient orange (Slice 1)
1344 | ]
1345 | , description = """Pour all ingredients into a highball glass filled with ice. Stir gently. Garnish with an orange slice."""
1346 | , glass = Highball
1347 | , video = Nothing
1348 | }
1349 |
1350 | -- https://iba-world.com/iba-official-cocktails/sidecar/
1351 | , { name = "Sidecar"
1352 | , ingredients =
1353 | [ ingredient cognac (CL 5)
1354 | , ingredient tripleSec (CL 2)
1355 | , ingredient lemonJuice (CL 2)
1356 | ]
1357 | , description = """Pour all ingredients into cocktail shaker filled with ice. Shake well and strain into cocktail glass."""
1358 | , glass = Cocktail
1359 | , video = Just (Epicurious "29:31")
1360 | }
1361 |
1362 | -- https://en.wikipedia.org/wiki/Stinger_(cocktail)
1363 | -- https://iba-world.com/iba-official-cocktails/stinger/
1364 | , { name = "Stinger"
1365 | , ingredients =
1366 | [ ingredient cognac (CL 5)
1367 | , ingredient whiteCremeDeMenthe (CL 2)
1368 | ]
1369 | , description = """Pour in a mixing glass with ice, stir and strain into a cocktail glass. May also be served on rocks in a rocks glass."""
1370 | , glass = Cocktail
1371 | , video = Nothing
1372 | }
1373 |
1374 | -- https://en.wikipedia.org/wiki/Tuxedo_(cocktail)
1375 | -- https://iba-world.com/iba-official-cocktails/tuxedo/
1376 | , { name = "Tuxedo"
1377 | , ingredients =
1378 | [ ingredient oldTomGin (CL 3)
1379 | , ingredient dryVermouth (CL 3)
1380 | , ingredient maraschino (Tsp 0.5)
1381 | , ingredient absinthe (Tsp 0.25)
1382 | , ingredient orangeBitters (Dash 3)
1383 | , ingredient cherry (Whole 1)
1384 | , ingredient lemon (Custom "twist")
1385 | ]
1386 | , description = """Stir all ingredients with ice and strain into cocktail glass. Garnish with a cocktail cherry and a lemon zest twist."""
1387 | , glass = Cocktail
1388 | , video = Nothing
1389 | }
1390 |
1391 | -- https://en.wikipedia.org/wiki/Whiskey_sour
1392 | -- https://iba-world.com/iba-official-cocktails/whiskey-sour/
1393 | , { name = "Whiskey sour"
1394 | , ingredients =
1395 | [ ingredient bourbonWhiskey (CL 4.5)
1396 | , ingredient lemonJuice (CL 3)
1397 | , ingredient simpleSyrup (CL 1.5)
1398 | , optionalIngredient eggWhite (Dash 1)
1399 | , ingredient cherry (Whole 1)
1400 | , ingredient orange (Slice 0.5)
1401 | ]
1402 | , description = """Egg white is optional. Pour all ingredients into cocktail shaker filled with ice. Shake well (a little harder if using egg white). Strain in cocktail glass. Garnish with half orange slice and maraschino cherry."""
1403 | , glass = OldFashioned
1404 | , video = Just (Epicurious "4:03")
1405 | }
1406 |
1407 | -- https://en.wikipedia.org/wiki/Sour_(cocktail)#White_Lady
1408 | -- https://iba-world.com/iba-official-cocktails/white-lady/
1409 | , { name = "White lady"
1410 | , ingredients =
1411 | [ ingredient gin (CL 4)
1412 | , ingredient tripleSec (CL 3)
1413 | , ingredient lemonJuice (CL 2)
1414 | ]
1415 | , description = """Add all ingredients into cocktail shaker filled with ice. Shake well and strain into large cocktail glass."""
1416 | , glass = Cocktail
1417 | , video = Nothing
1418 | }
1419 |
1420 | -- https://iba-world.com/cocktails/french-connection/
1421 | , { name = "French connection"
1422 | , ingredients =
1423 | [ ingredient cognac (CL 3.5)
1424 | , ingredient amaretto (CL 3.5)
1425 | ]
1426 | , description = """Pour all ingredients directly into old fashioned glass filled with ice cubes. Stir gently."""
1427 | , glass = OldFashioned
1428 | , video = Nothing
1429 | }
1430 |
1431 | -- https://iba-world.com/cocktails/mint-julep/
1432 | , { name = "Mint julep"
1433 | , ingredients =
1434 | [ ingredient bourbonWhiskey (CL 6)
1435 | , ingredient mint (Sprig 5)
1436 | , ingredient water (Tsp 2)
1437 | , ingredient powderedSugar (Tsp 1)
1438 | ]
1439 | , description = """In steel cup gently muddle 4 mint sprigs with sugar and water. Fill the glass with cracked ice, add the Bourbon and stir well until the cup frosts. Garnish with a mint sprig."""
1440 | , glass = SteelCup
1441 | , video = Just (Epicurious "12:05")
1442 | }
1443 |
1444 | -- https://en.wikipedia.org/wiki/White_Russian_(cocktail)
1445 | , { name = "White russian"
1446 | , ingredients =
1447 | [ ingredient vodka (CL 5)
1448 | , ingredient coffeeLiqueur (CL 2)
1449 | , ingredient cream (CL 3)
1450 | ]
1451 | , description = """Pour vodka and coffee liqueur into an old fashioned glass filled with ice cubes. Float fresh cream on the top and stir in slowly.."""
1452 | , glass = OldFashioned
1453 | , video = Nothing
1454 | }
1455 |
1456 | -- https://iba-world.com/cocktails/bloody-mary/
1457 | , { name = "Bloody Mary"
1458 | , ingredients =
1459 | [ ingredient vodka (CL 4.5)
1460 | , ingredient tomatoJuice (CL 9)
1461 | , ingredient lemonJuice (CL 1.5)
1462 | , ingredient worcestershireSauce (Dash 2)
1463 | , ingredient tabasco None
1464 | , ingredient celerySalt None
1465 | , ingredient pepper None
1466 | , ingredient celery None
1467 | , ingredient lemon (Wedge 1)
1468 | ]
1469 | , description = """Stir gently all the ingredients in a mixing glass with ice. Add tabasco, celery salt, pepper to taste. Pour into rocks glass. Garnish with celery and lemon wedge. If requested served with ice, pour into highball glass."""
1470 | , glass = Highball
1471 | , video = Nothing
1472 | }
1473 |
1474 | -- https://iba-world.com/cocktails/champagne-cocktail/
1475 | , { name = "Champagne cocktail"
1476 | , ingredients =
1477 | [ ingredient champagne (CL 9)
1478 | , ingredient cognac (CL 1)
1479 | , ingredient angosturaBitters (Dash 2)
1480 | , ingredient sugar (Cube 1)
1481 | , optionalIngredient grandMarnier FewDrops
1482 | , ingredient orange (Custom "zest")
1483 | , ingredient cherry (Whole 1)
1484 | ]
1485 | , description = """Place the sugar cube with 2 dashes of bitters in a large Champagne glass, add the cognac. Optionally add a few drops of Grand Marnier. Pour gently chilled Champagne. Garnish with orange zest and cherry."""
1486 | , glass = Cocktail
1487 | , video = Nothing
1488 | }
1489 |
1490 | -- https://iba-world.com/cocktails/kir/
1491 | , { name = "Kir"
1492 | , ingredients =
1493 | [ ingredient dryWhiteWine (CL 9)
1494 | , ingredient cremeDeCassis (CL 1)
1495 | ]
1496 | , description = """Pour Crème de Cassis into glass, top up with white wine."""
1497 | , glass = Wine
1498 | , video = Nothing
1499 | }
1500 | , { name = "Kir royal"
1501 | , ingredients =
1502 | [ ingredient champagne (CL 9)
1503 | , ingredient cremeDeCassis (CL 1)
1504 | ]
1505 | , description = """Pour Crème de Cassis into glass, top up with Champagne."""
1506 | , glass = Wine
1507 | , video = Nothing
1508 | }
1509 |
1510 | -- https://iba-world.com/cocktails/long-island-iced-tea/
1511 | , { name = "Long island iced tea"
1512 | , ingredients =
1513 | [ ingredient vodka (CL 1.5)
1514 | , ingredient tequila (CL 1.5)
1515 | , ingredient whiteRum (CL 1.5)
1516 | , ingredient gin (CL 1.5)
1517 | , ingredient cointreau (CL 1.5)
1518 | , ingredient lemonJuice (CL 2.5)
1519 | , ingredient simpleSyrup (CL 3)
1520 | , ingredient cola None
1521 | , ingredient lemon (Slice 1)
1522 | ]
1523 | , description = """Add all ingredients into highball glass filled with ice. Top with cola. Stir gently. Garnish with lemon slice."""
1524 | , glass = Highball
1525 | , video = Nothing
1526 | }
1527 |
1528 | -- https://en.wikipedia.org/wiki/Mai_Tai
1529 | -- https://iba-world.com/cocktails/mai-tai/
1530 | , { name = "Mai-tai"
1531 | , ingredients =
1532 | [ ingredient whiteRum (CL 3) -- “Amber Jamaican Rum”
1533 | , ingredient darkRum (CL 3) -- “Martinique Molasses Rhum”
1534 | , ingredient curacao (CL 1.5)
1535 | , ingredient orgeatSyrup (CL 1.5)
1536 | , ingredient limeJuice (CL 3)
1537 | , ingredient simpleSyrup (CL 0.75)
1538 | , ingredient pineapple (Custom "spear")
1539 | , ingredient mint (Custom "leaves")
1540 | , ingredient lime (Custom "peel")
1541 | ]
1542 | , description = """Add all ingredients into a shaker with ice. Shake and pour into a double rocks glass or an highball glass. Garnish with pineapple spear, mint leaves, and lime peel."""
1543 | , glass = Highball
1544 | , video = Just (Epicurious "27:20")
1545 | }
1546 |
1547 | -- https://iba-world.com/cocktails/margarita/
1548 | , { name = "Margarita"
1549 | , ingredients =
1550 | [ ingredient tequila (CL 5)
1551 | , ingredient tripleSec (CL 2)
1552 | , ingredient limeJuice (CL 1.5)
1553 | , optionalIngredient salt None
1554 | ]
1555 | , description = """Add all ingredients into a shaker with ice. Shake and strain into a chilled cocktail glass. Garnish with a half salt rim (optional)."""
1556 | , glass = Margarita
1557 | , video = Just (Epicurious "24:13")
1558 | }
1559 |
1560 | -- https://iba-world.com/new-era-drinks/tommys-margarita/
1561 | , { name = "Tommy’s margarita"
1562 | , ingredients =
1563 | [ ingredient tequila (CL 4.5)
1564 | , ingredient limeJuice (CL 1.5)
1565 | , ingredient agaveNectar (Tsp 2)
1566 | ]
1567 | , description = """Shake and strain into a chilled cocktail glass."""
1568 | , glass = Margarita
1569 | , video = Nothing
1570 | }
1571 |
1572 | -- https://iba-world.com/new-era-drinks/b52-2/
1573 | , { name = "B52"
1574 | , ingredients =
1575 | [ ingredient coffeeLiqueur (CL 2)
1576 | , ingredient tripleSec (CL 2)
1577 | , ingredient irishCream (CL 2)
1578 | ]
1579 | , description = """Layer ingredients one at a time starting with coffee liqueur, followed by irish cream and top with triple sec. Flame the triple sec, serve while the flame is still on, accompanied with a straw on side plate."""
1580 | , glass = OldFashioned
1581 | , video = Nothing
1582 | }
1583 |
1584 | -- https://en.wikipedia.org/wiki/Barracuda_(cocktail)
1585 | -- https://iba-world.com/new-era-drinks/barracuda/
1586 | , { name = "Barracuda"
1587 | , ingredients =
1588 | [ ingredient goldRum (CL 4.5)
1589 | , ingredient galliano (CL 1.5)
1590 | , ingredient pineappleJuice (CL 6)
1591 | , ingredient limeJuice (Dash 1)
1592 | , ingredient prosecco None
1593 | ]
1594 | , description = """Shake together with ice. Strain into glass and serve."""
1595 | , glass = Margarita
1596 | , video = Nothing
1597 | }
1598 |
1599 | -- https://iba-world.com/cocktails/corpse-reviver-2-all-day/
1600 | , { name = "Corpse reviver #2"
1601 | , ingredients =
1602 | [ ingredient gin (CL 3)
1603 | , ingredient cointreau (CL 3)
1604 | , ingredient lilletBlanc (CL 3)
1605 | , ingredient lemonJuice (CL 3)
1606 | , ingredient absinthe (Dash 1)
1607 | , ingredient orange (Custom "zest")
1608 | ]
1609 | , description = """Pour all ingredients into shaker with ice. Shake well and strain in chilled cocktail glass. Garnish with orange zest."""
1610 | , glass = Cocktail
1611 | , video = Just (Epicurious "16:10")
1612 | }
1613 |
1614 | -- https://iba-world.com/cocktails/cosmopolitan/
1615 | , { name = "Cosmopolitan"
1616 | , ingredients =
1617 | [ ingredient vodka (CL 4) -- Vodka citron
1618 | , ingredient cointreau (CL 1.5)
1619 | , ingredient limeJuice (CL 1.5)
1620 | , ingredient cranberryJuice (CL 3)
1621 | , ingredient lemon (Custom "twist")
1622 | ]
1623 | , description = """Add all ingredients into cocktail shaker filled with ice. Shake well and strain into large cocktail glass. Garnish with lemon twist."""
1624 | , glass = Cocktail
1625 | , video = Nothing
1626 | }
1627 |
1628 | -- https://iba-world.com/new-era-drinks/dirty-martini/
1629 | , { name = "Dirty martini"
1630 | , ingredients =
1631 | [ ingredient vodka (CL 6)
1632 | , ingredient dryVermouth (CL 1)
1633 | , ingredient oliveJuice (CL 1)
1634 | , ingredient olive (Whole 1)
1635 | ]
1636 | , description = """Pour all ingredients into mixing glass with ice cubes. Stir well. Strain in chilled martini glass. Garnish with green olive."""
1637 | , glass = Cocktail
1638 | , video = Nothing
1639 | }
1640 |
1641 | -- https://iba-world.com/new-era-drinks/espresso-martini/
1642 | , { name = "Espresso martini"
1643 | , ingredients =
1644 | [ ingredient vodka (CL 5)
1645 | , ingredient coffeeLiqueur (CL 1)
1646 | , ingredient simpleSyrup None
1647 | , ingredient espresso (Custom "1 shot")
1648 | ]
1649 | , description = """Shake and strain into a chilled cocktail glass."""
1650 | , glass = Cocktail
1651 | , video = Nothing
1652 | }
1653 |
1654 | -- https://iba-world.com/cocktails/golden-dream/
1655 | , { name = "Golden dream"
1656 | , ingredients =
1657 | [ ingredient tripleSec (CL 2)
1658 | , ingredient galliano (CL 2)
1659 | , ingredient oj (CL 2)
1660 | , ingredient cream (CL 1)
1661 | ]
1662 | , description = """Pour all ingredients into shaker filled with ice. Shake briskly for few seconds. Strain into chilled cocktail glass."""
1663 | , glass = Cocktail
1664 | , video = Nothing
1665 | }
1666 |
1667 | -- https://iba-world.com/cocktails/grasshopper/
1668 | , { name = "Grasshopper"
1669 | , ingredients =
1670 | [ ingredient whiteCremeDeCacao (CL 2)
1671 | , ingredient greenCremeDeMenthe (CL 2)
1672 | , ingredient cream (CL 2)
1673 | , optionalIngredient mint (Custom "1 leave")
1674 | ]
1675 | , description = """Pour all ingredients into shaker filled with ice. Shake briskly for few seconds. Strain into chilled cocktail glass. Garnish with mint leaf (optional)."""
1676 | , glass = Cocktail
1677 | , video = Nothing
1678 | }
1679 |
1680 | -- https://iba-world.com/cocktails/hemingway-special/
1681 | , { name = "Hemingway special"
1682 | , ingredients =
1683 | [ ingredient whiteRum (CL 6) -- IBA doesn’t specify “white”
1684 | , ingredient grapefruitJuice (CL 4)
1685 | , ingredient maraschino (CL 1.5)
1686 | , ingredient limeJuice (CL 1.5)
1687 | ]
1688 | , description = """Pour all ingredients into a shaker with ice. Shake well and strain into a large cocktail glass."""
1689 | , glass = Cocktail
1690 | , video = Nothing
1691 | }
1692 |
1693 | -- https://iba-world.com/cocktails/horses-neck/
1694 | , { name = "Horse’s neck"
1695 | , ingredients =
1696 | [ ingredient cognac (CL 4)
1697 | , ingredient gingerAle (CL 12)
1698 | , ingredient angosturaBitters FewDashes
1699 | , ingredient lemon (Custom "peel")
1700 | ]
1701 | , description = """Pour Cognac and ginger ale directly into highball glass with ice cubes. Stir gently. If preferred, add dashes of Angostura Bitter. Garnish with rind of one lemon spiral."""
1702 | , glass = Collins
1703 | , video = Nothing
1704 | }
1705 |
1706 | -- https://iba-world.com/cocktails/irish-coffee/
1707 | , { name = "Irish coffee"
1708 | , ingredients =
1709 | [ ingredient irishWhiskey (CL 5)
1710 | , ingredient coffee (CL 12)
1711 | , ingredient cream (CL 5)
1712 | , ingredient sugar (Tsp 1)
1713 | ]
1714 | , description = """Warm black coffee is poured into a pre-heated Irish coffee glass. Whiskey and at least one teaspoon of sugar is added and stirred until dissolved. Fresh thick chilled cream is carefully poured over the back of a spoon held just above the surface of the coffee. The layer of cream will float on the coffee without mixing. Plain sugar can be replaced with sugar syrup."""
1715 | , glass = IrishCoffeeMug
1716 | , video = Nothing
1717 | }
1718 | , { name = "Tom collins"
1719 | , ingredients =
1720 | [ ingredient oldTomGin (CL 4.5)
1721 | , ingredient lemonJuice (CL 3)
1722 | , ingredient simpleSyrup (CL 1.5)
1723 | , ingredient sodaWater (CL 6)
1724 | , ingredient angosturaBitters (Dash 1)
1725 | , ingredient lemon (Slice 1)
1726 | , ingredient cherry (Whole 1)
1727 | ]
1728 | , description = """Pour all ingredients directly into highball glass filled with ice. Stir gently. Garnish with lemon slice and maraschino cherry. Add a dash of Angostura bitters."""
1729 | , glass = Collins
1730 | , video = Just (Epicurious "17:13")
1731 | }
1732 |
1733 | -- https://iba-world.com/cocktails/pina-colada/
1734 | , { name = "Pina Colada"
1735 | , ingredients =
1736 | [ ingredient whiteRum (CL 5)
1737 | , ingredient coconutCream (CL 3)
1738 | , ingredient pineappleJuice (CL 5)
1739 | , ingredient pineapple (Slice 1)
1740 | , ingredient cherry (Whole 1)
1741 | ]
1742 | , description = """Blend all the ingredients with ice in a electric blender, pour into a large glass and serve with straws. Garnish with a slice of pineapple with a cocktail cherry. 4 slices of fresh pineapple can be used instead of juice. Historically a few drops of fresh lime juice was added to taste."""
1743 | , glass = Hurricane
1744 | , video = Nothing
1745 | }
1746 |
1747 | -- https://iba-world.com/new-era-drinks/pisco-sour/
1748 | -- https://iba-world.com/cocktails/pisco-sour-2/
1749 | , { name = "Pisco Sour"
1750 | , ingredients =
1751 | [ ingredient pisco (CL 4.5)
1752 | , ingredient simpleSyrup (CL 2)
1753 | , ingredient lemonJuice (CL 3)
1754 | , ingredient eggWhite (Whole 1)
1755 | , ingredient angosturaBitters (Dash 1)
1756 | ]
1757 | , description = """Shake and strain into a chilled champagne flute. Dash some Angostura bitters on top."""
1758 | , glass = ChampagneFlute
1759 | , video = Just (Epicurious "35:00")
1760 | }
1761 |
1762 | -- https://iba-world.com/new-era-drinks/russian-spring-punch/
1763 | , { name = "Russian spring punch"
1764 | , ingredients =
1765 | [ ingredient vodka (CL 2.5)
1766 | , ingredient lemonJuice (CL 2.5)
1767 | , ingredient cremeDeCassis (CL 1.5)
1768 | , ingredient simpleSyrup (CL 1)
1769 | , ingredient sparklingWine None
1770 | , ingredient lemon (Slice 1)
1771 | , ingredient blackberry (Whole 1)
1772 | ]
1773 | , description = """Shake the ingredients and pour into highball glass. Top with Sparkling wine. Garnish with a lemon slice and a blackberry."""
1774 | , glass = Highball
1775 | , video = Nothing
1776 | }
1777 |
1778 | -- https://en.wikipedia.org/wiki/Sea_Breeze_(cocktail)
1779 | -- https://iba-world.com/cocktails/sea-breeze/
1780 | , { name = "Sea breeze"
1781 | , ingredients =
1782 | [ ingredient vodka (CL 4)
1783 | , ingredient cranberryJuice (CL 12)
1784 | , ingredient grapefruitJuice (CL 3)
1785 | , ingredient orange (Custom "zest")
1786 | , ingredient cherry (Whole 1)
1787 | ]
1788 | , description = """Build all ingredients in a highball glass filled with ice. Garnish with an orange zest and cherry."""
1789 | , glass = Highball
1790 | , video = Nothing
1791 | }
1792 |
1793 | -- https://en.wikipedia.org/wiki/Sex_on_the_Beach
1794 | -- https://iba-world.com/cocktails/sex-on-the-beach/
1795 | , { name = "Sex on the beach"
1796 | , ingredients =
1797 | [ ingredient vodka (CL 4)
1798 | , ingredient peachSchnapps (CL 2)
1799 | , ingredient oj (CL 4)
1800 | , ingredient cranberryJuice (CL 4)
1801 | , ingredient grapefruitJuice (CL 3)
1802 | , ingredient orange (Slice 0.5)
1803 | ]
1804 | , description = """Build all ingredients in a highball glass filled with ice. Garnish with an orange zest and cherry."""
1805 | , glass = Highball
1806 | , video = Nothing
1807 | }
1808 |
1809 | -- https://en.wikipedia.org/wiki/Singapore_Sling
1810 | -- https://iba-world.com/cocktails/singapore-sling/
1811 | , { name = "Singapore sling"
1812 | , ingredients =
1813 | [ ingredient gin (CL 3)
1814 | , ingredient cherryLiqueur (CL 1.5)
1815 | , ingredient cointreau (CL 0.75)
1816 | , ingredient domBenedictine (CL 0.75)
1817 | , ingredient pineappleJuice (CL 12)
1818 | , ingredient limeJuice (CL 1.5)
1819 | , ingredient grenadine (CL 1)
1820 | , ingredient angosturaBitters (Dash 1)
1821 | , ingredient cherry (Whole 1)
1822 | , ingredient pineapple (Slice 1)
1823 | ]
1824 | , description = """Pour all ingredients into cocktail shaker filled with ice cubes. Shake well. Strain into Hurricane glass. Garnish with pineapple and maraschino cherry."""
1825 | , glass = Hurricane
1826 | , video = Nothing
1827 | }
1828 |
1829 | -- https://en.wikipedia.org/wiki/Spritz_Veneziano
1830 | -- https://iba-world.com/cocktails/tequila-sunrise/
1831 | , { name = "Tequila sunrise"
1832 | , ingredients =
1833 | [ ingredient tequila (CL 4.5)
1834 | , ingredient oj (CL 9)
1835 | , ingredient grenadine (CL 1.5)
1836 | , ingredient orange (Slice 0.5)
1837 | ]
1838 | , description = """Pour tequila and orange juice directly into highball glass filled with ice cubes. Add the grenadine syrup to create chromatic effect (sunrise), do not stir. Garnish with half orange slice or an orange zest."""
1839 | , glass = Collins
1840 | , video = Nothing
1841 | }
1842 |
1843 | -- https://en.wikipedia.org/wiki/Yellow_Bird_(cocktail)
1844 | -- https://iba-world.com/new-era-drinks/yellow-bird/
1845 | -- https://open.spotify.com/track/5ced30fVo8XlDpmoVnUZqp
1846 | , { name = "Yellow bird"
1847 | , ingredients =
1848 | [ ingredient whiteRum (CL 3)
1849 | , ingredient galliano (CL 1.5)
1850 | , ingredient tripleSec (CL 1.5)
1851 | , ingredient limeJuice (CL 1.5)
1852 | ]
1853 | , description = """Shake and strain into a chilled cocktail glass."""
1854 | , glass = Cocktail
1855 | , video = Nothing
1856 | }
1857 |
1858 | -- https://en.wikipedia.org/wiki/Zombie_(cocktail)
1859 | -- https://iba-world.com/cocktails/zombie/
1860 | , { name = "Zombie"
1861 | , ingredients =
1862 | [ ingredient darkRum (CL 4.5) -- Jamaican dark rum
1863 | , ingredient goldRum (CL 4.5) -- Gold Puerto Rican rum
1864 | , ingredient demeraraRum (CL 3) -- Rum from guyana
1865 | , ingredient limeJuice (CL 2)
1866 | , ingredient falernum (CL 1.5)
1867 | , ingredient grapefruitJuice (CL 1) -- With cinnamon syrup making up “Donn’s mix”.
1868 | , ingredient cinnamonSyrup (CL 0.5)
1869 | , ingredient grenadine (Tsp 1)
1870 | , ingredient angosturaBitters (Dash 1)
1871 | , ingredient absinthe (Dash 2)
1872 | , ingredient mint (Custom "leaves")
1873 | ]
1874 | , description = """Add all ingredients into an electric blender with 170 grams of cracked ice. With pulse bottom blend for a few seconds. Serve in a tall tumbler glass. Garnish with mint leaves."""
1875 | , glass = ZombieGlass
1876 | , video = Nothing
1877 | }
1878 |
1879 | -- https://iba-world.com/iba-official-cocktails/brandy-crusta/
1880 | , { name = "Brandy crusta"
1881 | , ingredients =
1882 | [ ingredient brandy (CL 5.25)
1883 | , ingredient lemonJuice (CL 1.5)
1884 | , ingredient maraschino (CL 0.75)
1885 | , ingredient curacao (Tsp 1)
1886 | , ingredient simpleSyrup (Tsp 1)
1887 | , ingredient aromaticBitters (Dash 2)
1888 | , ingredient orange (Slice 1)
1889 | , ingredient powderedSugar (Tsp 1)
1890 | ]
1891 | , description = """Mix together all ingredients with ice cubes in a mixing glass and strain into prepared slim cocktail glass. Rub a slice of orange (or lemon) around the rim of the glass and dip it in pulverized white sugar, so that the sugar will adhere to the edge of the glass. Carefully curling place the orange/lemon peel around the inside of the glass."""
1892 | , glass = Cocktail
1893 | , video = Nothing
1894 | }
1895 |
1896 | -- https://en.wikipedia.org/wiki/Hanky-Panky_cocktail
1897 | -- https://iba-world.com/iba-official-cocktails/hanky-panky/
1898 | , { name = "Hanky panky"
1899 | , ingredients =
1900 | [ ingredient londonDryGin (CL 4.5)
1901 | , ingredient sweetRedVermouth (CL 4.5)
1902 | , ingredient fernetBranca (CL 0.75)
1903 | , ingredient orange (Custom "zest")
1904 | ]
1905 | , description = """Pour all ingredients into mixing glass with ice cubes. Stir well. Strain into chilled cocktail glass. Garnish with orange zest."""
1906 | , glass = Cocktail
1907 | , video = Nothing
1908 | }
1909 |
1910 | -- https://en.wikipedia.org/wiki/Last_Word_(cocktail)
1911 | -- https://iba-world.com/iba-official-cocktails/last-word/
1912 | , { name = "Last word"
1913 | , ingredients =
1914 | [ ingredient gin (CL 2.25)
1915 | , ingredient greenChartreuse (CL 2.25)
1916 | , ingredient maraschino (CL 2.25)
1917 | , ingredient limeJuice (CL 2.25)
1918 | ]
1919 | , description = """Add all ingredients into a cocktail shaker. Shake with ice and strain into a chilled cocktail glass."""
1920 | , glass = Cocktail
1921 | , video = Just (Epicurious "21:18")
1922 | }
1923 |
1924 | -- https://en.wikipedia.org/wiki/Martinez_(cocktail)
1925 | -- https://iba-world.com/iba-official-cocktails/martinez/
1926 | , { name = "Martinez"
1927 | , ingredients =
1928 | [ ingredient londonDryGin (CL 4.5)
1929 | , ingredient sweetRedVermouth (CL 4.5)
1930 | , ingredient maraschino (Tsp 1)
1931 | , ingredient orangeBitters (Dash 2)
1932 | , ingredient lemon (Custom "zest")
1933 | ]
1934 | , description = """Pour all ingredients into mixing glass with ice cubes. Stir well. Strain into chilled cocktail glass. Garnish with Lemon zest."""
1935 | , glass = Cocktail
1936 | , video = Just (Epicurious "14:20")
1937 | }
1938 |
1939 | -- https://iba-world.com/iba-official-cocktails/vieux-carre/
1940 | , { name = "Vieux carré"
1941 | , ingredients =
1942 | [ ingredient ryeWhiskey (CL 3)
1943 | , ingredient cognac (CL 3)
1944 | , ingredient sweetRedVermouth (CL 3)
1945 | , ingredient domBenedictine (Tsp 1)
1946 | , ingredient peychaudsBitters (Dash 2)
1947 | , ingredient orange (Custom "zest")
1948 | , ingredient cherry (Whole 1)
1949 | ]
1950 | , description = """Pour all ingredients into mixing glass with ice cubes. Stir well. Strain into chilled cocktail glass. Garnish with orange zest and maraschino cherry."""
1951 | , glass = Cocktail
1952 | , video = Just (Epicurious "31:04")
1953 | }
1954 |
1955 | -- https://en.wikipedia.org/wiki/Bee's_Knees_(cocktail)
1956 | -- https://iba-world.com/new-era-drinks/bees-knees/
1957 | , { name = "Bee’s knees"
1958 | , ingredients =
1959 | [ ingredient londonDryGin (CL 5.25) -- Just “dry gin”
1960 | , ingredient honeySyrup (Tsp 2)
1961 | , ingredient lemonJuice (CL 2.25)
1962 | , ingredient oj (CL 2.25)
1963 | , optionalIngredient orange (Custom "zest")
1964 | ]
1965 | , description = """Stir honey with lemon and orange juices until it dissolves, add gin and shake with ice. Strain into a chilled cocktail glass. Optionally garnish with a lemon or orange zest."""
1966 | , glass = Cocktail
1967 | , video = Just (Epicurious "20:46")
1968 | }
1969 |
1970 | -- https://iba-world.com/news/cachanchara/
1971 | , { name = "Cachanchara"
1972 | , ingredients =
1973 | [ ingredient cubanAguardiente (CL 6)
1974 | , ingredient lemonJuice (CL 1.5)
1975 | , ingredient honey (CL 1.5)
1976 | , ingredient water (CL 5)
1977 | , ingredient lime (Wedge 1)
1978 | ]
1979 | , description = """Mix honey with water and lime juice and spread the mixture on the bottom and sides of the glass. Add cracked ice, and then the rum. End by energetically stirring from bottom to top. Garnish with Lime wedge."""
1980 | , glass = OldFashioned
1981 | , video = Nothing
1982 | }
1983 |
1984 | -- https://iba-world.com/new-era-drinks/fernandito/
1985 | , { name = "Fernandito"
1986 | , ingredients =
1987 | [ ingredient fernetBranca (CL 5)
1988 | , ingredient cola None
1989 | ]
1990 | , description = """Pour the Fernet Branca into a double old fashioned glass with ice, fill the glass up with Cola. Gently stir."""
1991 | , glass = Highball -- Double old fashioned?
1992 | , video = Nothing
1993 | }
1994 |
1995 | -- https://iba-world.com/new-era-drinks/old-cuban/
1996 | , { name = "Old cuban"
1997 | , ingredients =
1998 | [ ingredient rum (CL 4.5) -- “Aged rum”
1999 | , ingredient sparklingWine (CL 6) -- “Brut champagne or prosecco”
2000 | , ingredient limeJuice (CL 2.25)
2001 | , ingredient simpleSyrup (CL 3)
2002 | , ingredient angosturaBitters (Dash 2)
2003 | , ingredient mint (Sprig 3)
2004 | ]
2005 | , description = """Pour all ingredients into cocktail shaker except the wine, shake well with ice, strain into chilled elegant cocktail glass. Top up with the sparkling wine. Garnish with mint springs."""
2006 | , glass = Cocktail
2007 | , video = Nothing
2008 | }
2009 |
2010 | -- https://iba-world.com/new-era-drinks/paloma/
2011 | , { name = "Paloma"
2012 | , ingredients =
2013 | [ ingredient tequila (CL 5) -- “100% Agave Tequila”
2014 | , ingredient grapefruitSoda (CL 10) -- Pink Grapefruit Soda
2015 | , ingredient limeJuice (Tsp 2)
2016 | , ingredient salt None
2017 | , ingredient lime (Slice 1)
2018 | ]
2019 | , description = """Pour the tequila into a highball glass, squeeze the lime juice. Add ice and salt, fill up pink grapefruit soda. Stir gently. Garnish with a slice of lime."""
2020 | , glass = Highball
2021 | , video = Just (Epicurious "24:47")
2022 | }
2023 |
2024 | -- https://iba-world.com/new-era-drinks/paper-plane/
2025 | , { name = "Paper plane"
2026 | , ingredients =
2027 | [ ingredient bourbonWhiskey (CL 3)
2028 | , ingredient amaroNonino (CL 3)
2029 | , ingredient aperol (CL 3)
2030 | , ingredient lemonJuice (CL 3)
2031 | ]
2032 | , description = """Pour all ingredients into cocktail shaker, shake well with ice, strain into chilled cocktail glass."""
2033 | , glass = Cocktail
2034 | , video = Nothing
2035 | }
2036 |
2037 | -- https://iba-world.com/new-era-drinks/penicillin/
2038 | , { name = "Penicillin"
2039 | , ingredients =
2040 | [ ingredient blendedScotchWhiskey (CL 6) -- “Blended Scotch Whisky”
2041 | , ingredient islaySingleMaltScotch (CL 0.75) -- “Lagavulin 16y”
2042 | , ingredient lemonJuice (CL 2.25)
2043 | , ingredient honeySyrup (CL 2.25)
2044 | , ingredient ginger (Slice 3)
2045 | , ingredient candiedGinger (Whole 1)
2046 | ]
2047 | , description = """Muddle fresh ginger in a shaker and add the remaining ingredients, except for the Islay single malt whiskey. Fill the shaker with ice and shake. Double-strain into a chilled old fashioned glass with ice. Float the single malt whisky on top. Garnish with a candied ginger."""
2048 | , glass = OldFashioned
2049 | , video = Nothing
2050 | }
2051 |
2052 | -- https://iba-world.com/new-era-drinks/southside/
2053 | , { name = "Southside"
2054 | , ingredients =
2055 | [ ingredient londonDryGin (CL 6)
2056 | , ingredient lemonJuice (CL 3)
2057 | , ingredient simpleSyrup (CL 1.5)
2058 | , ingredient mint (Sprig 2)
2059 | , optionalIngredient eggWhite (CL 3)
2060 | ]
2061 | , description = """Egg white optional. Pour all ingredients into a cocktail shaker, shake well with ice, double-strain into chilled cocktail glass. If egg white is used shake vigorously. Garnish with mint springs."""
2062 | , glass = Cocktail
2063 | , video = Nothing
2064 | }
2065 |
2066 | -- https://iba-world.com/new-era-drinks/spicy-fifty/
2067 | , { name = "Spicy fifty"
2068 | , ingredients =
2069 | [ ingredient vodka (CL 5)
2070 | , ingredient elderflowerSyrup (CL 1.5) -- “Elder flower cordial”
2071 | , ingredient lemonJuice (CL 1.5)
2072 | , ingredient honeySyrup (CL 1) -- “Monin Honey Syrup”
2073 | , ingredient vanillaExtract (Drop 1) -- “Vodka Vanilla” is used by IBA, so I instead added a drop of extract.
2074 | , ingredient redChiliPepper None
2075 | ]
2076 | , description = """Pour all ingredients (including 2 thin slices of pepper) into a cocktail shaker, shake well with ice, double-strain into chilled cocktail glass. Garnish with a red chili pepper."""
2077 | , glass = Cocktail
2078 | , video = Nothing
2079 | }
2080 |
2081 | -- https://iba-world.com/new-era-drinks/suffering-bastard/
2082 | , { name = "Suffering bastard"
2083 | , ingredients =
2084 | [ ingredient brandy (CL 3)
2085 | , ingredient gin (CL 3)
2086 | , ingredient limeJuice (CL 1.5)
2087 | , ingredient angosturaBitters (Dash 2)
2088 | , ingredient gingerBeer None
2089 | , ingredient mint (Sprig 1)
2090 | , optionalIngredient orange (Slice 1)
2091 | ]
2092 | , description = """Pour all ingredients into cocktail shaker except the ginger beer, shake well with ice, Pour unstrained into a Collins glass or in the original S. Bastard mug and top up with ginger beer. Garnish with mint sprig and optionally an orange slice as well."""
2093 | , glass = Collins
2094 | , video = Nothing
2095 | }
2096 |
2097 | -- https://iba-world.com/new-era-drinks/tipperary/
2098 | , { name = "Tipperary"
2099 | , ingredients =
2100 | [ ingredient irishWhiskey (CL 5)
2101 | , ingredient sweetRedVermouth (CL 2.5)
2102 | , ingredient greenChartreuse (CL 1.5)
2103 | , ingredient angosturaBitters (Dash 2)
2104 | , ingredient orange (Slice 1)
2105 | ]
2106 | , description = """Pour all ingredients into mixing glass with ice cubes. Stir well. Strain into chilled martini cocktail glass. Garnish with a slice of orange."""
2107 | , glass = Cocktail
2108 | , video = Nothing
2109 | }
2110 |
2111 | -- https://iba-world.com/new-era-drinks/trinidad-sour/
2112 | , { name = "Trinidad sour"
2113 | , ingredients =
2114 | [ ingredient angosturaBitters (CL 4.5)
2115 | , ingredient orgeatSyrup (CL 3)
2116 | , ingredient lemonJuice (CL 2.25)
2117 | , ingredient ryeWhiskey (CL 1.5)
2118 | ]
2119 | , description = """Pour all ingredients into mixing glass with ice cubes. Stir well. Strain into chilled cocktail glass."""
2120 | , glass = Cocktail
2121 | , video = Nothing
2122 | }
2123 |
2124 | -- https://iba-world.com/new-era-drinks/ve-n-to/
2125 | , { name = "Ve.n.to"
2126 | , ingredients =
2127 | [ ingredient grappa (CL 4.5) -- “White smooth grappa”
2128 | , ingredient lemonJuice (CL 2.25)
2129 | , ingredient honeySyrup (CL 1.5) -- “Honey mix”
2130 | , ingredient chamomileSyrup (CL 1.5) -- “Chamomile cordial”
2131 | , ingredient honeySyrup (CL 1.5) -- “Honey mix”
2132 | , optionalIngredient eggWhite (CL 3)
2133 | , ingredient lemon (Custom "zest")
2134 | , ingredient whiteGrape (Whole 3)
2135 | ]
2136 | , description = """Egg white optional. Pour all ingredients into the shaker. Shake vigorously with ice. Strain into a chilled small tumbler glass filled with ice. Garnish with lemon zest and white grapes."""
2137 | , glass = OldFashioned
2138 | , video = Nothing
2139 | }
2140 |
2141 | -- https://iba-world.com/new-era-drinks/illegal/
2142 | , { name = "Illegal"
2143 | , ingredients =
2144 | [ ingredient mezcal (CL 3) -- “Espadin Mezcal”
2145 | , ingredient overproofWhiteRum (CL 1.5) -- “Jamaica Overproof White Rum”
2146 | , ingredient limeJuice (CL 2.25)
2147 | , ingredient falernum (CL 1.5)
2148 | , ingredient simpleSyrup (CL 1.5)
2149 | , ingredient maraschino (Tsp 1)
2150 | , optionalIngredient eggWhite (CL 3)
2151 | ]
2152 | , description = """Egg white optional. Pour all ingredients into the shaker. Shake vigorously with ice. Strain into a chilled cocktail glass, or “on the rocks” in a traditional clay or terracotta mug."""
2153 | , glass = Cocktail
2154 | , video = Nothing
2155 | }
2156 |
2157 | -- https://iba-world.com/new-era-drinks/naked-and-famous/
2158 | , { name = "Naked and famous"
2159 | , ingredients =
2160 | [ ingredient mezcal (CL 2.25)
2161 | , ingredient yellowChartreuse (CL 2.25)
2162 | , ingredient aperol (CL 2.25)
2163 | , ingredient limeJuice (CL 2.25)
2164 | ]
2165 | , description = """Pour all ingredients into cocktail shaker, shake well with ice, strain into chilled cocktail glass."""
2166 | , glass = Cocktail
2167 | , video = Nothing
2168 | }
2169 |
2170 | -- https://iba-world.com/new-era-drinks/new-york-sour/
2171 | , { name = "New York sour"
2172 | , ingredients =
2173 | [ ingredient ryeWhiskey (CL 6) -- Or bourbon
2174 | , ingredient lemonJuice (CL 3)
2175 | , ingredient eggWhite (CL 3)
2176 | , ingredient simpleSyrup (CL 2.25)
2177 | , ingredient redWine (CL 1.5) -- “Shiraz or Malbech”
2178 | , ingredient lemon (Custom "zest")
2179 | , ingredient cherry (Whole 1)
2180 | ]
2181 | , description = """Bourbon can be used instead of rye. Pour all ingredients into the shaker. Shake vigorously with ice. Strain into a chilled rocks glass filled with ice. Float the wine on top. Garnish with lemon or orange zest with cherry."""
2182 | , glass = OldFashioned
2183 | , video = Nothing
2184 | }
2185 |
2186 | -- https://iba-world.com/new-era-drinks/spritz/
2187 | -- Also the 'Aperol spritz'
2188 | , { name = "Spritz"
2189 | , ingredients =
2190 | [ ingredient prosecco (CL 9)
2191 | , ingredient aperol (CL 6)
2192 | , ingredient sodaWater (Splash 1)
2193 | , ingredient orange (Slice 1)
2194 | ]
2195 | , description = """Build all ingredients into a wine glass filled with ice. Stir gently. Garnish with a slice of orange."""
2196 | , glass = Wine
2197 | , video = Just (Epicurious "33:11")
2198 | }
2199 | , { name = "Gimlet"
2200 | , ingredients =
2201 | [ ingredient limeJuice (CL 2)
2202 | , ingredient simpleSyrup (CL 2)
2203 | , ingredient gin (CL 6)
2204 | ]
2205 | , description = """Pour all ingredients into a cocktail strainer, shake well with ice, strain into chilled cocktail glass."""
2206 | , glass = Cocktail
2207 | , video = Just (Epicurious "15:00")
2208 | }
2209 | , { name = "Martini"
2210 | , ingredients =
2211 | [ ingredient gin (CL 6)
2212 | , ingredient dryVermouth (CL 6)
2213 | , optionalIngredient lemon (Custom "twist")
2214 | ]
2215 | , description = """Mix gin and vermouth in a chilled pint glass, stir with ice. Strain into a champagne coupe."""
2216 | , glass = ChampagneCoupe
2217 | , video = Just (Epicurious "13:39")
2218 | }
2219 | , { name = "Vodka martini"
2220 | , ingredients =
2221 | [ ingredient vodka (CL 8)
2222 | , ingredient dryVermouth (Drop 1)
2223 | , optionalIngredient lemon (Custom "twist")
2224 | , optionalIngredient olive (Whole 1)
2225 | ]
2226 | , description = """Mix vodka and dry vermouth in a pint glass. The amount of vermouth used varies and can be as little as a drop. Stir with ice. Optionally add olive brine to make a dirty martini."""
2227 | , glass = Cocktail
2228 | , video = Just (Epicurious "21:53")
2229 | }
2230 |
2231 | -- https://en.wikipedia.org/wiki/20th_Century_(cocktail)
2232 | , { name = "20th century"
2233 | , ingredients =
2234 | [ ingredient gin (CL 4.5)
2235 | , ingredient lemonJuice (CL 2)
2236 | , ingredient whiteCremeDeCacao (CL 1.5)
2237 | , ingredient kinaLillet (CL 2)
2238 | , ingredient lemon (Custom "twist")
2239 | ]
2240 | , description = """Combine ingredients in a cocktail shaker, shake with ice, strain into a cocktail glass."""
2241 | , glass = Cocktail
2242 | , video = Just (Epicurious "19:51")
2243 | }
2244 | , { name = "Artillery"
2245 | , ingredients =
2246 | [ ingredient sweetRedVermouth (Tsp 1.5)
2247 | , ingredient gin (CL 4.5)
2248 | , ingredient angosturaBitters (Dash 2)
2249 | ]
2250 | , description = """Stir all ingredients with ice and strain into a cocktail glass."""
2251 | , glass = Cocktail
2252 | , video = Nothing
2253 | }
2254 | , { name = "Whiskey fix"
2255 | , ingredients =
2256 | [ ingredient lemonJuice (CL 2)
2257 | , ingredient simpleSyrup (CL 2)
2258 | , ingredient whiskey (CL 6)
2259 | , ingredient lemon (Wedge 1)
2260 | , ingredient cherry (Whole 1)
2261 | ]
2262 | , description = """Shake in a cocktail shaker with a small piece of ice. Drain into the glass and top with crushed ice and garnish with a lemon wedge and a luxardo cherry."""
2263 | , glass = OldFashioned
2264 | , video = Just (Epicurious "7:11")
2265 | }
2266 | ]
2267 |
2268 |
2269 |
2270 | -- Presbyterian 8:58
2271 | -- Blinker 9:58
2272 | -- Improved Whiskey Cocktail 10:41
2273 | -- Monte Carlo 11:25
2274 | -- Gin Rickey 15:34
2275 | -- Aviation Number 1 16:37
2276 | -- Headless Horseman 23:19
2277 | -- Mexican Firing Squad Special 25:30
2278 | -- Hemingway Daiquiri 26:32
2279 | -- Hotel Nacional Special 28:47
2280 | -- Brandy Alexander 30:45
2281 | -- Pink Lady 31:38
2282 | -- Delmonico 32:13
2283 | -- Jack Rose 32:42
2284 | -- Pan American Clipper 33:01
2285 | -- Champagne Cocktail 34:03
2286 | -- Bamboo 34:26
2287 |
--------------------------------------------------------------------------------
/front/src/Main.elm:
--------------------------------------------------------------------------------
1 | port module Main exposing (Msg(..), main, setDark, update, view)
2 |
3 | import Browser
4 | import Browser.Dom as Dom
5 | import Browser.Events
6 | import Browser.Navigation as Nav
7 | import Data exposing (Glass(..), Ingredient, Material, MaterialType(..), Recipe, SuperMaterial(..), Video(..), recipes)
8 | import Element exposing (Element, alignTop, column, el, fill, height, html, padding, paddingEach, paragraph, row, shrink, spacing, text, width)
9 | import Element.Background
10 | import Element.Border as Border
11 | import Element.Events exposing (onClick)
12 | import Element.Font as Font exposing (bold, strike)
13 | import Element.Input as Input
14 | import Element.Region
15 | import Html exposing (details, iframe, label, option, select, summary)
16 | import Html.Attributes exposing (value)
17 | import Html.Events
18 | import Http
19 | import Json.Decode as Decode exposing (Decoder, bool, field, string)
20 | import Json.Encode as Encode
21 | import Quantity exposing (Quantity(..), Units(..), printQuantity)
22 | import Set
23 | import Set.Any exposing (AnySet)
24 | import Slug
25 | import Svg exposing (path, svg)
26 | import Svg.Attributes exposing (d, stroke, strokeWidth, viewBox)
27 | import Task
28 | import Url exposing (Url)
29 |
30 |
31 |
32 | -- Types
33 |
34 |
35 | type alias CachedMaterial =
36 | ( Int, Bool, Material )
37 |
38 |
39 | type alias MaterialsGroup =
40 | { label : String
41 | , materials : List CachedMaterial
42 | }
43 |
44 |
45 | type alias MaterialSet =
46 | AnySet String Material
47 |
48 |
49 | type Mode
50 | = Normal
51 | | Grid
52 |
53 |
54 | type Sort
55 | = Feasibility
56 | | Alphabetical
57 |
58 |
59 | type Device
60 | = Desktop
61 | | Mobile
62 |
63 |
64 | type Tab
65 | = TMaterials
66 | | TCocktails
67 | | TDetail
68 | | TSettings
69 |
70 |
71 | type alias Model =
72 | { recipes : List Recipe
73 | , materials : List Material
74 | , selectedRecipe : Maybe Recipe
75 | , availableMaterials : MaterialSet
76 | , units : Units
77 | , mode : Mode
78 | , pedantic : Bool
79 | , sort : Sort
80 | , dark : Bool
81 | , email : String
82 | , syncing : Bool
83 | , sentEmail : Bool
84 | , countedMaterials : List MaterialsGroup
85 | , device : Device
86 | , tab : Tab
87 | , key : Nav.Key
88 | }
89 |
90 |
91 | type Msg
92 | = SelectRecipe Recipe
93 | | ToggleIngredient Material Bool
94 | | SetUnits String
95 | | SetMode Mode
96 | | SetSubsitute Bool
97 | | SetDark Bool
98 | | SetSort String
99 | | MoveUp
100 | | MoveDown
101 | | Ignored
102 | | GotOk (Result Http.Error Bool)
103 | | GotMagic (Result Http.Error Bool)
104 | | GotDevice Device
105 | | GotInventory (Result Http.Error (List String))
106 | | GetMagicLink
107 | | SetEmail String
108 | | SetTab Tab
109 | | ChangedUrl Url
110 | | ClickedLink Browser.UrlRequest
111 |
112 |
113 |
114 | -- Icons
115 |
116 |
117 | icon : String -> Element.Element Msg
118 | icon pathSpec =
119 | svg
120 | [ Svg.Attributes.width "20", Svg.Attributes.height "20", Svg.Attributes.fill "none", viewBox "0 0 20 20" ]
121 | [ path [ d pathSpec, stroke "currentColor", strokeWidth "1" ]
122 | []
123 | ]
124 | |> html
125 |
126 |
127 | glassIcon : Recipe -> Element.Element Msg
128 | glassIcon recipe =
129 | case recipe.glass of
130 | OldFashioned ->
131 | icon "M14 5H6v10c.582.209 2.06.5 4 .5s3.612-.291 4-.5V5zm-8 5h8"
132 |
133 | Cocktail ->
134 | icon "M9 9.5L5.5 4h9l-3.501 5.5v4.75H13v1.25H7v-1.25h2.056L9 9.5zm-2.5-4h7"
135 |
136 | Collins ->
137 | icon "M13 4H7v12.5c.5.25 1.5.5 3 .5s2.712-.24 3-.5V4zm-6 6.554l6-.054"
138 |
139 | CopperMug ->
140 | icon "M13 5H5v9.973c.593.213 2.024.527 4 .527s3.605-.314 4-.527V5zm0 2.5h3V12h-3"
141 |
142 | ChampagneFlute ->
143 | icon "M9.25 17v-5.616L8 7.5 8.5 3h3l.5 4.5-1.25 3.884V17H12v.63H8V17h1.25zM8 4.856h3.616"
144 |
145 | -- These are the same because they look so similar
146 | Highball ->
147 | icon "M13 4H6v11c.512.239 1.792.5 3.5.5s3.158-.261 3.5-.5V4zm-7 6h7"
148 |
149 | ZombieGlass ->
150 | icon "M13 4H6v11c.512.239 1.792.5 3.5.5s3.158-.261 3.5-.5V4zm-7 6h7"
151 |
152 | Margarita ->
153 | icon "M9.25 16.5V9L7.8 8l-.3-1.5L5 5.25 4.5 3h11L15 5.25 12.5 6.5 12.2 8l-1.45 1v7.5h1.582v.5H7.196v-.5H9.25z"
154 |
155 | Hurricane ->
156 | icon "M9.25 17.5V14L7.5 12.5 7 10l.5-1.75.25-1.75-.25-1.75L7 3h6l-.5 1.75-.25 1.75.25 1.75L13 10l-.5 2.5-1.75 1.5v3.5h2.05v.5H7.196v-.5H9.25z"
157 |
158 | IrishCoffeeMug ->
159 | icon "M9 15v-2l-1-1-1-.5V4h6v7.5l-1 .5-1 1v2l1.332.5v.5H7.196v-.5L9 15zm4-9.5h2.5V9L13 10M7 6.5h6"
160 |
161 | SteelCup ->
162 | icon "M14.5 4h-9L7 14.5c.5.25.75.5 3 .5 1.75 0 2.75-.25 3-.5L14.5 4zM6 6h8"
163 |
164 | Wine ->
165 | icon "M9.25 16.5v-6L7.8 9 7 7l1-4h4l1 4-.8 2-1.45 1.5v6h1.582v.5H7.196v-.5H9.25z"
166 |
167 | ChampagneCoupe ->
168 | icon "M9.25 16.5v-8L8 7 6 6 4.5 4.5V3h11v1.5l-1.5 2-2 .5-1.25 1.5v8h1.582v.5H7.196v-.5H9.25z"
169 |
170 |
171 |
172 | -- Constants
173 |
174 |
175 | white : Element.Color
176 | white =
177 | Element.rgb 229 227 224
178 |
179 |
180 | black : Element.Color
181 | black =
182 | Element.rgb 0 0 0
183 |
184 |
185 | blue : Element.Color
186 | blue =
187 | Element.rgb255 29 27 124
188 |
189 |
190 | lightBlue : Element.Color
191 | lightBlue =
192 | Element.rgb255 129 127 224
193 |
194 |
195 | edges : { top : Int, right : Int, bottom : Int, left : Int }
196 | edges =
197 | { top = 0
198 | , right = 0
199 | , bottom = 0
200 | , left = 0
201 | }
202 |
203 |
204 | selectCmd : Nav.Key -> Maybe Recipe -> Cmd Msg
205 | selectCmd key selected =
206 | case selected of
207 | Just r ->
208 | Cmd.batch
209 | [ Task.attempt (\_ -> Ignored) (Dom.focus (recipeSlug r))
210 | , Nav.replaceUrl key ("#" ++ recipeSlug r)
211 | ]
212 |
213 | Nothing ->
214 | Cmd.none
215 |
216 |
217 |
218 | -- Application loop
219 |
220 |
221 | update : Msg -> Model -> ( Model, Cmd Msg )
222 | update msg model =
223 | case msg of
224 | SelectRecipe recipe ->
225 | ( { model
226 | | selectedRecipe =
227 | if Just recipe == model.selectedRecipe then
228 | Nothing
229 |
230 | else
231 | Just recipe
232 | , tab = TDetail
233 | }
234 | , selectCmd model.key (Just recipe)
235 | )
236 |
237 | SetUnits units ->
238 | ( { model
239 | | units =
240 | case units of
241 | "Cl" ->
242 | Cl
243 |
244 | "Oz" ->
245 | Oz
246 |
247 | "Ml" ->
248 | Ml
249 |
250 | _ ->
251 | Cl
252 | }
253 | , Cmd.none
254 | )
255 |
256 | SetSort sort ->
257 | ( { model
258 | | sort =
259 | case sort of
260 | "Alphabetical" ->
261 | Alphabetical
262 |
263 | "Feasibility" ->
264 | Feasibility
265 |
266 | _ ->
267 | Alphabetical
268 | }
269 | |> deriveMaterials
270 | , Cmd.none
271 | )
272 |
273 | MoveUp ->
274 | let
275 | selected =
276 | model.selectedRecipe
277 | |> Maybe.map (\r -> getNext (List.reverse model.recipes) r)
278 | |> Maybe.withDefault (List.head model.recipes)
279 | in
280 | ( { model
281 | | selectedRecipe = selected
282 | }
283 | , selectCmd model.key selected
284 | )
285 |
286 | MoveDown ->
287 | let
288 | selected =
289 | model.selectedRecipe
290 | |> Maybe.map (\r -> getNext model.recipes r)
291 | |> Maybe.withDefault (List.head model.recipes)
292 | in
293 | ( { model
294 | | selectedRecipe = selected
295 | }
296 | , selectCmd model.key selected
297 | )
298 |
299 | ToggleIngredient material checked ->
300 | ( { model
301 | | availableMaterials =
302 | if checked then
303 | Set.Any.insert material model.availableMaterials
304 |
305 | else
306 | Set.Any.remove material model.availableMaterials
307 | }
308 | |> deriveMaterials
309 | , if model.syncing then
310 | saveMaterial material.name checked
311 |
312 | else
313 | Cmd.none
314 | )
315 |
316 | SetMode mode ->
317 | ( { model | mode = mode }, Cmd.none )
318 |
319 | SetSubsitute on ->
320 | ( { model | pedantic = on }
321 | |> deriveMaterials
322 | , Cmd.none
323 | )
324 |
325 | SetDark on ->
326 | ( { model | dark = on }, setDark on )
327 |
328 | SetEmail e ->
329 | ( { model | email = e }, Cmd.none )
330 |
331 | SetTab tab ->
332 | ( { model | tab = tab }, Cmd.none )
333 |
334 | Ignored ->
335 | ( model, Cmd.none )
336 |
337 | GetMagicLink ->
338 | ( { model | email = "" }, getMagicLink model.email )
339 |
340 | GotInventory result ->
341 | case result of
342 | Ok inventory ->
343 | let
344 | inventorySet =
345 | Set.fromList inventory
346 | in
347 | ( { model
348 | | availableMaterials =
349 | List.filter
350 | (\m ->
351 | Set.member
352 | m.name
353 | inventorySet
354 | )
355 | model.materials
356 | |> Set.Any.fromList materialKey
357 | , syncing = True
358 | }
359 | |> deriveMaterials
360 | , Cmd.none
361 | )
362 |
363 | Err _ ->
364 | ( { model
365 | | syncing = False
366 | }
367 | , Cmd.none
368 | )
369 |
370 | GotMagic _ ->
371 | ( { model | sentEmail = True }, Cmd.none )
372 |
373 | GotDevice device ->
374 | ( { model | device = device }, Cmd.none )
375 |
376 | GotOk _ ->
377 | ( model, Cmd.none )
378 |
379 | ChangedUrl _ ->
380 | ( model, Cmd.none )
381 |
382 | ClickedLink urlRequest ->
383 | case urlRequest of
384 | Browser.Internal _ ->
385 | ( model, Cmd.none )
386 |
387 | Browser.External href ->
388 | ( model, Nav.load href )
389 |
390 |
391 | resizeHandler : Int -> Int -> Msg
392 | resizeHandler w _ =
393 | if w < 640 then
394 | GotDevice Mobile
395 |
396 | else
397 | GotDevice Desktop
398 |
399 |
400 | subscriptions : Model -> Sub Msg
401 | subscriptions _ =
402 | Sub.batch
403 | [ Browser.Events.onKeyDown keyDecoder
404 | , Browser.Events.onResize resizeHandler
405 | ]
406 |
407 |
408 | okDecoder : Decoder Bool
409 | okDecoder =
410 | field "ok" bool
411 |
412 |
413 | inventoryDecoder : Decoder (List String)
414 | inventoryDecoder =
415 | Decode.list string
416 |
417 |
418 | saveMaterial : String -> Bool -> Cmd Msg
419 | saveMaterial material add =
420 | Http.post
421 | { url = "/api/store"
422 | , body =
423 | Http.jsonBody
424 | (Encode.object
425 | [ ( "material", Encode.string material )
426 | , ( "add", Encode.bool add )
427 | ]
428 | )
429 | , expect = Http.expectJson GotOk okDecoder
430 | }
431 |
432 |
433 | getMagicLink : String -> Cmd Msg
434 | getMagicLink email =
435 | Http.post
436 | { url = "/api/signin"
437 | , body =
438 | Http.jsonBody
439 | (Encode.object
440 | [ ( "email", Encode.string email )
441 | ]
442 | )
443 | , expect = Http.expectJson GotMagic okDecoder
444 | }
445 |
446 |
447 | loadInventory : Cmd Msg
448 | loadInventory =
449 | Http.get
450 | { url = "/api/inventory"
451 | , expect = Http.expectJson GotInventory inventoryDecoder
452 | }
453 |
454 |
455 | init : Bool -> Url -> Nav.Key -> ( Model, Cmd Msg )
456 | init isMobile url key =
457 | ( { recipes = recipes
458 | , materials = []
459 | , countedMaterials = []
460 | , selectedRecipe =
461 | List.filter
462 | (\r ->
463 | recipeSlug r
464 | == (url.fragment
465 | |> Maybe.withDefault ""
466 | )
467 | )
468 | recipes
469 | |> List.head
470 | , availableMaterials = Set.Any.empty materialKey
471 | , units = Ml
472 | , mode = Normal
473 | , pedantic = True
474 | , sort = Feasibility
475 | , dark = False
476 | , syncing = False
477 | , sentEmail = False
478 | , email = ""
479 | , key = key
480 | , device =
481 | if isMobile then
482 | Mobile
483 |
484 | else
485 | Desktop
486 | , tab = TDetail
487 | }
488 | |> deriveMaterials
489 | , loadInventory
490 | )
491 |
492 |
493 | port setDark : Bool -> Cmd msg
494 |
495 |
496 | main : Program Bool Model Msg
497 | main =
498 | Browser.application
499 | { init = init
500 | , view = view
501 | , update = update
502 | , subscriptions = subscriptions
503 | , onUrlChange = ChangedUrl
504 | , onUrlRequest = ClickedLink
505 | }
506 |
507 |
508 | materialKey : Material -> String
509 | materialKey ingredient =
510 | ingredient.name
511 |
512 |
513 |
514 | -- Translate between M:SS (minutes:seconds) syntax into
515 | -- a number of seconds, for the benefit of YouTube’s skip-to-time
516 | -- method
517 |
518 |
519 | parseTime : String -> String
520 | parseTime str =
521 | str
522 | |> String.split ":"
523 | |> List.reverse
524 | |> List.indexedMap
525 | (\idx val -> (String.toInt val |> Maybe.withDefault 0) * max 1 (idx * 60))
526 | |> List.sum
527 | |> String.fromInt
528 |
529 |
530 | countMaterials : Model -> MaterialType -> List CachedMaterial
531 | countMaterials model t =
532 | List.filter (\material -> material.t == t) model.materials
533 | |> List.map
534 | (\material ->
535 | ( recipesWithMaterial model.pedantic recipes material
536 | , Set.Any.member material model.availableMaterials
537 | , material
538 | )
539 | )
540 | |> List.sortBy (\( count, _, _ ) -> -count)
541 |
542 |
543 | deriveMaterials : Model -> Model
544 | deriveMaterials model =
545 | let
546 | recipesByGap =
547 | List.map
548 | (\recipe -> ( recipe, missingIngredients model.pedantic model.availableMaterials recipe |> Set.Any.size |> toFloat ))
549 | model.recipes
550 |
551 | ( sufficient, unsortedInsufficient ) =
552 | List.partition
553 | (\( _, gapSize ) -> gapSize == 0)
554 | recipesByGap
555 |
556 | insufficient =
557 | List.sortBy
558 | (\( recipe, gapSize ) ->
559 | gapSize
560 | / (List.length recipe.ingredients
561 | |> toFloat
562 | )
563 | )
564 | unsortedInsufficient
565 |
566 | alphabetical =
567 | List.sortBy .name model.recipes
568 |
569 | orderedRecipes =
570 | case model.sort of
571 | Feasibility ->
572 | sufficient
573 | ++ insufficient
574 | |> List.map (\( recipe, _ ) -> recipe)
575 |
576 | Alphabetical ->
577 | alphabetical
578 | in
579 | { model
580 | | materials =
581 | List.concatMap
582 | (\recipe ->
583 | List.concatMap
584 | (\ingredient ->
585 | [ aliasMaterial False ingredient.material
586 | , ingredient.material
587 | ]
588 | )
589 | recipe.ingredients
590 | )
591 | model.recipes
592 | |> List.foldl (::) []
593 | |> Set.Any.fromList materialKey
594 | |> Set.Any.toList
595 | , recipes = orderedRecipes
596 | }
597 | |> (\m ->
598 | { m
599 | | countedMaterials =
600 | List.map
601 | (\( label, t ) ->
602 | { label = label
603 | , materials = countMaterials m t
604 | }
605 | )
606 | [ ( "SPIRITS", Spirit )
607 | , ( "LIQUEUR", Liqueur )
608 | , ( "FORTIFIED", Fortified )
609 | , ( "BASE", Base )
610 | , ( "BITTERS", Bitters )
611 | , ( "SYRUP", Syrup )
612 | , ( "JUICE", Juice )
613 | , ( "SODA", Soda )
614 | , ( "FRUIT", Fruit )
615 | , ( "SEASONING", Seasoning )
616 | , ( "OTHER", Other )
617 | ]
618 | }
619 | )
620 |
621 |
622 | keyDecoder : Decode.Decoder Msg
623 | keyDecoder =
624 | Decode.map toDirection (Decode.field "key" Decode.string)
625 |
626 |
627 | toDirection : String -> Msg
628 | toDirection string =
629 | case string of
630 | "k" ->
631 | MoveUp
632 |
633 | "j" ->
634 | MoveDown
635 |
636 | _ ->
637 | Ignored
638 |
639 |
640 | aliasMaterial : Bool -> Material -> Material
641 | aliasMaterial pedantic material =
642 | if pedantic then
643 | material
644 |
645 | else
646 | case material.super of
647 | SuperMaterial Nothing ->
648 | material
649 |
650 | SuperMaterial (Just m) ->
651 | m
652 |
653 |
654 | getMaterials : Bool -> Recipe -> MaterialSet
655 | getMaterials pedantic recipe =
656 | recipe.ingredients
657 | |> List.filter
658 | (\ingredient -> not ingredient.optional && (pedantic || (ingredient.material.t /= Fruit && ingredient.material.t /= Seasoning)))
659 | |> List.map
660 | (\ingredient -> aliasMaterial pedantic ingredient.material)
661 | |> Set.Any.fromList
662 | materialKey
663 |
664 |
665 | missingIngredients : Bool -> MaterialSet -> Recipe -> MaterialSet
666 | missingIngredients pedantic availableMaterials recipe =
667 | Set.Any.diff (getMaterials pedantic recipe) availableMaterials
668 |
669 |
670 | getNeighbors : Model -> Recipe -> List ( List Material, List Material, Recipe )
671 | getNeighbors model recipe =
672 | model.recipes
673 | |> List.filter (\r -> r.name /= recipe.name)
674 | |> List.map
675 | (\r ->
676 | let
677 | rMaterials =
678 | getMaterials model.pedantic r
679 |
680 | recipeMaterials =
681 | getMaterials model.pedantic recipe
682 | in
683 | ( Set.Any.diff rMaterials recipeMaterials |> Set.Any.toList
684 | , Set.Any.diff recipeMaterials rMaterials |> Set.Any.toList
685 | , r
686 | )
687 | )
688 | |> List.filter (\( add, remove, _ ) -> List.length add + List.length remove < 4)
689 | |> List.sortBy (\( add, remove, _ ) -> List.length add + List.length remove)
690 |
691 |
692 | recipesWithMaterial : Bool -> List Recipe -> Material -> Int
693 | recipesWithMaterial pedantic allRecipes material =
694 | List.filter (\recipe -> hasMaterial pedantic recipe material) allRecipes
695 | |> List.length
696 |
697 |
698 | getNext : List Recipe -> Recipe -> Maybe Recipe
699 | getNext recipes target =
700 | List.head recipes
701 | |> Maybe.andThen
702 | (\x ->
703 | if x == target then
704 | List.drop 1 recipes |> List.head
705 |
706 | else
707 | getNext (List.drop 1 recipes) target
708 | )
709 |
710 |
711 |
712 | -- User interface
713 |
714 |
715 | checkboxIconX : Int -> Bool -> Element msg
716 | checkboxIconX x checked =
717 | let
718 | wh =
719 | x |> String.fromInt
720 |
721 | c =
722 | x // 2 |> String.fromInt
723 |
724 | r =
725 | x // 3 |> String.fromInt
726 | in
727 | svg
728 | [ Svg.Attributes.width wh
729 | , Svg.Attributes.height wh
730 | ]
731 | [ Svg.circle
732 | [ Svg.Attributes.cx c
733 | , Svg.Attributes.cy c
734 | , Svg.Attributes.r r
735 | , Svg.Attributes.stroke "currentColor"
736 | , Svg.Attributes.strokeWidth "1"
737 | , Svg.Attributes.fill
738 | (if checked then
739 | "currentColor"
740 |
741 | else
742 | "transparent"
743 | )
744 | ]
745 | []
746 | ]
747 | |> html
748 |
749 |
750 | checkboxIcon : Bool -> Element Msg
751 | checkboxIcon =
752 | checkboxIconX 12
753 |
754 |
755 | checkboxIconL : Bool -> Element Msg
756 | checkboxIconL =
757 | checkboxIconX 14
758 |
759 |
760 | materialNavigationItem : CachedMaterial -> Element.Element Msg
761 | materialNavigationItem ( count, checked, material ) =
762 | Input.checkbox []
763 | { onChange = \_ -> ToggleIngredient material (not checked)
764 | , icon = checkboxIcon
765 | , checked = checked
766 | , label =
767 | Input.labelRight []
768 | (text
769 | (String.fromInt count
770 | ++ " "
771 | ++ (if material.name == "Islay Single Malt Scotch whiskey" then
772 | "Islay Whiskey"
773 |
774 | else
775 | material.name
776 | )
777 | )
778 | )
779 | }
780 |
781 |
782 | title : String -> Element.Element Msg
783 | title name =
784 | el
785 | [ bold
786 | , paddingEach { edges | bottom = 5 }
787 | ]
788 | (text name)
789 |
790 |
791 | listMaterials : Bool -> List MaterialsGroup -> Element.Element Msg
792 | listMaterials pedantic countedMaterials =
793 | countedMaterials
794 | |> List.concatMap
795 | (\{ label, materials } ->
796 | [ column [ spacing 8, alignTop ]
797 | (title label
798 | :: List.map
799 | materialNavigationItem
800 | (if pedantic then
801 | List.filter
802 | (\( count, _, _ ) -> count > 0)
803 | materials
804 |
805 | else
806 | List.filter
807 | (\( count, _, material ) -> count > 0 && material.super == SuperMaterial Nothing)
808 | materials
809 | )
810 | )
811 | ]
812 | )
813 | |> column [ spacing 20, alignTop, width shrink ]
814 |
815 |
816 | glassName : Glass -> String
817 | glassName glass =
818 | case glass of
819 | -- Tumblers
820 | Collins ->
821 | "Collins glass"
822 |
823 | Highball ->
824 | "Highball glass"
825 |
826 | OldFashioned ->
827 | "Old Fashioned glass"
828 |
829 | -- Stemware:
830 | ChampagneFlute ->
831 | "Champagne flute"
832 |
833 | Cocktail ->
834 | "Cocktail glass"
835 |
836 | Hurricane ->
837 | "Hurricane glass"
838 |
839 | Margarita ->
840 | "Margarita glass"
841 |
842 | Wine ->
843 | "Wine glass"
844 |
845 | -- Other:
846 | CopperMug ->
847 | "Copper mug"
848 |
849 | IrishCoffeeMug ->
850 | "Irish coffee mug"
851 |
852 | SteelCup ->
853 | "Steel cup"
854 |
855 | ZombieGlass ->
856 | "Zombie glass"
857 |
858 | ChampagneCoupe ->
859 | "Champagne coupe"
860 |
861 |
862 | capitalize : String -> String
863 | capitalize str =
864 | case String.toList str of
865 | x :: xs ->
866 | String.fromChar (Char.toUpper x) ++ String.fromList xs
867 |
868 | [] ->
869 | ""
870 |
871 |
872 | neighborBlock : ( List Material, List Material, Recipe ) -> Element.Element Msg
873 | neighborBlock ( add, remove, neighbor ) =
874 | column
875 | [ spacing 10
876 | , Element.pointer
877 | , width fill
878 | , height fill
879 | , alignTop
880 | , onClick (SelectRecipe neighbor)
881 | ]
882 | [ el [ Font.italic, Font.underline ] (text neighbor.name)
883 | , paragraph []
884 | [ List.map (\a -> "add " ++ a.name) add
885 | ++ List.map (\a -> "remove " ++ a.name) remove
886 | |> String.join ", "
887 | |> capitalize
888 | |> text
889 | |> el []
890 | ]
891 | ]
892 |
893 |
894 | recipeSlug : Recipe -> String
895 | recipeSlug recipe =
896 | Maybe.map Slug.toString
897 | (Slug.generate recipe.name)
898 | |> Maybe.withDefault ""
899 |
900 |
901 | recipeBlock : Model -> Recipe -> Element.Element Msg
902 | recipeBlock model recipe =
903 | let
904 | viable =
905 | missingIngredients model.pedantic model.availableMaterials recipe |> Set.Any.isEmpty
906 |
907 | selected =
908 | model.selectedRecipe == Just recipe
909 | in
910 | column
911 | ([ spacing 10
912 | , Element.pointer
913 | , Element.alpha
914 | (if viable then
915 | 1
916 |
917 | else
918 | 0.5
919 | )
920 | , width fill
921 | , height fill
922 | , alignTop
923 | , onClick (SelectRecipe recipe)
924 | , Element.htmlAttribute
925 | (Html.Attributes.id (recipeSlug recipe))
926 | , Element.htmlAttribute
927 | (Html.Attributes.tabindex 0)
928 | ]
929 | ++ (if selected then
930 | if model.dark then
931 | [ Border.color
932 | (Element.rgb255
933 | 239
934 | 237
935 | 234
936 | )
937 | , Border.width 1
938 | , padding 9
939 | ]
940 |
941 | else
942 | [ Element.Background.color
943 | (Element.rgb255
944 | 229
945 | 227
946 | 224
947 | )
948 | , padding 10
949 | ]
950 |
951 | else
952 | [ padding 10
953 | ]
954 | )
955 | )
956 | [ row []
957 | [ glassIcon recipe
958 | , el [ Font.italic, Font.underline ] (text recipe.name)
959 | ]
960 | , recipe.ingredients
961 | |> List.filter (\ingredient -> not ingredient.optional)
962 | |> List.map
963 | (listIngredientInBlock model)
964 | |> List.intersperse
965 | (el [] (text ", "))
966 | |> paragraph [ paddingEach { edges | left = 5 } ]
967 | ]
968 |
969 |
970 | listIngredientInBlock : Model -> Ingredient -> Element.Element Msg
971 | listIngredientInBlock model ingredient =
972 | el
973 | (if
974 | Set.Any.member (aliasMaterial model.pedantic ingredient.material)
975 | model.availableMaterials
976 | then
977 | []
978 |
979 | else
980 | [ strike ]
981 | )
982 | (text ingredient.material.name)
983 |
984 |
985 | noneSelected : Model -> Element.Element Msg
986 | noneSelected model =
987 | column [ spacing 20, paddingEach { edges | bottom = 40 }, alignTop ]
988 | [ el [ Font.bold ] (text "Hi.")
989 | , paragraph [ spacing 10 ] [ el [] (text """
990 | This is a website that I made about cocktails. I'm not a huge cocktail nerd (drinking is bad, probably), but think that they're cool.
991 | And the world's pretty bad right now and making this has been calming.""") ]
992 | , paragraph [ spacing 10 ] [ el [] (text """It gave me a chance to both tinker with technology I usually don't use (Elm),
993 | and explore some of the cool properties of cocktails: notably that they're pretty similar and have standardized ingredients,
994 | so they can be described in relationship to each other.""") ]
995 | , paragraph [ spacing 10 ] [ el [] (text """So some of it might seem funky. By default, the list is sorted by 'feasibility': as you add
996 | ingredients that you have, it'll put recipes that you can make (or barely make) closer to the top. Also, click on 'Grid' for a wacky adjacency grid
997 | of cocktails and their ingredients.""") ]
998 | , paragraph [ spacing 10 ] [ el [] (text """Also, for vim fans, there’s j & k support.""") ]
999 | , el []
1000 | (Element.link
1001 | [ Font.color
1002 | (if model.dark then
1003 | lightBlue
1004 |
1005 | else
1006 | blue
1007 | )
1008 | , Font.underline
1009 | ]
1010 | { url = "https://tommacwright.typeform.com/to/M7tx4u", label = text "👋 Feedback welcome!" }
1011 | )
1012 | , el []
1013 | (Element.link
1014 | [ Font.color
1015 | (if model.dark then
1016 | lightBlue
1017 |
1018 | else
1019 | blue
1020 | )
1021 | , Font.underline
1022 | ]
1023 | { url = "https://github.com/tmcw/flair", label = paragraph [] [ text "Pull requests welcome - this is open source on GitHub!" ] }
1024 | )
1025 | ]
1026 |
1027 |
1028 | displayRecipe : Model -> Recipe -> Element.Element Msg
1029 | displayRecipe model recipe =
1030 | let
1031 | neighbors =
1032 | getNeighbors model recipe
1033 | in
1034 | column [ spacing 20, alignTop ]
1035 | ([ title recipe.name
1036 | , row [ spacing 4, spacing 10 ]
1037 | [ el [ alignTop ] (glassIcon recipe)
1038 | , paragraph [] [ text ("Served in a " ++ glassName recipe.glass) ]
1039 | ]
1040 | , recipe.ingredients
1041 | |> List.map
1042 | (\ingredient ->
1043 | paragraph []
1044 | [ text
1045 | ("- "
1046 | ++ printQuantity ingredient.material.name ingredient.quantity model.units
1047 | ++ " "
1048 | ++ replacement model.pedantic ingredient
1049 | ++ (if ingredient.optional then
1050 | " (optional)"
1051 |
1052 | else
1053 | ""
1054 | )
1055 | )
1056 | ]
1057 | )
1058 | |> column
1059 | [ alignTop, spacing 8 ]
1060 | , paragraph [ spacing 10, alignTop, width fill ] [ text recipe.description ]
1061 | ]
1062 | ++ (case recipe.video of
1063 | Nothing ->
1064 | []
1065 |
1066 | Just (Epicurious time) ->
1067 | [ el [ paddingEach { edges | top = 20, bottom = 20 }, width fill ]
1068 | (details
1069 | []
1070 | [ summary [] [ Html.text "Video" ]
1071 | , iframe
1072 | [ Html.Attributes.src ("https://www.youtube-nocookie.com/embed/b0IuTL3Z-kk?start=" ++ parseTime time)
1073 | , Html.Attributes.style "width" "100%"
1074 | , Html.Attributes.height 315
1075 | , Html.Attributes.attribute "frameborder" "0"
1076 | , Html.Attributes.attribute "allowfullscreen" "true"
1077 | ]
1078 | []
1079 | ]
1080 | |> html
1081 | )
1082 | ]
1083 | )
1084 | ++ (if List.isEmpty neighbors then
1085 | []
1086 |
1087 | else
1088 | title "NEIGHBORS"
1089 | :: List.map neighborBlock neighbors
1090 | )
1091 | )
1092 |
1093 |
1094 | replacement : Bool -> Ingredient -> String
1095 | replacement pedantic ingredient =
1096 | if pedantic == False && ingredient.material.super /= SuperMaterial Nothing then
1097 | " (or " ++ (aliasMaterial pedantic ingredient.material).name ++ ")"
1098 |
1099 | else
1100 | ""
1101 |
1102 |
1103 | listRecipes : Model -> Element.Element Msg
1104 | listRecipes model =
1105 | column [ spacing 10, width fill ]
1106 | (List.map
1107 | (recipeBlock model)
1108 | model.recipes
1109 | )
1110 |
1111 |
1112 | header : Model -> Element.Element Msg
1113 | header model =
1114 | let
1115 | leftItems =
1116 | [ case model.device of
1117 | Desktop ->
1118 | Input.radioRow
1119 | [ spacing 10
1120 | ]
1121 | { onChange = SetMode
1122 | , selected = Just model.mode
1123 | , label = Input.labelHidden "Mode"
1124 | , options =
1125 | [ Input.option Normal (text "List")
1126 | , Input.option Grid (text "Grid")
1127 | ]
1128 | }
1129 |
1130 | Mobile ->
1131 | Element.none
1132 | , Input.checkbox
1133 | []
1134 | { onChange = SetSubsitute
1135 | , icon = checkboxIconL
1136 | , checked = model.pedantic
1137 | , label =
1138 | Input.labelRight []
1139 | (text "Pedantic")
1140 | }
1141 | , Input.checkbox
1142 | []
1143 | { onChange = SetDark
1144 | , icon = checkboxIconL
1145 | , checked = model.dark
1146 | , label =
1147 | Input.labelRight []
1148 | (text "Dark")
1149 | }
1150 | ]
1151 |
1152 | rightItems =
1153 | [ row [ spacing 10 ]
1154 | [ el
1155 | []
1156 | (label [ Html.Attributes.for "sort" ] [ Html.text "Sort" ] |> html)
1157 | , el []
1158 | (select
1159 | [ Html.Events.onInput SetSort
1160 | , Html.Attributes.id
1161 | "sort"
1162 | ]
1163 | [ option [ value "Feasibility" ]
1164 | [ Html.text "Feasibility"
1165 | ]
1166 | , option [ value "Alphabetical" ]
1167 | [ Html.text "Alphabetical"
1168 | ]
1169 | ]
1170 | |> html
1171 | )
1172 | ]
1173 | , row [ spacing 10 ]
1174 | [ el
1175 | []
1176 | (label [ Html.Attributes.for "units" ] [ Html.text "Units" ] |> html)
1177 | , el []
1178 | (select
1179 | [ Html.Events.onInput SetUnits
1180 | , Html.Attributes.id
1181 | "units"
1182 | ]
1183 | [ option [ value "Ml" ]
1184 | [ Html.text "Ml"
1185 | ]
1186 | , option [ value "Cl" ]
1187 | [ Html.text "Cl"
1188 | ]
1189 | , option [ value "Oz" ]
1190 | [ Html.text "Oz"
1191 | ]
1192 | ]
1193 | |> html
1194 | )
1195 | ]
1196 | ]
1197 | in
1198 | if model.device == Mobile then
1199 | column
1200 | [ paddingEach { edges | left = 20, right = 20 }
1201 | , spacing 20
1202 | ]
1203 | (title "SETTINGS"
1204 | :: leftItems
1205 | ++ rightItems
1206 | )
1207 |
1208 | else
1209 | row [ width fill, paddingEach { edges | left = 20, right = 20, top = 20, bottom = 30 } ]
1210 | [ row [ width shrink, Element.alignLeft, spacing 20 ]
1211 | leftItems
1212 | , row [ width shrink, Element.alignRight, spacing 20 ]
1213 | rightItems
1214 | ]
1215 |
1216 |
1217 |
1218 | -- A fast check to see if a given recipe has an ingredient. Tries to skip
1219 | -- any allocations
1220 |
1221 |
1222 | hasMaterial : Bool -> Recipe -> Material -> Bool
1223 | hasMaterial pedantic recipe material =
1224 | List.foldl
1225 | (\i has ->
1226 | if has then
1227 | has
1228 |
1229 | else
1230 | aliasMaterial pedantic i.material == material
1231 | )
1232 | False
1233 | recipe.ingredients
1234 |
1235 |
1236 | renderDot : Material -> Recipe -> Html.Html Msg
1237 | renderDot material recipe =
1238 | let
1239 | on =
1240 | hasMaterial False recipe material
1241 | in
1242 | svg
1243 | [ Svg.Attributes.width "22"
1244 | , Svg.Attributes.height "22"
1245 | ]
1246 | [ Svg.circle
1247 | [ Svg.Attributes.cx "11"
1248 | , Svg.Attributes.cy "11"
1249 | , Svg.Attributes.fill "currentColor"
1250 | , Svg.Attributes.r
1251 | (if on then
1252 | "4"
1253 |
1254 | else
1255 | "1"
1256 | )
1257 | ]
1258 | []
1259 | ]
1260 |
1261 |
1262 | getColumn : Material -> Html.Html Msg
1263 | getColumn material =
1264 | svg
1265 | [ Svg.Attributes.width "22"
1266 | , Svg.Attributes.height "160"
1267 | , Svg.Attributes.viewBox "0 0 20 160"
1268 | ]
1269 | [ Svg.text_
1270 | [ Svg.Attributes.x "-140"
1271 | , Svg.Attributes.y "10"
1272 | , Svg.Attributes.width "22"
1273 | , Svg.Attributes.height "160"
1274 | , Svg.Attributes.rx "15"
1275 | , Svg.Attributes.ry "15"
1276 | , Svg.Attributes.transform "rotate(-90, 10, 8)"
1277 | , Svg.Attributes.textAnchor "start"
1278 | , Svg.Attributes.fill "currentColor"
1279 | ]
1280 | [ Svg.text material.name
1281 | ]
1282 | ]
1283 |
1284 |
1285 | gridView : Model -> Element.Element Msg
1286 | gridView model =
1287 | let
1288 | mod =
1289 | model
1290 |
1291 | sortedMaterials =
1292 | List.concatMap (\{ materials } -> materials) mod.countedMaterials
1293 | |> List.map (\( _, _, material ) -> material)
1294 | in
1295 | Html.table
1296 | [ Html.Attributes.class "grid"
1297 | ]
1298 | [ Html.thead []
1299 | (Html.th []
1300 | [ Html.text ""
1301 | ]
1302 | :: List.map
1303 | (\ingredient -> Html.th [] [ getColumn ingredient ])
1304 | sortedMaterials
1305 | )
1306 | , Html.tbody []
1307 | (List.map
1308 | (\recipe ->
1309 | Html.tr []
1310 | (Html.th [] [ Html.text recipe.name ]
1311 | :: List.map
1312 | (\material -> Html.td [] [ renderDot material recipe ])
1313 | sortedMaterials
1314 | )
1315 | )
1316 | model.recipes
1317 | )
1318 | ]
1319 | |> html
1320 |
1321 |
1322 | view : Model -> Browser.Document Msg
1323 | view model =
1324 | let
1325 | mWidth : Int -> Element.Attribute Msg
1326 | mWidth w =
1327 | if model.device == Mobile then
1328 | width fill
1329 |
1330 | else
1331 | width (Element.px w)
1332 |
1333 | recipes =
1334 | column
1335 | [ alignTop
1336 | , paddingEach
1337 | (if model.device == Desktop then
1338 | { edges | left = 20, right = 20 }
1339 |
1340 | else
1341 | { edges | left = 10, right = 10 }
1342 | )
1343 | , spacing 5
1344 | , Element.Region.navigation
1345 | , mWidth 360
1346 | , height fill
1347 | ]
1348 | [ row
1349 | [ width fill
1350 | , spacing 10
1351 | ]
1352 | [ el
1353 | [ paddingEach { edges | left = 10 }
1354 | , width fill
1355 | ]
1356 | (title
1357 | "COCKTAILS"
1358 | )
1359 | ]
1360 | , el
1361 | [ Element.scrollbarY, height fill ]
1362 | (listRecipes
1363 | model
1364 | )
1365 | ]
1366 |
1367 | detail =
1368 | column
1369 | [ alignTop
1370 | , paddingEach { edges | left = 20, right = 20 }
1371 | , spacing 5
1372 | , Element.Region.mainContent
1373 | , width (Element.maximum 640 fill)
1374 | , height fill
1375 | , Element.scrollbarY
1376 | ]
1377 | [ case model.selectedRecipe of
1378 | Just r ->
1379 | displayRecipe model r
1380 |
1381 | Nothing ->
1382 | noneSelected model
1383 | ]
1384 |
1385 | materials =
1386 | column
1387 | [ spacing 5
1388 | , paddingEach { edges | left = 20, right = 20 }
1389 | , alignTop
1390 | , mWidth 260
1391 | , height
1392 | fill
1393 | , Element.scrollbarY
1394 | ]
1395 | (listMaterials model.pedantic model.countedMaterials
1396 | :: (if model.syncing then
1397 | [ el
1398 | [ paddingEach { edges | top = 30 }
1399 | ]
1400 | (Element.link
1401 | [ Font.color
1402 | (if model.dark then
1403 | lightBlue
1404 |
1405 | else
1406 | blue
1407 | )
1408 | ]
1409 | { url = "/api/signout", label = text "Sign out" }
1410 | )
1411 | ]
1412 |
1413 | else if model.sentEmail then
1414 | [ paragraph
1415 | [ paddingEach { edges | bottom = 10, top = 30 }
1416 | ]
1417 | [ el [] (text "Check your email!") ]
1418 | ]
1419 |
1420 | else
1421 | [ column
1422 | [ paddingEach { edges | top = 30, bottom = 50 }
1423 | , spacing 5
1424 | ]
1425 | [ paragraph
1426 | [ paddingEach { edges | bottom = 10 }
1427 | ]
1428 | [ el [] (text "Sign in to save your ingredients") ]
1429 | , Input.text
1430 | [ Html.Events.stopPropagationOn "keydown" (Decode.succeed ( Ignored, True ))
1431 | |> Element.htmlAttribute
1432 | ]
1433 | { onChange = \email -> SetEmail email
1434 | , text = model.email
1435 | , placeholder = Just (Input.placeholder [] (text "Email"))
1436 | , label = Input.labelHidden "Email"
1437 | }
1438 | , Input.button
1439 | [ Font.center
1440 | , width fill
1441 | ]
1442 | { onPress = Just GetMagicLink
1443 | , label = text "GET LINK"
1444 | }
1445 | ]
1446 | ]
1447 | )
1448 | )
1449 | in
1450 | { title =
1451 | "Old Fashioned"
1452 | ++ (model.selectedRecipe
1453 | |> Maybe.map (\r -> ": " ++ r.name)
1454 | |> Maybe.withDefault ""
1455 | )
1456 | , body =
1457 | [ Element.layout
1458 | [ Font.size 13
1459 | , Font.color
1460 | (if model.dark then
1461 | white
1462 |
1463 | else
1464 | black
1465 | )
1466 | , Font.family
1467 | [ Font.typeface "IBM Plex Mono"
1468 | , Font.typeface "SFMono-Regular"
1469 | , Font.typeface "Consolas"
1470 | , Font.typeface "Liberation Mono"
1471 | , Font.typeface "Menlo"
1472 | , Font.monospace
1473 | ]
1474 | , width fill
1475 | , height fill
1476 | ]
1477 | (column
1478 | [ width fill
1479 | , height fill
1480 | , if model.device == Mobile then
1481 | spacing 20
1482 |
1483 | else
1484 | spacing 0
1485 | ]
1486 | [ if model.device == Desktop then
1487 | header model
1488 |
1489 | else
1490 | Element.none
1491 | , if model.device == Mobile then
1492 | row
1493 | [ Border.widthEach { edges | bottom = 1 }
1494 | , Element.spaceEvenly
1495 | , padding 20
1496 | , width fill
1497 | ]
1498 | [ Input.button
1499 | [ Font.alignLeft
1500 | , if model.tab == TMaterials then
1501 | Font.underline
1502 |
1503 | else
1504 | Font.unitalicized
1505 | ]
1506 | { onPress = Just (SetTab TMaterials)
1507 | , label = text "Ingredients"
1508 | }
1509 | , Input.button
1510 | [ Font.alignLeft
1511 | , if model.tab == TCocktails then
1512 | Font.underline
1513 |
1514 | else
1515 | Font.unitalicized
1516 | ]
1517 | { onPress = Just (SetTab TCocktails)
1518 | , label = text "Cocktails"
1519 | }
1520 | , Input.button
1521 | [ Font.alignLeft
1522 | , if model.tab == TDetail then
1523 | Font.underline
1524 |
1525 | else
1526 | Font.unitalicized
1527 | ]
1528 | { onPress = Just (SetTab TDetail)
1529 | , label = text "Detail"
1530 | }
1531 | , Input.button
1532 | [ Font.alignLeft
1533 | , if model.tab == TSettings then
1534 | Font.underline
1535 |
1536 | else
1537 | Font.unitalicized
1538 | ]
1539 | { onPress = Just (SetTab TSettings)
1540 | , label = text "*"
1541 | }
1542 | ]
1543 |
1544 | else
1545 | Element.none
1546 | , if model.mode == Grid then
1547 | gridView model
1548 |
1549 | else if model.device == Mobile then
1550 | el [ Element.scrollbarY, height fill, width fill ]
1551 | (case model.tab of
1552 | TMaterials ->
1553 | materials
1554 |
1555 | TCocktails ->
1556 | recipes
1557 |
1558 | TDetail ->
1559 | detail
1560 |
1561 | TSettings ->
1562 | header model
1563 | )
1564 |
1565 | else
1566 | row
1567 | [ width fill
1568 | , height fill
1569 | , Element.scrollbarY
1570 | ]
1571 | [ materials
1572 | , recipes
1573 | , detail
1574 | ]
1575 | ]
1576 | )
1577 | ]
1578 | }
1579 |
--------------------------------------------------------------------------------
/front/src/Quantity.elm:
--------------------------------------------------------------------------------
1 | module Quantity exposing (Quantity(..), Units(..), printQuantity)
2 |
3 |
4 | type Quantity
5 | = CL Float
6 | | Cube Int
7 | | Dash Int
8 | | Drop Int
9 | | FewDrops
10 | | FewDashes
11 | | Slice Float
12 | | Splash Int
13 | | Sprig Int
14 | | Tsp Float
15 | | Wedge Int
16 | | Whole Int
17 | | Custom String
18 | | None
19 |
20 |
21 | type Units
22 | = Cl
23 | | Ml
24 | | Oz
25 |
26 |
27 | formatInteger : Float -> String
28 | formatInteger d =
29 | if floor d == 0 then
30 | ""
31 | else
32 | String.fromInt (floor d)
33 |
34 |
35 | -- Expects number between 0 and 100, floored.
36 | formatFractional : Float -> String
37 | formatFractional d =
38 | if d == 0 then
39 | ""
40 | else if d == 10 then
41 | "⅒"
42 | else if d == 11 then
43 | "⅑"
44 | else if d == 12 then
45 | "⅛"
46 | else if d == 14 then
47 | "⅐"
48 | else if d == 16 then
49 | "⅙"
50 | else if d == 20 then
51 | "⅕"
52 | else if d == 25 then
53 | "¼"
54 | else if d == 33 then
55 | "⅓"
56 | else if d == 37 then
57 | "⅜"
58 | else if d == 40 then
59 | "⅖"
60 | else if d == 50 then
61 | "½"
62 | else if d == 60 then
63 | "⅗"
64 | else if d == 62 then
65 | "⅝"
66 | else if d == 66 then
67 | "⅔"
68 | else if d == 75 then
69 | "¾"
70 | else if d == 80 then
71 | "⅘"
72 | else if d == 83 then
73 | "⅚"
74 | else if d == 87 then
75 | "⅞"
76 | else
77 | "." ++ String.fromFloat d
78 |
79 |
80 | formatFloat : Float -> String
81 | formatFloat d =
82 | formatInteger d ++ formatFractional ( toFloat ( floor ( ( d - toFloat ( floor d ) ) * 100 ) ) )
83 |
84 |
85 | plural : String -> String -> Int -> String
86 | plural word suffix d =
87 | if d == 1 then
88 | word
89 | else
90 | word ++ suffix
91 |
92 |
93 | printQuantity : String -> Quantity -> Units -> String
94 | printQuantity name quantity units =
95 | case quantity of
96 | CL a ->
97 | case units of
98 | Cl ->
99 | formatFloat a ++ " Cl " ++ name
100 |
101 | Ml ->
102 | formatFloat (a * 10) ++ " Ml " ++ name
103 |
104 | Oz ->
105 | formatFloat (a * 1 / 3) ++ " Oz " ++ name
106 |
107 | Cube a ->
108 | name ++ ": " ++ String.fromInt a ++ plural " cube" "s" a
109 |
110 | Dash a ->
111 | name ++ ": " ++ String.fromInt a ++ plural " dash" "es" a
112 |
113 | Drop a ->
114 | name ++ ": " ++ String.fromInt a ++ plural " drop" "s" a
115 |
116 | FewDrops ->
117 | name ++ ": Few drops"
118 |
119 | FewDashes ->
120 | name ++ ": Few dashes"
121 |
122 | Slice a ->
123 | name ++ ": " ++ formatFloat a ++ plural " slice" "s" ( floor a )
124 |
125 | Splash a ->
126 | name ++ ": " ++ String.fromInt a ++ plural " splash" "es" a
127 |
128 | Sprig a ->
129 | name ++ ": " ++ String.fromInt a ++ plural " sprig" "s" a
130 |
131 | Tsp a ->
132 | formatFloat a ++ " Tsp " ++ name
133 |
134 | Wedge a ->
135 | name ++ ": " ++ String.fromInt a ++ plural " wedge" "s" a
136 |
137 | Whole a ->
138 | name ++ ": " ++ String.fromInt a
139 |
140 | Custom str ->
141 | name ++ ": " ++ str
142 |
143 | None ->
144 | name
145 |
--------------------------------------------------------------------------------
/front/src/index.js:
--------------------------------------------------------------------------------
1 | import { Elm } from "./Main.elm";
2 |
3 | const app = Elm.Main.init({
4 | node: document.getElementById("root"),
5 | flags: window.innerWidth < 640,
6 | });
7 |
8 | document.body.style.transition = "background, color 600ms ease-in-out";
9 | document.body.style.transition = "background 600ms ease-in-out";
10 |
11 | app.ports.setDark.subscribe((dark) => {
12 | if (dark) {
13 | document.body.style.background = "#3c3c3c";
14 | document.body.style.color = "rgb(229, 227, 224)";
15 | } else {
16 | document.body.style.background = "rgb(249, 247, 244)";
17 | document.body.style.color = "#000";
18 | }
19 | });
20 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "front",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "cd front && elm-app build && mv build ../public"
8 | },
9 | "dependencies": {
10 | "create-elm-app": "^5"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------