Skip to main content

background-contrast

Default opacity and scrim values for pages and app UI that sit on a photographic or video background. Use whenever a background image is "barely visible" or washed out, when text over artwork needs contrast, when adding a hero/plate image behind a layout, or when sidebars, panels and cards need to read against a busy backdrop. Carries the compounding-gradient trap and the arithmetic to check any stack.

소스 정보

저장소
Violinet-tech/background-contrast
최근 소스 활동
2026년 10월 5일 18:48
감지된 SKILL.md 언어
영어
스타
0
포크
0

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
background-contrast
description
Default opacity and scrim values for pages and app UI that sit on a photographic or video background. Use whenever a background image is "barely visible" or washed out, when text over artwork needs contrast, when adding a hero/plate image behind a layout, or when sidebars, panels and cards need to read against a busy backdrop. Carries the compounding-gradient trap and the arithmetic to check any stack.
# Backgrounds you can actually see The failure this exists to prevent: buying a background, covering it to make text legible, and shipping a page where the artwork is invisible. It has happened twice in this workspace, both times because **stacked translucent layers compound and the numbers were never worked out**. ## The arithmetic, which is the whole skill Opacities multiply; they do not add. For a plate at opacity `p` under scrims of opacity `s1, s2, …`: ``` visible fraction of the artwork = p x (1 - s1) x (1 - s2) x ... ``` Two real cases from this workspace: ``` radial .86 over linear .93 -> 1 - (0.14 x 0.07) = 99% opaque plate .34 under a .86-.93 scrim -> artwork visible at 2.4 - 6.8% ``` Both looked reasonable as individual numbers. Neither was. **Always compute the product before shipping.** One line: ```python p, scrims = 0.55, [0.58] print(f"{p * math.prod(1 - s for s in scrims) * 100:.0f}% visible") ``` ## Defaults | | value | notes | |---|---|---| | **Artwork visible** | **25–45%** | the target the numbers above must land in | | Full-page scrim | **0.28 top → 0.62 bottom** | one gradient, not two stacked | | Plate opacity (app UI) | **0.55** | when the plate is a separate layer | | Local pool behind bare text | 0.55–0.60 radial | spend darkness *here*, not everywhere | | Panels / cards over artwork | **0.80–0.90** | they carry their own ground | | Sticky nav / sidebar | 0.90 → 0.42 gradient + `backdrop-filter: blur(14px)` | | | Text shadow, bare text only | `0 2px 26px rgba(bg,.85)` | display type; `0 1px 16px` for body | ## The principle **Win contrast locally, not globally.** A full-page wash is the blunt fix: it costs the artwork everywhere to solve a problem that exists in two places. 1. Keep the global scrim light. 2. Put a **soft radial pool** behind the one or two runs of text that sit on bare artwork (usually the hero copy). 3. Everything else already has a panel — raise *those* backgrounds instead. 4. Add a text-shadow to display type as the last 10%. ## Applying it ```css /* the plate: a still or a looping video */ .plate{ position:fixed; inset:0; z-index:-3; pointer-events:none; width:100%; height:100%; object-fit:cover; background:url('bg.webp') center/cover no-repeat; } /* ONE scrim. Darkness concentrated where bare text lands. */ .scrim{ position:fixed; inset:0; z-index:-1; pointer-events:none; background: radial-gradient(75% 55% at 26% 34%, rgba(6,9,26,.58), transparent 72%), linear-gradient(180deg, rgba(6,9,26,.28) 0%, rgba(6,9,26,.40) 48%, rgba(6,9,26,.62) 100%); } ``` That radial-over-linear pair compounds to 68% only where the hero copy sits and 28–62% everywhere else — which is the point of splitting them. ## Checking it Contrast still has to pass: **4.5:1 for body text, 3:1 at 24px+** (5.5:1 is a better floor in practice — see the stray-overlay story below, where 4.91:1 passed AA and was still too dim at 11px). Over a background the worst case is one of the artwork's two EXTREMES composited under the panel — which extreme depends on which way the palette runs, and checking only one is a trap: - **Light text on a dark panel** loses contrast where the artwork is **brightest** (a light source bleeding through drags the surface up toward the text). - **Dark text on a light panel** loses it where the artwork is **darkest** (a dark pixel drags the surface down toward the text). A check written for one direction and never revisited becomes silently wrong the moment a palette flips — it would pass the exact failure it exists to catch. Check both extremes (composite pure white AND pure black under the panel at its real alpha) and take whichever is worse; don't assume. A good place to put this is a small script run in CI or `npm run check:contrast` that covers token-on-panel pairs: a direction-agnostic worst-case check, plus a literal-rgba assertion so a `.panel` rule can't silently drift from its own token. It does **not** cover text sitting directly on bare backdrop with no panel under it — do that arithmetic by hand. ## Before touching the palette, rule out a stray overlay **"No opacity" is not always a contrast problem.** One app's dark palette was rebuilt, failed, and reverted to light four times before the real bug was found: `#modal { display: flex }` outranked `.hidden { display: none }` (an ID selector beats a class one), so the approval overlay stayed painted over the whole app on every one of those runs — a 70%-black wash greying every panel, which looks *identical* to a genuine value-separation failure whether or not the palette underneath is sound. Four rounds of retuning scrim and panel numbers never touched the actual bug, because it wasn't in the palette. A token-pair contrast check cannot see this class of bug — it only reads CSS custom properties, not runtime state, a stray full-screen element, or a hardcoded fill that never used a token at all. Two other real examples from this workspace: `calc()` inside `color-mix()` computed correctly under `getComputedStyle` and painted nothing, and `backdrop-filter` does the same under software compositing — both leave a panel fully transparent while every tool reports the palette as applied. Before redesigning contrast values in response to a "washed out" or "no opacity" report, rule out: a modal/overlay with a specificity bug, a hardcoded (non-token) fill, and a CSS feature that silently no-ops under the current renderer. Only redo the arithmetic once you've confirmed the palette is what's actually on screen. Related: `seamless-loop-video` for when the plate is a video. Remove any CSS drift from a plate that moves on its own.
GitHub에서 보기