[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"playground-requestidlecallback-can-wait-forever":3,"verticals":19,"search-suggestions":58},{"slug":4,"title":5,"summary":6,"html":7,"css":8,"js":9,"config":10,"doc":13},"requestidlecallback-can-wait-forever","requestIdleCallback vs. a busy page — interactive playground",null,"\u003Cdiv class=\"pg\">\n    \u003Ch1 class=\"pg-title\">🎮 requestIdleCallback vs. a busy page\u003C\u002Fh1>\n    \u003Cp class=\"pg-sub\">\n      Turn up the simulated load below, then schedule a background task with\n      \u003Ccode>requestIdleCallback\u003C\u002Fcode> and watch whether the browser ever finds\n      a moment to run it.\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"banner info\" id=\"supportBanner\" style=\"display:none\">\u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">1\u003C\u002Fspan> Simulate main-thread load\u003C\u002Fdiv>\n      \u003Cdiv class=\"field\">\n        \u003Clabel class=\"ctl-label\" for=\"load\">Work per animation frame: \u003Cspan id=\"loadVal\">0\u003C\u002Fspan> ms\u003C\u002Flabel>\n        \u003Cinput type=\"range\" id=\"load\" min=\"0\" max=\"35\" step=\"1\" value=\"0\" \u002F>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"presets\" style=\"margin-top:12px\">\n        \u003Cbutton class=\"btn sm\" data-preset=\"0\">Quiet page\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"10\">Normal scrolling\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"30\">Heavy load (near-never idle)\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\">\n        This is real work running on this page's own main thread — the same way a\n        scroll handler, an animation, or a chat widget keeps a real page busy.\n        Higher values leave less (or no) idle time for the browser to hand out.\n      \u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">2\u003C\u002Fspan> Schedule a background task\u003C\u002Fdiv>\n      \u003Cdiv class=\"btn-row\">\n        \u003Cbutton class=\"btn primary\" id=\"schedule\">Schedule idle task\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn ghost\" id=\"cancel\">Cancel pending\u003C\u002Fbutton>\n        \u003Clabel class=\"toggle\" style=\"margin-left:6px\">\n          \u003Cinput type=\"checkbox\" id=\"timeout\" \u002F>\u003Cspan class=\"switch\">\u003C\u002Fspan>\n          \u003Cspan>force with \u003Ccode>timeout: 1500\u003C\u002Fcode>\u003C\u002Fspan>\n        \u003C\u002Flabel>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"status\" id=\"status\" style=\"margin-top:14px\">\n        \u003Cspan class=\"dot info\" id=\"dot\">\u003C\u002Fspan>\u003Cspan id=\"statusText\">Ready — schedule a task and watch it wait.\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\" id=\"hint\">Nothing scheduled yet.\u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">3\u003C\u002Fspan> Log\u003C\u002Fdiv>\n      \u003Cdiv class=\"log\" id=\"log\">\u003C\u002Fdiv>\n      \u003Cdiv class=\"btn-row\" style=\"margin-top:10px\">\n        \u003Cbutton class=\"btn sm ghost\" id=\"clearLog\">Clear log\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>","\u002F*\n * Shared playground design system — daily-post\n * ============================================\n * Link it from any post's playground\u002Findex.html (no per-post boilerplate):\n *\n *   \u003Clink rel=\"stylesheet\" href=\"..\u002F..\u002F..\u002Fplayground\u002Fassets\u002Fbase.css\" \u002F>\n *\n * The relative path resolves the same locally (posts\u002F\u003Cbase>\u002Fplayground\u002F) and on\n * the deployed host (public\u002Fposts\u002F\u003Cbase>\u002Fplayground\u002F), because build-site.js\n * mirrors this file to public\u002Fplayground\u002Fassets\u002Fbase.css. For a CodeSandbox\n * --embed, playground.js inlines this file so the single-file sandbox still styles.\n *\n * Then write ONLY the demo-specific CSS in your inline \u003Cstyle>. Everything below\n * — tokens, layout, panels, buttons, toggles, sliders, code blocks, status\u002Flog,\n * banners, presets — comes for free and looks consistent across every post.\n *\n * Theme-aware: dark by default, light via prefers-color-scheme AND a\n * [data-theme] override on \u003Chtml> (so a toggle can force either). Accessible:\n * real focus rings, adequate contrast, reduced-motion honored.\n *\u002F\n\n\u002F* ---------- design tokens ---------- *\u002F\n:root {\n  --bg: #0d1117;\n  --bg-grid: #ffffff0a;\n  --panel: #161b22;\n  --panel-2: #0b0e14;\n  --card: #0b0e14;\n  --ink: #e8ebf2;\n  --muted: #9aa4b2;\n  --faint: #6b7688;\n  --line: #232a35;\n  --line-soft: #ffffff12;\n  --accent: #7c9cff;\n  --accent-ink: #0b0e14;\n  --good: #3fb950;\n  --warn: #f0883e;\n  --bad: #f85149;\n  --info: #58a6ff;\n  --radius: 14px;\n  --radius-sm: 9px;\n  --shadow: 0 8px 30px #0006;\n  --shadow-sm: 0 4px 14px #0005;\n  --mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n  --sans: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif;\n  color-scheme: dark;\n}\n\n\u002F* Light theme — system preference, overridable by [data-theme] on \u003Chtml>. *\u002F\n@media (prefers-color-scheme: light) {\n  :root:not([data-theme=\"dark\"]) {\n    --bg: #f4f6fb;\n    --bg-grid: #00000008;\n    --panel: #ffffff;\n    --panel-2: #f0f3f9;\n    --card: #f7f9fc;\n    --ink: #131722;\n    --muted: #5b6472;\n    --faint: #8a93a3;\n    --line: #e2e7f0;\n    --line-soft: #00000010;\n    --accent: #2f6df0;\n    --accent-ink: #ffffff;\n    --good: #1a7f37;\n    --warn: #bc4c00;\n    --bad: #cf222e;\n    --info: #0969da;\n    --shadow: 0 8px 30px #0000001f;\n    --shadow-sm: 0 4px 14px #00000014;\n    color-scheme: light;\n  }\n}\n:root[data-theme=\"light\"] {\n  --bg: #f4f6fb;\n  --bg-grid: #00000008;\n  --panel: #ffffff;\n  --panel-2: #f0f3f9;\n  --card: #f7f9fc;\n  --ink: #131722;\n  --muted: #5b6472;\n  --faint: #8a93a3;\n  --line: #e2e7f0;\n  --line-soft: #00000010;\n  --accent: #2f6df0;\n  --accent-ink: #ffffff;\n  --good: #1a7f37;\n  --warn: #bc4c00;\n  --bad: #cf222e;\n  --info: #0969da;\n  --shadow: 0 8px 30px #0000001f;\n  --shadow-sm: 0 4px 14px #00000014;\n  color-scheme: light;\n}\n\n\u002F* ---------- reset & base ---------- *\u002F\n*, *::before, *::after { box-sizing: border-box; }\n\nbody {\n  margin: 0;\n  min-height: 100vh;\n  font-family: var(--sans);\n  color: var(--ink);\n  background:\n    radial-gradient(circle at 1px 1px, var(--bg-grid) 1px, transparent 0) 0 0 \u002F 24px 24px,\n    var(--bg);\n  display: flex;\n  justify-content: center;\n  align-items: flex-start;\n  padding: clamp(14px, 3vw, 26px);\n  line-height: 1.55;\n  -webkit-font-smoothing: antialiased;\n  text-rendering: optimizeLegibility;\n}\n\n\u002F* The single content column. *\u002F\n.pg {\n  width: 100%;\n  max-width: 860px;\n}\n\n\u002F* ---------- header ---------- *\u002F\n.pg-title {\n  font-size: clamp(1.15rem, 1rem + 1vw, 1.4rem);\n  font-weight: 800;\n  letter-spacing: -0.02em;\n  margin: 0 0 4px;\n  display: flex;\n  align-items: center;\n  gap: 9px;\n}\n.pg-sub {\n  color: var(--muted);\n  font-size: 0.92rem;\n  line-height: 1.5;\n  margin: 0 0 16px;\n  max-width: 66ch;\n}\n.pg-sub strong, .pg-sub b { color: var(--ink); font-weight: 700; }\n.pg-sub code { font-family: var(--mono); background: var(--line-soft); color: var(--accent);\n  padding: 1px 6px; border-radius: 6px; font-size: 0.85em; }\n\n\u002F* ---------- panels ---------- *\u002F\n.panel {\n  background: var(--panel);\n  border: 1px solid var(--line);\n  border-radius: var(--radius);\n  padding: 16px 18px;\n  margin-bottom: 14px;\n  box-shadow: var(--shadow-sm);\n}\n.panel-label {\n  font-size: 0.7rem;\n  text-transform: uppercase;\n  letter-spacing: 0.08em;\n  color: var(--muted);\n  font-weight: 800;\n  margin: 0 0 13px;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n.panel-label .n {\n  display: inline-grid; place-items: center;\n  width: 18px; height: 18px; border-radius: 50%;\n  background: var(--accent); color: var(--accent-ink);\n  font-size: 0.66rem; font-weight: 800;\n}\n\n\u002F* ---------- layout helpers ---------- *\u002F\n.row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }\n.row.between { justify-content: space-between; }\n.col { display: flex; flex-direction: column; gap: 10px; }\n.grow { flex: 1 1 0; min-width: 0; }\n.wrap-controls { display: flex; flex-wrap: wrap; gap: 16px 24px; align-items: flex-end; }\n.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }\n.field > .label, .ctl-label {\n  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;\n  color: var(--muted); font-weight: 800;\n}\n\n\u002F* ---------- buttons ---------- *\u002F\n.btn {\n  font: inherit; font-weight: 650; font-size: 0.88rem;\n  cursor: pointer; user-select: none;\n  border: 1px solid var(--line);\n  background: var(--card); color: var(--ink);\n  padding: 9px 15px; border-radius: var(--radius-sm);\n  display: inline-flex; align-items: center; gap: 8px;\n  transition: background .15s ease, border-color .15s ease, transform .05s ease, color .15s ease;\n}\n.btn:hover { background: var(--line-soft); border-color: var(--muted); }\n.btn:active { transform: translateY(1px); }\n.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n.btn[disabled] { opacity: 0.45; cursor: not-allowed; }\n.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }\n.btn.primary:hover { filter: brightness(1.08); background: var(--accent); }\n.btn.good { background: var(--good); border-color: var(--good); color: #04140a; }\n.btn.bad  { background: var(--bad);  border-color: var(--bad);  color: #fff; }\n.btn.ghost { background: transparent; }\n.btn.sm { padding: 6px 11px; font-size: 0.82rem; }\n\n.btn-row { display: flex; flex-wrap: wrap; gap: 9px; }\n\n\u002F* Segmented control — a row of buttons where one is active (aria-pressed). *\u002F\n.seg { display: inline-flex; background: var(--card); border: 1px solid var(--line);\n  border-radius: var(--radius-sm); padding: 3px; gap: 3px; }\n.seg button {\n  font: inherit; font-weight: 650; font-size: 0.84rem; cursor: pointer;\n  border: none; background: transparent; color: var(--muted);\n  padding: 7px 13px; border-radius: 7px;\n  transition: background .15s ease, color .15s ease;\n}\n.seg button:hover { color: var(--ink); }\n.seg button[aria-pressed=\"true\"] { background: var(--accent); color: var(--accent-ink); }\n.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* Preset chips — worked-example shortcuts. *\u002F\n.presets { display: flex; flex-wrap: wrap; gap: 8px; }\n.presets .btn { background: var(--panel-2); }\n\n\u002F* ---------- toggle switch ---------- *\u002F\n.toggle { display: inline-flex; align-items: center; gap: 10px; cursor: pointer;\n  font-size: 0.88rem; font-weight: 600; user-select: none; }\n.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }\n.switch { position: relative; width: 44px; height: 25px; border-radius: 999px; flex: none;\n  background: var(--line); border: 1px solid var(--line); transition: background .18s ease; }\n.switch::after { content: \"\"; position: absolute; top: 2px; left: 2px; width: 19px; height: 19px;\n  border-radius: 50%; background: #cfd6e4; transition: transform .18s ease; box-shadow: 0 1px 3px #0006; }\n.toggle input:checked + .switch { background: var(--good); border-color: var(--good); }\n.toggle input:checked + .switch::after { transform: translateX(19px); background: #fff; }\n.toggle input:focus-visible + .switch { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* ---------- range slider ---------- *\u002F\ninput[type=\"range\"] {\n  -webkit-appearance: none; appearance: none; width: 100%; height: 6px;\n  background: var(--line); border-radius: 999px; outline: none; cursor: pointer; margin: 8px 0;\n}\ninput[type=\"range\"]::-webkit-slider-thumb {\n  -webkit-appearance: none; appearance: none; width: 19px; height: 19px; border-radius: 50%;\n  background: var(--accent); border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]::-moz-range-thumb {\n  width: 19px; height: 19px; border-radius: 50%; background: var(--accent);\n  border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }\n\n\u002F* ---------- text inputs \u002F selects ---------- *\u002F\ninput[type=\"text\"], input[type=\"number\"], input[type=\"search\"], select, textarea {\n  font: inherit; color: var(--ink); background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 8px 11px; width: 100%;\n}\ntextarea { font-family: var(--mono); font-size: 0.85rem; line-height: 1.6; resize: vertical; }\ninput:focus-visible, select:focus-visible, textarea:focus-visible {\n  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);\n}\n\n\u002F* ---------- status \u002F output \u002F log ---------- *\u002F\n.status {\n  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;\n  font-size: 0.9rem; font-weight: 600;\n  padding: 11px 14px; border-radius: var(--radius-sm);\n  background: var(--panel-2); border: 1px solid var(--line); color: var(--ink);\n}\n.status.good { border-color: color-mix(in srgb, var(--good) 55%, var(--line)); }\n.status.warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); }\n.status.bad  { border-color: color-mix(in srgb, var(--bad) 55%, var(--line)); }\n.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }\n.dot.good { background: var(--good); } .dot.warn { background: var(--warn); }\n.dot.bad { background: var(--bad); } .dot.info { background: var(--info); }\n\n.output { background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; }\n.log { font-family: var(--mono); font-size: 0.8rem; line-height: 1.7; background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 11px 14px;\n  max-height: 220px; overflow-y: auto; white-space: pre-wrap; }\n\n\u002F* ---------- code block + syntax tokens ---------- *\u002F\n.code {\n  font-family: var(--mono); font-size: 0.82rem; line-height: 1.7;\n  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; overflow-x: auto; white-space: pre; tab-size: 2;\n}\n.code .tok-sel  { color: var(--accent); }\n.code .tok-prop { color: #86e29b; }\n.code .tok-kw   { color: #ff9bcb; }\n.code .tok-str  { color: #f0c674; }\n.code .tok-num  { color: #f0883e; }\n.code .tok-cmt  { color: var(--faint); font-style: italic; }\n.code .hl { background: color-mix(in srgb, var(--good) 22%, transparent); border-radius: 4px; padding: 0 3px; }\n\n\u002F* ---------- hint \u002F caption ---------- *\u002F\n.hint { font-size: 0.83rem; color: var(--muted); line-height: 1.55; margin: 10px 0 0; }\n.hint code { font-family: var(--mono); color: var(--ink); background: var(--line-soft);\n  padding: 1px 5px; border-radius: 5px; font-size: 0.86em; }\n.hint strong { color: var(--ink); }\n\n\u002F* ---------- banner (feature-support \u002F notices) ---------- *\u002F\n.banner {\n  display: none; margin-bottom: 14px; padding: 11px 14px; border-radius: 11px;\n  font-size: 0.85rem; line-height: 1.5;\n  color: var(--ink); background: color-mix(in srgb, var(--warn) 12%, var(--panel));\n  border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--line));\n}\n.banner.show { display: block; }\n.banner.info { background: color-mix(in srgb, var(--info) 12%, var(--panel));\n  border-color: color-mix(in srgb, var(--info) 45%, var(--line)); }\n.banner code { font-family: var(--mono); background: var(--line-soft); padding: 1px 5px; border-radius: 5px; }\n\n\u002F* ---------- misc atoms ---------- *\u002F\n.pill { display: inline-flex; align-items: center; gap: 6px; font-size: 0.75rem; font-weight: 700;\n  padding: 3px 9px; border-radius: 999px; background: var(--line-soft); color: var(--muted); }\n.pill.good { color: var(--good); background: color-mix(in srgb, var(--good) 16%, transparent); }\n.pill.bad  { color: var(--bad);  background: color-mix(in srgb, var(--bad) 16%, transparent); }\n.mono { font-family: var(--mono); }\n.muted { color: var(--muted); }\n.big-num { font-family: var(--mono); font-size: 1.6rem; font-weight: 800; letter-spacing: -0.02em; }\n\n\u002F* Optional theme toggle button (top-right). Wire it in JS if you want it. *\u002F\n.theme-btn { position: fixed; top: 12px; right: 12px; z-index: 20; }\n\n\u002F* ---------- responsive ---------- *\u002F\n@media (max-width: 560px) {\n  .panel { padding: 14px; }\n  .wrap-controls { gap: 14px; }\n  .btn { padding: 9px 13px; }\n}\n\n\u002F* ---------- reduced motion ---------- *\u002F\n@media (prefers-reduced-motion: reduce) {\n  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }\n}\n\n\n\u002F* Demo-specific CSS only — base.css owns tokens, layout, panels, buttons. *\u002F\n  .log-entry { padding: 2px 0; }\n  .log-entry.warn { color: var(--warn); }\n  .log-entry.bad { color: var(--bad); }\n  .log-entry.good { color: var(--good); }\n  .log-entry.muted { color: var(--faint); }","\"use strict\";\n  const $ = (id) => document.getElementById(id);\n\n  let idleHandle = null;\n  let scheduledAt = null;\n  let tickInterval = null;\n  let usingFallback = false;\n\n  const nativeRIC = window.requestIdleCallback;\n  const nativeCIC = window.cancelIdleCallback;\n\n  \u002F\u002F Same shim shape as the post: it fakes the API's signature, not real idle\n  \u002F\u002F detection. setTimeout has no idea whether the main thread is actually free.\n  function shimRIC(cb, opts) {\n    const start = Date.now();\n    const forced = !!(opts && opts.timeout);\n    return setTimeout(() => {\n      cb({\n        didTimeout: forced,\n        timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),\n      });\n    }, 1);\n  }\n\n  const requestIdle = nativeRIC ? nativeRIC.bind(window) : shimRIC;\n  const cancelIdle = nativeCIC ? nativeCIC.bind(window) : clearTimeout;\n\n  if (!nativeRIC) {\n    usingFallback = true;\n    const b = $(\"supportBanner\");\n    b.style.display = \"block\";\n    b.innerHTML = \"Your browser doesn't implement \u003Ccode>requestIdleCallback\u003C\u002Fcode> natively \" +\n      \"(expected in Safari) — this demo is running the \u003Ccode>setTimeout\u003C\u002Fcode> fallback from \" +\n      \"the post, which can't actually detect idle time.\";\n  }\n\n  \u002F\u002F --- Busy-page simulation: real work on THIS page's main thread. ---\n  function busyFrame() {\n    const load = Number($(\"load\").value);\n    if (load > 0) {\n      const end = performance.now() + load;\n      while (performance.now() \u003C end) {\n        \u002F* burn CPU on purpose — this is the \"page that never idles\" *\u002F\n      }\n    }\n    requestAnimationFrame(busyFrame);\n  }\n  requestAnimationFrame(busyFrame);\n\n  $(\"load\").addEventListener(\"input\", () => {\n    $(\"loadVal\").textContent = $(\"load\").value;\n  });\n\n  document.querySelectorAll(\"[data-preset]\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => {\n      const v = btn.dataset.preset;\n      $(\"load\").value = v;\n      $(\"loadVal\").textContent = v;\n    });\n  });\n\n  \u002F\u002F --- Log + status helpers ---\n  function fmtClock(d) {\n    return d.toLocaleTimeString([], { hour12: false }) + \".\" + String(d.getMilliseconds()).padStart(3, \"0\");\n  }\n\n  function log(msg, kind) {\n    const el = document.createElement(\"div\");\n    el.className = \"log-entry\" + (kind ? \" \" + kind : \"\");\n    el.textContent = msg;\n    $(\"log\").prepend(el);\n  }\n\n  function setStatus(text, kind) {\n    $(\"statusText\").textContent = text;\n    $(\"dot\").className = \"dot \" + (kind || \"info\");\n  }\n\n  \u002F\u002F --- Schedule \u002F fire \u002F cancel ---\n  $(\"schedule\").addEventListener(\"click\", () => {\n    if (idleHandle !== null) return; \u002F\u002F one in flight keeps the log readable\n\n    scheduledAt = performance.now();\n    const usingTimeout = $(\"timeout\").checked;\n    setStatus(\"Scheduled — waiting for an idle moment…\", \"warn\");\n    $(\"hint\").textContent = \"Watching the clock. If the page stays busy, this can take a while — or never fire before you cancel it.\";\n    log(`[${fmtClock(new Date())}] scheduled${usingTimeout ? \" (timeout: 1500ms)\" : \"\"}`);\n\n    tickInterval = setInterval(() => {\n      if (idleHandle === null) return;\n      const waited = Math.round(performance.now() - scheduledAt);\n      $(\"statusText\").textContent = `Waiting for idle… ${waited}ms so far`;\n    }, 100);\n\n    const opts = usingTimeout ? { timeout: 1500 } : undefined;\n    idleHandle = requestIdle((deadline) => {\n      const waited = Math.round(performance.now() - scheduledAt);\n      clearInterval(tickInterval);\n      idleHandle = null;\n\n      const timedOut = !!deadline.didTimeout;\n      const tr = Math.round(deadline.timeRemaining() * 10) \u002F 10;\n      const kind = timedOut ? \"warn\" : \"good\";\n\n      setStatus(`Fired after ${waited}ms${timedOut ? \" (forced by timeout)\" : \"\"}`, kind);\n      $(\"hint\").textContent = timedOut\n        ? \"The browser never found real idle time, so the timeout forced this to run anyway — at zero time remaining.\"\n        : \"The browser found a real gap and ran this at low priority, without blocking anything else.\";\n      log(\n        `[${fmtClock(new Date())}] fired after ${waited}ms — didTimeout: ${timedOut} — timeRemaining: ${tr}ms${usingFallback ? \" (shimmed)\" : \"\"}`,\n        kind\n      );\n    }, opts);\n  });\n\n  $(\"cancel\").addEventListener(\"click\", () => {\n    if (idleHandle === null) return;\n    cancelIdle(idleHandle);\n    clearInterval(tickInterval);\n    const waited = Math.round(performance.now() - scheduledAt);\n    idleHandle = null;\n    setStatus(\"Cancelled — it never got a moment.\", \"bad\");\n    log(`[${fmtClock(new Date())}] cancelled after ${waited}ms — still waiting for idle`, \"bad\");\n  });\n\n  $(\"clearLog\").addEventListener(\"click\", () => {\n    $(\"log\").innerHTML = \"\";\n  });",{"height":11,"autorun":12},520,true,{"slug":4,"title":14,"excerpt":15,"coverUrl":16,"vertical":17},"The Background Task That Waited 40 Seconds for 'Idle'","requestIdleCallback promises to run only when the browser has spare cycles — but on a page that's never truly idle, that promise has no upper bound. Here's why the callback can stall, why the timeout escape hatch has its own trap, and when to reach for it instead of setTimeout or scheduler.yield.","\u002Fmedia\u002Fcovers\u002Frequestidlecallback-can-wait-forever.png",{"slug":18},"dev",[20,34,46],{"id":21,"slug":18,"name":22,"tagline":23,"description":24,"accentFrom":25,"accentTo":26,"icon":27,"defaultLocale":28,"locales":29,"features":31,"position":33},"019fe637-3d33-714b-b57f-23e163ffca0c","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[28,30],"fa",{"courses":12,"paths":12,"articles":12,"exams":32,"flashcards":32,"packages":12,"community":12,"certificates":12,"teams":12,"commerce":12},false,0,{"id":35,"slug":36,"name":37,"tagline":38,"description":39,"accentFrom":40,"accentTo":25,"icon":41,"defaultLocale":28,"locales":42,"features":44,"position":45},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[28,30,43],"es",{"courses":32,"paths":32,"articles":12,"exams":32,"flashcards":12,"packages":32,"community":12,"certificates":12,"teams":32,"commerce":32},2,{"id":47,"slug":48,"name":49,"tagline":50,"description":51,"accentFrom":52,"accentTo":53,"icon":54,"defaultLocale":28,"locales":55,"features":56,"position":57},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[28],{"courses":32,"paths":32,"articles":12,"exams":32,"flashcards":32,"packages":32,"community":12,"certificates":32,"teams":32,"commerce":32},3,[59,63,67,71,75,79,83,87,91,95,98,102],{"slug":60,"name":61,"articles":62},"webdev","Webdev",111,{"slug":64,"name":65,"articles":66},"javascript","Javascript",93,{"slug":68,"name":69,"articles":70},"frontend","Frontend",73,{"slug":72,"name":73,"articles":74},"tutorial","Tutorial",39,{"slug":76,"name":77,"articles":78},"css","Css",36,{"slug":80,"name":81,"articles":82},"typescript","Typescript",16,{"slug":84,"name":85,"articles":86},"performance","Performance",14,{"slug":88,"name":89,"articles":90},"react","React",13,{"slug":92,"name":93,"articles":94},"browser","Browser",10,{"slug":96,"name":97,"articles":94},"node","Node",{"slug":99,"name":100,"articles":101},"html","Html",8,{"slug":103,"name":104,"articles":105},"accessibility","Accessibility",7]