[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-css-scope-native-style-scoping":32},[4,20],{"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","Build. Learn. Ship.","Practical courses, engineering-grade articles and open-source tools for people who ship.","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.","Structured paths, listening drills and spaced repetition that actually sticks.","amber-400","⌘",[13,15,29],"es",{"courses":17,"paths":17,"articles":18,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":17},2,{"slug":33,"title":34,"summary":35,"html":36,"css":37,"js":38,"config":39,"doc":41},"css-scope-native-style-scoping","@scope — native CSS style scoping",null,"\u003Cdiv class=\"pg\">\n    \u003Ch1 class=\"pg-title\">🔒 @scope — draw the boundary yourself\u003C\u002Fh1>\n    \u003Cp class=\"pg-sub\">\n      \u003Ccode>@scope\u003C\u002Fcode> keeps CSS where you put it, with no BEM naming and no build step.\n      Pick a selector strategy below and watch which \u003Ccode>.title\u003C\u002Fcode> elements it styles.\n      The CSS you build is \u003Cstrong>really injected into this page\u003C\u002Fstrong> — the verdicts are\n      measured with \u003Ccode>getComputedStyle\u003C\u002Fcode>, not faked.\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"banner\" id=\"support\">\n      ⚠️ This browser doesn't support \u003Ccode>@scope\u003C\u002Fcode> (needs Chrome 118+, Firefox 128+, or\n      Safari 17.4+). The \u003Ccode>@scope\u003C\u002Fcode> option below will style \u003Cem>nothing\u003C\u002Fem> — which is\n      exactly the graceful degradation the post describes.\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">1\u003C\u002Fspan> Pick a strategy, then draw the boundary\u003C\u002Fdiv>\n\n      \u003Cdiv class=\"wrap-controls\">\n        \u003Cdiv class=\"field\">\n          \u003Cspan class=\"ctl-label\">Selector strategy\u003C\u002Fspan>\n          \u003Cdiv class=\"seg\" id=\"modeSeg\">\n            \u003Cbutton data-mode=\"plain\" aria-pressed=\"false\">.title\u003C\u002Fbutton>\n            \u003Cbutton data-mode=\"desc\" aria-pressed=\"false\">.card .title\u003C\u002Fbutton>\n            \u003Cbutton data-mode=\"scope\" aria-pressed=\"true\">@scope (.card)\u003C\u002Fbutton>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"field\">\n          \u003Cspan class=\"ctl-label\">Lower boundary\u003C\u002Fspan>\n          \u003Clabel class=\"toggle\">\n            \u003Cinput type=\"checkbox\" id=\"limit\" \u002F>\u003Cspan class=\"switch\">\u003C\u002Fspan>\n            \u003Cspan>stop at \u003Cspan class=\"mono\">.testimonial\u003C\u002Fspan>\u003C\u002Fspan>\n          \u003C\u002Flabel>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"presets\" style=\"margin-top:16px\">\n        \u003Cbutton class=\"btn sm\" data-preset=\"leak\">😱 The leak\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"bem\">The BEM-era way\u003C\u002Fbutton>\n        \u003Cbutton class=\"btn sm\" data-preset=\"scoped\">✅ Scoped with a boundary\u003C\u002Fbutton>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">2\u003C\u002Fspan> The CSS you just wrote\u003C\u002Fdiv>\n      \u003Cdiv class=\"code\" id=\"cssOut\">\u003C\u002Fdiv>\n      \u003Cp class=\"hint\" id=\"cssHint\">\u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">3\u003C\u002Fspan> What it actually styled\u003C\u002Fdiv>\n\n      \u003Cdiv class=\"page\" id=\"tree\">\n        \u003Cspan class=\"tag\">.page\u003C\u002Fspan>\n        \u003Cdiv class=\"ti\">\n          \u003Cdiv class=\"title\">Page heading\u003C\u002Fdiv>\n          \u003Cspan class=\"loc\">outside the card\u003C\u002Fspan>\n          \u003Cspan class=\"pill\">—\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n\n        \u003Cdiv class=\"card\">\n          \u003Cspan class=\"tag\">.card\u003C\u002Fspan>\n          \u003Cdiv class=\"ti\">\n            \u003Cdiv class=\"title\">Card title\u003C\u002Fdiv>\n            \u003Cspan class=\"loc\">.card &rsaquo; .header\u003C\u002Fspan>\n            \u003Cspan class=\"pill\">—\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n          \u003Cdiv class=\"ti\">\n            \u003Cdiv class=\"title\">Section title\u003C\u002Fdiv>\n            \u003Cspan class=\"loc\">.card &rsaquo; .body\u003C\u002Fspan>\n            \u003Cspan class=\"pill\">—\u003C\u002Fspan>\n          \u003C\u002Fdiv>\n\n          \u003Cdiv class=\"testimonial\">\n            \u003Cspan class=\"tag lim\">.testimonial &nbsp;— a nested component\u003C\u002Fspan>\n            \u003Cdiv class=\"ti\">\n              \u003Cdiv class=\"title\">What people say\u003C\u002Fdiv>\n              \u003Cspan class=\"loc\">.card &rsaquo; .testimonial\u003C\u002Fspan>\n              \u003Cspan class=\"pill\">—\u003C\u002Fspan>\n            \u003C\u002Fdiv>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"status\" id=\"status\" style=\"margin-top:13px\">\n        \u003Cspan class=\"dot info\" id=\"dot\">\u003C\u002Fspan>\n        \u003Cspan class=\"tally\">\n          \u003Cspan class=\"big-num\" id=\"count\">–\u003C\u002Fspan>\n          \u003Cspan id=\"statusText\">of 4 titles styled.\u003C\u002Fspan>\n        \u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\" id=\"hint\">\u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">4\u003C\u002Fspan> Bonus: proximity is a tiebreaker, not a trump card\u003C\u002Fdiv>\n      \u003Cp class=\"hint\" style=\"margin:0 0 13px\">\n        Two scopes both style \u003Cspan class=\"mono\">.button\u003C\u002Fspan>. The one whose scoping root is\n        \u003Cem>closest\u003C\u002Fem> wins — but only when specificity ties. In the cascade, scope proximity sits\n        \u003Cstrong>below\u003C\u002Fstrong> specificity, so raising the far rule's specificity takes the win back.\n      \u003C\u002Fp>\n\n      \u003Cdiv class=\"btn-row\" style=\"margin-bottom:14px\">\n        \u003Clabel class=\"toggle\">\n          \u003Cinput type=\"checkbox\" id=\"inSidebar\" checked \u002F>\u003Cspan class=\"switch\">\u003C\u002Fspan>\n          \u003Cspan>button lives inside \u003Cspan class=\"mono\">.sidebar\u003C\u002Fspan>\u003C\u002Fspan>\n        \u003C\u002Flabel>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"btn-row\" style=\"margin-bottom:14px\">\n        \u003Clabel class=\"toggle\">\n          \u003Cinput type=\"checkbox\" id=\"boost\" \u002F>\u003Cspan class=\"switch\">\u003C\u002Fspan>\n          \u003Cspan>give the \u003Cspan class=\"mono\">.page\u003C\u002Fspan> rule 3× the specificity\u003C\u002Fspan>\n        \u003C\u002Flabel>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"page\">\n        \u003Cspan class=\"tag\">.page\u003C\u002Fspan>\n        \u003Cdiv class=\"slot\" id=\"slotPage\">\u003C\u002Fdiv>\n        \u003Cdiv class=\"sidebar\">\n          \u003Cspan class=\"tag\">.sidebar\u003C\u002Fspan>\n          \u003Cdiv class=\"slot\" id=\"slotSide\">\n            \u003Cbutton class=\"button\" id=\"pbtn\" type=\"button\">Click me\u003C\u002Fbutton>\n          \u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n\n      \u003Cdiv class=\"code\" id=\"proxCss\" style=\"margin-top:13px\">\u003C\u002Fdiv>\n      \u003Cdiv class=\"status\" id=\"proxStatus\" style=\"margin-top:12px\">\n        \u003Cspan class=\"dot info\" id=\"proxDot\">\u003C\u002Fspan>\u003Cspan id=\"proxText\">\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\" id=\"proxHint\">\u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"btn-row\" style=\"margin-bottom:20px\">\n      \u003Cbutton class=\"btn ghost\" id=\"reset\">↺ Reset everything\u003C\u002Fbutton>\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 already provides tokens, layout, panels,\n     buttons, segmented control, toggles, code blocks, status, pills, etc. *\u002F\n\n  \u002F* The DOM tree the scopes act on. Nesting is shown with dashed boxes so the\n     reader can see exactly where each boundary falls. *\u002F\n  .page, .card, .testimonial, .sidebar {\n    position: relative;\n    border: 1px dashed var(--line);\n    border-radius: var(--radius-sm);\n    padding: 25px 12px 12px;\n    background: var(--panel-2);\n  }\n  .card, .sidebar { background: var(--card); margin-top: 10px; }\n  .testimonial { margin-top: 10px; background: var(--panel-2); }\n\n  \u002F* The little class name in each box's corner. *\u002F\n  .tag {\n    position: absolute; top: 6px; left: 11px;\n    font-family: var(--mono); font-size: 0.66rem; font-weight: 800;\n    color: var(--faint);\n  }\n  .tag.lim { color: var(--warn); }\n\n  \u002F* One row per .title element: the element itself, where it lives, its verdict. *\u002F\n  .ti { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 9px 0; }\n  .ti .loc { font-family: var(--mono); font-size: 0.68rem; color: var(--faint); }\n  .ti .pill { margin-left: auto; }\n\n  \u002F* IMPORTANT: no color\u002Fborder\u002Fpadding here — the injected @scope rule adds those,\n     and `border-inline-start-width` is what we probe to see if it matched. *\u002F\n  .title {\n    font-weight: 700; font-size: 0.95rem;\n    transition: color .15s ease, padding .15s ease;\n  }\n\n  \u002F* Panel 2: the button two scopes fight over. *\u002F\n  .slot { min-height: 42px; display: flex; align-items: center; }\n  .button {\n    font: inherit; font-weight: 700; font-size: 0.88rem; cursor: pointer;\n    border: none; border-radius: var(--radius-sm); padding: 9px 17px;\n    color: #fff; background: #64748b;\n    transition: background .2s ease;\n  }\n  .button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n  .tally { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }","\"use strict\";\n\nconst $ = (id) => document.getElementById(id);\n\n\u002F* The color the scoped rule paints. Shown literally in the code block so the\n   reader sees real CSS, not an abstraction. *\u002F\nconst ACCENT = \"#5b6cff\";\nconst BLUE = \"#3b82f6\";\nconst GREEN = \"#22c55e\";\n\n\u002F* ---------- feature detection ----------\n   A browser without @scope drops the whole at-rule at parse time, so the\n   stylesheet ends up with zero rules. That's the most reliable probe. *\u002F\nfunction supportsScope() {\n  const el = document.createElement(\"style\");\n  el.textContent = \"@scope (.a) { .b { color: red } }\";\n  document.head.appendChild(el);\n  const ok = !!(el.sheet && el.sheet.cssRules.length > 0);\n  el.remove();\n  return ok;\n}\nconst SCOPE_OK = supportsScope();\nif (!SCOPE_OK) $(\"support\").classList.add(\"show\");\n\n\u002F* ---------- the two live stylesheets we write into ----------\n   Appended to \u003Chead> AFTER the inline \u003Cstyle> above, so equal-specificity rules\n   from the demo win over the baseline .title styles. *\u002F\nconst sheet1 = document.createElement(\"style\");\nconst sheet2 = document.createElement(\"style\");\ndocument.head.append(sheet1, sheet2);\n\n\u002F* ---------- tiny CSS highlighter (for our own generated text only) ---------- *\u002F\nfunction esc(s) {\n  return s.replace(\u002F&\u002Fg, \"&amp;\").replace(\u002F\u003C\u002Fg, \"&lt;\").replace(\u002F>\u002Fg, \"&gt;\");\n}\nfunction hl(css) {\n  return css.split(\"\\n\").map((line) => {\n    const e = esc(line);\n    if (\u002F^\\s*\\\u002F\\*\u002F.test(line)) return '\u003Cspan class=\"tok-cmt\">' + e + \"\u003C\u002Fspan>\";\n    if (\u002F^\\s*@scope\u002F.test(line)) {\n      return e\n        .replace(\"@scope\", '\u003Cspan class=\"tok-kw\">@scope\u003C\u002Fspan>')\n        .replace(\u002F\\(([^)]*)\\)\u002Fg, '(\u003Cspan class=\"tok-sel\">$1\u003C\u002Fspan>)')\n        .replace(\u002F to \u002F, ' \u003Cspan class=\"tok-kw\">to\u003C\u002Fspan> ');\n    }\n    const sel = e.match(\u002F^(\\s*)(\\S[^{]*?)\\s*\\{$\u002F);\n    if (sel) return sel[1] + '\u003Cspan class=\"tok-sel\">' + sel[2] + \"\u003C\u002Fspan> {\";\n    const dec = e.match(\u002F^(\\s*)([a-z-]+):(.*)$\u002F);\n    if (dec) {\n      const val = dec[3]\n        .replace(\u002F(#[0-9a-f]{3,8})\u002Fgi, '\u003Cspan class=\"tok-str\">$1\u003C\u002Fspan>')\n        .replace(\u002F\\b(\\d+(?:\\.\\d+)?(?:px|rem|em|%))\\b\u002Fg, '\u003Cspan class=\"tok-num\">$1\u003C\u002Fspan>');\n      return dec[1] + '\u003Cspan class=\"tok-prop\">' + dec[2] + \"\u003C\u002Fspan>:\" + val;\n    }\n    return e;\n  }).join(\"\\n\");\n}\n\n\u002F* ================= PANEL 1–3: the boundary builder ================= *\u002F\n\nlet mode = \"scope\";\n\nconst DECLS = [\n  \"color: \" + ACCENT + \";\",\n  \"border-inline-start: 3px solid;\",\n  \"padding-inline-start: 10px;\",\n];\n\nfunction buildCss(mode, limit) {\n  const pad = (n) => DECLS.map((d) => \" \".repeat(n) + d).join(\"\\n\");\n\n  if (mode === \"scope\") {\n    const prelude = limit ? \"@scope (.card) to (.testimonial)\" : \"@scope (.card)\";\n    return prelude + \" {\\n  .title {\\n\" + pad(4) + \"\\n  }\\n}\";\n  }\n\n  const base = mode === \"desc\" ? \".card .title\" : \".title\";\n  if (!limit) return base + \" {\\n\" + pad(2) + \"\\n}\";\n\n  \u002F* No @scope here, so the boundary has to be bolted onto the selector itself —\n     and repeated on every single rule you write. That cost is the lesson. *\u002F\n  return (\n    \"\u002F* No @scope, so the boundary must ride along on the selector — *\u002F\\n\" +\n    \"\u002F* and be repeated, correctly, in every rule of the component. *\u002F\\n\" +\n    base + \":not(.testimonial *) {\\n\" + pad(2) + \"\\n}\"\n  );\n}\n\nconst HINTS = {\n  \"plain:false\":\n    \"A bare \u003Ccode>.title\u003C\u002Fcode> matches every title on the page. This is the leak that BEM, \" +\n    \"CSS Modules, and CSS-in-JS were all invented to prevent.\",\n  \"plain:true\":\n    \"The \u003Ccode>:not()\u003C\u002Fcode> escape hatch works — but you're now hand-maintaining the boundary \" +\n    \"inside the selector, with nothing scoping the rule to the card at all.\",\n  \"desc:false\":\n    \"The pre-\u003Ccode>@scope\u003C\u002Fcode> way. It keeps styles inside the card, but it still reaches into \" +\n    \"the nested \u003Ccode>.testimonial\u003C\u002Fcode> — and you repeat \u003Ccode>.card\u003C\u002Fcode> in every selector.\",\n  \"desc:true\":\n    \"It gets the right answer, at a price: this \u003Ccode>:not(.testimonial *)\u003C\u002Fcode> has to be copied \" +\n    \"onto every rule in the component, and one omission silently reopens the leak.\",\n  \"scope:false\":\n    \"Same result as the descendant selector — which is the point. \u003Ccode>@scope\u003C\u002Fcode> alone is not \" +\n    \"the feature. Turn on the lower boundary to see what is.\",\n  \"scope:true\":\n    \"\u003Cstrong>This is the part that has no pre-\u003Ccode>@scope\u003C\u002Fcode> equivalent.\u003C\u002Fstrong> The boundary \" +\n    \"is declared \u003Cem>once\u003C\u002Fem>, in the prelude, and every rule in the block inherits it.\",\n};\n\nfunction render1() {\n  const limit = $(\"limit\").checked;\n  const css = buildCss(mode, limit);\n\n  sheet1.textContent = css;\n  $(\"cssOut\").innerHTML = hl(css);\n  $(\"cssHint\").innerHTML = HINTS[mode + \":\" + limit];\n\n  \u002F* Ask the BROWSER which titles matched, rather than predicting it ourselves:\n     the injected rule is the only thing that sets an inline-start border. *\u002F\n  const titles = [...$(\"tree\").querySelectorAll(\".title\")];\n  let hit = 0;\n  titles.forEach((t) => {\n    const w = getComputedStyle(t).getPropertyValue(\"border-inline-start-width\");\n    const on = parseFloat(w) > 0;\n    if (on) hit++;\n    const pill = t.parentElement.querySelector(\".pill\");\n    pill.className = \"pill\" + (on ? \" good\" : \"\");\n    pill.textContent = on ? \"✓ styled\" : \"untouched\";\n  });\n\n  $(\"count\").textContent = hit;\n  const leaked = titles[3].parentElement.querySelector(\".pill\").classList.contains(\"good\");\n  const outside = titles[0].parentElement.querySelector(\".pill\").classList.contains(\"good\");\n\n  let msg, kind;\n  if (!SCOPE_OK && mode === \"scope\") {\n    msg = \"of 4 — your browser ignored the @scope block entirely.\";\n    kind = \"bad\";\n  } else if (outside) {\n    msg = \"of 4 — including the heading outside the card. That's the leak.\";\n    kind = \"bad\";\n  } else if (leaked) {\n    msg = \"of 4 — the nested testimonial's title got styled too.\";\n    kind = \"warn\";\n  } else {\n    msg = \"of 4 — styles stopped at the testimonial's edge. 🎯\";\n    kind = \"good\";\n  }\n  $(\"statusText\").textContent = msg;\n  $(\"dot\").className = \"dot \" + kind;\n  $(\"status\").className = \"status \" + kind;\n\n  $(\"hint\").innerHTML = leaked\n    ? \"The \u003Cspan class='mono'>.testimonial\u003C\u002Fspan> is a component of its own — it should style its \" +\n      \"own title, not inherit the card's.\"\n    : outside\n    ? \"Nothing is holding this rule inside the card, so an unrelated component two levels away \" +\n      \"changes appearance.\"\n    : \"The testimonial keeps its own look. Add a rule to the block and it obeys the same boundary \" +\n      \"automatically — that's what makes this structural rather than a naming convention.\";\n}\n\n$(\"modeSeg\").addEventListener(\"click\", (e) => {\n  const btn = e.target.closest(\"button[data-mode]\");\n  if (!btn) return;\n  mode = btn.dataset.mode;\n  [...$(\"modeSeg\").children].forEach((b) =>\n    b.setAttribute(\"aria-pressed\", String(b === btn))\n  );\n  render1();\n});\n\n$(\"limit\").addEventListener(\"change\", render1);\n\nconst PRESETS = {\n  leak: { mode: \"plain\", limit: false },\n  bem: { mode: \"desc\", limit: false },\n  scoped: { mode: \"scope\", limit: true },\n  \u002F* The boot state: scoped, but boundary OFF — so flipping it is the discovery. *\u002F\n  start: { mode: \"scope\", limit: false },\n};\ndocument.querySelectorAll(\"[data-preset]\").forEach((b) => {\n  b.addEventListener(\"click\", () => applyPreset(b.dataset.preset));\n});\nfunction applyPreset(name) {\n  const p = PRESETS[name];\n  mode = p.mode;\n  $(\"limit\").checked = p.limit;\n  [...$(\"modeSeg\").children].forEach((b) =>\n    b.setAttribute(\"aria-pressed\", String(b.dataset.mode === mode))\n  );\n  render1();\n}\n\n\u002F* ================= PANEL 4: proximity vs specificity ================= *\u002F\n\nfunction rgb(hex) {\n  const n = parseInt(hex.slice(1), 16);\n  return \"rgb(\" + [(n >> 16) & 255, (n >> 8) & 255, n & 255].join(\", \") + \")\";\n}\n\nfunction render2() {\n  const inSidebar = $(\"inSidebar\").checked;\n  const boost = $(\"boost\").checked;\n\n  ($(inSidebar ? \"slotSide\" : \"slotPage\")).appendChild($(\"pbtn\"));\n\n  const pageSel = boost ? \".button.button.button\" : \".button\";\n  const css =\n    \"@scope (.page) {\\n  \" + pageSel + \" {\\n    background: \" + BLUE + \";\\n  }\\n}\\n\\n\" +\n    \"@scope (.sidebar) {\\n  .button {\\n    background: \" + GREEN + \";\\n  }\\n}\";\n\n  sheet2.textContent = css;\n  $(\"proxCss\").innerHTML = hl(css);\n\n  const bg = getComputedStyle($(\"pbtn\")).backgroundColor;\n  const isGreen = bg === rgb(GREEN);\n  const isBlue = bg === rgb(BLUE);\n\n  let text, kind, hint;\n  if (!SCOPE_OK) {\n    text = \"Neither rule applied — this browser has no @scope support.\";\n    kind = \"bad\";\n    hint = \"The button keeps its default gray.\";\n  } else if (isGreen) {\n    text = \"Green wins — computed background is \" + bg;\n    kind = \"good\";\n    hint =\n      \"Both rules are specificity \u003Cstrong>0,1,0\u003C\u002Fstrong>, so the tie goes to proximity: \" +\n      \"\u003Cspan class='mono'>.sidebar\u003C\u002Fspan> is closer to the button than \u003Cspan class='mono'>.page\u003C\u002Fspan>. \" +\n      \"Now boost the other rule's specificity and watch what happens.\";\n  } else if (isBlue && inSidebar) {\n    text = \"Blue wins — computed background is \" + bg;\n    kind = \"warn\";\n    hint =\n      \"The far rule won. \u003Cspan class='mono'>.page\u003C\u002Fspan> is now \u003Cstrong>0,3,0\u003C\u002Fstrong> against green's \" +\n      \"\u003Cstrong>0,1,0\u003C\u002Fstrong>, and specificity is compared \u003Cem>before\u003C\u002Fem> scope proximity — so proximity \" +\n      \"never gets to weigh in. This is the part most \u003Cspan class='mono'>@scope\u003C\u002Fspan> explainers get \" +\n      \"backwards: proximity breaks ties, it doesn't override specificity.\";\n  } else if (isBlue) {\n    text = \"Blue wins — computed background is \" + bg;\n    kind = \"info\";\n    hint =\n      \"The button is outside \u003Cspan class='mono'>.sidebar\u003C\u002Fspan> now, so only the \" +\n      \"\u003Cspan class='mono'>.page\u003C\u002Fspan> scope matches it. Nothing to arbitrate.\";\n  } else {\n    text = \"Unstyled — computed background is \" + bg;\n    kind = \"warn\";\n    hint = \"Neither scope matched this element.\";\n  }\n\n  $(\"proxText\").textContent = text;\n  $(\"proxDot\").className = \"dot \" + kind;\n  $(\"proxStatus\").className = \"status \" + kind;\n  $(\"proxHint\").innerHTML = hint;\n}\n\n$(\"inSidebar\").addEventListener(\"change\", render2);\n$(\"boost\").addEventListener(\"change\", render2);\n\n\u002F* ---------- reset ---------- *\u002F\n$(\"reset\").addEventListener(\"click\", () => {\n  $(\"inSidebar\").checked = true;\n  $(\"boost\").checked = false;\n  applyPreset(\"start\");\n  render2();\n});\n\n\u002F* ---------- boot ---------- *\u002F\nrender1();\nrender2();",{"height":40,"autorun":17},520,{"slug":33,"title":42,"excerpt":43,"coverUrl":44},"CSS has no native scoping. `@scope` changes that.","BEM, CSS Modules, and scoped component styles all exist because CSS leaks — a rule written for one component can silently affect another. `@scope` gives the cascade a lower boundary so styles stay where you put them, natively, with no build step.","\u002Fmedia\u002Fcovers\u002Fcss-scope-native-style-scoping.png"]