[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-inert-attribute-disable-interaction":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},"inert-attribute-disable-interaction","inert vs aria-hidden — the modal focus leak",null,"\u003Cdiv class=\"pg\">\n    \u003Ch1 class=\"pg-title\">🔒 inert vs aria-hidden: the modal focus leak\u003C\u002Fh1>\n    \u003Cp class=\"pg-sub\">\n      Pick a background-protection mode, open the modal, then really try to\n      click or Tab to the button behind it. This runs in your actual browser —\n      only one mode genuinely stops you.\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">1\u003C\u002Fspan> Background protection mode\u003C\u002Fdiv>\n      \u003Cdiv class=\"seg\" id=\"modeSeg\" role=\"tablist\" aria-label=\"Protection mode\">\n        \u003Cbutton class=\"seg-btn\" data-mode=\"none\" aria-pressed=\"true\">Unprotected\u003C\u002Fbutton>\n        \u003Cbutton class=\"seg-btn\" data-mode=\"aria-hidden\" aria-pressed=\"false\">aria-hidden only (old way)\u003C\u002Fbutton>\n        \u003Cbutton class=\"seg-btn\" data-mode=\"inert\" aria-pressed=\"false\">inert (new way)\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"btn-row\" style=\"margin-top:14px\">\n        \u003Cbutton class=\"btn primary\" id=\"openModal\">Open modal\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn ghost\" id=\"resetBtn\">Reset\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"presets\" style=\"margin-top:14px\">\n        \u003Cbutton class=\"btn sm\" data-preset=\"none\">Show: no protection\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"aria-hidden\">Show: the old bug\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"inert\">Show: the fix\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\">Switching modes takes effect immediately, even while the modal is already open.\u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel stageWrap\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">2\u003C\u002Fspan> The page (background) + the modal\u003C\u002Fdiv>\n\n      \u003Cdiv id=\"background\" class=\"bg-area\">\n        \u003Cp class=\"muted\" style=\"margin:0\">This is \"the rest of the page,\" sitting behind the modal.\u003C\u002Fp>\n        \u003Cbutton class=\"btn sm bad\" id=\"trapBtn\">🪤 Delete account (background button)\u003C\u002Fbutton>\n        \u003Cinput class=\"bg-input\" type=\"text\" placeholder=\"Type here (background input)\" \u002F>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv id=\"modal\" class=\"modal-box\" hidden>\n        \u003Cdiv class=\"modal-inner\">\n          \u003Ch3>Delete account?\u003C\u002Fh3>\n          \u003Cp>This can't be undone.\u003C\u002Fp>\n          \u003Cdiv class=\"btn-row\">\n            \u003Cbutton class=\"btn bad\" id=\"modalConfirm\">Delete\u003C\u002Fbutton>\n            \u003Cbutton class=\"btn ghost\" id=\"modalClose\">Cancel\u003C\u002Fbutton>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">3\u003C\u002Fspan> What's happening\u003C\u002Fdiv>\n      \u003Cdiv class=\"status\" id=\"statusRow\">\n        \u003Cspan class=\"dot info\" id=\"dot\">\u003C\u002Fspan>\n        \u003Cspan id=\"statusText\">Modal closed. Background is fully interactive.\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"code code-line\" id=\"attrCode\">&lt;div id=\"background\"&gt;\u003C\u002Fdiv>\n      \u003Cp class=\"hint\">Background clicks that got through: \u003Cstrong id=\"leakCount\">0\u003C\u002Fstrong>\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.bg-area {\n    position: relative;\n    border: 1px dashed var(--line);\n    border-radius: var(--radius-sm);\n    background: var(--panel-2);\n    padding: 16px;\n    display: grid;\n    gap: 10px;\n    transition: opacity .2s ease, filter .2s ease;\n  }\n  .bg-area.visual-dim { opacity: .4; filter: grayscale(.6); }\n  .bg-area:inert { opacity: .35; filter: grayscale(.7); }\n\n  .bg-input {\n    background: var(--bg);\n    border: 1px solid var(--line);\n    border-radius: var(--radius-sm);\n    color: var(--fg);\n    padding: 8px 10px;\n    font: inherit;\n    width: 100%;\n    max-width: 280px;\n  }\n\n  .stageWrap { position: relative; min-height: 300px; }\n\n  .modal-box {\n    position: absolute;\n    inset: 0;\n    display: grid;\n    place-items: center;\n    background: color-mix(in srgb, var(--bg) 55%, transparent);\n    border-radius: var(--radius-sm);\n  }\n  .modal-inner {\n    background: var(--panel);\n    border: 1px solid var(--line);\n    border-radius: var(--radius);\n    padding: 18px 20px;\n    max-width: 300px;\n    box-shadow: 0 10px 30px rgba(0,0,0,.35);\n  }\n  .modal-inner h3 { margin: 0 0 6px; }\n  .modal-inner p { margin: 0 0 14px; color: var(--muted); }\n\n  #trapBtn:focus-visible {\n    outline: 3px solid var(--bad, #e5484d);\n    outline-offset: 2px;\n  }\n\n  .code-line { white-space: pre; }","\"use strict\";\n  const $ = (id) => document.getElementById(id);\n\n  let mode = \"none\";\n  let modalOpen = false;\n  let leakCount = 0;\n\n  const bg = $(\"background\");\n  const modal = $(\"modal\");\n  const trapBtn = $(\"trapBtn\");\n\n  function setStatus(text, kind) {\n    $(\"statusText\").textContent = text;\n    $(\"dot\").className = \"dot \" + kind;\n  }\n\n  function updateCode() {\n    let tag = '&lt;div id=\"background\"';\n    if (modalOpen && mode === \"aria-hidden\") tag += ' aria-hidden=\"true\"';\n    if (modalOpen && mode === \"inert\") tag += \" inert\";\n    tag += \"&gt;\";\n    $(\"attrCode\").innerHTML = tag;\n  }\n\n  function applyProtection() {\n    bg.removeAttribute(\"aria-hidden\");\n    bg.removeAttribute(\"inert\");\n    bg.classList.remove(\"visual-dim\");\n\n    if (modalOpen) {\n      if (mode === \"aria-hidden\") {\n        bg.setAttribute(\"aria-hidden\", \"true\");\n        bg.classList.add(\"visual-dim\"); \u002F\u002F dimming has to be added by hand here\n      } else if (mode === \"inert\") {\n        bg.setAttribute(\"inert\", \"\"); \u002F\u002F dimming comes free from the :inert CSS rule\n      }\n    }\n    updateCode();\n  }\n\n  function describeMode() {\n    if (!modalOpen) return \"Modal closed. Background is fully interactive.\";\n    if (mode === \"none\") return \"Modal open, background unprotected — click or Tab to the trap button, it still works.\";\n    if (mode === \"aria-hidden\") return \"Modal open, aria-hidden only — screen readers skip it, but a real click or Tab still reaches it.\";\n    return \"Modal open, inert applied — try clicking or Tabbing to the trap button. The browser blocks it.\";\n  }\n\n  function setMode(next) {\n    mode = next;\n    document.querySelectorAll(\".seg-btn\").forEach((b) => {\n      const active = b.dataset.mode === mode;\n      b.setAttribute(\"aria-pressed\", String(active));\n    });\n    applyProtection();\n    setStatus(describeMode(), modalOpen && mode === \"inert\" ? \"good\" : modalOpen ? \"warn\" : \"info\");\n  }\n\n  document.querySelectorAll(\".seg-btn\").forEach((b) => {\n    b.addEventListener(\"click\", () => setMode(b.dataset.mode));\n  });\n\n  $(\"openModal\").addEventListener(\"click\", () => {\n    modalOpen = true;\n    modal.hidden = false;\n    applyProtection();\n    setStatus(describeMode(), mode === \"inert\" ? \"good\" : \"warn\");\n    $(\"modalClose\").focus();\n  });\n\n  function closeModal() {\n    modalOpen = false;\n    modal.hidden = true;\n    applyProtection();\n    setStatus(\"Modal closed. Background is fully interactive again.\", \"info\");\n  }\n\n  $(\"modalClose\").addEventListener(\"click\", closeModal);\n  $(\"modalConfirm\").addEventListener(\"click\", closeModal);\n\n  trapBtn.addEventListener(\"click\", () => {\n    leakCount++;\n    $(\"leakCount\").textContent = leakCount;\n    setStatus(\"🚨 Leak #\" + leakCount + \" — the background button fired a click while the modal was open (mode: \" + mode + \").\", \"bad\");\n  });\n\n  trapBtn.addEventListener(\"focus\", () => {\n    if (modalOpen) {\n      setStatus(\"⚠️ Focus reached the background button while the modal was open (mode: \" + mode + \").\", \"bad\");\n    }\n  });\n\n  $(\"resetBtn\").addEventListener(\"click\", () => {\n    leakCount = 0;\n    $(\"leakCount\").textContent = \"0\";\n    closeModal();\n    setMode(\"none\");\n  });\n\n  document.querySelectorAll(\"[data-preset]\").forEach((b) => {\n    b.addEventListener(\"click\", () => {\n      modalOpen = true;\n      modal.hidden = false;\n      setMode(b.dataset.preset);\n      $(\"modalClose\").focus();\n    });\n  });\n\n  \u002F\u002F init\n  updateCode();",{"height":52,"autorun":17},520,{"slug":45,"title":54,"excerpt":55,"coverUrl":56,"vertical":57},"The Modal Was Open. So Was Everything Behind It.","A keyboard-only pass found a background button still worked while a modal was open — aria-hidden had hidden it from screen readers but never touched focus or clicks. One boolean attribute, inert, closes that gap in a single line.","\u002Fmedia\u002Fcovers\u002Finert-attribute-disable-interaction.png",{"slug":6},[59,63,67,71,75,79,83,87,91,95,98,101],{"slug":60,"name":61,"articles":62},"webdev","Webdev",60,{"slug":64,"name":65,"articles":66},"frontend","Frontend",54,{"slug":68,"name":69,"articles":70},"javascript","Javascript",51,{"slug":72,"name":73,"articles":74},"css","Css",23,{"slug":76,"name":77,"articles":78},"typescript","Typescript",10,{"slug":80,"name":81,"articles":82},"performance","Performance",8,{"slug":84,"name":85,"articles":86},"grammar","Grammar",6,{"slug":88,"name":89,"articles":90},"react","React",5,{"slug":92,"name":93,"articles":94},"html","Html",4,{"slug":96,"name":97,"articles":94},"tutorial","Tutorial",{"slug":99,"name":100,"articles":94},"node","Node",{"slug":102,"name":103,"articles":43},"ai","Ai"]