Cookbook — Serving scaler (½× · 2× quantities)
A live serving-size scaler for a printed-style recipe card. Pick ½×, 1×, 2×, or 3×, or nudge a servings stepper, and every ingredient quantity recomputes in place with a soft saffron flash. The math snaps to cook-friendly fractions like ½, ⅓, ¼, and ¾, handles ranges such as twelve to fourteen ounces of tomatoes, and leaves non-scalable items — basil to taste, a single pan — exactly where they are. The servings count is announced via aria-live, and the layout prints cleanly.
MCP
Code
:root {
--cream: #faf6ef;
--paper: #fffdf8;
--ink: #2b2622;
--ink-2: #5c534a;
--muted: #8a7f73;
--tomato: #d6452b;
--tomato-d: #b8351e;
--saffron: #e8a33d;
--sage: #7c8a6b;
--clay: #c8775a;
--line: rgba(43, 38, 34, 0.12);
--line-2: rgba(43, 38, 34, 0.2);
--ok: #3f8f5f;
--warn: #d98a2b;
--danger: #c8412b;
--r-sm: 8px;
--r-md: 14px;
--r-lg: 22px;
--sh-1: 0 1px 2px rgba(43, 38, 34, 0.1);
--sh-2: 0 10px 30px rgba(43, 38, 34, 0.1);
--serif: "Fraunces", Georgia, serif;
--sans: "Inter", system-ui, -apple-system, sans-serif;
}
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: var(--sans);
line-height: 1.6;
color: var(--ink);
background:
radial-gradient(1100px 500px at 12% -8%, rgba(232, 163, 61, 0.12), transparent 60%),
radial-gradient(900px 480px at 100% 0%, rgba(214, 69, 43, 0.08), transparent 55%),
var(--cream);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.page {
max-width: 1080px;
margin: 0 auto;
padding: clamp(20px, 4vw, 48px) clamp(16px, 4vw, 40px) 64px;
}
/* ---------- masthead ---------- */
.masthead { text-align: center; margin-bottom: clamp(24px, 4vw, 40px); }
.kicker {
font-family: var(--sans);
text-transform: uppercase;
letter-spacing: 0.22em;
font-size: 0.72rem;
font-weight: 600;
color: var(--tomato);
margin: 0 0 0.6rem;
}
.masthead h1 {
font-family: var(--serif);
font-weight: 600;
font-size: clamp(2rem, 5.5vw, 3.4rem);
line-height: 1.08;
letter-spacing: -0.01em;
margin: 0 0 0.6rem;
color: var(--ink);
}
.dek {
max-width: 56ch;
margin: 0 auto 1.4rem;
color: var(--ink-2);
font-size: clamp(1rem, 1.6vw, 1.1rem);
}
.meta-row {
list-style: none;
margin: 0 auto;
padding: 0;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0;
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
max-width: 720px;
}
.meta-row li {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
padding: 0.7rem 1.3rem;
position: relative;
}
.meta-row li + li::before {
content: "";
position: absolute;
left: 0;
top: 22%;
bottom: 22%;
width: 1px;
background: var(--line);
}
.meta-k {
font-size: 0.66rem;
text-transform: uppercase;
letter-spacing: 0.14em;
color: var(--muted);
font-weight: 600;
}
.meta-v {
font-family: var(--serif);
font-weight: 600;
font-size: 1.02rem;
color: var(--ink);
}
/* ---------- layout ---------- */
.layout {
display: grid;
grid-template-columns: 1.1fr 0.9fr;
grid-template-areas:
"scaler ingredients"
"method ingredients";
gap: clamp(16px, 2.4vw, 28px);
align-items: start;
}
.scaler-card { grid-area: scaler; }
.ingredients-card { grid-area: ingredients; }
.method-card { grid-area: method; }
.card {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r-lg);
box-shadow: var(--sh-1);
padding: clamp(18px, 2.4vw, 26px);
}
.card h2 {
font-family: var(--serif);
font-weight: 600;
font-size: 1.4rem;
margin: 0;
color: var(--ink);
}
/* ---------- photo placeholder ---------- */
.photo {
position: relative;
border-radius: var(--r-md);
overflow: hidden;
border: 1px solid var(--line);
box-shadow: inset 0 0 0 6px rgba(255, 253, 248, 0.5);
}
.photo--hero {
aspect-ratio: 16 / 7;
margin-bottom: clamp(18px, 2.4vw, 24px);
background:
radial-gradient(120px 90px at 26% 38%, rgba(214, 69, 43, 0.95), rgba(184, 53, 30, 0.6) 60%, transparent 72%),
radial-gradient(90px 70px at 58% 64%, rgba(214, 69, 43, 0.8), transparent 70%),
radial-gradient(70px 60px at 74% 32%, rgba(232, 163, 61, 0.9), transparent 72%),
radial-gradient(140px 110px at 88% 78%, rgba(124, 138, 107, 0.55), transparent 70%),
linear-gradient(135deg, #f4d9a6, #e8b56b 45%, #d98c52 100%);
}
.photo-emoji {
position: absolute;
font-size: 2.4rem;
top: 18%;
left: 12%;
filter: drop-shadow(0 4px 6px rgba(43, 38, 34, 0.25));
}
.photo-emoji--2 { top: 56%; left: auto; right: 14%; font-size: 2rem; }
.photo-tag {
position: absolute;
left: 14px;
bottom: 12px;
background: rgba(43, 38, 34, 0.78);
color: #fff;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
padding: 4px 10px;
border-radius: 999px;
}
/* ---------- scaler controls ---------- */
.scaler-head { margin-bottom: 1.1rem; }
.scaler-sub { margin: 0.25rem 0 0; color: var(--ink-2); font-size: 0.92rem; }
.multipliers {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 8px;
margin-bottom: 1.2rem;
}
.mult {
font-family: var(--serif);
font-weight: 600;
font-size: 1.15rem;
padding: 0.7rem 0.4rem;
border: 1px solid var(--line-2);
background: var(--cream);
color: var(--ink-2);
border-radius: var(--r-md);
cursor: pointer;
transition: transform 0.12s ease, background 0.18s ease, color 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease;
}
.mult:hover { background: #fff; color: var(--ink); border-color: var(--clay); }
.mult:active { transform: translateY(1px) scale(0.98); }
.mult.is-active {
background: var(--tomato);
border-color: var(--tomato-d);
color: #fff;
box-shadow: var(--sh-2);
}
.servings {
background: var(--cream);
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: 1rem 1.1rem;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.6rem 1rem;
}
.servings-label {
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.14em;
font-weight: 700;
color: var(--muted);
}
.stepper {
display: inline-flex;
align-items: center;
gap: 0;
background: var(--paper);
border: 1px solid var(--line-2);
border-radius: 999px;
padding: 4px;
}
.step {
width: 36px;
height: 36px;
border: none;
background: transparent;
font-size: 1.4rem;
line-height: 1;
color: var(--ink);
cursor: pointer;
border-radius: 999px;
transition: background 0.15s ease, color 0.15s ease;
}
.step:hover { background: var(--tomato); color: #fff; }
.step:disabled { opacity: 0.35; cursor: not-allowed; }
.step:disabled:hover { background: transparent; color: var(--ink); }
.servings-count {
font-family: var(--serif);
font-weight: 700;
font-size: 1.4rem;
min-width: 2.2ch;
text-align: center;
color: var(--ink);
}
.servings-note {
flex-basis: 100%;
margin: 0.2rem 0 0;
font-size: 0.9rem;
color: var(--ink-2);
}
.servings-note strong { color: var(--ink); }
#multReadout { color: var(--tomato-d); font-weight: 700; }
/* ---------- ingredients ---------- */
.ing-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
margin-bottom: 0.6rem;
}
.reset-btn {
font-family: var(--sans);
font-size: 0.78rem;
font-weight: 600;
color: var(--tomato-d);
background: none;
border: none;
cursor: pointer;
padding: 4px 6px;
border-radius: var(--r-sm);
transition: background 0.15s ease;
}
.reset-btn:hover { background: rgba(214, 69, 43, 0.1); }
.ing-list { list-style: none; margin: 0; padding: 0; }
.ing-list li {
display: flex;
align-items: baseline;
gap: 0.6rem;
padding: 0.62rem 0;
border-bottom: 1px dashed var(--line);
}
.ing-list li:last-child { border-bottom: none; }
.ing-qty {
font-family: var(--serif);
font-weight: 600;
font-size: 1.02rem;
color: var(--tomato-d);
min-width: 5.5ch;
white-space: nowrap;
transition: color 0.2s ease;
}
.ing-qty.flash {
animation: flash 0.55s ease;
}
@keyframes flash {
0% { background: rgba(232, 163, 61, 0.55); }
100% { background: transparent; }
}
.ing-name { color: var(--ink); }
.ing-name .ing-note { color: var(--muted); font-style: italic; font-size: 0.9em; }
.ing-list li.is-fixed .ing-qty { color: var(--sage); }
.ing-foot {
margin: 0.9rem 0 0;
font-size: 0.84rem;
color: var(--muted);
}
.ing-foot em { font-style: italic; }
/* ---------- method ---------- */
.method-list { list-style: none; counter-reset: step; margin: 0.6rem 0 0; padding: 0; }
.method-list li {
display: flex;
gap: 0.9rem;
padding: 0.6rem 0;
border-top: 1px solid var(--line);
}
.method-list li:first-child { border-top: none; }
.method-list p { margin: 0; color: var(--ink-2); }
.step-n {
flex: 0 0 auto;
width: 30px;
height: 30px;
display: grid;
place-items: center;
border-radius: 999px;
background: var(--saffron);
color: var(--ink);
font-family: var(--serif);
font-weight: 700;
font-size: 0.95rem;
}
/* ---------- footnote ---------- */
.footnote {
margin-top: clamp(28px, 4vw, 44px);
text-align: center;
font-size: 0.8rem;
color: var(--muted);
}
/* ---------- toast ---------- */
.toast {
position: fixed;
left: 50%;
bottom: 24px;
transform: translateX(-50%) translateY(20px);
background: var(--ink);
color: var(--paper);
font-size: 0.88rem;
font-weight: 500;
padding: 0.7rem 1.1rem;
border-radius: 999px;
box-shadow: var(--sh-2);
opacity: 0;
pointer-events: none;
transition: opacity 0.22s ease, transform 0.22s ease;
z-index: 20;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* ---------- focus ---------- */
:focus-visible {
outline: 2.5px solid var(--clay);
outline-offset: 2px;
border-radius: var(--r-sm);
}
/* ---------- responsive ---------- */
@media (max-width: 720px) {
.layout {
grid-template-columns: 1fr;
grid-template-areas:
"scaler"
"ingredients"
"method";
}
.photo--hero { aspect-ratio: 16 / 9; }
.meta-row li { padding: 0.6rem 0.9rem; }
}
@media print {
body { background: #fff; }
.multipliers, .stepper, .reset-btn, .toast, .photo { display: none; }
.card { box-shadow: none; border-color: #ccc; }
}(function () {
"use strict";
var BASE_SERVINGS = 4;
var MIN_SERVINGS = 1;
var MAX_SERVINGS = 24;
// Each ingredient: qty (base amount for BASE_SERVINGS) or qty + qtyTo for a range.
// fixed: true -> never scales (to taste, a pinch, a single pan...)
// unit is appended after the formatted number.
var INGREDIENTS = [
{ qty: 1.5, unit: "cups", name: "orzo pasta" },
{ qty: 2, unit: "tbsp", name: "olive oil" },
{ qty: 12, qtyTo: 14, unit: "oz", name: "cherry tomatoes", note: "halved" },
{ qty: 3, unit: "cloves", name: "garlic", note: "thinly sliced" },
{ qty: 0.25, unit: "tsp", name: "saffron threads" },
{ qty: 3, unit: "cups", name: "vegetable stock", note: "warm" },
{ qty: 2, unit: "tbsp", name: "unsalted butter" },
{ qty: 0.5, unit: "", name: "lemon", note: "juiced" },
{ qty: null, unit: "", name: "fresh basil", note: "to taste", fixed: true },
{ qty: null, unit: "", name: "shaved pecorino", note: "to finish", fixed: true },
{ qty: 1, unit: "", name: "wide deep skillet or saute pan", fixed: true, integer: true }
];
var state = { servings: BASE_SERVINGS };
var els = {
list: document.getElementById("ingList"),
mults: Array.prototype.slice.call(document.querySelectorAll(".mult")),
count: document.getElementById("servingsCount"),
stepUp: document.getElementById("stepUp"),
stepDown: document.getElementById("stepDown"),
base: document.getElementById("baseServings"),
target: document.getElementById("targetServings"),
multReadout: document.getElementById("multReadout"),
reset: document.getElementById("resetBtn"),
toast: document.getElementById("toast")
};
/* ---------- fraction-friendly formatting ---------- */
var FRACTIONS = [
{ v: 0, g: "" },
{ v: 1 / 8, g: "⅛" },
{ v: 1 / 4, g: "¼" },
{ v: 1 / 3, g: "⅓" },
{ v: 3 / 8, g: "⅜" },
{ v: 1 / 2, g: "½" },
{ v: 5 / 8, g: "⅝" },
{ v: 2 / 3, g: "⅔" },
{ v: 3 / 4, g: "¾" },
{ v: 7 / 8, g: "⅞" },
{ v: 1, g: "", carry: 1 }
];
function nearestFraction(frac) {
var best = FRACTIONS[0];
var bestDist = Infinity;
for (var i = 0; i < FRACTIONS.length; i++) {
var d = Math.abs(FRACTIONS[i].v - frac);
if (d < bestDist) { bestDist = d; best = FRACTIONS[i]; }
}
return best;
}
// Format a scaled numeric quantity into a cook-friendly string.
function formatQty(value, integer) {
if (integer) {
return String(Math.max(1, Math.round(value)));
}
// Larger amounts: round to a sensible decimal, no fractions needed.
if (value >= 10) {
return String(Math.round(value));
}
var whole = Math.floor(value);
var frac = value - whole;
var match = nearestFraction(frac);
if (match.carry) { whole += 1; }
var glyph = match.carry ? "" : match.g;
if (whole === 0 && glyph === "") {
// Very small amount that rounded to zero — show one decimal instead.
var d = Math.round(value * 100) / 100;
if (d === 0) { d = 0.01; }
return trimDecimal(d);
}
if (whole === 0) { return glyph; }
if (glyph === "") { return String(whole); }
return whole + " " + glyph; // narrow no-break space between whole and fraction
}
function trimDecimal(n) {
return String(parseFloat(n.toFixed(2)));
}
/* ---------- rendering ---------- */
function buildList() {
els.list.innerHTML = "";
INGREDIENTS.forEach(function (ing, idx) {
var li = document.createElement("li");
if (ing.fixed) { li.classList.add("is-fixed"); }
var qty = document.createElement("span");
qty.className = "ing-qty";
qty.dataset.idx = String(idx);
var name = document.createElement("span");
name.className = "ing-name";
var html = ing.name;
if (ing.note) { html += ' <span class="ing-note">(' + ing.note + ")</span>"; }
name.innerHTML = html;
li.appendChild(qty);
li.appendChild(name);
els.list.appendChild(li);
});
}
function qtyText(ing, factor) {
if (ing.qty == null) { return ing.fixed && !ing.note ? "" : "—"; }
var lo = formatQty(ing.qty * factor, ing.integer);
var unit = ing.unit ? " " + ing.unit : "";
if (ing.qtyTo != null) {
var hi = formatQty(ing.qtyTo * factor, ing.integer);
return lo + "–" + hi + unit;
}
return lo + unit;
}
function render(flash) {
var factor = state.servings / BASE_SERVINGS;
var multStr = trimDecimal(factor);
INGREDIENTS.forEach(function (ing, idx) {
var el = els.list.querySelector('.ing-qty[data-idx="' + idx + '"]');
if (!el) { return; }
var next = ing.fixed ? qtyText(ing, 1) : qtyText(ing, factor);
if (el.textContent !== next) {
el.textContent = next;
if (flash && !ing.fixed) {
el.classList.remove("flash");
// reflow to restart animation
void el.offsetWidth;
el.classList.add("flash");
}
}
});
els.count.textContent = String(state.servings);
els.target.textContent = String(state.servings);
els.base.textContent = String(BASE_SERVINGS);
els.multReadout.textContent = multStr + "×";
// sync multiplier radiogroup
els.mults.forEach(function (btn) {
var active = Math.abs(parseFloat(btn.dataset.mult) - factor) < 0.001;
btn.classList.toggle("is-active", active);
btn.setAttribute("aria-checked", active ? "true" : "false");
});
els.stepDown.disabled = state.servings <= MIN_SERVINGS;
els.stepUp.disabled = state.servings >= MAX_SERVINGS;
}
function setServings(n, flash) {
n = Math.max(MIN_SERVINGS, Math.min(MAX_SERVINGS, Math.round(n)));
state.servings = n;
render(flash);
}
/* ---------- toast ---------- */
var toastTimer;
function toast(msg) {
if (!els.toast) { return; }
els.toast.textContent = msg;
els.toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(function () {
els.toast.classList.remove("show");
}, 1800);
}
/* ---------- events ---------- */
els.mults.forEach(function (btn, i) {
btn.addEventListener("click", function () {
var mult = parseFloat(btn.dataset.mult);
setServings(BASE_SERVINGS * mult, true);
toast("Scaled to " + state.servings + " servings (" + btn.textContent.trim() + ")");
});
// arrow-key navigation within the radiogroup
btn.addEventListener("keydown", function (e) {
var dir = 0;
if (e.key === "ArrowRight" || e.key === "ArrowDown") { dir = 1; }
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { dir = -1; }
if (!dir) { return; }
e.preventDefault();
var next = (i + dir + els.mults.length) % els.mults.length;
els.mults[next].focus();
els.mults[next].click();
});
});
els.stepUp.addEventListener("click", function () {
setServings(state.servings + 1, true);
});
els.stepDown.addEventListener("click", function () {
setServings(state.servings - 1, true);
});
els.reset.addEventListener("click", function () {
setServings(BASE_SERVINGS, true);
toast("Reset to base (" + BASE_SERVINGS + " servings)");
});
/* ---------- init ---------- */
buildList();
render(false);
})();<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Cookbook — Serving Scaler</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,500;9..144,600;9..144,700&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="page">
<header class="masthead" role="banner">
<p class="kicker">The Weeknight Kitchen · No. 14</p>
<h1>Charred Tomato & Saffron Orzo</h1>
<p class="dek">A one-pan supper that scales cleanly from a solo bowl to a table of six — toasty orzo, blistered cherry tomatoes, and a saffron-stained broth finished with torn basil.</p>
<ul class="meta-row" aria-label="Recipe details">
<li><span class="meta-k">Prep</span><span class="meta-v">15 min</span></li>
<li><span class="meta-k">Cook</span><span class="meta-v">30 min</span></li>
<li><span class="meta-k">Skill</span><span class="meta-v">Easy</span></li>
<li><span class="meta-k">Base yield</span><span class="meta-v" id="metaBaseYield">4 servings</span></li>
</ul>
</header>
<main class="layout">
<section class="card scaler-card" aria-labelledby="scalerHeading">
<div class="photo photo--hero" role="img" aria-label="Illustrative photo of saffron orzo with charred tomatoes">
<span class="photo-emoji" aria-hidden="true">🍅</span>
<span class="photo-emoji photo-emoji--2" aria-hidden="true">🌿</span>
<span class="photo-tag" aria-hidden="true">on the stove</span>
</div>
<div class="scaler" role="group" aria-labelledby="scalerHeading">
<div class="scaler-head">
<h2 id="scalerHeading">Scale the recipe</h2>
<p class="scaler-sub">Quantities recompute live and snap to friendly fractions.</p>
</div>
<div class="multipliers" role="radiogroup" aria-label="Scale multiplier">
<button type="button" class="mult" role="radio" aria-checked="false" data-mult="0.5">½×</button>
<button type="button" class="mult is-active" role="radio" aria-checked="true" data-mult="1">1×</button>
<button type="button" class="mult" role="radio" aria-checked="false" data-mult="2">2×</button>
<button type="button" class="mult" role="radio" aria-checked="false" data-mult="3">3×</button>
</div>
<div class="servings">
<span class="servings-label" id="servingsLabel">Servings</span>
<div class="stepper" role="group" aria-labelledby="servingsLabel">
<button type="button" class="step" id="stepDown" aria-label="Decrease servings">−</button>
<output class="servings-count" id="servingsCount" aria-live="polite">4</output>
<button type="button" class="step" id="stepUp" aria-label="Increase servings">+</button>
</div>
<p class="servings-note">
Base <strong id="baseServings">4</strong> → now
<strong id="targetServings">4</strong> · <span id="multReadout">1×</span>
</p>
</div>
</div>
</section>
<section class="card ingredients-card" aria-labelledby="ingHeading">
<div class="ing-head">
<h2 id="ingHeading">Ingredients</h2>
<button type="button" class="reset-btn" id="resetBtn">Reset to base</button>
</div>
<ul class="ing-list" id="ingList" aria-live="polite"></ul>
<p class="ing-foot">Items marked <em>to taste</em> or fixed (a single pan, a pinch) stay put no matter the scale.</p>
</section>
<section class="card method-card" aria-labelledby="methodHeading">
<h2 id="methodHeading">Method</h2>
<ol class="method-list">
<li><span class="step-n" aria-hidden="true">1</span><p>Toast the orzo in olive oil over medium heat until nutty and golden, about 3 minutes.</p></li>
<li><span class="step-n" aria-hidden="true">2</span><p>Add the cherry tomatoes and garlic; let them blister and burst, pressing a few with a spoon.</p></li>
<li><span class="step-n" aria-hidden="true">3</span><p>Bloom the saffron in the warm stock, pour in, season, and simmer until the orzo is tender and creamy.</p></li>
<li><span class="step-n" aria-hidden="true">4</span><p>Off the heat, fold through butter and a squeeze of lemon. Finish with torn basil and shaved cheese.</p></li>
</ol>
</section>
</main>
<footer class="footnote" role="contentinfo">
Illustrative UI only — recipe & quantities are fictional, not dietary advice.
</footer>
</div>
<div class="toast" id="toast" role="status" aria-live="polite"></div>
<script src="script.js"></script>
</body>
</html>Serving scaler (½× · 2× quantities)
An editorial recipe card — charred tomato and saffron orzo — with a scaling control bolted onto its ingredient list. A segmented ½× · 1× · 2× · 3× radiogroup and a numeric servings stepper drive the same underlying factor, so choosing a multiplier and tapping the stepper stay perfectly in sync. A small readout spells out the base yield, the new target, and the active multiplier.
Every change live-recomputes each quantity in place and snaps the result to cook-friendly fractions — ½, ⅓, ¼, ¾ and friends — rather than spitting out raw decimals. Ranges scale at both ends (twelve to fourteen ounces becomes six to seven at ½×), large amounts round to whole numbers, and tiny amounts fall back to a clean decimal so nothing ever reads as zero. Items marked to taste or fixed — fresh basil, shaved pecorino, the single skillet — are flagged and never scaled.
Each updated quantity gets a brief saffron flash so the eye can follow what changed, the servings count is wrapped in an aria-live region for screen readers, the multiplier buttons are arrow-key navigable, and a Reset to base action returns the card to its original four servings. The whole layout collapses to a single column by 720px and hides its controls under @media print.
Illustrative UI only — recipes & nutrition data are fictional, not dietary advice.