Four things you fix without opening an image editor
There is no shortage of filter demos that make texture. The places where filters actually save you work are elsewhere. In all four below, the left is what you used to hand-tune every time and the right is the same material with one declaration added. The numbers come from capturing this screen and measuring it.
A scrim shaped like the letters
산미 8.5 · 로스팅 2일차
just placed on top
산미 8.5 · 로스팅 2일차
filter: url(#scrim)
A caption over a photo fails 4.5:1. Every photo has a different brightness, so the translucent box gets hand-tuned each time.
feMorphology fattens the glyph alpha, a blur softens it, and feFlood fills only that shape. It follows the letters instead of covering the photo with a box.
white-on-photo contrast 1.70:1 → 4.79:1 · swap the photo, nothing to re-tune
Colour-vision simulation
통과주의실패
what you see
통과주의실패
deuteranopia
State encoded in colour alone becomes one badge for 1 in 12 men. No amount of looking at your own screen catches it.
A single feColorMatrix. This is the one filter that must not run in sRGB — colour-vision transforms are physical mixing, so linearRGB is correct here.
minimum colour distance across the three badges 82 → 2 · warning and failure become one colour
Separate shapes fusing into one
no filter · 3 circles
filter: url(#goo)
Getting pieces to melt together usually means reaching for canvas or a physics library. It comes up in loaders, merging menus, indicator transitions.
Blur hard, then stand the alpha up 18× with feColorMatrix. Whether they fuse is arithmetic, not taste — the blurred alpha midway between two circles has to clear the threshold (7÷18 ≒ 0.39). At 18px apart with a blur of 7 it sat near 0.2 and never merged; 12px and 10 clears it.
counted across the middle row: 3 blobs → 1 · 0 lines of JS
An overlay that bends the backdrop
산미 8.5
backdrop-filter: blur
산미 8.5
blur + url(#lens)
A translucent panel only blurs what is behind it. Real glass bends light — that difference puts the panel inside the picture instead of on top of it.
Hand a displacement map to backdrop-filter. R is the X offset, G is the Y offset, 128 is neutral. Build the map with feTurbulence and every value collapses toward 128, so nothing moves — the map has to be a gradient you draw, pushing inward from the edges.
the grid compresses at the edges · size the map wrong and nothing happens, silently · Chromium only
Your language is probably not Latin
Latin defaults break Korean in three places
Western design tools carry no Korean typesetting rules. Leave the defaults in place and all three of these fail at once. Left is the default, right is what this skill sets. Same text, same width, same font.
Line breaking
default
스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 한다.
word-break: keep-all
스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 한다.
The default breaks mid-word. Look at the last line on the left — one word straddles two lines.
Leading
line-height 1.45
스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 한다.
line-height 1.75
스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 한다.
The Latin standard of 1.4-1.5 is tight for syllable blocks with a final consonant.
Size
11px
스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 한다.
15px
스페셜티커피의 로스팅프로파일을 구분하려면 산미와 바디를 수치로 봐야 한다.
11px is a label size in Latin. Korean glyphs carry more strokes and smear at it.
Without word-break: keep-all, words break at arbitrary points
line-height between 1.6 and 1.8 — looser than Latin
Never use negative tracking. Korean is already near-monospaced
25 to 40 characters per line. The Latin 65 is far too long
Shipping without a Korean font stack is itself a tell
This page, measured
Fixing the copy does not remove the slop
Polishing sentences fixes sentences. The radius vocabulary, the translucent panels, the number of type steps all stay. So here are this page's own form metrics. If a number comes back badly, it ships as it is.
0.6%JS to first paintof the 50KB budget actually spent
12/12hard gatesstatic checks plus real renders at 320px and 1440px
three.js payload
184KB
fetched only when the inspection frame enters view. Never in the initial bundle
slop fingerprints
0
9 patterns grepped
radius vocabulary
2
3px and 14px. The pill shape is gone
translucent panels
2
grew to 29 before being pulled back. Glass stayed only on frames that hold something
type scale steps
6
as defined in tokens. Display sits outside the scale, and the largest type is 6x the body
font families
2
each language loads only its own
accent colours
1
light and dark are the same colour
time to interactive
not yet
measured with Lighthouse after deploy
Measured on the production build. Unmeasured entries stay empty. A hole is more honest than an invented number. This page is a product demo, so the JS budget was raised to carry three.js. Saying so here is the rule.
What this page actually got caught on
Not the result of auditing someone else. The result of auditing itself.
Text width jumped 13% at the moment the font swapped
Before the web font arrives the system font draws instead, and Malgun Gothic runs 13% wider than Pretendard, so line breaks are recalculated on swap. Matched the fallback with size-adjust. Measured difference is now 0.0%. System fonts differ per OS, so each gets its own fallback.
29 glass cards. Only the material had changed. The structure was still a row of cards
Our own rule says cards are for when elevation carries real hierarchy. Figures became typography, rules became an offset list, the audit became a log. Five panels of glass remain.
We wrote down that Chrome drops backdrop-filter with SVG. It does not
Attach url() to backdrop-filter and Chrome reports true through CSS.supports while discarding the whole declaration at render time. The computed value becomes none. Refraction is demonstrated for real by the SVG tiles in the materials section, and the panels build thickness from blur and edge light instead.
444px of horizontal overflow. The entire layout was off its rails
The grid class had gone missing from main. Subgrid only works when the parent is a grid, so every section had lost its alignment. It merely looked slightly off until the width was actually measured.
Scrolling to the very bottom still left the last 3 of 6 pipeline stages invisible
Elements near the page bottom never reach the end of a scroll-driven animation range. Visible with effects off but missing with them on is not the same as having a fallback. Rebuilt so that once revealed, it stays revealed.
The button inside the dark band sat at 2.88:1. The threshold is 4.5:1
The band inverted the accent colour but left the text on it white. Added an inverted counterpart token and switched both together.
13px of horizontal overflow at 320px, even with the table inside a scroll container
A grid item's default min-width is auto, so content pushes the parent wider. Overflow only works once min-width:0 is set.
6 eyebrow labels. The count rule caps them at 3
Kept three, dropped the rest. Sections read fine without a label above the heading.
The text column was 960px. The measure we had just set was 561px
Bound the grid's main column to the token. The skill's own example code was wrong too, so that got fixed as well.
We skipped this skill's own stage 1 when the direction changed
Switching the material to glass is a stage-2 decision, and that means going back to references first. We jumped straight to implementation instead. The result was glass painted onto the previous direction's skeleton, while design.md still described the old decisions. We re-ran the whole path from stage 0.
The glass panes faced the camera head-on, so Fresnel was structurally zero
When a flat plane faces the camera, the dot product of normal and view is 1, so no edge reflection is computed. The one signal that makes a surface read as glass was dead, which is why six panes looked like blurry rectangles. We bend the normal slightly and derive the rim light from the UV edge instead.
The pipeline, the spine of this page, used only 43% of the viewport
625px out of 1440. Narrowest of the nine sections while being the tallest at 1637px. Opening each stage into two columns brought it to 1113px.
The contrast script treated translucent backgrounds as opaque
It reported "English 1.15:1", which was wrong. Compositing alpha bottom-up gave zero failures. A checker that is wrong makes both its passes and its failures meaningless.
All thirteen were caught while building this page, and fixed after being caught. Three came from a person rather than a checker: the cards look generic, the typography is not handled well, and did we actually use our own skill.
Every rule states why
A prohibition without a reason does not survive contact with a brief. If the user asks for it explicitly, the user wins.
Never start without references
Starting from memory produces the category average.