What unslop checks
unslop is a skill by Damir Sokolovsky (CC BY 4.0) for turning AI-looking screens into designed ones. At its core is a list of 60 signs in nine groups: surfaces and colour, type, icons, spacing, copy, components, landing-page heroes, visuals and motion. Each sign comes with its fix: a purple-to-blue gradient becomes one neutral surface, an uppercase tracked label becomes sentence case, a pill badge over the headline goes.
It also sets a process. Audit first, listing every sign with where it lives. Then decide how much to change: heavy slop gets the full redesign, light slop gets polish (fix what the audit found, keep the palette, type and layout), and a good screen gets left alone. Then fix in passes, in a fixed order: spacing, colour, icons, copy, type, layout, details, visuals, motion.
Thirteen signs, all in the details
A scan of 249 components and 71 stylesheets, plus three pages read by eye, found 13 of the 60. None were the loud ones: no purple gradients, no gradient text, no glows, no sparkles, no “Trusted by 10,000+ teams”. By unslop’s own scale that is light slop, so it got polish.
| Sign | How much | Where |
|---|---|---|
| Uppercase labels with wide tracking | 169 CSS rules | Nav, buttons, stat labels, section eyebrows |
| Spacing off the 4px grid | 642 rules | Mostly 2px steps (10, 14, 18), about 160 odd values |
| Everything a full pill | 44 controls | Buttons, the nav bar, chips, tags, tabs |
| Headlines ending in a period | 60 headings | Page and section titles |
| Negative tracking on small text | 21 rules | -0.01em on 13–17px text |
| Italic for emphasis | about 12 | <em> in prose, a blog rule that italicised all of it |
| A pill badge above the headline | 1 | “New in v2.22.0” for hooks shipped months before |
| Emoji as a label | 1 | “⚡ Playground” in the docs |
| Exclamation marks | 2 | “copied!” on every code block |
| Hype words | 3 | “seamlessly” |
| Wordy placeholders | 2 | The playground’s text fields |
| A loop on something not live | 1 | A glowing pulse that never stopped |
| Glass | 2 | A frosted nav and its menu |
Spacing: onto the 4px grid
unslop puts everything on a 4px grid. Most of our off-grid values were 2px steps a designer might choose on purpose, so the codemod needed a rule that was predictable rather than clever:
// Margins, paddings, gaps and row heights onto the 4px grid.
// Odd values go to the nearest multiple of 4; a 2px step (6, 10, 14…)
// sits exactly between two, so it goes to the one that is a multiple of 8.
const snap = (n) => {
if (n <= 2 || n % 4 === 0) return n; // 1–2px nudges stay
if (n % 2) return Math.round(n / 4) * 4;
return (n - 2) % 8 === 0 ? n - 2 : n + 2;
};
// Left alone: negative and fractional values (they centre something
// exactly) and heights under 16px (dots and rules are drawings).633 declarations moved by one to four pixels. A later pass caught 52 more hiding inside clamp(), and the demo sites, which write whole rules on one line, had 2,145.
Type: the transform was hiding the text
The biggest sign was the label style: mono, uppercase, 0.14em tracking, on 169 rules. Deleting text-transform: uppercase is one line. The problem is what it uncovers. Under an uppercase transform it does not matter how a label is written, so many were written in lowercase, and now showed that way.
/* Before: the stylesheet made every label look the same. */
.label { text-transform: uppercase; letter-spacing: 0.14em; }
<span class="label">in this post</span> → IN THIS POST
<span class="label">npm i svg-scroll-draw</span> → NPM I SVG-SCROLL-DRAW
/* After: no transform, so the source decides. English gets a capital,
code keeps its case. */
<span class="label">In this post</span>
<span class="label">npm i svg-scroll-draw</span>A blanket CSS capital would mangle code, so the English labels were sentence-cased where they are written, in two rounds: 85 that were JSX text, then 136 that were data. A script lists every short lowercase label still on the site, page by page; the ones left are code, option names, package names and path-style breadcrumbs, which stay as they are.
Copy: say it once, plainly
Sixty headlines lost their final period. On the demo sites, 34 headlines used the two-beat fragment cadence (“Four finishes. One sound.”) and became one line (“Four finishes, one sound”). The pill badge above our own headline was not even true any more: it announced as new a feature from months before. “Seamlessly” became what actually happens: the JS engine draws the same thing.
Radius by size, solid surfaces, no idle loops
Buttons, chips and tabs went from full pills to an 8px radius, tags to 4px, with nested corners worked out (a copy button 4px inside an 8px chip gets 4). Switches, sliders and dots stay round, because they are round. The frosted-glass nav became the page surface with a hairline. A pulse that looped forever under a blurred accent glow now runs once.
The demo sites had the loud signs
The 23 showcase sites are fictional brands built to show the library, and they had what the main site did not: a pill badge above the headline on ten of them (“New”, mostly), gradient headlines on four, “Trusted by 48,000 teams” and two like it, 19 coloured glows, 58 colour washes behind content and 19 glass panels. All of it went. Two things stayed on purpose: one site’s sunset sky, which is the scene its product lights, and looping animations that show a live state in a product mock-up, like a recording dot or a CI spinner.
Contrast, and the mistakes we made
The demo sites had never been through an accessibility check, and a new test found 708 elements below WCAG AA. Most came down to a handful of colours per site, so we fixed colours, not elements. Along the way:
- Lighter weights move the threshold. Text from 18.66px counts as large, needing only 3:1, when it is bold (700). The type pass took weights down to 600, and two rows of logo wordmarks fell under the 4.5:1 that normal text needs.
- A colour lives on more than one background. One site’s grey failed on a single light pill, was darkened everywhere, and broke 68 places on dark backgrounds where it had been fine. The fix that sticks checks every background the colour is used on.
- Opacity cannot pass. Inactive states faded to 22–60% opacity are below 4.5:1 however good their colour; each went up to the lowest value that reads.
- White on a bright accent needs a darker accent, or dark text, as unslop says; eight buttons were darkened, and one amber avatar got dark text instead.
// The nearest shade of the same hue that clears the ratio on EVERY
// background the colour sits on, not only the one where it failed.
function shade(fg, backgrounds, need) {
const light = backgrounds.every((b) => luminance(b) > 0.18);
const target = light ? [0, 0, 0] : [255, 255, 255];
for (let k = 0; k <= 1; k += 0.01) {
const c = mix(fg, target, k);
if (backgrounds.every((b) => ratio(c, b) >= need)) return c;
}
return null;
}All 23 sites now pass, and the check runs in CI so it stays that way.
The sign no scan found
Looking at the pages caught one more: rows of stars and claim chips stacked under the hero buttons, the kind of social proof unslop asks you to make quiet and specific or remove. One demo’s three unverifiable claims went; three others kept their rating figure, such as “4.9 from 8,214 reviews”, and lost the row of star icons.
What stayed, and why
unslop lists parallax and elements that move on scroll as signs. On most sites they are. Here they are the product: the scroll demos stayed, and so did every effect a reader drives with their own scroll. The skill says as much itself: keep what the product is, and keep motion that explains something. A checklist is a way to look, not a reason to stop looking.