{"product_id":"colombie-francy-castillo-varietes-rares","title":"COLOMBIA - FRANCY CASTILLO'S FANCY VARIETIES","description":"\u003c!-- =========================================================\n  SECTION 1 : TASTING NOTES\n========================================================= --\u003e\n\u003cdiv style=\"width: 100%; margin: 0 auto 12px auto; border: 2px solid #000; border-radius: 12px; overflow: hidden;\"\u003e\n\u003cdiv style=\"padding: 10px 14px; font-weight: 800; text-transform: uppercase; font-size: 14px; letter-spacing: .02em; background: #000; color: #fff; text-align: center;\"\u003eTasting Notes\u003c\/div\u003e\n\u003cdiv style=\"padding: 16px 14px; font-weight: 900; text-transform: uppercase; font-size: 28px; letter-spacing: .05em; text-align: center; background: transparent;\"\u003eHONEYDEW MELON + TANGERINE + OOLONG\u003c\/div\u003e\n\u003c\/div\u003e\n\u003c!-- ========= END SECTION 1 ========= --\u003e\n\u003cp\u003e\u0026nbsp;\u003c\/p\u003e\n\u003c!-- =========================================================\n  SECTION 2 : SOURCING\n========================================================= --\u003e\n\u003cdiv style=\"width: 100%; margin: 0 auto 12px auto;\"\u003e\n\u003cdiv style=\"padding: 12px 14px; font-weight: 800; text-transform: uppercase; font-size: 14px; letter-spacing: .02em; background: #000; color: #fff; text-align: center; border-radius: 12px 12px 0 0;\"\u003eSourcing\u003c\/div\u003e\n\u003ctable style=\"width: 100%; border-collapse: collapse; background: transparent;\" cellspacing=\"1\" cellpadding=\"1\" border=\"3\"\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd style=\"width: 20%;\"\u003eORIGIN\u003c\/td\u003e\n\u003ctd\u003eArboleda region, Nariño department, Colombia\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePRODUCERS\u003c\/td\u003e\n\u003ctd\u003eFrancy Castillo\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eFARM\u003c\/td\u003e\n\u003ctd\u003eEl Ubérrimo\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eVARIETY\u003c\/td\u003e\n\u003ctd\u003ePink Bourbon, Geisha, Sidra and Wush wush\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePROCESS\u003c\/td\u003e\n\u003ctd\u003eWashed\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eALTITUDE\u003c\/td\u003e\n\u003ctd\u003e2118 m\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003ePARTNERS\u003c\/td\u003e\n\u003ctd\u003eApex\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eVOLUME PURCHASED\u003c\/td\u003e\n\u003ctd\u003e96 kg\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eFOB\u003c\/td\u003e\n\u003ctd\u003e$8.44 USD\/lb\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eLANDED PRICE\u003c\/td\u003e\n\u003ctd\u003e$18.22 CAD\/lb\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003eRELATIONSHIP\u003c\/td\u003e\n\u003ctd\u003e4 years\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003c\/tbody\u003e\n\u003c\/table\u003e\n\u003c\/div\u003e\n\u003c!-- ========= END SECTION 2 ========= --\u003e\n\u003cp\u003e\u0026nbsp;\u003c\/p\u003e\n\u003c!-- =========================================================\n  SECTION 3 : COFFEE INFORMATION\n========================================================= --\u003e\n\u003cdiv style=\"position: relative; width: 100%; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 8px;\"\u003e\u003ciframe src=\"https:\/\/www.youtube.com\/embed\/PasUD7LZEUQ?si=aqQ2pJqOIrqiiNWr\" style=\"position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: 0; border-radius: 8px;\"\u003e\u003c\/iframe\u003e\u003c\/div\u003e\n\u003cp\u003e\u0026nbsp;\u003c\/p\u003e\n\u003cdiv style=\"width: 100%; margin: 0 auto 12px auto;\"\u003e\n\u003cdiv style=\"padding: 12px 14px; font-weight: 800; text-transform: uppercase; font-size: 14px; letter-spacing: .02em; background: #000; color: #fff; text-align: center; border-radius: 12px 12px 0 0;\"\u003eAbout this coffee\u003c\/div\u003e\n\u003cdiv style=\"border: 2px solid #000; border-top: none; border-radius: 0 0 12px 12px; padding: 16px 14px; background: transparent;\"\u003e\n\u003cp style=\"margin: 0 0 12px 0;\"\u003eIn the summer of 2023, we had the privilege of being welcomed in Colombia's Nariño region by none other than the remarkable Francy Castillo. Following her enthusiastic footsteps and her beaming laugh, we made our way up to 2,000 metres of altitude along a narrow and demanding trail, finally arriving at her coffee farm. The view across the mountains was breathtaking, and Francy's hospitality even more so.\u003c\/p\u003e\n\u003cp style=\"margin: 0;\"\u003eFrancy is an extraordinary woman — not only for her exceptional expertise, but also for the dedication she shows to her community. She works her own farm and family lands, while also collaborating with over 900 small producers in her region. She oversees the Berruecos project, which consolidates cherries from local smallholders at her collection point. This initiative gives isolated producers access to the specialty coffee market, allowing them to earn premium prices. The project generates larger, more manageable lots sourced from outstanding farms and producers.\u003c\/p\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e\n\u003c!-- ========= END SECTION 3 ========= --\u003e\n\u003cp\u003e\u0026nbsp;\u003c\/p\u003e\n\u003c!-- =========================================================\n  SECTION 4 : V60\n========================================================= --\u003e\n\u003cdiv style=\"width: 100%; margin: 0 auto;\"\u003e\n\u003cdetails style=\"border: 2px solid #000; border-radius: 12px; overflow: hidden; margin: 0 0 12px 0; background: rgba(255,255,255,0.5);\" open=\"\" id=\"v60Details-francycastillo\"\u003e\n\u003csummary style=\"cursor: pointer; list-style: none; padding: 12px 14px; font-weight: 800; text-transform: uppercase; font-size: 14px; letter-spacing: .02em; background: #000; color: #fff; text-align: center;\"\u003eV60 – Recipe\u003c\/summary\u003e\n\u003cdiv style=\"padding: 12px 14px;\"\u003e\n\u003cdiv style=\"padding: 10px 0 10px; border-bottom: 1px solid rgba(0,0,0,.2);\"\u003e\n\u003cdiv style=\"font-weight: 900; font-size: 16px;\"\u003eRatio 1:16\u003c\/div\u003e\n\u003cdiv style=\"margin-top: 4px; opacity: .7; font-size: 13px;\"\u003e15 g in – 240 g out\u003c\/div\u003e\n\u003cdiv style=\"margin-top: 6px; font-size: 13px;\"\u003e\u003cstrong\u003eWater temperature:\u003c\/strong\u003e 92 °C\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv style=\"display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; padding: 10px 0 8px;\"\u003e\n\u003cdiv style=\"display: flex; gap: 8px; align-items: center; flex-wrap: wrap;\"\u003e\u003cbutton id=\"v60StartBtn-francycastillo\" type=\"button\" style=\"cursor: pointer; border: 2px solid #000; background: #000; color: #fff; font-weight: 900; padding: 8px 10px; border-radius: 10px; text-transform: uppercase; letter-spacing: .02em; font-size: 12px;\"\u003eStart\u003c\/button\u003e \u003cbutton id=\"v60StopBtn-francycastillo\" type=\"button\" disabled=\"disabled\" style=\"cursor: pointer; border: 2px solid #000; background: #fff; color: #000; font-weight: 900; padding: 8px 10px; border-radius: 10px; text-transform: uppercase; letter-spacing: .02em; font-size: 12px; opacity: .5;\"\u003eStop\u003c\/button\u003e\u003c\/div\u003e\n\u003cdiv style=\"display: flex; gap: 10px; align-items: baseline; line-height: 1;\"\u003e\u003cspan style=\"opacity: .70; font-size: 12px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em;\"\u003eTimer\u003c\/span\u003e \u003cspan id=\"v60Timer-francycastillo\" style=\"font-weight: 950; font-size: 28px; letter-spacing: .01em;\"\u003e0:00\u003c\/span\u003e\u003c\/div\u003e\n\u003cdiv id=\"v60WakeStatus-francycastillo\" style=\"flex-basis: 100%; opacity: .6; font-size: 12px; line-height: 1.3; margin-top: 2px;\"\u003eℹ️ Press \"Start\" to keep your screen on.\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv style=\"margin-top: 8px; display: grid; gap: 10px;\"\u003e\n\u003cdiv id=\"v60-card-1-francycastillo\" style=\"border: 1px solid rgba(0,0,0,.18); border-radius: 12px; padding: 10px 12px;\"\u003e\n\u003cdiv style=\"display: flex; gap: 10px; align-items: baseline; margin-bottom: 6px;\"\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003e0:00\u003c\/div\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003ePhase 1 — Bloom\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv style=\"display: flex; gap: 8px; flex-wrap: wrap; font-size: 12.5px; font-weight: 800;\"\u003e\u003cspan id=\"v60-pill-1-francycastillo\" style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12);\"\u003ePour for 0:10s\u003c\/span\u003e \u003cspan style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12);\"\u003eTotal 50 g\u003c\/span\u003e\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv id=\"v60-card-2-francycastillo\" style=\"border: 1px solid rgba(0,0,0,.18); border-radius: 12px; padding: 10px 12px;\"\u003e\n\u003cdiv style=\"display: flex; gap: 10px; align-items: baseline; margin-bottom: 6px;\"\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003e0:30\u003c\/div\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003ePhase 2\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv style=\"display: flex; gap: 8px; flex-wrap: wrap; font-size: 12.5px; font-weight: 800;\"\u003e\u003cspan id=\"v60-pill-2-francycastillo\" style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12);\"\u003ePour for 0:30s\u003c\/span\u003e \u003cspan style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12);\"\u003eTotal 115 g\u003c\/span\u003e \u003cspan style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12); opacity: .6;\"\u003e+65 g\u003c\/span\u003e\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv id=\"v60-card-3-francycastillo\" style=\"border: 1px solid rgba(0,0,0,.18); border-radius: 12px; padding: 10px 12px;\"\u003e\n\u003cdiv style=\"display: flex; gap: 10px; align-items: baseline; margin-bottom: 6px;\"\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003e1:15\u003c\/div\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003ePhase 3\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv style=\"display: flex; gap: 8px; flex-wrap: wrap; font-size: 12.5px; font-weight: 800;\"\u003e\u003cspan id=\"v60-pill-3-francycastillo\" style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12);\"\u003ePour for 0:20s\u003c\/span\u003e \u003cspan style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12);\"\u003eTotal 170 g\u003c\/span\u003e \u003cspan style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12); opacity: .6;\"\u003e+55 g\u003c\/span\u003e\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv id=\"v60-card-4-francycastillo\" style=\"border: 1px solid rgba(0,0,0,.18); border-radius: 12px; padding: 10px 12px;\"\u003e\n\u003cdiv style=\"display: flex; gap: 10px; align-items: baseline; margin-bottom: 6px;\"\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003e1:50\u003c\/div\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003ePhase 4\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv style=\"display: flex; gap: 8px; flex-wrap: wrap; font-size: 12.5px; font-weight: 800;\"\u003e\u003cspan id=\"v60-pill-4-francycastillo\" style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12);\"\u003ePour for 0:25s\u003c\/span\u003e \u003cspan style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12);\"\u003eTotal 240 g\u003c\/span\u003e \u003cspan style=\"padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.05); border: 1px solid rgba(0,0,0,.12); opacity: .6;\"\u003e+70 g\u003c\/span\u003e\u003c\/div\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv style=\"margin-top: 12px; border-top: 1px solid rgba(0,0,0,.2); padding-top: 10px;\"\u003e\n\u003cdiv style=\"display: flex; gap: 10px; align-items: baseline;\"\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003e3:00\u003c\/div\u003e\n\u003cdiv style=\"font-weight: 900;\"\u003eTarget drawdown time\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv style=\"margin-top: 8px; padding: 8px 10px; border-radius: 8px; background: rgba(0,0,0,.04); border: 1px dashed rgba(0,0,0,.2); font-size: 12px; opacity: .6; line-height: 1.4;\"\u003e\u003cstrong\u003eℹ️ Tip —\u003c\/strong\u003e If the total time is shorter, your grind is likely too coarse. If it's longer, your grind is likely too fine.\u003c\/div\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e\n\u003c\/details\u003e\n\u003c\/div\u003e\n\u003c!-- ========= END SECTION 4 ========= --\u003e\u003csvg style=\"display: none;\"\u003e\n\u003cstyle\u003e\n  @keyframes v60PulsePhase {\n    0%   { transform: scale(1);    box-shadow: 0 0 0 0 rgba(200,169,126,.35); }\n    50%  { transform: scale(1.06); box-shadow: 0 0 0 10px rgba(200,169,126,0); }\n    100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(200,169,126,0); }\n  }\n  .v60-pulse {\n    animation: v60PulsePhase 700ms ease-out 1;\n    transform-origin: center;\n    will-change: transform;\n  }\n  @keyframes v60BlinkPill {\n    0%, 100% { opacity: 1; transform: scale(1); }\n    50%      { opacity: .22; transform: scale(1.03); }\n  }\n  .v60-pill-pour-active {\n    background: #ffe7b7 !important;\n    border: 2px solid #d2a44a !important;\n    color: #3b2a12 !important;\n    font-weight: 900 !important;\n    letter-spacing: .02em;\n    animation: v60BlinkPill 2500ms ease-in-out infinite;\n    box-shadow:\n      0 0 0 2px rgba(210,164,74,.18) inset,\n      0 10px 22px rgba(0,0,0,.12),\n      0 0 0 6px rgba(210,164,74,.10);\n  }\n  @media (prefers-reduced-motion: reduce) {\n    .v60-pill-pour-active { animation: none; }\n    .v60-pulse { animation: none; }\n  }\n\u003c\/style\u003e\n\u003cscript\u003e\n(function() {\n  var SUFFIX = '-francycastillo';\n  var END_AT = 180;\n  var phases = [\n    { key: 1, t: 0,   pourLen: 10 },\n    { key: 2, t: 30,  pourLen: 30 },\n    { key: 3, t: 75,  pourLen: 20 },\n    { key: 4, t: 110, pourLen: 25 }\n  ];\n  var latte = {\n    cardBg:     '#f6f1ea',\n    cardBorder: '#c8a97e',\n    pillBg:     '#eadfce'\n  };\n  var details  = document.getElementById('v60Details'    + SUFFIX);\n  var startBtn = document.getElementById('v60StartBtn'   + SUFFIX);\n  var stopBtn  = document.getElementById('v60StopBtn'    + SUFFIX);\n  var timerEl  = document.getElementById('v60Timer'      + SUFFIX);\n  var statusEl = document.getElementById('v60WakeStatus' + SUFFIX);\n  var pills = {\n    1: document.getElementById('v60-pill-1' + SUFFIX),\n    2: document.getElementById('v60-pill-2' + SUFFIX),\n    3: document.getElementById('v60-pill-3' + SUFFIX),\n    4: document.getElementById('v60-pill-4' + SUFFIX)\n  };\n  var cards = {\n    1: document.getElementById('v60-card-1' + SUFFIX),\n    2: document.getElementById('v60-card-2' + SUFFIX),\n    3: document.getElementById('v60-card-3' + SUFFIX),\n    4: document.getElementById('v60-card-4' + SUFFIX)\n  };\n  function setCardActive(key) {\n    Object.keys(cards).forEach(function(k) {\n      var c = cards[k];\n      if (!c) return;\n      if (parseInt(k) === key) {\n        c.style.borderColor = latte.cardBorder;\n        c.style.background  = latte.cardBg;\n        c.style.boxShadow   = '0 0 0 2px rgba(200,169,126,.25) inset, 0 8px 22px rgba(200,169,126,.28)';\n      } else {\n        c.style.borderColor = 'rgba(0,0,0,.18)';\n        c.style.background  = 'transparent';\n        c.style.boxShadow   = 'none';\n      }\n    });\n  }\n  function setPillPhaseActive(key, doPulse) {\n    Object.keys(pills).forEach(function(k) {\n      var p = pills[k];\n      if (!p) return;\n      p.style.background  = 'rgba(0,0,0,.05)';\n      p.style.borderColor = 'rgba(0,0,0,.12)';\n      p.style.boxShadow   = 'none';\n      p.classList.remove('v60-pulse');\n    });\n    var pill = pills[key];\n    if (pill) {\n      pill.style.background  = latte.pillBg;\n      pill.style.borderColor = latte.cardBorder;\n      pill.style.boxShadow   = '0 0 0 2px rgba(200,169,126,.25) inset';\n      if (doPulse) {\n        pill.classList.remove('v60-pulse');\n        void pill.offsetWidth;\n        pill.classList.add('v60-pulse');\n      }\n    }\n  }\n  function resetPourPills() {\n    Object.values(pills).forEach(function(p) {\n      if (!p) return;\n      p.classList.remove('v60-pill-pour-active');\n    });\n  }\n  function setPourPillActive(key) {\n    resetPourPills();\n    if (!key) return;\n    var pill = pills[key];\n    if (pill) pill.classList.add('v60-pill-pour-active');\n  }\n  function clearAll() {\n    Object.values(cards).forEach(function(c) {\n      if (!c) return;\n      c.style.borderColor = 'rgba(0,0,0,.18)';\n      c.style.background  = 'transparent';\n      c.style.boxShadow   = 'none';\n    });\n    Object.values(pills).forEach(function(p) {\n      if (!p) return;\n      p.style.background  = 'rgba(0,0,0,.05)';\n      p.style.borderColor = 'rgba(0,0,0,.12)';\n      p.style.boxShadow   = 'none';\n      p.classList.remove('v60-pulse');\n      p.classList.remove('v60-pill-pour-active');\n    });\n  }\n  var wakeLock  = null;\n  var startTime = null;\n  var rafId     = null;\n  var lastPhaseKey = null;\n  var lastPourKey  = null;\n  function fmt(sec) {\n    sec = Math.max(0, Math.floor(sec));\n    var m = Math.floor(sec \/ 60);\n    var s = sec % 60;\n    return m + ':' + (s \u0026lt; 10 ? '0' : '') + s;\n  }\n  function getCurrentPhaseKey(elapsed) {\n    var current = phases[0].key;\n    for (var i = 0; i \u0026lt; phases.length; i++) if (elapsed \u0026gt;= phases[i].t) current = phases[i].key;\n    return current;\n  }\n  function getPourKey(elapsed) {\n    for (var i = phases.length - 1; i \u0026gt;= 0; i--) {\n      var p = phases[i];\n      if (elapsed \u0026gt;= p.t \u0026amp;\u0026amp; elapsed \u0026lt; (p.t + p.pourLen)) return p.key;\n    }\n    return null;\n  }\n  function enableWakeLock() {\n    if (!('wakeLock' in navigator)) {\n      statusEl.textContent = '⚠️ Your browser does not support the screen wake lock.';\n      return Promise.resolve();\n    }\n    return navigator.wakeLock.request('screen').then(function(wl) {\n      wakeLock = wl;\n      statusEl.textContent = '✅ Screen kept on during the recipe.';\n      wakeLock.addEventListener('release', function() {\n        statusEl.textContent = 'ℹ️ Screen wake lock released.';\n      });\n    }).catch(function() {\n      statusEl.textContent = '⚠️ Unable to activate screen wake lock.';\n      wakeLock = null;\n    });\n  }\n  function disableWakeLock() {\n    if (wakeLock) { wakeLock.release(); wakeLock = null; }\n  }\n  function setButtons(running) {\n    startBtn.disabled = running;\n    stopBtn.disabled  = !running;\n    stopBtn.style.opacity = running ? '1' : '.5';\n  }\n  function tick() {\n    var elapsed = (Date.now() - startTime) \/ 1000;\n    timerEl.textContent = fmt(elapsed);\n    var phaseKey = getCurrentPhaseKey(elapsed);\n    if (phaseKey !== lastPhaseKey) {\n      setCardActive(phaseKey);\n      setPillPhaseActive(phaseKey, lastPhaseKey !== null);\n      lastPhaseKey = phaseKey;\n    }\n    var pourKey = getPourKey(elapsed);\n    if (pourKey !== lastPourKey) {\n      setPourPillActive(pourKey);\n      lastPourKey = pourKey;\n    }\n    if (elapsed \u0026gt;= END_AT) { stopRecipe(true); return; }\n    rafId = requestAnimationFrame(tick);\n  }\n  function startRecipe() {\n    setButtons(true);\n    timerEl.textContent = '0:00';\n    lastPhaseKey = null;\n    lastPourKey  = null;\n    enableWakeLock().then(function() {\n      setCardActive(1);\n      setPillPhaseActive(1, false);\n      setPourPillActive(1);\n      lastPhaseKey = 1;\n      lastPourKey  = 1;\n      startTime = Date.now();\n      if (rafId) cancelAnimationFrame(rafId);\n      rafId = requestAnimationFrame(tick);\n    });\n  }\n  function stopRecipe(autoEnded) {\n    if (rafId) cancelAnimationFrame(rafId);\n    rafId     = null;\n    startTime = null;\n    disableWakeLock();\n    setButtons(false);\n    clearAll();\n    lastPhaseKey = null;\n    lastPourKey  = null;\n    if (autoEnded) {\n      statusEl.textContent = '✅ Recipe complete — screen released.';\n      timerEl.textContent  = fmt(END_AT);\n    } else {\n      statusEl.textContent = 'ℹ️ Recipe stopped — screen released.';\n    }\n  }\n  startBtn.addEventListener('click', startRecipe);\n  stopBtn.addEventListener('click', function() { stopRecipe(false); });\n  details.addEventListener('toggle', function() { if (!details.open) stopRecipe(false); });\n  document.addEventListener('visibilitychange', function() {\n    if (document.visibilityState === 'visible' \u0026amp;\u0026amp; startTime \u0026amp;\u0026amp; !wakeLock) enableWakeLock();\n  });\n})();\n\u003c\/script\u003e\n\u003c\/svg\u003e","brand":"ZAB","offers":[{"title":"200g","offer_id":52990481629493,"sku":"CAF-SIN-FRAN-FANC-GB","price":22.95,"currency_code":"EUR","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/2716\/1842\/files\/francy-spring2026-200g-new.jpg?v=1781285595","url":"https:\/\/zabcafe.com\/en\/products\/colombie-francy-castillo-varietes-rares","provider":"Zab Café","version":"1.0","type":"link"}