[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-layout-thrashing-forced-reflow":44,"search-suggestions":58},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","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",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"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","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"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","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"slug":45,"title":46,"summary":47,"html":48,"css":49,"js":50,"config":51,"doc":53},"layout-thrashing-forced-reflow","Layout thrashing — interactive playground",null,"\u003Cdiv class=\"pg\">\n    \u003Ch1 class=\"pg-title\">🎮 Layout thrashing: read-write loops vs batched\u003C\u002Fh1>\n    \u003Cp class=\"pg-sub\">\n      Both buttons below do the exact same visual thing: expand every row in the list.\n      One reads \u003Ccode>scrollHeight\u003C\u002Fcode> and writes \u003Ccode>style.height\u003C\u002Fcode> for each row,\n      one row at a time. The other reads every \u003Ccode>scrollHeight\u003C\u002Fcode> first, then writes\n      every \u003Ccode>style.height\u003C\u002Fcode>. Time both — the browser's own clock tells you which one\n      is \u003Cstrong>layout thrashing\u003C\u002Fstrong>.\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">1\u003C\u002Fspan> Setup\u003C\u002Fdiv>\n      \u003Cdiv class=\"wrap-controls\">\n        \u003Cdiv class=\"field\">\n          \u003Clabel class=\"label\" for=\"rowCount\">Rows (\u003Cspan id=\"rowCountLabel\">400\u003C\u002Fspan>)\u003C\u002Flabel>\n          \u003Cinput type=\"range\" id=\"rowCount\" min=\"50\" max=\"1000\" step=\"50\" value=\"400\" \u002F>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"presets\" style=\"margin-top:14px\">\n        \u003Cbutton class=\"btn sm\" data-preset=\"50\">Small list (50 rows)\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"1000\">Big list (1000 rows)\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\">\n        More rows means more forced layouts for the naive version — the batched version barely\n        notices, because it only ever forces one.\n      \u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">2\u003C\u002Fspan> Run it\u003C\u002Fdiv>\n      \u003Cdiv class=\"btn-row\">\n        \u003Cbutton class=\"btn bad\" id=\"runNaive\">Expand all — naive (read+write per row)\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn good\" id=\"runBatched\">Expand all — batched (read all, then write all)\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn ghost\" id=\"collapse\">Collapse all\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"status\" id=\"status\" style=\"margin-top:12px\">\n        \u003Cspan class=\"dot info\" id=\"dot\">\u003C\u002Fspan>\u003Cspan id=\"statusText\">Pick a run — both expand the same list below.\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">3\u003C\u002Fspan> The list\u003C\u002Fdiv>\n      \u003Cdiv class=\"rows\" id=\"rows\">\u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">4\u003C\u002Fspan> Result\u003C\u002Fdiv>\n      \u003Cdiv class=\"compare\">\n        \u003Cdiv class=\"card\">\n          \u003Cdiv class=\"k\">Naive (interleaved read\u002Fwrite)\u003C\u002Fdiv>\n          \u003Cdiv class=\"big-num\" id=\"naiveTime\">—\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"card\">\n          \u003Cdiv class=\"k\">Batched (read all, then write all)\u003C\u002Fdiv>\n          \u003Cdiv class=\"big-num\" id=\"batchedTime\">—\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"banner info show\" id=\"diffBanner\" style=\"margin-top:12px\">\n        Run both at least once to see the difference, measured live in your own browser.\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\">\n        Open your browser's performance profiler, click \"naive,\" and look for the purple\n        \"Forced reflow\" bars — one per row. The batched run won't have them.\n      \u003C\u002Fp>\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.rows {\n    max-height: 260px;\n    overflow-y: auto;\n    border: 1px solid var(--line);\n    border-radius: var(--radius-sm);\n    background: var(--panel-2);\n    padding: 8px;\n  }\n  .row {\n    border-bottom: 1px dashed var(--line);\n    padding: 8px 6px;\n  }\n  .row:last-child { border-bottom: none; }\n  .row-title {\n    font-weight: 600;\n    font-size: 0.85rem;\n    color: var(--ink);\n  }\n  .row-content {\n    height: 0;\n    overflow: hidden;\n    font-size: 0.8rem;\n    color: var(--muted);\n    line-height: 1.5;\n  }\n  .compare {\n    display: grid;\n    grid-template-columns: 1fr 1fr;\n    gap: 12px;\n    margin-top: 12px;\n  }\n  .compare .card {\n    border: 1px solid var(--line);\n    border-radius: var(--radius-sm);\n    padding: 12px;\n    background: var(--card);\n  }\n  .compare .card .k { font-size: 0.75rem; color: var(--muted); margin-bottom: 4px; }","\"use strict\";\n  const $ = (id) => document.getElementById(id);\n  const rowsEl = $(\"rows\");\n  let naiveMs = null, batchedMs = null;\n\n  const LOREM = [\n    \"Changes take effect the next time you open the settings panel.\",\n    \"This option is synced across every device signed in to your account.\",\n    \"Disabling this may slow down background updates for related features.\",\n    \"You can override this per-project in the advanced configuration file.\",\n    \"Most teams leave this on; a few turn it off for compliance reasons.\",\n  ];\n\n  function setStatus(text, kind) {\n    $(\"statusText\").textContent = text;\n    $(\"dot\").className = \"dot \" + kind;\n  }\n\n  function buildRows(n) {\n    rowsEl.innerHTML = \"\";\n    const frag = document.createDocumentFragment();\n    for (let i = 0; i \u003C n; i++) {\n      const row = document.createElement(\"div\");\n      row.className = \"row\";\n      const title = document.createElement(\"div\");\n      title.className = \"row-title\";\n      title.textContent = \"FAQ item \" + (i + 1);\n      const content = document.createElement(\"div\");\n      content.className = \"row-content\";\n      const sentences = (i % 3) + 1;\n      let text = \"\";\n      for (let s = 0; s \u003C sentences; s++) text += LOREM[(i + s) % LOREM.length] + \" \";\n      content.textContent = text;\n      row.appendChild(title);\n      row.appendChild(content);\n      frag.appendChild(row);\n    }\n    rowsEl.appendChild(frag);\n  }\n\n  function collapseAll() {\n    const contents = rowsEl.querySelectorAll(\".row-content\");\n    contents.forEach((c) => { c.style.height = \"0px\"; });\n  }\n\n  \u002F\u002F The naive version: read this row's scrollHeight, write this row's height —\n  \u002F\u002F then move to the NEXT row and read again. That next read can only be answered\n  \u002F\u002F correctly if the browser recalculates layout first, because the previous\n  \u002F\u002F row's write just invalidated it. One forced layout per row.\n  function expandNaive() {\n    const contents = rowsEl.querySelectorAll(\".row-content\");\n    const t0 = performance.now();\n    contents.forEach((c) => {\n      const h = c.scrollHeight; \u002F\u002F read — forces a flush if a prior write is pending\n      c.style.height = h + \"px\"; \u002F\u002F write — invalidates layout for the next read\n    });\n    return performance.now() - t0;\n  }\n\n  \u002F\u002F The batched version: read every scrollHeight first (one pass, one flush),\n  \u002F\u002F THEN write every height (a second pass, no further reads in between).\n  function expandBatched() {\n    const contents = rowsEl.querySelectorAll(\".row-content\");\n    const t0 = performance.now();\n    const heights = [];\n    contents.forEach((c) => heights.push(c.scrollHeight)); \u002F\u002F all reads\n    contents.forEach((c, i) => { c.style.height = heights[i] + \"px\"; }); \u002F\u002F all writes\n    return performance.now() - t0;\n  }\n\n  function updateDiff() {\n    if (naiveMs == null || batchedMs == null) return;\n    const factor = batchedMs > 0 ? (naiveMs \u002F batchedMs) : naiveMs;\n    $(\"diffBanner\").innerHTML =\n      \"Naive took \u003Ccode>\" + naiveMs.toFixed(1) + \"ms\u003C\u002Fcode>, batched took \u003Ccode>\" +\n      batchedMs.toFixed(1) + \"ms\u003C\u002Fcode> — about \u003Ccode>\" + factor.toFixed(1) +\n      \"×\u003C\u002Fcode> slower for identical output. Numbers vary by device; the ratio is the point.\";\n  }\n\n  $(\"runNaive\").addEventListener(\"click\", () => {\n    collapseAll();\n    requestAnimationFrame(() => {\n      const ms = expandNaive();\n      naiveMs = ms;\n      $(\"naiveTime\").textContent = ms.toFixed(1) + \"ms\";\n      setStatus(\"Naive run: one forced layout per row, \" + rowsEl.children.length + \" rows.\", \"bad\");\n      updateDiff();\n    });\n  });\n\n  $(\"runBatched\").addEventListener(\"click\", () => {\n    collapseAll();\n    requestAnimationFrame(() => {\n      const ms = expandBatched();\n      batchedMs = ms;\n      $(\"batchedTime\").textContent = ms.toFixed(1) + \"ms\";\n      setStatus(\"Batched run: one forced layout total, \" + rowsEl.children.length + \" rows.\", \"good\");\n      updateDiff();\n    });\n  });\n\n  $(\"collapse\").addEventListener(\"click\", () => {\n    collapseAll();\n    setStatus(\"Collapsed. Pick a run.\", \"info\");\n  });\n\n  $(\"rowCount\").addEventListener(\"input\", (e) => {\n    $(\"rowCountLabel\").textContent = e.target.value;\n    buildRows(Number(e.target.value));\n    naiveMs = batchedMs = null;\n    $(\"naiveTime\").textContent = \"—\";\n    $(\"batchedTime\").textContent = \"—\";\n    setStatus(\"List rebuilt with \" + e.target.value + \" rows. Pick a run.\", \"info\");\n  });\n\n  document.querySelectorAll(\"[data-preset]\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => {\n      const n = Number(btn.dataset.preset);\n      $(\"rowCount\").value = n;\n      $(\"rowCountLabel\").textContent = n;\n      buildRows(n);\n      naiveMs = batchedMs = null;\n      $(\"naiveTime\").textContent = \"—\";\n      $(\"batchedTime\").textContent = \"—\";\n      setStatus(\"List rebuilt with \" + n + \" rows. Pick a run.\", \"info\");\n    });\n  });\n\n  buildRows(400);",{"height":52,"autorun":17},520,{"slug":45,"title":54,"excerpt":55,"coverUrl":56,"vertical":57},"Your 'Expand All' Button Is Quietly Causing Layout Thrashing","An 'expand all' click that stutters isn't a slow CPU — it's layout thrashing, a loop that forces the browser to recalculate layout once per row instead of once per click. Here's how to spot it and the two-line fix.","\u002Fmedia\u002Fcovers\u002Flayout-thrashing-forced-reflow.png",{"slug":6},[59,63,67,71,75,79,83,87,91,95,99,103],{"slug":60,"name":61,"articles":62},"webdev","Webdev",146,{"slug":64,"name":65,"articles":66},"javascript","Javascript",119,{"slug":68,"name":69,"articles":70},"frontend","Frontend",87,{"slug":72,"name":73,"articles":74},"tutorial","Tutorial",56,{"slug":76,"name":77,"articles":78},"css","Css",44,{"slug":80,"name":81,"articles":82},"performance","Performance",24,{"slug":84,"name":85,"articles":86},"typescript","Typescript",19,{"slug":88,"name":89,"articles":90},"react","React",18,{"slug":92,"name":93,"articles":94},"browser","Browser",14,{"slug":96,"name":97,"articles":98},"node","Node",12,{"slug":100,"name":101,"articles":102},"programming","Programming",11,{"slug":104,"name":105,"articles":106},"accessibility","Accessibility",10]