[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"playground-css-relative-color-syntax":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-relative-color-syntax","CSS relative color syntax — interactive playground",null,"\u003Cdiv class=\"pg\">\n    \u003Ch1 class=\"pg-title\">🎮 Derive color variants, don't hardcode them\u003C\u002Fh1>\n    \u003Cp class=\"pg-sub\">\n      Pick a brand color. Every swatch below is computed \u003Cstrong>live, in CSS\u003C\u002Fstrong>,\n      from that one value using \u003Ccode>oklch(from var(--brand) ...)\u003C\u002Fcode> — nothing here\n      is a second hex code waiting to go stale.\n    \u003C\u002Fp>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">1\u003C\u002Fspan> Pick your brand color\u003C\u002Fdiv>\n      \u003Cdiv class=\"color-input-row\">\n        \u003Cinput type=\"color\" id=\"baseColor\" value=\"#3b6fd6\" \u002F>\n        \u003Cdiv class=\"presets\">\n          \u003Cbutton class=\"btn sm\" data-color=\"#3b6fd6\">Blue\u003C\u002Fbutton>\n          \u003Cbutton class=\"btn sm\" data-color=\"#c2410c\">Burnt orange\u003C\u002Fbutton>\n          \u003Cbutton class=\"btn sm\" data-color=\"#0f766e\">Teal\u003C\u002Fbutton>\n          \u003Cbutton class=\"btn sm\" data-color=\"#1a1a2e\">Near-black\u003C\u002Fbutton>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"slider-row\">\n        \u003Clabel for=\"darkenAmount\">Hover darkens by\u003C\u002Flabel>\n        \u003Cinput type=\"range\" id=\"darkenAmount\" min=\"0\" max=\"0.35\" step=\"0.01\" value=\"0.12\" \u002F>\n        \u003Cspan class=\"pill amount-pill\" id=\"amountLabel\">0.12\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n      \u003Cp class=\"hint\">\n        That slider changes one CSS custom property (\u003Ccode>--darken-amount\u003C\u002Fcode>). The\n        \u003Ccode>calc(l - var(--darken-amount))\u003C\u002Fcode> below recomputes on every move — this\n        is CSS doing the math, not JavaScript repainting swatches.\n      \u003C\u002Fp>\n    \u003C\u002Fdiv>\n\n    \u003Cdiv class=\"panel\">\n      \u003Cdiv class=\"panel-label\">\u003Cspan class=\"n\">2\u003C\u002Fspan> Every variant, derived from the one color above\u003C\u002Fdiv>\n      \u003Cdiv class=\"swatch-row\" id=\"swatches\">\n        \u003Cdiv class=\"swatch\">\n          \u003Cdiv class=\"swatch-color\" style=\"background: var(--brand);\">\u003C\u002Fdiv>\n          \u003Cdiv class=\"swatch-meta\">\u003Cdiv class=\"name\">Base\u003C\u002Fdiv>\u003Cdiv class=\"val\">var(--brand)\u003C\u002Fdiv>\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"swatch\">\n          \u003Cdiv class=\"swatch-color\" style=\"background: var(--brand-hover);\">\u003C\u002Fdiv>\n          \u003Cdiv class=\"swatch-meta\">\u003Cdiv class=\"name\">Hover\u003C\u002Fdiv>\u003Cdiv class=\"val\">oklch(from var(--brand) calc(l - amt) c h)\u003C\u002Fdiv>\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"swatch\">\n          \u003Cdiv class=\"swatch-color\" style=\"background: var(--brand-active);\">\u003C\u002Fdiv>\n          \u003Cdiv class=\"swatch-meta\">\u003Cdiv class=\"name\">Active \u002F pressed\u003C\u002Fdiv>\u003Cdiv class=\"val\">oklch(from var(--brand) calc(l - amt*2) c h)\u003C\u002Fdiv>\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"swatch\">\n          \u003Cdiv class=\"swatch-color\" style=\"background: var(--brand-border);\">\u003C\u002Fdiv>\n          \u003Cdiv class=\"swatch-meta\">\u003Cdiv class=\"name\">Border\u003C\u002Fdiv>\u003Cdiv class=\"val\">oklch(from var(--brand) calc(l + 0.15) c h)\u003C\u002Fdiv>\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n        \u003Cdiv class=\"swatch\">\n          \u003Cdiv class=\"swatch-color\" style=\"background: var(--brand-tint); background-image: linear-gradient(45deg, #8884 25%, transparent 25%, transparent 75%, #8884 75%), linear-gradient(45deg, #8884 25%, transparent 25%, transparent 75%, #8884 75%); background-size: 12px 12px; background-position: 0 0, 6px 6px;\">\u003C\u002Fdiv>\n          \u003Cdiv class=\"swatch-meta\">\u003Cdiv class=\"name\">Tint (8% alpha)\u003C\u002Fdiv>\u003Cdiv class=\"val\">oklch(from var(--brand) l c h \u002F 8%)\u003C\u002Fdiv>\u003C\u002Fdiv>\n        \u003C\u002Fdiv>\n      \u003C\u002Fdiv>\n      \u003Cdiv class=\"status\" id=\"status\" style=\"margin-top:14px\">\n        \u003Cspan class=\"dot good\" id=\"dot\">\u003C\u002Fspan>\n        \u003Cspan id=\"statusText\">Change the color or the slider — every swatch recomputes with no JS repaint.\u003C\u002Fspan>\n      \u003C\u002Fdiv>\n    \u003C\u002Fdiv>\n  \u003C\u002Fdiv>","\u002F*\n * Shared playground design system — daily-post\n * ============================================\n * Link it from any post's playground\u002Findex.html (no per-post boilerplate):\n *\n *   \u003Clink rel=\"stylesheet\" href=\"..\u002F..\u002F..\u002Fplayground\u002Fassets\u002Fbase.css\" \u002F>\n *\n * The relative path resolves the same locally (posts\u002F\u003Cbase>\u002Fplayground\u002F) and on\n * the deployed host (public\u002Fposts\u002F\u003Cbase>\u002Fplayground\u002F), because build-site.js\n * mirrors this file to public\u002Fplayground\u002Fassets\u002Fbase.css. For a CodeSandbox\n * --embed, playground.js inlines this file so the single-file sandbox still styles.\n *\n * Then write ONLY the demo-specific CSS in your inline \u003Cstyle>. Everything below\n * — tokens, layout, panels, buttons, toggles, sliders, code blocks, status\u002Flog,\n * banners, presets — comes for free and looks consistent across every post.\n *\n * Theme-aware: dark by default, light via prefers-color-scheme AND a\n * [data-theme] override on \u003Chtml> (so a toggle can force either). Accessible:\n * real focus rings, adequate contrast, reduced-motion honored.\n *\u002F\n\n\u002F* ---------- design tokens ---------- *\u002F\n:root {\n  --bg: #0d1117;\n  --bg-grid: #ffffff0a;\n  --panel: #161b22;\n  --panel-2: #0b0e14;\n  --card: #0b0e14;\n  --ink: #e8ebf2;\n  --muted: #9aa4b2;\n  --faint: #6b7688;\n  --line: #232a35;\n  --line-soft: #ffffff12;\n  --accent: #7c9cff;\n  --accent-ink: #0b0e14;\n  --good: #3fb950;\n  --warn: #f0883e;\n  --bad: #f85149;\n  --info: #58a6ff;\n  --radius: 14px;\n  --radius-sm: 9px;\n  --shadow: 0 8px 30px #0006;\n  --shadow-sm: 0 4px 14px #0005;\n  --mono: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\n  --sans: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", sans-serif;\n  color-scheme: dark;\n}\n\n\u002F* Light theme — system preference, overridable by [data-theme] on \u003Chtml>. *\u002F\n@media (prefers-color-scheme: light) {\n  :root:not([data-theme=\"dark\"]) {\n    --bg: #f4f6fb;\n    --bg-grid: #00000008;\n    --panel: #ffffff;\n    --panel-2: #f0f3f9;\n    --card: #f7f9fc;\n    --ink: #131722;\n    --muted: #5b6472;\n    --faint: #8a93a3;\n    --line: #e2e7f0;\n    --line-soft: #00000010;\n    --accent: #2f6df0;\n    --accent-ink: #ffffff;\n    --good: #1a7f37;\n    --warn: #bc4c00;\n    --bad: #cf222e;\n    --info: #0969da;\n    --shadow: 0 8px 30px #0000001f;\n    --shadow-sm: 0 4px 14px #00000014;\n    color-scheme: light;\n  }\n}\n:root[data-theme=\"light\"] {\n  --bg: #f4f6fb;\n  --bg-grid: #00000008;\n  --panel: #ffffff;\n  --panel-2: #f0f3f9;\n  --card: #f7f9fc;\n  --ink: #131722;\n  --muted: #5b6472;\n  --faint: #8a93a3;\n  --line: #e2e7f0;\n  --line-soft: #00000010;\n  --accent: #2f6df0;\n  --accent-ink: #ffffff;\n  --good: #1a7f37;\n  --warn: #bc4c00;\n  --bad: #cf222e;\n  --info: #0969da;\n  --shadow: 0 8px 30px #0000001f;\n  --shadow-sm: 0 4px 14px #00000014;\n  color-scheme: light;\n}\n\n\u002F* ---------- reset & base ---------- *\u002F\n*, *::before, *::after { box-sizing: border-box; }\n\nbody {\n  margin: 0;\n  min-height: 100vh;\n  font-family: var(--sans);\n  color: var(--ink);\n  background:\n    radial-gradient(circle at 1px 1px, var(--bg-grid) 1px, transparent 0) 0 0 \u002F 24px 24px,\n    var(--bg);\n  display: flex;\n  justify-content: center;\n  align-items: flex-start;\n  padding: clamp(14px, 3vw, 26px);\n  line-height: 1.55;\n  -webkit-font-smoothing: antialiased;\n  text-rendering: optimizeLegibility;\n}\n\n\u002F* The single content column. *\u002F\n.pg {\n  width: 100%;\n  max-width: 860px;\n}\n\n\u002F* ---------- header ---------- *\u002F\n.pg-title {\n  font-size: clamp(1.15rem, 1rem + 1vw, 1.4rem);\n  font-weight: 800;\n  letter-spacing: -0.02em;\n  margin: 0 0 4px;\n  display: flex;\n  align-items: center;\n  gap: 9px;\n}\n.pg-sub {\n  color: var(--muted);\n  font-size: 0.92rem;\n  line-height: 1.5;\n  margin: 0 0 16px;\n  max-width: 66ch;\n}\n.pg-sub strong, .pg-sub b { color: var(--ink); font-weight: 700; }\n.pg-sub code { font-family: var(--mono); background: var(--line-soft); color: var(--accent);\n  padding: 1px 6px; border-radius: 6px; font-size: 0.85em; }\n\n\u002F* ---------- panels ---------- *\u002F\n.panel {\n  background: var(--panel);\n  border: 1px solid var(--line);\n  border-radius: var(--radius);\n  padding: 16px 18px;\n  margin-bottom: 14px;\n  box-shadow: var(--shadow-sm);\n}\n.panel-label {\n  font-size: 0.7rem;\n  text-transform: uppercase;\n  letter-spacing: 0.08em;\n  color: var(--muted);\n  font-weight: 800;\n  margin: 0 0 13px;\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\n.panel-label .n {\n  display: inline-grid; place-items: center;\n  width: 18px; height: 18px; border-radius: 50%;\n  background: var(--accent); color: var(--accent-ink);\n  font-size: 0.66rem; font-weight: 800;\n}\n\n\u002F* ---------- layout helpers ---------- *\u002F\n.row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }\n.row.between { justify-content: space-between; }\n.col { display: flex; flex-direction: column; gap: 10px; }\n.grow { flex: 1 1 0; min-width: 0; }\n.wrap-controls { display: flex; flex-wrap: wrap; gap: 16px 24px; align-items: flex-end; }\n.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }\n.field > .label, .ctl-label {\n  font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.04em;\n  color: var(--muted); font-weight: 800;\n}\n\n\u002F* ---------- buttons ---------- *\u002F\n.btn {\n  font: inherit; font-weight: 650; font-size: 0.88rem;\n  cursor: pointer; user-select: none;\n  border: 1px solid var(--line);\n  background: var(--card); color: var(--ink);\n  padding: 9px 15px; border-radius: var(--radius-sm);\n  display: inline-flex; align-items: center; gap: 8px;\n  transition: background .15s ease, border-color .15s ease, transform .05s ease, color .15s ease;\n}\n.btn:hover { background: var(--line-soft); border-color: var(--muted); }\n.btn:active { transform: translateY(1px); }\n.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n.btn[disabled] { opacity: 0.45; cursor: not-allowed; }\n.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }\n.btn.primary:hover { filter: brightness(1.08); background: var(--accent); }\n.btn.good { background: var(--good); border-color: var(--good); color: #04140a; }\n.btn.bad  { background: var(--bad);  border-color: var(--bad);  color: #fff; }\n.btn.ghost { background: transparent; }\n.btn.sm { padding: 6px 11px; font-size: 0.82rem; }\n\n.btn-row { display: flex; flex-wrap: wrap; gap: 9px; }\n\n\u002F* Segmented control — a row of buttons where one is active (aria-pressed). *\u002F\n.seg { display: inline-flex; background: var(--card); border: 1px solid var(--line);\n  border-radius: var(--radius-sm); padding: 3px; gap: 3px; }\n.seg button {\n  font: inherit; font-weight: 650; font-size: 0.84rem; cursor: pointer;\n  border: none; background: transparent; color: var(--muted);\n  padding: 7px 13px; border-radius: 7px;\n  transition: background .15s ease, color .15s ease;\n}\n.seg button:hover { color: var(--ink); }\n.seg button[aria-pressed=\"true\"] { background: var(--accent); color: var(--accent-ink); }\n.seg button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* Preset chips — worked-example shortcuts. *\u002F\n.presets { display: flex; flex-wrap: wrap; gap: 8px; }\n.presets .btn { background: var(--panel-2); }\n\n\u002F* ---------- toggle switch ---------- *\u002F\n.toggle { display: inline-flex; align-items: center; gap: 10px; cursor: pointer;\n  font-size: 0.88rem; font-weight: 600; user-select: none; }\n.toggle input { position: absolute; opacity: 0; width: 0; height: 0; }\n.switch { position: relative; width: 44px; height: 25px; border-radius: 999px; flex: none;\n  background: var(--line); border: 1px solid var(--line); transition: background .18s ease; }\n.switch::after { content: \"\"; position: absolute; top: 2px; left: 2px; width: 19px; height: 19px;\n  border-radius: 50%; background: #cfd6e4; transition: transform .18s ease; box-shadow: 0 1px 3px #0006; }\n.toggle input:checked + .switch { background: var(--good); border-color: var(--good); }\n.toggle input:checked + .switch::after { transform: translateX(19px); background: #fff; }\n.toggle input:focus-visible + .switch { outline: 2px solid var(--accent); outline-offset: 2px; }\n\n\u002F* ---------- range slider ---------- *\u002F\ninput[type=\"range\"] {\n  -webkit-appearance: none; appearance: none; width: 100%; height: 6px;\n  background: var(--line); border-radius: 999px; outline: none; cursor: pointer; margin: 8px 0;\n}\ninput[type=\"range\"]::-webkit-slider-thumb {\n  -webkit-appearance: none; appearance: none; width: 19px; height: 19px; border-radius: 50%;\n  background: var(--accent); border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]::-moz-range-thumb {\n  width: 19px; height: 19px; border-radius: 50%; background: var(--accent);\n  border: 2px solid var(--panel); box-shadow: var(--shadow-sm); cursor: pointer;\n}\ninput[type=\"range\"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }\n\n\u002F* ---------- text inputs \u002F selects ---------- *\u002F\ninput[type=\"text\"], input[type=\"number\"], input[type=\"search\"], select, textarea {\n  font: inherit; color: var(--ink); background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 8px 11px; width: 100%;\n}\ntextarea { font-family: var(--mono); font-size: 0.85rem; line-height: 1.6; resize: vertical; }\ninput:focus-visible, select:focus-visible, textarea:focus-visible {\n  outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent);\n}\n\n\u002F* ---------- status \u002F output \u002F log ---------- *\u002F\n.status {\n  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;\n  font-size: 0.9rem; font-weight: 600;\n  padding: 11px 14px; border-radius: var(--radius-sm);\n  background: var(--panel-2); border: 1px solid var(--line); color: var(--ink);\n}\n.status.good { border-color: color-mix(in srgb, var(--good) 55%, var(--line)); }\n.status.warn { border-color: color-mix(in srgb, var(--warn) 55%, var(--line)); }\n.status.bad  { border-color: color-mix(in srgb, var(--bad) 55%, var(--line)); }\n.dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); flex: none; }\n.dot.good { background: var(--good); } .dot.warn { background: var(--warn); }\n.dot.bad { background: var(--bad); } .dot.info { background: var(--info); }\n\n.output { background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; }\n.log { font-family: var(--mono); font-size: 0.8rem; line-height: 1.7; background: var(--card);\n  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 11px 14px;\n  max-height: 220px; overflow-y: auto; white-space: pre-wrap; }\n\n\u002F* ---------- code block + syntax tokens ---------- *\u002F\n.code {\n  font-family: var(--mono); font-size: 0.82rem; line-height: 1.7;\n  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-sm);\n  padding: 13px 15px; overflow-x: auto; white-space: pre; tab-size: 2;\n}\n.code .tok-sel  { color: var(--accent); }\n.code .tok-prop { color: #86e29b; }\n.code .tok-kw   { color: #ff9bcb; }\n.code .tok-str  { color: #f0c674; }\n.code .tok-num  { color: #f0883e; }\n.code .tok-cmt  { color: var(--faint); font-style: italic; }\n.code .hl { background: color-mix(in srgb, var(--good) 22%, transparent); border-radius: 4px; padding: 0 3px; }\n\n\u002F* ---------- hint \u002F caption ---------- *\u002F\n.hint { font-size: 0.83rem; color: var(--muted); line-height: 1.55; margin: 10px 0 0; }\n.hint code { font-family: var(--mono); color: var(--ink); background: var(--line-soft);\n  padding: 1px 5px; border-radius: 5px; font-size: 0.86em; }\n.hint strong { color: var(--ink); }\n\n\u002F* ---------- banner (feature-support \u002F notices) ---------- *\u002F\n.banner {\n  display: none; margin-bottom: 14px; padding: 11px 14px; border-radius: 11px;\n  font-size: 0.85rem; line-height: 1.5;\n  color: var(--ink); background: color-mix(in srgb, var(--warn) 12%, var(--panel));\n  border: 1px solid color-mix(in srgb, var(--warn) 45%, var(--line));\n}\n.banner.show { display: block; }\n.banner.info { background: color-mix(in srgb, var(--info) 12%, var(--panel));\n  border-color: color-mix(in srgb, var(--info) 45%, var(--line)); }\n.banner code { font-family: var(--mono); background: var(--line-soft); padding: 1px 5px; border-radius: 5px; }\n\n\u002F* ---------- misc atoms ---------- *\u002F\n.pill { display: inline-flex; align-items: center; gap: 6px; font-size: 0.75rem; font-weight: 700;\n  padding: 3px 9px; border-radius: 999px; background: var(--line-soft); color: var(--muted); }\n.pill.good { color: var(--good); background: color-mix(in srgb, var(--good) 16%, transparent); }\n.pill.bad  { color: var(--bad);  background: color-mix(in srgb, var(--bad) 16%, transparent); }\n.mono { font-family: var(--mono); }\n.muted { color: var(--muted); }\n.big-num { font-family: var(--mono); font-size: 1.6rem; font-weight: 800; letter-spacing: -0.02em; }\n\n\u002F* Optional theme toggle button (top-right). Wire it in JS if you want it. *\u002F\n.theme-btn { position: fixed; top: 12px; right: 12px; z-index: 20; }\n\n\u002F* ---------- responsive ---------- *\u002F\n@media (max-width: 560px) {\n  .panel { padding: 14px; }\n  .wrap-controls { gap: 14px; }\n  .btn { padding: 9px 13px; }\n}\n\n\u002F* ---------- reduced motion ---------- *\u002F\n@media (prefers-reduced-motion: reduce) {\n  *, *::before, *::after { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }\n}\n\n\n\u002F* Demo-specific CSS only — base.css owns tokens, panels, buttons, sliders. *\u002F\n  .swatch-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 14px; margin-top: 14px; }\n  .swatch {\n    border: 1px solid var(--line); border-radius: var(--radius-sm); overflow: hidden;\n    background: var(--panel-2);\n  }\n  .swatch-color { height: 64px; border-bottom: 1px solid var(--line); }\n  .swatch-meta { padding: 8px 10px; }\n  .swatch-meta .name { font-weight: 600; font-size: 0.85rem; }\n  .swatch-meta .val { font-family: var(--mono, monospace); font-size: 0.72rem; color: var(--muted); word-break: break-all; }\n  .color-input-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }\n  input[type=\"color\"] {\n    width: 56px; height: 40px; border: 1px solid var(--line); border-radius: var(--radius-sm);\n    background: none; padding: 2px; cursor: pointer;\n  }\n  .slider-row { display: flex; align-items: center; gap: 10px; margin-top: 14px; }\n  .slider-row label { font-size: 0.85rem; white-space: nowrap; }\n  .slider-row input[type=\"range\"] { flex: 1; }\n  .amount-pill { font-variant-numeric: tabular-nums; min-width: 3.5em; text-align: right; }","\"use strict\";\n  const $ = (id) => document.getElementById(id);\n  const root = document.documentElement;\n  const baseColor = $(\"baseColor\");\n  const darkenAmount = $(\"darkenAmount\");\n  const amountLabel = $(\"amountLabel\");\n\n  function applyBrand(hex) {\n    root.style.setProperty(\"--brand\", hex);\n  }\n\n  function applyAmount(amount) {\n    root.style.setProperty(\"--darken-amount\", amount);\n    amountLabel.textContent = Number(amount).toFixed(2);\n  }\n\n  \u002F\u002F The swatches reference --brand-hover \u002F --brand-active \u002F --brand-border \u002F\n  \u002F\u002F --brand-tint, which are defined ONCE below using relative color syntax —\n  \u002F\u002F the same formulas shown in the article, just wired to custom properties\n  \u002F\u002F the controls above can move.\n  const style = document.createElement(\"style\");\n  style.textContent = `\n    :root {\n      --darken-amount: 0.12;\n      --brand-hover:  oklch(from var(--brand) clamp(0, calc(l - var(--darken-amount)), 1) c h);\n      --brand-active: oklch(from var(--brand) clamp(0, calc(l - var(--darken-amount) * 2), 1) c h);\n      --brand-border: oklch(from var(--brand) clamp(0, calc(l + 0.15), 1) c h);\n      --brand-tint:   oklch(from var(--brand) l c h \u002F 8%);\n    }\n  `;\n  document.head.appendChild(style);\n\n  baseColor.addEventListener(\"input\", () => {\n    applyBrand(baseColor.value);\n    $(\"statusText\").textContent = \"Base color changed to \" + baseColor.value + \" — all four derived swatches just recomputed.\";\n  });\n\n  darkenAmount.addEventListener(\"input\", () => {\n    applyAmount(darkenAmount.value);\n    $(\"statusText\").textContent = \"Hover now darkens lightness by \" + Number(darkenAmount.value).toFixed(2) + \" — watch the Hover and Active swatches move together.\";\n  });\n\n  document.querySelectorAll(\"[data-color]\").forEach((btn) => {\n    btn.addEventListener(\"click\", () => {\n      baseColor.value = btn.dataset.color;\n      applyBrand(btn.dataset.color);\n      $(\"statusText\").textContent = \"Switched brand color to \" + btn.textContent + \" (\" + btn.dataset.color + \").\";\n    });\n  });\n\n  \u002F\u002F init\n  applyBrand(baseColor.value);\n  applyAmount(darkenAmount.value);",{"height":52,"autorun":17},520,{"slug":45,"title":54,"excerpt":55,"coverUrl":56,"vertical":57},"Stop Hardcoding Color Variants. Relative Color Syntax Does It.","A design system ships one brand color and four hand-picked hex variants for hover, pressed, border, and tint. Change the brand color and four of those five silently go stale. CSS relative color syntax derives every variant from the one color you actually update.","\u002Fmedia\u002Fcovers\u002Fcss-relative-color-syntax.png",{"slug":6},[59,63,67,71,75,79,83,87,91,95,98,102],{"slug":60,"name":61,"articles":62},"webdev","Webdev",161,{"slug":64,"name":65,"articles":66},"javascript","Javascript",130,{"slug":68,"name":69,"articles":70},"frontend","Frontend",92,{"slug":72,"name":73,"articles":74},"tutorial","Tutorial",63,{"slug":76,"name":77,"articles":78},"css","Css",49,{"slug":80,"name":81,"articles":82},"performance","Performance",27,{"slug":84,"name":85,"articles":86},"typescript","Typescript",23,{"slug":88,"name":89,"articles":90},"react","React",19,{"slug":92,"name":93,"articles":94},"programming","Programming",16,{"slug":96,"name":97,"articles":94},"browser","Browser",{"slug":99,"name":100,"articles":101},"node","Node",14,{"slug":103,"name":104,"articles":105},"html","Html",11]