[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F5MD0VGECVK74RKA7PFXDB":3,"skill-same:01M6F5MD0VGECVK74RKA7PFXDB":78},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":27,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":36,"commitSha":52,"skillDirectory":53,"forksCount":54,"repositoryHtmlUrl":55,"repositoryHomepageUrl":6,"ownerHtmlUrl":56,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":57,"install":58,"resources":71},"01M6F5MD0VGECVK74RKA7PFXDB","design-spatial",null,"Design — spatial composition","boisenoise\u002Fskills-collections","boisenoise","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F193180?v=4","01M6CDBDQK6BDN55ZMA6B6PKBM","01M6CDBDVSJ3JPTBYN26FZ7TZK",12,"MIT","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-design-spatial\u002FSKILL.md",[17,22],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000015","视觉设计","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000032","设计师","PROFESSION","适用职业",[28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"design","Spatial","design spatial","github","license","https","Composition","community","main","wed","deterministic","critical","tree","blob","2026","2026-11-10 10:12:30",false,"---\nname: design-spatial\ndescription: Design — spatial composition\nrisk: critical\nsource: https:\u002F\u002Fgithub.com\u002Fconnerkward\u002Fckw-design-skill\u002Ftree\u002Fmain\u002Fdeterministic-design\u002Fdesign-spatial\nsource_repo: connerkward\u002Fckw-design-skill\nsource_type: community\ndate_added: 2026-07-01\nlicense: MIT\nlicense_source: https:\u002F\u002Fgithub.com\u002Fconnerkward\u002Fckw-design-skill\u002Fblob\u002Fmain\u002FLICENSE\n---\n\n# Design — spatial composition\n## When to Use\n\nUse this skill when you need design — spatial composition.\n\n\nA model cannot trust its own UI output. Everything else follows from two failures.\n\n## 1. It can't see what it made\n\nUI is generated as a token stream, never as pixels — so the model cannot perceive collisions, overlap, imbalance, or broken spacing. It will write a headline that runs into the hero image and have no idea.\n\n**Render it and judge the image, not the code.** Serve with any static server (e.g. `python3 -m http.server` or `npx serve`) and screenshot headless via Playwright. Screenshot at a few widths.\n\n**Critique with fresh eyes — not your own.** Grading your own output rationalizes it; the builder looks at its overlapping headline and calls it fine (this is exactly how a real collision shipped in testing). Use a separate judge — a subagent that did *not* write the page — and tell it to hunt for what's *wrong*: collisions, edge tangents, ragged alignment, lopsided weight, no clear focal point, breaks at some width. Fix, re-render, re-judge.\n\n## 2. Its first idea is the average\n\nWhatever it produces first is the mean of its training data — and there is more than one mean:\n\n- the **generic-AI mean**: Inter, purple-on-white gradients, centered single column, three equal cards;\n- the **designer-trend mean**: oversized condensed caps, dark-mode + grain, monospace \"vibes\" microtext, sticker badges.\n\nLanding on the second isn't taste — it's a more flattering average, which is why it slips past. **Treat your first instinct as the mean and deviate deliberately — toward *this product's specific world*** (use design-thinking's domain \u002F color-world \u002F signature as the direction), **not toward another trend.** If the result could be any startup, you shipped the mean.\n\n## 3. So don't prescribe a style\n\nAny fixed rule — a 12-col grid, an 8-point scale, \"mono = data\" — *becomes* next cycle's mean, and a blind model executes it into collisions anyway. Prescribe the **process, not the look**: see it with fresh eyes, and push off the average toward the domain. Taste supplies the direction (design-thinking \u002F design-philosophy); this skill only insists you **look** and **don't ship the mean**.\n\nFor iterative spatial tuning, a local page with live controls (sliders, pickers, drag handles) beats one-shot critique.\n\n## 4. NEVER ship horizontal overflow — THE mandatory gate, no exceptions\n\n> **BLOCKING GATE. You may not call any web UI \"done\", \"working\", \"fixed\", or\n> \"looks good\" until you have run the `scrollWidth` check below at a narrow width\n> THIS turn and seen `0`. Not \"I added overflow-x:clip so it's fine.\" Not \"it\n> looked fine at my width.\" MEASURE. Narrow. Every time. If you didn't measure,\n> it isn't done — say \"haven't checked overflow yet\" instead of claiming done.**\n\nA side-to-side scrollbar that doesn't match the content is the **single most common\nand most embarrassing** layout failure, and it ships *over and over* because the dev\nviewport is wide enough to hide it — the overflow only appears once the window is\nnarrower than some element. It is **invisible at desktop width**, so the §1\nrender-critique loop will NOT catch it unless you screenshot narrow. Separate,\nexplicit, non-negotiable gate.\n\n**It recurs because layouts GROW after they were last checked.** Every time you add a\nnav tab, a toolbar button, a header control, a chip, a wider equation\u002F`\u003Cpre>`, or any\nnew item to a `flex`\u002F`inline` row, you have invalidated the last overflow check — the\nrow that fit yesterday now pushes past the edge between ~720–1200px while your 1440px\ndev window shows nothing wrong. (Real ship, 2026-06, TWICE: a progress-bar edge label\noverflowed 23px; then a `flex-wrap:nowrap` header that grew 4 tabs scrolled the whole\npage 309px across 720–1200px — both invisible at dev width, both caught only by\nmeasuring narrow.) **So: any change that adds an element to a horizontal row re-arms\nthis gate. Re-measure.**\n\n**Default defenses to apply up front (so the gate passes by construction):**\n- **Header \u002F nav \u002F toolbar rows: `flex-wrap: wrap`, never `nowrap`.** A growing\n  single-row flex is the #1 source of this bug. Wrapping is a no-op when it fits and\n  saves you when it doesn't.\n- Use clipping only for intentional decorative overflow after checking focus rings, menus and offscreen content; do not apply it globally to conceal a layout defect.\n\n**The check — run before calling ANY page done:** `document.documentElement.scrollWidth - document.documentElement.clientWidth` must equal `0`, tested at your dev width AND resized narrow (≤1024px, and a phone width ~390px). If > 0, find the offender:\n```js\ndocument.querySelectorAll('*').forEach(el=>{const r=el.getBoundingClientRect();\n  if(r.right>innerWidth+1||r.left\u003C-1) console.log(Math.round(r.right), el);});\n```\n\n**Safety net:** `overflow-x: clip` on `body` (prefer `clip` over `hidden` — it clips without creating a scroll container, so it won't break `position:sticky`\u002Fanchored layouts). But a net is not a fix — **find and kill the root cause:**\n\n- **`position:absolute` + `white-space:nowrap` anchored at an edge** (`left:100%`, `right:0`): a *centered* nowrap label on the right edge juts past the viewport. (Real ship, 2026-06: a progress bar's \"300 · learned model\" milestone label at `left:100%` with `translateX(-50%)` overflowed 23px → phantom horizontal scroll at sub-1180px widths.) **Anchor edge labels inward** — right end `right:0; transform:none`, left end `left:0; transform:none`.\n- **`100vw`** — includes the scrollbar width (~15px), so on any vertically-scrolling page it guarantees ~15px of horizontal overflow. Use `100%`.\n- **flex \u002F grid children without `min-width:0`** — they refuse to shrink below their content and blow out the track (a long title in a flex card, a `\u003Cpre>` in a grid cell). Add `min-width:0`.\n- **long unbreakable strings** (URLs, hashes, tokens): `overflow-wrap:anywhere` or `word-break:break-word`.\n- fixed pixel widths wider than the viewport; large negative margins; oversized `position:absolute` elements.\n\nThe generalization: **anything pinned to an edge or sized in viewport units is a horizontal-overflow suspect — test narrow, measure `scrollWidth`, clip the body as backstop, and anchor edge-pinned content inward.**\n\n## 5. Lay out in TASK order — minimize transition cost\n\nBefore placing elements, **walk the user's actual step sequence for completing the\npage's action**, then arrange elements in that same perceptual\u002Fview order. The\nlayout should read like the task: orient → work → confirm. Any mouse travel or\nscrolling that serves no practical purpose is a defect.\n\n- **Orient at top:** controls\u002Foptions up top are good — they tell the user what\n  the page is for and what it can do before they commit to reading it.\n- **Confirm where the work ENDS:** if the task is \"review a long list, then act\"\n  (approve, flag, submit, save), the action buttons must ALSO exist at the\n  bottom — where the user's eyes and cursor are when they finish. The original\n  failure: a delete-review page with confirm buttons only in the top toolbar —\n  after scrolling through 120 images, the user had to scroll all the way back up\n  to click \"flag the rest.\" Duplicate the action bar at the bottom (or make the\n  toolbar sticky); both are one line of code, the scroll-back is paid per page.\n- **The heuristic: save the user transit time.** Every interaction has a path:\n  where the eyes\u002Fcursor are when a step ends vs where the next step's control\n  is. Sum those distances; shrink the big ones. Fitts's law for the page as a\n  whole, not just one button.\n- **Check it in the render-and-critique loop (§1):** ask the judge \"trace the\n  task: where is the user when they finish each step, and how far is the next\n  control?\" — a layout can be aligned, balanced, and still force a round trip.\n\n## 6. Balance is measurable — don't eyeball it (or trust a VLM's eye)\n\n§1 says render and have fresh eyes critique it. That qualitative pass catches\ncollisions and ragged alignment, but **a model has no reliable sense of visual\nbalance** — ask a VLM \"is this centered \u002F balanced?\" and it confabulates a verdict.\nThe fix is to stop asking opinions and **measure a number**, then keep that number\nhonest with an *independent* check. Use both: §1's fresh-eyes critique AND the hard\nnumber below. (This pairs a live in-browser box model + auto-balancer with an\noffline pixel-oracle that re-measures the rendered screenshot — see the\n`layout-audit.js` companion script in this skill.)\n\n**The principle.** Visual balance is the *center of mass of visual weight*. It's\narithmetic, not taste — so compute it.\n\n**Optical center, not geometric.** Target `x = 0.50`, `y ≈ 0.46` — slightly high,\nbecause a centroid at literal 50% reads as sagging.\n\n**Visual weight estimate = area × guessed ink-density.** These coefficients and optical-center tolerances are exploratory heuristics, not validated perceptual laws. Same-size ≠ same-weight: a\nsolid-black heading is heavy; a grey\u002FASCII\u002Flight image reads far lighter than its\narea; body text is sparse. Calibrated starting multipliers (from `asym.html`, re-tune\nper project — these were hand-guesses until corrected against the pixel oracle):\n```js\nconst DENS = {portrait:0.34, h1:0.82, kicker:0.42, lead:0.22, body:0.16, meta:0.5};\n```\n\n**Centroid.** Per axis, `centroid = Σ(wᵢ·posᵢ) \u002F Σwᵢ`; balanced ⇔ the centroid sits\non the optical center. To FIX imbalance, think see-saw: what counts is the **moment**\n= weight × distance-from-axis, so a heavy element near the edge is counterweighted by\n(a) an opposing weight, (b) a bigger element on the other side, (c) pulling the heavy\nelement inward (shorter lever arm), or (d) shrinking it. That's exactly the\nauto-balancer's escalation order in `asym.html` — grow the opposing heading first\n(cheapest), then add weight, then pull the heavy element in, then shrink it (last\nresort).\n\n**Two models — and why you need the independent one:**\n- **Cheap box model** (live tuning): put each element's weight at its bounding-box\n  *center*. Instant, fine for dragging sliders. BUT it has a systematic bug —\n  left-aligned text's ink sits *left* of its box, so the box model misplaces the\n  weight. A metric that shares the layout's own assumptions is **circular**; it once\n  reported \"balanced\" at a pixel-measured 0.93 lopsided.\n- **Ground-truth pixel oracle** (`analyze.py`): rasterize the *rendered* page\n  (Playwright screenshot or html2canvas) and take the centroid of actual non-paper\n  pixels, weighting each pixel by its distance from the background color. It knows\n  nothing about the layout's intent — it just counts ink. **When the box model and\n  the pixels disagree, the pixels win.** (`asym.html` closes the loop: it regresses\n  the box-vs-pixel discrepancy and offers a trust dial α to blend toward the oracle.)\n- **Acceptance criterion (measurable):** `|centroid_x − 0.50| \u003C 0.03` and\n  `|centroid_y − 0.46| \u003C 0.04`, plus low left\u002Fright and top\u002Fbottom imbalance\n  (`|w_left − w_right| \u002F total`).\n\n**The verification gate (lighter than §4's, same spirit).** Before calling a\nbalance-critical layout \"balanced\", do NOT assert it from the code or a VLM opinion —\nscreenshot the *rendered* page, compute the ink-centroid offset from optical center,\nand report the actual number. This is the design-skill application of\n`verify-outputs-rule`: look at the real artifact, and make the validating check\n(pixels) independent of the thing you tuned (the layout). It's the quantitative\ncomplement to §1's qualitative critique.\n\n## 7. The layout audit — metrics that MEDIATE the eye, never replace it\n\n§6 covers balance; this generalizes it to a full deterministic sweep, and fixes the\nfailure mode that matters most: **the model reads a metric\u002FJSON and never looks at the\nscreenshot, so it can't apply the common sense that catches the metric being wrong.**\n\n`scripts\u002Flayout-audit.js` is a dependency-free pass you run via Playwright MCP\n`browser_evaluate` on a rendered page. It measures six things deterministically — all\ngeometry, color, and pixels, no \"does this look right?\":\n\n| check | how (deterministic) | tier |\n|---|---|---|\n| **collision** | content-rect intersection ≥12% | gate |\n| **contrast** | WCAG luminance ratio of text vs effective bg (\u003C4.5, large \u003C3) | gate |\n| **tap** | interactive targets \u003C44×44 CSS px (conservative project heuristic, not a WCAG verdict) | review |\n| **overflow** | `scrollWidth − clientWidth` (the §4 gate) | gate |\n| **alignment** | left-edge clusters → near-misses 1–7px off the shared line | signal |\n| **spacing** | gap CoV among a container's children | signal |\n| **balance** | ink-density-weighted centroid vs optical center (§6) | signal |\n\n**What makes it mediate rather than replace:** it doesn't just return JSON — it **draws\nevery finding as an SVG overlay onto the page**, so the *next* `browser_take_screenshot`\nis an **annotated screenshot**. The number tells you WHERE to look; you then look and\ndecide. This is mandatory, not optional:\n\n```\nbrowser_evaluate({ function: \"() => { \u003Cpaste scripts\u002Flayout-audit.js> ; return __audit({}); }\" })\nbrowser_take_screenshot()      \u002F\u002F ← the overlay is now on the page. VIEW IT. Reason over it.\n```\n\nPass `{align:'.card .title,.card .price', space:'.feature-list'}` to scope the two\nselector-dependent checks; pass `{contentSelector:'…'}` for non-semantic layouts where\ncollision needs help finding the blocks.\n\n**These are HEURISTICS, not laws — and they split into two kinds you must not conflate:**\n\n- **GATES = correctness** (overflow, contrast, tap). These measure accessibility\u002F\n  usability *facts*, not taste. Failing one is a real defect. Safe to **block** on.\n  (Collision is a near-gate: usually a real bug, but can be intentional — so eye-confirm,\n  don't auto-fail.)\n- **SIGNALS = convention** (balance, alignment, spacing rhythm). These measure how\n  closely the layout matches a *symmetric, regular, gridded* aesthetic — which is exactly\n  the **generic mean** §2 tells you to push *away* from. **Optimizing a layout to maximize\n  these scores makes it blander.** An off-center balance, a deliberate misalignment, an\n  uneven rhythm are core creative tools and frequently the best thing on the page. Treat\n  signals as \"worth a look,\" **never** as defects to fix.\n\n**The discipline (the whole point — bias hard toward this):**\n- **Never accept a metric you have not looked at.** A flag is a *pointer to look*, not a\n  verdict. Reading `collisions: 1` and acting without viewing the annotated shot is the\n  exact failure this section exists to kill.\n- **Use signals to catch ACCIDENTS, never to enforce convention.** A 7px alignment drift\n  you didn't mean, a phantom scrollbar, a 1.9:1 caption — catch those. But the *same*\n  balance\u002Falignment\u002Fspacing signal fires on deliberate asymmetry, intentional overlap,\n  and expressive rhythm. **When the metric and the interesting choice conflict, the\n  interesting choice usually wins.** Do not \"fix\" a signal toward symmetry\u002Fevenness unless\n  the eye judges the deviation actually worse. A model that maximizes these scores designs\n  the mean.\n- **Overrule flags the eye judges intentional.** Brutalist headline overlap, avatar on a\n  banner, asymmetric hero — the metric flags them; common sense overrules. Proven live in\n  the worked example: obeying the collision check on the brutalist mock removes the overlap\n  and the design goes *flat*.\n- **Gates are necessary, not sufficient.** `gates_pass:true` (overflow\u002Fcontrast\u002Ftap all 0)\n  clears the deterministic floor — it does **not** mean the layout is good. A bland centered\n  template passes every gate and is still the mean. After gates pass, the real judgment (§1\n  fresh-eyes critique, taste, brand fit) still has to happen.\n\n**The proof, made concrete:** build a page that runs all six algorithms live on a few\nrealistic mock sites in different styles, each with a toggle between the layout **as\ndesigned** and the version **obeying the metric**, plus on-render overlays. It shows both\nhalves: obeying a *gate* fixes a real bug (low-contrast CTA, sub-44 tap target, overlapping\ncards), while obeying a *signal* makes it worse (an asymmetric editorial hero is the more\ninteresting layout; \"correcting\" a deliberate brutalist overlap flattens it). That explorable\nis where `layout-audit.js` was distilled from.\n\n## 8. Optical craft — perception beats geometry\n\nThe audit's alignment check (§7) measures *geometric* edges. The eye doesn't read geometry,\nit reads perception — so a few cases need a manual nudge the metric can't make. These are\neye-judgments, not gates. (From the Web Interface Guidelines, `vercel-labs\u002Fweb-interface-guidelines` @ `4e799d4`.)\n\n- **Optical alignment — nudge ±1–2px when it *looks* off though it measures centered.** A\n  play-triangle in a round button must shift right of geometric center to look centered (its\n  visual mass is left-biased). Glyphs, arrows, and asymmetric icons often need the same. Text\n  vertically centered by box metrics frequently sits a hair low — lift it. Geometry is the\n  starting point; the eye is the judge.\n- **Balance icon\u002Ftext lockups.** When an icon sits beside text, match their *visual weight* —\n  adjust the icon's stroke, size, spacing, or color so neither overpowers. A thin-stroke icon\n  next to medium-weight text looks weak; thicken its stroke (or size it up slightly) so they\n  read as one lockup. Optical size, not equal pixel size, is the target.\n- This is the same principle as the §6\u002F§7 debias: the number gets you close; the eye makes the\n  final 1px call. Don't let a geometric alignment metric *prevent* an optical correction.\n\n## Limitations\n\n- WCAG 2.2 AA target minimum is 24×24 CSS px with spacing and other exceptions; 44×44 is a stricter target, not a universal failure threshold. The script does not implement exceptions or full contrast compositing (images, gradients, transparent ancestors and modern color syntax). `gates_pass` only summarizes this partial heuristic; it cannot certify accessibility.\n- `analyze.py` and `asym.html` mentioned above are not bundled. Pixel centroid is background\u002Fviewport-dependent and cannot establish visual quality by itself. Verify intentional scroll regions and keyboard\u002Fzoom behavior in addition to root scrollWidth.\n\n- Use this skill only when the task clearly matches its upstream source and local project context.\n- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.\n- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.\n","# Design — spatial composition\n## When to Use\n\nUse this skill when you need design — spatial composition.\n\n\nA model cannot trust its own UI output. Everything else follows from two failures.\n\n## 1. It can't see what it made\n\nUI is generated as a token stream, never as pixels — so the model cannot perceive collisions, overlap, imbalance, or broken spacing. It will write a headline that runs into the hero image and have no idea.\n\n**Render it and judge the image, not the code.** Serve with any static server (e.g. `python3 -m http.server` or `npx serve`) and screenshot headless via Playwright. Screenshot at a few widths.\n\n**Critique with fresh eyes — not your own.** Grading your own output rationalizes it; the builder looks at its overlapping headline and calls it fine (this is exactly how a real collision shipped in testing). Use a separate judge — a subagent that did *not* write the page — and tell it to hunt for what's *wrong*: collisions, edge tangents, ragged alignment, lopsided weight, no clear focal point, breaks at some width. Fix, re-render, re-judge.\n\n## 2. Its first idea is the average\n\nWhatever it produces first is the mean of its training data — and there is more than one mean:\n\n- the **generic-AI mean**: Inter, purple-on-white gradients, centered single column, three equal cards;\n- the **designer-trend mean**: oversized condensed caps, dark-mode + grain, monospace \"vibes\" microtext, sticker badges.\n\nLanding on the second isn't taste — it's a more flattering average, which is why it slips past. **Treat your first instinct as the mean and deviate deliberately — toward *this product's specific world*** (use design-thinking's domain \u002F color-world \u002F signature as the direction), **not toward another trend.** If the result could be any startup, you shipped the mean.\n\n## 3. So don't prescribe a style\n\nAny fixed rule — a 12-col grid, an 8-point scale, \"mono = data\" — *becomes* next cycle's mean, and a blind model executes it into collisions anyway. Prescribe the **process, not the look**: see it with fresh eyes, and push off the average toward the domain. Taste supplies the direction (design-thinking \u002F design-philosophy); this skill only insists you **look** and **don't ship the mean**.\n\nFor iterative spatial tuning, a local page with live controls (sliders, pickers, drag handles) beats one-shot critique.\n\n## 4. NEVER ship horizontal overflow — THE mandatory gate, no exceptions\n\n> **BLOCKING GATE. You may not call any web UI \"done\", \"working\", \"fixed\", or\n> \"looks good\" until you have run the `scrollWidth` check below at a narrow width\n> THIS turn and seen `0`. Not \"I added overflow-x:clip so it's fine.\" Not \"it\n> looked fine at my width.\" MEASURE. Narrow. Every time. If you didn't measure,\n> it isn't done — say \"haven't checked overflow yet\" instead of claiming done.**\n\nA side-to-side scrollbar that doesn't match the content is the **single most common\nand most embarrassing** layout failure, and it ships *over and over* because the dev\nviewport is wide enough to hide it — the overflow only appears once the window is\nnarrower than some element. It is **invisible at desktop width**, so the §1\nrender-critique loop will NOT catch it unless you screenshot narrow. Separate,\nexplicit, non-negotiable gate.\n\n**It recurs because layouts GROW after they were last checked.** Every time you add a\nnav tab, a toolbar button, a header control, a chip, a wider equation\u002F`\u003Cpre>`, or any\nnew item to a `flex`\u002F`inline` row, you have invalidated the last overflow check — the\nrow that fit yesterday now pushes past the edge between ~720–1200px while your 1440px\ndev window shows nothing wrong. (Real ship, 2026-06, TWICE: a progress-bar edge label\noverflowed 23px; then a `flex-wrap:nowrap` header that grew 4 tabs scrolled the whole\npage 309px across 720–1200px — both invisible at dev width, both caught only by\nmeasuring narrow.) **So: any change that adds an element to a horizontal row re-arms\nthis gate. Re-measure.**\n\n**Default defenses to apply up front (so the gate passes by construction):**\n- **Header \u002F nav \u002F toolbar rows: `flex-wrap: wrap`, never `nowrap`.** A growing\n  single-row flex is the #1 source of this bug. Wrapping is a no-op when it fits and\n  saves you when it doesn't.\n- Use clipping only for intentional decorative overflow after checking focus rings, menus and offscreen content; do not apply it globally to conceal a layout defect.\n\n**The check — run before calling ANY page done:** `document.documentElement.scrollWidth - document.documentElement.clientWidth` must equal `0`, tested at your dev width AND resized narrow (≤1024px, and a phone width ~390px). If > 0, find the offender:\n```js\ndocument.querySelectorAll('*').forEach(el=>{const r=el.getBoundingClientRect();\n  if(r.right>innerWidth+1||r.left\u003C-1) console.log(Math.round(r.right), el);});\n```\n\n**Safety net:** `overflow-x: clip` on `body` (prefer `clip` over `hidden` — it clips without creating a scroll container, so it won't break `position:sticky`\u002Fanchored layouts). But a net is not a fix — **find and kill the root cause:**\n\n- **`position:absolute` + `white-space:nowrap` anchored at an edge** (`left:100%`, `right:0`): a *centered* nowrap label on the right edge juts past the viewport. (Real ship, 2026-06: a progress bar's \"300 · learned model\" milestone label at `left:100%` with `translateX(-50%)` overflowed 23px → phantom horizontal scroll at sub-1180px widths.) **Anchor edge labels inward** — right end `right:0; transform:none`, left end `left:0; transform:none`.\n- **`100vw`** — includes the scrollbar width (~15px), so on any vertically-scrolling page it guarantees ~15px of horizontal overflow. Use `100%`.\n- **flex \u002F grid children without `min-width:0`** — they refuse to shrink below their content and blow out the track (a long title in a flex card, a `\u003Cpre>` in a grid cell). Add `min-width:0`.\n- **long unbreakable strings** (URLs, hashes, tokens): `overflow-wrap:anywhere` or `word-break:break-word`.\n- fixed pixel widths wider than the viewport; large negative margins; oversized `position:absolute` elements.\n\nThe generalization: **anything pinned to an edge or sized in viewport units is a horizontal-overflow suspect — test narrow, measure `scrollWidth`, clip the body as backstop, and anchor edge-pinned content inward.**\n\n## 5. Lay out in TASK order — minimize transition cost\n\nBefore placing elements, **walk the user's actual step sequence for completing the\npage's action**, then arrange elements in that same perceptual\u002Fview order. The\nlayout should read like the task: orient → work → confirm. Any mouse travel or\nscrolling that serves no practical purpose is a defect.\n\n- **Orient at top:** controls\u002Foptions up top are good — they tell the user what\n  the page is for and what it can do before they commit to reading it.\n- **Confirm where the work ENDS:** if the task is \"review a long list, then act\"\n  (approve, flag, submit, save), the action buttons must ALSO exist at the\n  bottom — where the user's eyes and cursor are when they finish. The original\n  failure: a delete-review page with confirm buttons only in the top toolbar —\n  after scrolling through 120 images, the user had to scroll all the way back up\n  to click \"flag the rest.\" Duplicate the action bar at the bottom (or make the\n  toolbar sticky); both are one line of code, the scroll-back is paid per page.\n- **The heuristic: save the user transit time.** Every interaction has a path:\n  where the eyes\u002Fcursor are when a step ends vs where the next step's control\n  is. Sum those distances; shrink the big ones. Fitts's law for the page as a\n  whole, not just one button.\n- **Check it in the render-and-critique loop (§1):** ask the judge \"trace the\n  task: where is the user when they finish each step, and how far is the next\n  control?\" — a layout can be aligned, balanced, and still force a round trip.\n\n## 6. Balance is measurable — don't eyeball it (or trust a VLM's eye)\n\n§1 says render and have fresh eyes critique it. That qualitative pass catches\ncollisions and ragged alignment, but **a model has no reliable sense of visual\nbalance** — ask a VLM \"is this centered \u002F balanced?\" and it confabulates a verdict.\nThe fix is to stop asking opinions and **measure a number**, then keep that number\nhonest with an *independent* check. Use both: §1's fresh-eyes critique AND the hard\nnumber below. (This pairs a live in-browser box model + auto-balancer with an\noffline pixel-oracle that re-measures the rendered screenshot — see the\n`layout-audit.js` companion script in this skill.)\n\n**The principle.** Visual balance is the *center of mass of visual weight*. It's\narithmetic, not taste — so compute it.\n\n**Optical center, not geometric.** Target `x = 0.50`, `y ≈ 0.46` — slightly high,\nbecause a centroid at literal 50% reads as sagging.\n\n**Visual weight estimate = area × guessed ink-density.** These coefficients and optical-center tolerances are exploratory heuristics, not validated perceptual laws. Same-size ≠ same-weight: a\nsolid-black heading is heavy; a grey\u002FASCII\u002Flight image reads far lighter than its\narea; body text is sparse. Calibrated starting multipliers (from `asym.html`, re-tune\nper project — these were hand-guesses until corrected against the pixel oracle):\n```js\nconst DENS = {portrait:0.34, h1:0.82, kicker:0.42, lead:0.22, body:0.16, meta:0.5};\n```\n\n**Centroid.** Per axis, `centroid = Σ(wᵢ·posᵢ) \u002F Σwᵢ`; balanced ⇔ the centroid sits\non the optical center. To FIX imbalance, think see-saw: what counts is the **moment**\n= weight × distance-from-axis, so a heavy element near the edge is counterweighted by\n(a) an opposing weight, (b) a bigger element on the other side, (c) pulling the heavy\nelement inward (shorter lever arm), or (d) shrinking it. That's exactly the\nauto-balancer's escalation order in `asym.html` — grow the opposing heading first\n(cheapest), then add weight, then pull the heavy element in, then shrink it (last\nresort).\n\n**Two models — and why you need the independent one:**\n- **Cheap box model** (live tuning): put each element's weight at its bounding-box\n  *center*. Instant, fine for dragging sliders. BUT it has a systematic bug —\n  left-aligned text's ink sits *left* of its box, so the box model misplaces the\n  weight. A metric that shares the layout's own assumptions is **circular**; it once\n  reported \"balanced\" at a pixel-measured 0.93 lopsided.\n- **Ground-truth pixel oracle** (`analyze.py`): rasterize the *rendered* page\n  (Playwright screenshot or html2canvas) and take the centroid of actual non-paper\n  pixels, weighting each pixel by its distance from the background color. It knows\n  nothing about the layout's intent — it just counts ink. **When the box model and\n  the pixels disagree, the pixels win.** (`asym.html` closes the loop: it regresses\n  the box-vs-pixel discrepancy and offers a trust dial α to blend toward the oracle.)\n- **Acceptance criterion (measurable):** `|centroid_x − 0.50| \u003C 0.03` and\n  `|centroid_y − 0.46| \u003C 0.04`, plus low left\u002Fright and top\u002Fbottom imbalance\n  (`|w_left − w_right| \u002F total`).\n\n**The verification gate (lighter than §4's, same spirit).** Before calling a\nbalance-critical layout \"balanced\", do NOT assert it from the code or a VLM opinion —\nscreenshot the *rendered* page, compute the ink-centroid offset from optical center,\nand report the actual number. This is the design-skill application of\n`verify-outputs-rule`: look at the real artifact, and make the validating check\n(pixels) independent of the thing you tuned (the layout). It's the quantitative\ncomplement to §1's qualitative critique.\n\n## 7. The layout audit — metrics that MEDIATE the eye, never replace it\n\n§6 covers balance; this generalizes it to a full deterministic sweep, and fixes the\nfailure mode that matters most: **the model reads a metric\u002FJSON and never looks at the\nscreenshot, so it can't apply the common sense that catches the metric being wrong.**\n\n`scripts\u002Flayout-audit.js` is a dependency-free pass you run via Playwright MCP\n`browser_evaluate` on a rendered page. It measures six things deterministically — all\ngeometry, color, and pixels, no \"does this look right?\":\n\n| check | how (deterministic) | tier |\n|---|---|---|\n| **collision** | content-rect intersection ≥12% | gate |\n| **contrast** | WCAG luminance ratio of text vs effective bg (\u003C4.5, large \u003C3) | gate |\n| **tap** | interactive targets \u003C44×44 CSS px (conservative project heuristic, not a WCAG verdict) | review |\n| **overflow** | `scrollWidth − clientWidth` (the §4 gate) | gate |\n| **alignment** | left-edge clusters → near-misses 1–7px off the shared line | signal |\n| **spacing** | gap CoV among a container's children | signal |\n| **balance** | ink-density-weighted centroid vs optical center (§6) | signal |\n\n**What makes it mediate rather than replace:** it doesn't just return JSON — it **draws\nevery finding as an SVG overlay onto the page**, so the *next* `browser_take_screenshot`\nis an **annotated screenshot**. The number tells you WHERE to look; you then look and\ndecide. This is mandatory, not optional:\n\n```\nbrowser_evaluate({ function: \"() => { \u003Cpaste scripts\u002Flayout-audit.js> ; return __audit({}); }\" })\nbrowser_take_screenshot()      \u002F\u002F ← the overlay is now on the page. VIEW IT. Reason over it.\n```\n\nPass `{align:'.card .title,.card .price', space:'.feature-list'}` to scope the two\nselector-dependent checks; pass `{contentSelector:'…'}` for non-semantic layouts where\ncollision needs help finding the blocks.\n\n**These are HEURISTICS, not laws — and they split into two kinds you must not conflate:**\n\n- **GATES = correctness** (overflow, contrast, tap). These measure accessibility\u002F\n  usability *facts*, not taste. Failing one is a real defect. Safe to **block** on.\n  (Collision is a near-gate: usually a real bug, but can be intentional — so eye-confirm,\n  don't auto-fail.)\n- **SIGNALS = convention** (balance, alignment, spacing rhythm). These measure how\n  closely the layout matches a *symmetric, regular, gridded* aesthetic — which is exactly\n  the **generic mean** §2 tells you to push *away* from. **Optimizing a layout to maximize\n  these scores makes it blander.** An off-center balance, a deliberate misalignment, an\n  uneven rhythm are core creative tools and frequently the best thing on the page. Treat\n  signals as \"worth a look,\" **never** as defects to fix.\n\n**The discipline (the whole point — bias hard toward this):**\n- **Never accept a metric you have not looked at.** A flag is a *pointer to look*, not a\n  verdict. Reading `collisions: 1` and acting without viewing the annotated shot is the\n  exact failure this section exists to kill.\n- **Use signals to catch ACCIDENTS, never to enforce convention.** A 7px alignment drift\n  you didn't mean, a phantom scrollbar, a 1.9:1 caption — catch those. But the *same*\n  balance\u002Falignment\u002Fspacing signal fires on deliberate asymmetry, intentional overlap,\n  and expressive rhythm. **When the metric and the interesting choice conflict, the\n  interesting choice usually wins.** Do not \"fix\" a signal toward symmetry\u002Fevenness unless\n  the eye judges the deviation actually worse. A model that maximizes these scores designs\n  the mean.\n- **Overrule flags the eye judges intentional.** Brutalist headline overlap, avatar on a\n  banner, asymmetric hero — the metric flags them; common sense overrules. Proven live in\n  the worked example: obeying the collision check on the brutalist mock removes the overlap\n  and the design goes *flat*.\n- **Gates are necessary, not sufficient.** `gates_pass:true` (overflow\u002Fcontrast\u002Ftap all 0)\n  clears the deterministic floor — it does **not** mean the layout is good. A bland centered\n  template passes every gate and is still the mean. After gates pass, the real judgment (§1\n  fresh-eyes critique, taste, brand fit) still has to happen.\n\n**The proof, made concrete:** build a page that runs all six algorithms live on a few\nrealistic mock sites in different styles, each with a toggle between the layout **as\ndesigned** and the version **obeying the metric**, plus on-render overlays. It shows both\nhalves: obeying a *gate* fixes a real bug (low-contrast CTA, sub-44 tap target, overlapping\ncards), while obeying a *signal* makes it worse (an asymmetric editorial hero is the more\ninteresting layout; \"correcting\" a deliberate brutalist overlap flattens it). That explorable\nis where `layout-audit.js` was distilled from.\n\n## 8. Optical craft — perception beats geometry\n\nThe audit's alignment check (§7) measures *geometric* edges. The eye doesn't read geometry,\nit reads perception — so a few cases need a manual nudge the metric can't make. These are\neye-judgments, not gates. (From the Web Interface Guidelines, `vercel-labs\u002Fweb-interface-guidelines` @ `4e799d4`.)\n\n- **Optical alignment — nudge ±1–2px when it *looks* off though it measures centered.** A\n  play-triangle in a round button must shift right of geometric center to look centered (its\n  visual mass is left-biased). Glyphs, arrows, and asymmetric icons often need the same. Text\n  vertically centered by box metrics frequently sits a hair low — lift it. Geometry is the\n  starting point; the eye is the judge.\n- **Balance icon\u002Ftext lockups.** When an icon sits beside text, match their *visual weight* —\n  adjust the icon's stroke, size, spacing, or color so neither overpowers. A thin-stroke icon\n  next to medium-weight text looks weak; thicken its stroke (or size it up slightly) so they\n  read as one lockup. Optical size, not equal pixel size, is the target.\n- This is the same principle as the §6\u002F§7 debias: the number gets you close; the eye makes the\n  final 1px call. Don't let a geometric alignment metric *prevent* an optical correction.\n\n## Limitations\n\n- WCAG 2.2 AA target minimum is 24×24 CSS px with spacing and other exceptions; 44×44 is a stricter target, not a universal failure threshold. The script does not implement exceptions or full contrast compositing (images, gradients, transparent ancestors and modern color syntax). `gates_pass` only summarizes this partial heuristic; it cannot certify accessibility.\n- `analyze.py` and `asym.html` mentioned above are not bundled. Pixel centroid is background\u002Fviewport-dependent and cannot establish visual quality by itself. Verify intentional scroll regions and keyboard\u002Fzoom behavior in addition to root scrollWidth.\n\n- Use this skill only when the task clearly matches its upstream source and local project context.\n- Verify commands, generated code, dependencies, credentials, and external service behavior before applying changes.\n- Do not treat examples as a substitute for environment-specific tests, security review, or user approval for destructive or costly actions.\n",{"name":5,"risk":39,"source":48,"license":14,"date_added":49,"description":7,"source_repo":50,"source_type":35,"license_source":51},"https:\u002F\u002Fgithub.com\u002Fconnerkward\u002Fckw-design-skill\u002Ftree\u002Fmain\u002Fdeterministic-design\u002Fdesign-spatial",1782864000000,"connerkward\u002Fckw-design-skill","https:\u002F\u002Fgithub.com\u002Fconnerkward\u002Fckw-design-skill\u002Fblob\u002Fmain\u002FLICENSE","7bf2279c8a65758319526a721d5c4b75683aec9c","skills\u002Fantigravity-design-spatial",3,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections","https:\u002F\u002Fgithub.com\u002Fboisenoise","2026-11-10 14:20:15",{"source":8,"skillName":5,"commands":59,"prompt":69,"zipAvailable":70},[60,63,66],{"tool":61,"command":62},"npx","npx skills add boisenoise\u002Fskills-collections --skill design-spatial",{"tool":64,"command":65},"bunx","bunx skills add boisenoise\u002Fskills-collections --skill design-spatial",{"tool":67,"command":68},"pnpm","pnpm dlx skills add boisenoise\u002Fskills-collections --skill design-spatial","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-design-spatial\u002FSKILL.md",true,[72],{"path":73,"resourceType":74,"resourceTypeDesc":75,"sizeBytes":76,"sourceHtmlUrl":77},"scripts\u002Flayout-audit.js","SCRIPT","脚本",11159,"https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-design-spatial\u002Fscripts\u002Flayout-audit.js",[79,107,130,153,172,195],{"skillNo":80,"name":81,"nameZh":6,"description":82,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":83,"licenseFileUrl":84,"sourceHtmlUrl":85,"categories":86,"keywords":87,"publishedAt":43},"01M6WNH92BHVFXHGPDZCPJ0AG0","web-access","Use Treg for public web or platform data only when existing search, browser and connected tools cannot cover the task, with explicit cost and data boundaries.","Apache-2.0","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-web-access\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-web-access\u002FSKILL.md",[],[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,39,103,104,105,106,42],"web","access","web access","official","development","browser","search","data","Tools","read","fetch","only","youtube","free tools","platform","free","cover","normal","when",{"skillNo":108,"name":109,"nameZh":6,"description":110,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":83,"licenseFileUrl":111,"sourceHtmlUrl":112,"categories":113,"keywords":117,"publishedAt":43},"01M6WNF9XAYAB2CZDDJBAKNM93","treg","Use the Treg CLI or MCP catalog to discover, price and call authorized external tools with team-scoped credentials and a user-approved budget.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-treg\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-treg\u002FSKILL.md",[114],{"categoryNo":115,"categoryName":116,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[109,118,119,31,91,92,32,96,120,121,122,123,124,125,126,39,127,128,129,41,42],"MCP","cli","Budget","credentials","team","catalog","use","call","discover","external tools","with","scoped",{"skillNo":131,"name":132,"nameZh":6,"description":133,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":83,"licenseFileUrl":134,"sourceHtmlUrl":135,"categories":136,"keywords":140,"publishedAt":43},"01M6WN93MEAZ4E41DYAHJRPHQT","recruit","Turn a job description into an evidence-linked candidate shortlist through Treg, with job-related ranking and optional authorized business contact enrichment.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-recruit\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-recruit\u002FSKILL.md",[137],{"categoryNo":138,"categoryName":139,"taxonomyType":25,"taxonomyTypeDesc":26},"01JBSEEDCAT000000000000038","人力资源",[132,31,91,32,33,141,142,143,144,145,39,146,147,148,128,149,150,151,152,41,109,42],"email","enrichment","business","evidence","phone","ranking","post","job description","for","person","candidate","reason",{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":83,"licenseFileUrl":157,"sourceHtmlUrl":158,"categories":159,"keywords":160,"publishedAt":43},"01M6WN8R5RXSMHM9B4D9FAFX42","rea-growth","Analyze a company’s public growth and content through Treg, distinguish observations from hypotheses, and produce an evidence-linked playbook within an approved budget.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-rea-growth\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-rea-growth\u002FSKILL.md",[],[161,162,163,31,91,32,164,165,33,166,120,167,168,169,144,39,170,171,41,109,42],"growth","rea","rea growth","produce","content","playbook","marketing","analyze","public","observations","company",{"skillNo":173,"name":174,"nameZh":6,"description":175,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":83,"licenseFileUrl":176,"sourceHtmlUrl":177,"categories":178,"keywords":179,"publishedAt":43},"01M6WN2EVE3RQRGWAKBT4S7YT2","make-ugc","Create original AI UGC-style product videos through Treg: research hooks, approve a licensed presenter and voice, generate clips, add captions and report costs.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-make-ugc\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-make-ugc\u002FSKILL.md",[],[180,181,31,182,91,183,32,184,185,186,187,188,39,189,190,191,192,193,194,41],"make","make ugc","research","create","Video","Captions","ads","short","hooks","head","style","niche","trending","talking-head","approve",{"skillNo":196,"name":197,"nameZh":6,"description":198,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":83,"licenseFileUrl":199,"sourceHtmlUrl":200,"categories":201,"keywords":202,"publishedAt":43},"01M6WN10ARAA63Y8K1YNGXJ2RM","lead-signals","Find and qualify public buying signals through Treg, rank fitting business accounts and optionally enrich authorized business contact information.","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-lead-signals\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fboisenoise\u002Fskills-collections\u002Fblob\u002Fmain\u002Fskills\u002Fantigravity-lead-signals\u002FSKILL.md",[],[203,204,205,31,91,32,206,207,143,208,209,39,210,211,212,213,214,215,216,41],"signals","lead","lead signals","how","find","ready","as","how to","qualify","people","information","enrich","changes","funding"]