[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-css-contain-rendering-isolation":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},"css-contain-rendering-isolation","CSS contain — draw the boundary",null,"\u003Cdiv class=\"pg\">\n    \u003Ch1 class=\"pg-title\">🎮 CSS \u003Ccode>contain\u003C\u002Fcode>: draw the boundary\u003C\u002Fh1>\n    \u003Cp class=\"pg-sub\">\n      Toggle \u003Ccode>contain\u003C\u002Fcode> on a noisy widget and watch which boxes the layout model\n      has to recheck — then see the one real gotcha that collapses an element to nothing.\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">1\u003C\u002Fspan> The boundary\u003C\u002Fdiv>\n      \u003Cdiv class=\"btn-row\">\n        \u003Cbutton class=\"btn primary\" id=\"update\">Update the widget\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn ghost\" id=\"resetRow\">Reset\u003C\u002Fbutton>\n        \u003Clabel class=\"toggle\" style=\"margin-left:6px\">\n          \u003Cinput type=\"checkbox\" id=\"containToggle\" \u002F>\n          \u003Cspan class=\"switch\">\u003C\u002Fspan>\n          \u003Cspan>\u003Ccode>contain: layout paint\u003C\u002Fcode> on the widget\u003C\u002Fspan>\n        \u003C\u002Flabel>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"row-stage\" id=\"rowStage\">\n        \u003Cdiv class=\"box\" id=\"widget\">\n          Widget\n          \u003Cspan class=\"big\" id=\"counter\">0\u003C\u002Fspan>\n          unread\n          \u003Cdiv class=\"box-tag\" id=\"tagWidget\">checked\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"box sibling\">Sidebar card\u003Cdiv class=\"box-tag\" id=\"tagS1\">checked\u003C\u002Fdiv>\u003C\u002Fdiv>\n        \u003Cdiv class=\"box sibling\">Sidebar card\u003Cdiv class=\"box-tag\" id=\"tagS2\">checked\u003C\u002Fdiv>\u003C\u002Fdiv>\n        \u003Cdiv class=\"box sibling\">Footer\u003Cdiv class=\"box-tag\" id=\"tagS3\">checked\u003C\u002Fdiv>\u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"status\" id=\"rowStatus\" style=\"margin-top:12px\">\n        \u003Cspan class=\"dot info\" id=\"rowDot\">\u003C\u002Fspan>\n        \u003Cspan id=\"rowStatusText\">Click \"Update the widget\" to see what has to be rechecked.\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\">\n        This half is a \u003Cstrong>model of the guarantee\u003C\u002Fstrong> \u003Ccode>contain\u003C\u002Fcode> makes —\n        not a captured performance trace. Without it, the browser can't rule out the widget's\n        change reaching its neighbors, so they stay \"in scope\" (amber). With it, the browser\n        has a promise that nothing escapes the dashed box, so the others are skipped (green).\n      \u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">2\u003C\u002Fspan> The gotcha — this part is real CSS, live in your browser\u003C\u002Fdiv>\n      \u003Cdiv class=\"seg\" id=\"sizeModeSeg\" role=\"tablist\">\n        \u003Cbutton class=\"btn sm\" data-mode=\"none\" aria-pressed=\"true\">No \u003Ccode>contain\u003C\u002Fcode>\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-mode=\"strict-nosize\">\u003Ccode>strict\u003C\u002Fcode>, no height set\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-mode=\"strict-size\">\u003Ccode>strict\u003C\u002Fcode>, height set\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"collapse-stage\">\n        \u003Cdiv class=\"collapse-box\" id=\"collapseBox\" data-mode=\"none\">\n          \u003Cp>This box has real text inside it, and (in this mode) no fixed height — its\n          height normally comes from measuring that content.\u003C\u002Fp>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"status\" id=\"collapseStatus\" style=\"margin-top:12px\">\n        \u003Cspan class=\"dot info\" id=\"collapseDot\">\u003C\u002Fspan>\n        \u003Cspan id=\"collapseStatusText\">Pick a mode above.\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\" id=\"collapseHint\">\n        \u003Ccode>size\u003C\u002Fcode> containment computes the box's size as if it had no content, on\n        purpose — so the browser never waits on the subtree to know how much space to\n        reserve. Without an explicit height to fall back on, \"as if it had no content\" means\n        the box collapses toward nothing, and paint containment clips the overflow instead\n        of letting it show.\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\u002F* Demo-specific CSS only — base.css owns tokens, .panel, .btn, .toggle, .seg, .status. *\u002F\n\n  .row-stage {\n    display: flex;\n    flex-wrap: wrap;\n    gap: 12px;\n    margin-top: 14px;\n  }\n  .box {\n    flex: 1 1 110px;\n    min-width: 100px;\n    padding: 14px 12px;\n    border: 2px dashed var(--line);\n    border-radius: var(--radius-sm);\n    background: var(--panel-2);\n    font-size: 0.82rem;\n    font-weight: 600;\n    color: var(--muted);\n    text-align: center;\n    transition: border-color .2s ease, color .2s ease;\n  }\n  .box .big {\n    display: block;\n    margin-top: 4px;\n    font-size: 1.3rem;\n    color: var(--ink);\n    font-family: var(--mono);\n  }\n  #widget.contained {\n    border-color: var(--accent);\n    border-style: solid;\n    color: var(--ink);\n  }\n  .box-tag {\n    display: inline-block;\n    margin-top: 6px;\n    font-size: 0.72rem;\n    font-weight: 700;\n    letter-spacing: .02em;\n    padding: 2px 6px;\n    border-radius: 999px;\n    opacity: 0;\n    transition: opacity .15s ease;\n  }\n  .box-tag.show { opacity: 1; }\n  .box-tag.rechecked { background: var(--warn); color: #1a1300; }\n  .box-tag.skipped { background: var(--good); color: #06220d; }\n\n  @keyframes pulseCheck {\n    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--warn) 55%, transparent); }\n    70%  { box-shadow: 0 0 0 9px color-mix(in srgb, var(--warn) 0%, transparent); }\n    100% { box-shadow: 0 0 0 0 transparent; }\n  }\n  @keyframes pulseSkip {\n    0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 45%, transparent); }\n    70%  { box-shadow: 0 0 0 6px color-mix(in srgb, var(--good) 0%, transparent); }\n    100% { box-shadow: 0 0 0 0 transparent; }\n  }\n  .box.pulse-check { animation: pulseCheck .7s ease; }\n  .box.pulse-skip { animation: pulseSkip .7s ease; }\n\n  @media (prefers-reduced-motion: reduce) {\n    .box.pulse-check, .box.pulse-skip { animation: none; }\n  }\n\n  \u002F* --- gotcha demo: this part is REAL contain, not a simulation --- *\u002F\n  .collapse-stage {\n    margin-top: 14px;\n    border-radius: var(--radius-sm);\n    background: repeating-linear-gradient(45deg, var(--bg-grid) 0 10px, transparent 10px 20px);\n    padding: 10px;\n  }\n  .collapse-box {\n    box-sizing: border-box;\n    border: 2px dashed var(--line);\n    border-radius: var(--radius-sm);\n    padding: 14px 16px;\n    background: var(--panel-2);\n    transition: height .2s ease, border-color .2s ease;\n  }\n  .collapse-box p { margin: 0; font-size: 0.92rem; line-height: 1.5; }\n  .collapse-box[data-mode=\"strict-nosize\"] { contain: strict; border-color: var(--bad); }\n  .collapse-box[data-mode=\"strict-size\"] { contain: strict; height: 110px; border-color: var(--accent); }\n  .collapse-box[data-mode=\"none\"] { contain: none; }","\"use strict\";\n  const $ = (id) => document.getElementById(id);\n\n  \u002F\u002F ---------- Panel 1: the boundary model ----------\n  let count = 0;\n\n  function flashRow(contained) {\n    const widget = $(\"widget\");\n    const siblings = [[\"tagS1\", document.querySelectorAll(\".box.sibling\")[0]],\n                      [\"tagS2\", document.querySelectorAll(\".box.sibling\")[1]],\n                      [\"tagS3\", document.querySelectorAll(\".box.sibling\")[2]]];\n\n    widget.classList.remove(\"pulse-check\", \"pulse-skip\");\n    void widget.offsetWidth; \u002F\u002F restart animation\n    widget.classList.add(\"pulse-check\");\n    $(\"tagWidget\").textContent = \"checked\";\n    $(\"tagWidget\").className = \"box-tag rechecked show\";\n\n    siblings.forEach(([tagId, el]) => {\n      el.classList.remove(\"pulse-check\", \"pulse-skip\");\n      void el.offsetWidth;\n      const tag = $(tagId);\n      if (contained) {\n        el.classList.add(\"pulse-skip\");\n        tag.textContent = \"skipped\";\n        tag.className = \"box-tag skipped show\";\n      } else {\n        el.classList.add(\"pulse-check\");\n        tag.textContent = \"rechecked\";\n        tag.className = \"box-tag rechecked show\";\n      }\n    });\n\n    setTimeout(() => {\n      [$(\"tagWidget\"), $(\"tagS1\"), $(\"tagS2\"), $(\"tagS3\")].forEach((t) => t.classList.remove(\"show\"));\n    }, 1200);\n  }\n\n  $(\"containToggle\").addEventListener(\"change\", (e) => {\n    $(\"widget\").classList.toggle(\"contained\", e.target.checked);\n    $(\"rowStatusText\").textContent = e.target.checked\n      ? \"contain is ON — update the widget and watch the siblings skip the recheck.\"\n      : \"contain is OFF (default) — update the widget and watch the recheck spread.\";\n    $(\"rowDot\").className = \"dot \" + (e.target.checked ? \"good\" : \"info\");\n  });\n\n  $(\"update\").addEventListener(\"click\", () => {\n    count += Math.floor(Math.random() * 9) + 1;\n    $(\"counter\").textContent = count;\n    const contained = $(\"containToggle\").checked;\n    flashRow(contained);\n    $(\"rowStatusText\").textContent = contained\n      ? \"Widget changed. The three neighbors were skipped — contain promised they're unaffected.\"\n      : \"Widget changed. All four boxes stayed in scope — nothing told the browser otherwise.\";\n    $(\"rowDot\").className = \"dot \" + (contained ? \"good\" : \"warn\");\n  });\n\n  $(\"resetRow\").addEventListener(\"click\", () => {\n    count = 0;\n    $(\"counter\").textContent = \"0\";\n    $(\"containToggle\").checked = false;\n    $(\"widget\").classList.remove(\"contained\");\n    $(\"rowStatusText\").textContent = 'Click \"Update the widget\" to see what has to be rechecked.';\n    $(\"rowDot\").className = \"dot info\";\n  });\n\n  \u002F\u002F ---------- Panel 2: the real collapse gotcha ----------\n  const hints = {\n    none: \"Baseline: no containment. The box sizes itself from its content, like any normal element.\",\n    \"strict-nosize\": \"contain: strict is applied with no explicit height. Size containment can't use the content to size the box, so it collapses — and paint containment clips what would've overflowed instead of letting it spill out.\",\n    \"strict-size\": \"Same contain: strict, but now with an explicit height (110px). Size containment no longer needs the content at all — the box never had to wait on it — and the text renders normally inside the box you sized.\",\n  };\n\n  $(\"sizeModeSeg\").addEventListener(\"click\", (e) => {\n    const btn = e.target.closest(\"button[data-mode]\");\n    if (!btn) return;\n    const mode = btn.dataset.mode;\n    $(\"sizeModeSeg\").querySelectorAll(\"button\").forEach((b) => b.setAttribute(\"aria-pressed\", String(b === btn)));\n    $(\"collapseBox\").dataset.mode = mode;\n    $(\"collapseHint\").textContent = hints[mode];\n    $(\"collapseStatusText\").textContent =\n      mode === \"strict-nosize\" ? \"Collapsed — open DevTools and check: this is a real browser applying contain: strict, not a trick.\" :\n      mode === \"strict-size\"   ? \"Fixed — the explicit height keeps the box usable.\" :\n                                  \"Normal height, no containment applied.\";\n    $(\"collapseDot\").className = \"dot \" + (mode === \"strict-nosize\" ? \"bad\" : mode === \"strict-size\" ? \"good\" : \"info\");\n  });",{"height":52,"autorun":17},520,{"slug":45,"title":54,"excerpt":55,"coverUrl":56,"vertical":57},"Stop Letting One Widget Reflow the Whole Page. `contain` Scopes It.","A live counter or chat widget shouldn't make the rest of your page recompute layout every time it updates. The CSS contain property draws a hard boundary the browser can actually trust — here's what it promises, and the one gotcha that breaks pages when people misuse it.","\u002Fmedia\u002Fcovers\u002Fcss-contain-rendering-isolation.png",{"slug":6},[59,63,67,71,75,79,83,87,91,95,99,103],{"slug":60,"name":61,"articles":62},"webdev","Webdev",141,{"slug":64,"name":65,"articles":66},"javascript","Javascript",114,{"slug":68,"name":69,"articles":70},"frontend","Frontend",86,{"slug":72,"name":73,"articles":74},"tutorial","Tutorial",53,{"slug":76,"name":77,"articles":78},"css","Css",43,{"slug":80,"name":81,"articles":82},"performance","Performance",23,{"slug":84,"name":85,"articles":86},"typescript","Typescript",18,{"slug":88,"name":89,"articles":90},"react","React",17,{"slug":92,"name":93,"articles":94},"browser","Browser",13,{"slug":96,"name":97,"articles":98},"node","Node",12,{"slug":100,"name":101,"articles":102},"programming","Programming",10,{"slug":104,"name":105,"articles":106},"accessibility","Accessibility",9]