Skip to content

Instantly share code, notes, and snippets.

@shmolyneaux
Last active July 5, 2021 18:24
Show Gist options
  • Save shmolyneaux/24baf2796235ff48dae4e8361bccf586 to your computer and use it in GitHub Desktop.
Save shmolyneaux/24baf2796235ff48dae4e8361bccf586 to your computer and use it in GitHub Desktop.
Modernist Cuisine HTML Recipe Example
{
"title": "caramelized carrot soup",
"properties": [
{
"name": "yield",
"value": "six servings (1.3 kg / 6 cups)"
},
{
"name": "time estimate",
"value": "40 minutes overall, including 20 minutes of preparation and 20 minutes unattended"
},
{
"name": "storage notes",
"value": "keeps for 3 days when refriderated or up to 2 months when frozen"
},
{
"name": "level of difficulty",
"value": "moderate"
},
{
"name": "special requirements",
"value": "pressure cooker, Stove-Top Carotene Butter (optional, see page 39)"
}
],
"description": "The quality of this soup depends entirely on the quality of the carrots that go into it, so use the highest-quality carrots you can find. Carrot cores, rich in calcium, can add a bitter taste and unpleasant texture to this delicate soup, so we always remove them. It's an optional step, however; you can try the soup both ways and compare.\nAdd a swirl of coconut cream and a few sprigs of tarragon in the final step to enhance the inherent sweetness of the carrots. Shredded young coconut and ajowan seeds are other favorite garnishes of ours.",
"steps": [
{
"ingredients": [
{
"name": "Carrots, peeled",
"weight": "500 g",
"volume": "5 cups / 5 medium",
"scaling": "100%"
}
],
"procedure": [
{
"short": "Core the carrots by quartering them lengthwise and slicing away any tough or fiborous cores. Cut the cored carrots into pieces 5 cm / 2 in long.",
"long": "Slice the carrots lengthwise into quarters, and cut out and discard the tough and fibrous core from each quarter. Then cut the cored carrots into pieces 5 cm / 2 in long. Removing the cores improves the texture and sweetness of the soup."
}
]
},
{
"ingredients": [
{
"name": "Unsalted butter",
"weight": "113 g",
"volume": "½ cup",
"scaling": "22.6%"
}
],
"procedure": [
{
"short": "Melt in the base of a pressure cooker over medium heat.",
"long": "Melt the butter in the base of a pressure cooker. The coating of butter helps to prevent the carrots from sticking."
}
]
},
{
"ingredients": [
{
"name": "Water",
"weight": "30 g",
"volume": "30 mL / ⅛ cup",
"scaling": "6%"
},
{
"name": "Salt",
"weight": "5 g",
"volume": "1 ¼ tsp",
"scaling": "1%"
},
{
"name": "Baking Soda",
"weight": "2.5 g",
"volume": "⅜ tsp",
"scaling": "0.5%"
}
],
"procedure": [
{
"short": "Stir to combine, and then add with the carrots to the melted butter.",
"long": "Stir the water, salt, and baking soda until combined. Add this mixture and the carrot pieces to the melted butter, and stir well."
},
{
"short": "Pressure-cook at a gauge pressure of 1 bar / 15 psi for 20 minutes.",
"long": "Pressure-cook the carrot mixture at a gauge pressure of 1 bar / 15 psi for 20 minutes. Start timing as soon as full pressure has been reached. Gas and electric burners tend to cause solids to catch on the bottom of the pressure cooker, so carefully give the pot a few shakes as it heats to prevent sticking. After 20 minutes, the carrots should be fully caramelized."
},
{
"short": "Depressurize the cooker quickly by running tepid water over the rim.",
"long": "Depressurize the cooker quickly by running tepid water over the rim."
},
{
"short": "Blend the mixture to a smooth puree.",
"long": "Blend the carrot mixture into a smooth puree."
},
{
"short": "Pass the puree through a fine sieve into a pot.",
"long": "Pass the puree through a fine sieve into a pot."
}
]
},
{
"ingredients": [
{
"name": "Fresh carrot juice",
"weight": "635 g",
"volume": "690 mL / 2 ½ cups",
"scaling": "127%"
}
],
"procedure": [
{
"short": "Bring to a boil in a separate pot, and then strain through a fine sieve.",
"long": "Bring the carrot juice to a boil in a separate pot. Then strain the juice through a fine sieve to remove any solids."
},
{
"short": "Stir into the carrot puree. Add water, if necessary, to thin the soup to the desired consistency.",
"long": "Stir the strained juice into the carrot puree, and return it to a simmer. Add water as needed to thin the soup to the desired consistency."
}
]
},
{
"ingredients": [
{
"name": "Stove-Top Carotene Butter (or unsalted butter) see page 39",
"weight": "113 g",
"volume": "½ cup",
"scaling": "22.6%"
}
],
"procedure": [
{
"short": "Blend into the soup by using an immersion blender until the butter has just melted.",
"long": "Blend the butter into the soup until it has just melted. Use an immersion blender; the blending is crucial to achieve a velvety texture."
}
]
},
{
"ingredients": [
{
"name": "Salt",
"weight": "to taste",
"volume": "",
"scaling": ""
}
],
"procedure": [
{
"short": "Season, and serve warm.",
"long": "Season the soup with salt to taste, and serve it warm."
}
]
}
],
"notes_html": [
"This recipe was one of the most popular in <i>Modernist Cuisine</i>. We do not use stock or cream in this recipe because we want to avoid diluting the carrot flavors. The juice from some kinds of carrots is so strongly flavored, however, that it overpowers the more delicate, caramelized carrot flavor. If that happens, substitute water, chicken stock, or vegetable stock for some or all of the carrot juice in step 8.",
"<h2>VARIATION: Caramelized Carrot Puree</h2>To make a delicious carrot puree that pairs well with Sous Vide Lobster Tail (see variation on page 130), skip steps 8-10. See the next page for additional variations."
]
}
def indent($columns):
[
split("\n")
| .[]
| " " * $columns + .
] | join("\n");
def toHtml:
(if .class then
" class='\(.class)'"
else
""
end) as $classText
| if (.content | type) == "string" then
"<\(.tag + $classText)>\(.content)</\(.tag)>"
else
"<\(.tag + $classText)>\n\([.content[] | toHtml] | join("\n"))\n</\(.tag)>"
end
| indent(4);
"<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<style>
/* CSS reset start */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* CSS reset end */
body {
display: flex;
justify-content: center;
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
background-color: rgb(240, 240, 240);
}
.recipe {
counter-reset: step;
}
.recipe_title {
font-size: 1.3em;
text-transform: uppercase;
margin-bottom: 1em;
font-weight: bold;
}
.recipe_step {
display: flex;
}
.recipe_step::before {
min-width: 40px;
vertical-align: middle;
font-size: 0.7em;
counter-increment: step;
content: counter(step, circled-digits);
margin-right: 4px;
}
@counter-style circled-digits {
system: fixed;
symbols: ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳;
suffix: ' ';
}
.recipe_properties {
row-gap: 0.5em;
margin-bottom: 1em;
display: flex;
flex-wrap: wrap;
column-gap: 1em;
}
.recipe_properties *:nth-child(odd) {
font-weight: bold;
flex: 1 0 30%;
}
.recipe_properties *:nth-child(even) {
flex: 1 0 60%;
}
.recipe_description {
margin-bottom: 1em;
}
.recipe_steps .row {
display: grid;
border-bottom: 1px solid black;
padding-top: 4px;
padding-bottom: 4px;
}
.recipe_step {
display: block;
}
@media print {
.detailed_steps {
margin-left: -1em;
}
}
.detailed_steps {
padding-top: 1.5em;
counter-reset: step 25;
padding-bottom: 1em;
}
.detailed_steps li {
counter-increment: step;
padding-right: 1em;
padding-bottom: 1.5em;
display: flex;
}
.detailed_steps li:before {
min-width: 40px;
text-align: right;
font-size: 2em;
content: counter(step);
margin-right: 0.3em;
margin-top: -0.15em;
font-weight: bold;
font-family: Cambria, Cochin, Georgia, Times, 'Times New Roman', serif
}
.notes {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2em;
}
.note {
margin-bottom: 2em;
}
@media only screen and (max-width: 1100px) {
body {
}
.recipe {
padding: 0.5rem;
}
.recipe_steps .row {
grid-template-columns: 2fr 1fr 2fr 1fr;
padding-top: 0.5em;
padding-bottom: 0.5em;
}
.recipe_steps .row > *:last-child {
padding-top: 0.5em;
grid-column: span 4;
}
.recipe_steps .header *:last-child {
display: none;
}
.detailed_steps li:before {
margin-right: 1em;
}
.notes {
display: grid;
grid-template-columns: 1fr;
gap: 2em;
}
}
@media only screen and (min-width: 1100px) {
body {
}
.recipe {
margin-top: 1em;
padding: 1em;
box-shadow: 0px 3px 5px 2px rgba(0, 0, 0, 0.2);
counter-reset: step;
max-width: 70em;
}
.recipe_description {
column-count: 2;
/* Align the right column with the 'Procedure' column in the recipe */
padding-right: 1em;
column-gap: 1em;
margin-bottom: 1em;
}
.header *:last-child {
display: inline;
}
.recipe_steps .row {
grid-template-columns: 2fr 1fr 2fr 1fr 50%;
}
.detailed_steps {
columns: 2;
}
}
@media print {
body {
margin: 0;
font-size: 0.7em;
}
.recipe_description {
column-count: 2;
/* Align the right column with the 'Procedure' column in the recipe */
padding-right: 1em;
column-gap: 1em;
margin-bottom: 1em;
}
.recipe_steps .row {
grid-template-columns: 2fr 1fr 2fr 1fr 50%;
}
.detailed_steps {
columns: 2;
}
}
.recipe_description p:not(:first-child) {
text-indent: 1em;
}
ul.recipe_properties span:nth-child(odd) {
text-transform: uppercase;
}
ul.recipe_properties span:nth-child(even) {
font-style: italic;
}
.recipe_steps .header {
text-transform: uppercase;
font-weight: bold;
}
.row .ingredients {
grid-column: span 4;
}
.ingredient {
grid-column: span 1;
display: grid;
grid-template-columns: 2fr 1fr 2fr 1fr;
}
.ingredient *:first-child {
font-weight: bold;
}
.ingredient * {
padding-right: 4px;
}
.ingredient_note {
display: block;
font-size: 0.8em;
}
h2 {
font-weight: bold;
}
</style>
</head>
<body>
<div class='recipe'>
<h1 class='recipe_title'>caramelized carrot soup</h1>
\(
{
tag: "ul",
class: "recipe_properties",
content: [
.properties[]
| [
{tag: "span", content: .name},
{tag: "span", content: .value}
]
| .[]
]
}
| toHtml
| indent(8)
)
<div class='recipe_description'>
\(
[
.description
| split("\n")[]
| "<p>\(.)</p>"
| indent(16)
]
| join("\n")
)
</div>
<div class='recipe_steps'>
<div class='row header'>
<span>ingredient</span>
<span>weight</span>
<span>volume</span>
<span>scaling</span>
<span>procedure</span>
</div>
\(
[
.steps[]
| {
tag: "div",
class: "row",
content: [
{
tag: "div",
class: "ingredients",
content: [
.ingredients[]
| {
tag: "div",
class: "ingredient",
content: [
{tag: "span", content: .name},
{tag: "span", content: .weight},
{tag: "span", content: .volume},
{tag: "span", content: .scaling}
]
}
]
},
{
tag: "div",
content: [
.procedure[]
| {
tag: "span",
class: "recipe_step",
content: .short
}
]
}
]
}
| toHtml
| indent(12)
]
| join("\n")
)
</div>
<ol class='detailed_steps'>
\(
[
.steps[]
| .procedure[]
| {tag: "li", content: .long}
| toHtml
| indent(12)
] | join("\n")
)
</ol>
<div class='notes'>
\(
[
.notes_html[]
| "<div class='note'>\(.)</div>"
| indent(16)
] | join("\n")
)
</div>
</div>
</body>
</html>"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment