{"id":"l-ds-1-2","courseId":"design-systems","lang":"en","kind":"lesson","summary":"Run an interface inventory of screens and code, count duplicated decisions like colors and buttons, and turn the counts into a ranked list of what the system should own first.","html":"<p>Before you design a single token, you need to know what already exists. Teams usually skip this step because they feel they already know the product is inconsistent. Feeling it is not enough. You need numbers that a product manager, an engineering lead and a VP will all accept, and you need them to decide what the system owns first.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">The screenshot inventory</h2>\n<p>An interface inventory is simple to describe and tedious to do: you screenshot every instance of every UI element across your products and group them by type. All the buttons on one board, all the form inputs on another, all the modals on a third.</p>\n<p>At Northwind I ran it with two product designers over four days, across the dispatch app and the marketing site. We used a shared Figma file with one page per category: buttons, inputs, selects, icons, colors, typography, cards, modals, tables, empty states, and “other”. Each screenshot carried its source URL so anyone could check it.</p>\n<p>The result was the most persuasive artifact I have ever made. Eleven button styles side by side end every debate about whether there is a consistency problem. Nobody argues with a wall of buttons that are almost the same blue.</p>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">Tip</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Time-box it</span></p>\n<p>Give the inventory a fixed budget, such as three to five days. You want enough coverage to show patterns, not a museum of every pixel. Stop when new screenshots stop teaching you anything new.</p>\n</aside>\n<h2 id=\"sec-2\" tabindex=\"-1\">The code audit</h2>\n<p>Screenshots miss what users never see on the screen in front of you: hover states, error states, the admin pages nobody demos. Code catches those. The fastest high-value audit is counting raw color values in your stylesheets.</p>\n<p>The first trap is notation. <code dir=\"ltr\">#FFF</code>, <code dir=\"ltr\">#ffffff</code> and <code dir=\"ltr\">#FfFfFf</code> are one color. Normalize before you count, or your numbers will be wrong and someone will notice.</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"0\" aria-label=\"Run this example\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>Run</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// A slice of Northwind's CSS, pulled from three repos.</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> css</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> `</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  .btn { background: #1F5AE0; color: #FFF; }</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  .btn:hover { background: #1b50c9; }</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  .card { border: 1px solid #DADFE4; color: #1f2933; }</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  .meta { color: #5f6b7a; }</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  .footer { color: #66737F; border-top: 1px solid #dde1e6; }</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  .title { color: #222B35; }</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  .banner { background: #ffffff; color: #1F2933; }</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">  .link { color: #1f5ae0; }</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">function</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> expand</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#953800;--shiki-dark:#FFA657\">hex</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> h</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> hex.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">slice</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toLowerCase</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ===</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 3</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> ?</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '#'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">h].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">c</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> c </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> c).</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">join</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">''</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">:</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '#'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> +</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> h;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> counts</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> new</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> Map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> match</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> css.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">match</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/#</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">[0-9a-fA-F]</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">{3,6}\\b</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">/</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">g</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">)) {</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> value</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\"> expand</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(match);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">set</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(value, (counts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">get</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(value) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> ranked</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">counts].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a[</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">]);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">`${</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">ranked</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">length</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">} distinct colors`</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">for</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">value</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">n</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">of</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> ranked) console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(value, n);</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>Run it and you get nine distinct values from eight short rules. Notice <code dir=\"ltr\">#1f2933</code> and <code dir=\"ltr\">#222b35</code>: two text colors nobody would tell apart. Notice <code dir=\"ltr\">#dadfe4</code> and <code dir=\"ltr\">#dde1e6</code>: two border greys. These near-duplicates are the real finding. Nobody chose nine colors; several people each chose one.</p>\n<p>On a real codebase you run the same idea over every <code dir=\"ltr\">.css</code>, <code dir=\"ltr\">.scss</code> and styled-component file, and you add <code dir=\"ltr\">rgb()</code> and <code dir=\"ltr\">hsl()</code> parsing. The output goes into a spreadsheet: value, count, files, and which cluster of near-identical values it belongs to.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Auditing only the main app</span></p>\n<p>The marketing site, the admin tool and the email templates are where drift hides, because they get the least design attention. At Northwind the marketing site alone contributed 23 of our 64 greys. If the system will serve a surface, audit that surface.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">From counts to priorities</h2>\n<p>Raw counts don’t tell you what to do. Two numbers per element do:</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Element</th>\n<th>Screens using it</th>\n<th>Distinct versions</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Button</td>\n<td>140</td>\n<td>11</td>\n</tr>\n<tr>\n<td>Text input</td>\n<td>96</td>\n<td>6</td>\n</tr>\n<tr>\n<td>Grey (any use)</td>\n<td>everywhere</td>\n<td>64</td>\n</tr>\n<tr>\n<td>Date picker</td>\n<td>3</td>\n<td>2</td>\n</tr>\n<tr>\n<td>Data table</td>\n<td>31</td>\n<td>5</td>\n</tr>\n</tbody>\n</table></div><p>The system should own first what is <strong>frequent and inconsistent</strong>. Buttons, inputs and greys win; the date picker can wait, even though it is the most painful to build. A component used on three screens is a local problem. A grey used on every screen is everyone’s problem.</p>\n<p>Then add cost. For each high-priority element, ask engineering how many separate implementations they maintain. Northwind had eleven button components in code, each with its own focus-style bug. “We fix the same bug eleven times” is the sentence that got our headcount approved. Designers often present the inventory as an argument about quality; leaders fund it as an argument about cost.</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Why this matters</span></p>\n<p>The inventory is also your baseline. Six months later you rerun the same color script and report “64 greys down to 9”. That before-and-after number is how you prove the system works, which is the subject of the last lesson in this course.</p>\n</aside>\n<h2 id=\"sec-4\" tabindex=\"-1\">What you hand over</h2>\n<p>A good inventory ends with three artifacts: the Figma boards, the code audit spreadsheet, and a one-page ranked list of what the system will own in its first two quarters. Keep the boards; you will reuse them to show progress. In the exercise below you do the first consolidation yourself: you turn seven near-identical greys into three named values. Next, you decide the principles and team shape that will keep those decisions from drifting again.</p>\n","toc":[{"id":"sec-1","text":"The screenshot inventory","level":2},{"id":"sec-2","text":"The code audit","level":2},{"id":"sec-3","text":"From counts to priorities","level":2},{"id":"sec-4","text":"What you hand over","level":2}],"takeaways":["An interface inventory collects every instance of each UI element side by side, so duplication becomes impossible to argue with.","Audit code as well as screens; a script that counts color values finds drift that screenshots hide.","Normalize values before counting, or <code dir=\"ltr\">#FFF</code> and <code dir=\"ltr\">#ffffff</code> look like two decisions.","Prioritize elements that are both frequent and inconsistent; those give the biggest return when the system owns them."],"further":[{"title":"CSS color values (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value"},{"title":"Using CSS custom properties (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Cascading_variables/Using_custom_properties"}],"quiz":[{"q":"<p>Your color script reports <code dir=\"ltr\">#FFF</code>, <code dir=\"ltr\">#ffffff</code> and <code dir=\"ltr\">rgb(255, 255, 255)</code> as three separate values. What should you do before presenting the numbers?</p>","options":[{"text":"Normalize every value to one notation, then count again.","why":"Correct. They are the same decision written three ways; counting them separately inflates the drift and undermines trust in your audit."},{"text":"Report them as three values, since engineers typed them differently.","why":"The audit measures design decisions, not spelling; reporting three whites makes the numbers easy to dismiss."},{"text":"Delete the <code dir=\"ltr\">rgb()</code> value from the codebase first.","why":"Changing code before the audit is finished mixes cleanup into measurement; normalize in the script instead."}],"answer":0},{"q":"<p>The inventory shows 2 date pickers used on 3 screens, and 11 button styles used on 140 screens. Where should Northwind UI start?</p>","options":[{"text":"Date pickers, because they are the hardest component to build.","why":"Difficulty doesn’t make something urgent; three screens is a small surface with little shared cost."},{"text":"Buttons, because they are both frequent and inconsistent.","why":"Correct. High usage times high variance is where consolidation saves the most time and fixes the most visible inconsistency."},{"text":"Both at once, so the system launches complete.","why":"Spreading a small team across everything delays the high-value win; a system earns trust by fixing the common pain first."},{"text":"Neither; the system should start with documentation.","why":"Documentation without anything to document gives teams nothing to adopt."}],"answer":1},{"q":"<p>A product manager says the inventory screenshots are “just designers being picky”. Which addition makes the audit hardest to dismiss?</p>","options":[{"text":"A mood board showing how the product could look after a redesign.","why":"A vision is useful later, but it reads as taste, which is the exact objection you are answering."},{"text":"A longer list of every inconsistency found.","why":"More examples of the same kind rarely change a skeptic’s mind; cost does."},{"text":"The engineering time spent maintaining each duplicate, such as 11 button implementations with separate bug fixes.","why":"Correct. Translating duplication into hours and bugs turns an aesthetic complaint into a cost the business already pays."}],"answer":2}],"exercise":{"kind":"web","mode":"dom","starter":{"html":"<article class=\"card\">\n  <h3 class=\"card-title\">Shipment NW-4471</h3>\n  <p class=\"card-meta\">Rotterdam to Hamburg · 2 pallets</p>\n  <p class=\"card-status\">In transit</p>\n  <footer class=\"card-footer\">Updated 4 min ago</footer>\n</article>","css":"/* Seven greys found in the audit. Collapse them into three tokens. */\n.card {\n  max-width: 320px;\n  padding: 16px;\n  border: 1px solid #dadfe4;\n  border-radius: 8px;\n}\n.card-title { margin: 0 0 4px; color: #1f2933; }\n.card-meta { margin: 0 0 12px; color: #5f6b7a; }\n.card-status {\n  margin: 0;\n  padding-top: 12px;\n  color: #222b35;\n  border-top: 1px solid #d7dce1;\n}\n.card-footer {\n  margin-top: 12px;\n  padding-top: 8px;\n  font-size: 14px;\n  color: #66737f;\n  border-top: 1px solid #dde1e6;\n}\n","js":""},"solution":{"html":"<article class=\"card\">\n  <h3 class=\"card-title\">Shipment NW-4471</h3>\n  <p class=\"card-meta\">Rotterdam to Hamburg · 2 pallets</p>\n  <p class=\"card-status\">In transit</p>\n  <footer class=\"card-footer\">Updated 4 min ago</footer>\n</article>","css":":root {\n  --nw-gray-900: #1f2933;\n  --nw-gray-600: #616e7c;\n  --nw-gray-200: #d9dee3;\n}\n.card {\n  max-width: 320px;\n  padding: 16px;\n  border: 1px solid var(--nw-gray-200);\n  border-radius: 8px;\n}\n.card-title { margin: 0 0 4px; color: var(--nw-gray-900); }\n.card-meta { margin: 0 0 12px; color: var(--nw-gray-600); }\n.card-status {\n  margin: 0;\n  padding-top: 12px;\n  color: var(--nw-gray-900);\n  border-top: 1px solid var(--nw-gray-200);\n}\n.card-footer {\n  margin-top: 12px;\n  padding-top: 8px;\n  font-size: 14px;\n  color: var(--nw-gray-600);\n  border-top: 1px solid var(--nw-gray-200);\n}\n","js":""},"checks":[{"id":"root-tokens","test":"const rules = [...document.styleSheets].flatMap((s) => [...s.cssRules]).filter((r) => r.selectorText); const root = rules.filter((r) => r.selectorText.trim() === ':root'); const names = new Set(); for (const r of root) for (const p of r.style) if (p.startsWith('--')) names.add(p); return names.size >= 3;"},{"id":"no-raw-colors","test":"const rules = [...document.styleSheets].flatMap((s) => [...s.cssRules]).filter((r) => r.selectorText); const own = rules.filter((r) => /card/.test(r.selectorText)); return own.length > 0 && own.every((r) => !/#[0-9a-f]{3,8}\\b|rgba?\\(|hsla?\\(/i.test(r.style.cssText));"},{"id":"three-greys","test":"const used = new Set(); for (const el of [$('.card'), ...$$('.card *')]) { const cs = getComputedStyle(el); if (el !== $('.card')) used.add(cs.color); if (parseFloat(cs.borderTopWidth) > 0) used.add(cs.borderTopColor); } return used.size <= 3;"},{"id":"hierarchy","test":"const lum = (c) => { const [r, g, b] = c.match(/[\\d.]+/g).map(Number); return 0.2126 * r + 0.7152 * g + 0.0722 * b; }; const t = lum(style('.card-title', 'color')); return t < lum(style('.card-meta', 'color')) && t < lum(style('.card-footer', 'color')) && lum(style('.card-meta', 'color')) < lum(style('.card-status', 'border-top-color'));"}],"title":"Collapse seven greys into three tokens","prompt":"<p>This shipment card is a slice of the Northwind audit: seven hard-coded greys that are really three decisions. Declare <strong>at least three</strong> grey custom properties on <code dir=\"ltr\">:root</code> (for example <code dir=\"ltr\">--nw-gray-900</code>, <code dir=\"ltr\">--nw-gray-600</code>, <code dir=\"ltr\">--nw-gray-200</code>), then rewrite every <code dir=\"ltr\">.card</code> rule to use them with <code dir=\"ltr\">var()</code>, with no hex, <code dir=\"ltr\">rgb()</code> or <code dir=\"ltr\">hsl()</code> values left in those rules. Keep the hierarchy: the title and status use the darkest grey, the meta and footer text a medium grey, and every border the lightest grey.</p>\n","hints":["Group the seven values by job: dark text, secondary text, borders.","Pick one value per group, for example #1f2933, #616e7c and #d9dee3, and declare them in a <code dir=\"ltr\">:root { … }</code> rule.","Replace each color in the <code dir=\"ltr\">.card</code> rules with <code dir=\"ltr\">var(--nw-gray-900)</code>, <code dir=\"ltr\">var(--nw-gray-600)</code> or <code dir=\"ltr\">var(--nw-gray-200)</code>."],"explanation":"<p>Seven values collapse into three because they only ever expressed three intentions: primary text, secondary text, and dividers. Declaring them once on <code dir=\"ltr\">:root</code> turns them into primitives the rest of the system can reference. The next time someone needs a border grey, they pick a name instead of eyedropping a hex value, which is how the 64 greys appeared in the first place. In the next section you add a semantic layer on top of these primitives.</p>\n","checkLabels":{"root-tokens":"<code dir=\"ltr\">:root</code> declares at least three custom properties","no-raw-colors":"The <code dir=\"ltr\">.card</code> rules contain no hard-coded hex, rgb() or hsl() colors","three-greys":"The card uses at most three distinct greys for text and borders","hierarchy":"Title is darker than meta and footer text, which are darker than the borders"}},"words":725,"readMinutes":3}