chore: add unslop skill
This commit is contained in:
1 parent
4a9c37f2f3
commit
cb547e3ced
14 files changed
+1294
No files matched your search
@@ -0,0 +1,113 @@
|
||||
# Effects
|
||||
|
||||
Code treatments that turn an image into something made for this page. They are optional. Use one only when it fits the character in the direction, on the main visual only, and never as decoration on other elements.
|
||||
|
||||
## Glyphs
|
||||
|
||||
Draw the image as a grid of characters, brightness picking the character. It works best when the characters come from the product's own material, so the image is literally made of what the product deals with.
|
||||
|
||||
- Money, invoices, finance: `. : 1 7 4 0 8 $ %`
|
||||
- Code, developer tools: `. : / { } < > = #`
|
||||
- Music: note names like `c d e g a` and `♭ ♯`
|
||||
- Writing, reading: lowercase letters of a word from the product
|
||||
- Data, monitoring: `. · : | + ▪`
|
||||
|
||||
Keep the cell between 6 and 10px, use the mono from the type choice, and color the glyphs from the image or from the ramp. Let a few cells change slowly over time so the picture feels alive.
|
||||
|
||||
### Implementation
|
||||
|
||||
```js
|
||||
function glyphs(canvas, image, { cell = 8, chars = ". :1740$%", font = "Geist Mono", fps = 8 } = {}) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const columns = Math.floor(canvas.width / cell);
|
||||
const rows = Math.floor(canvas.height / cell);
|
||||
|
||||
const sample = document.createElement("canvas");
|
||||
sample.width = columns;
|
||||
sample.height = rows;
|
||||
const sampler = sample.getContext("2d", { willReadFrequently: true });
|
||||
sampler.drawImage(image, 0, 0, columns, rows);
|
||||
const pixels = sampler.getImageData(0, 0, columns, rows).data;
|
||||
|
||||
const atlas = document.createElement("canvas");
|
||||
atlas.width = cell * chars.length;
|
||||
atlas.height = cell;
|
||||
const pen = atlas.getContext("2d");
|
||||
pen.font = `500 ${cell + 1}px ${font}`;
|
||||
pen.textAlign = "center";
|
||||
pen.textBaseline = "middle";
|
||||
pen.fillStyle = "#fff";
|
||||
[...chars].forEach((char, index) => pen.fillText(char, index * cell + cell / 2, cell / 2));
|
||||
|
||||
const cells = [];
|
||||
for (let y = 0; y < rows; y++) {
|
||||
for (let x = 0; x < columns; x++) {
|
||||
const i = (y * columns + x) * 4;
|
||||
const light = (0.2126 * pixels[i] + 0.7152 * pixels[i + 1] + 0.0722 * pixels[i + 2]) / 255;
|
||||
cells.push({ x, y, light, color: `rgb(${pixels[i]},${pixels[i + 1]},${pixels[i + 2]})`, seed: Math.random() });
|
||||
}
|
||||
}
|
||||
|
||||
const tinted = document.createElement("canvas");
|
||||
tinted.width = cell;
|
||||
tinted.height = cell;
|
||||
const tint = tinted.getContext("2d");
|
||||
|
||||
const draw = (step) => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
for (const c of cells) {
|
||||
if (c.light < 0.06) continue;
|
||||
const jitter = Math.sin(c.seed * 999 + step) > 0.92 ? 1 : 0;
|
||||
const pick = Math.min(chars.length - 1, Math.floor(c.light * chars.length) + jitter);
|
||||
tint.globalCompositeOperation = "copy";
|
||||
tint.drawImage(atlas, pick * cell, 0, cell, cell, 0, 0, cell, cell);
|
||||
tint.globalCompositeOperation = "source-in";
|
||||
tint.fillStyle = c.color;
|
||||
tint.fillRect(0, 0, cell, cell);
|
||||
ctx.drawImage(tinted, c.x * cell, c.y * cell);
|
||||
}
|
||||
};
|
||||
|
||||
let frame = 0;
|
||||
let last = 0;
|
||||
let step = 0;
|
||||
let visible = true;
|
||||
new IntersectionObserver(([entry]) => (visible = entry.isIntersecting)).observe(canvas);
|
||||
const loop = (now) => {
|
||||
frame = requestAnimationFrame(loop);
|
||||
if (!visible || now - last < 1000 / fps) return;
|
||||
last = now;
|
||||
draw(step++);
|
||||
};
|
||||
frame = requestAnimationFrame(loop);
|
||||
return () => cancelAnimationFrame(frame);
|
||||
}
|
||||
```
|
||||
|
||||
For a large canvas, bake the tinted glyphs for each cell once into an offscreen canvas and only redraw the cells that change on a step.
|
||||
|
||||
## Halftone dots
|
||||
|
||||
Dots on a grid whose size follows brightness. Suits editorial, print and crafted characters. Same sampling as glyphs, but draw `arc` circles with a radius of `light * cell / 2` in one ink color on the page color.
|
||||
|
||||
## Dithering
|
||||
|
||||
An ordered or error diffusion dither into two or three colors from the ramp. Suits retro, technical and playful characters. Do it once when the image loads, not on every frame.
|
||||
|
||||
## Duotone
|
||||
|
||||
Map the image's brightness onto two colors, the darkest surface of the ramp and the accent or the primary text color. The simplest way to make any generated image belong to the palette. In CSS, a grayscale image under a `mix-blend-mode: color` layer of the accent, or an SVG `feColorMatrix` filter.
|
||||
|
||||
## Performance
|
||||
|
||||
These effects broke on weak laptops before, so keep to these rules.
|
||||
|
||||
- Read pixels once when the image loads. Never call `getImageData` inside the animation loop, it stalls the GPU and drops frames to single digits.
|
||||
- Prepare glyph atlases and tinted cells ahead of time and draw them with `drawImage`.
|
||||
- Animate at 8 to 12 steps per second, not 60, and change only a small share of cells per step.
|
||||
- Pause when the canvas is off screen.
|
||||
- Size the canvas for the device pixel ratio, but cap it at 2.
|
||||
|
||||
---
|
||||
|
||||
Part of unslop by Damir Sokolovsky ([@heydamir](https://x.com/heydamir)), CC BY 4.0. Keep this credit in copies and adaptations.
|
||||
@@ -0,0 +1,17 @@
|
||||
# References
|
||||
|
||||
Finished screens by Damir Sokolovsky ([@heydamir](https://x.com/heydamir)). Look at them before the Visuals pass. Take their qualities, never their subject, palette or layout, so every product still gets its own screen. `unslop-dashboard-after` is the finished version of `examples/dashboard.html`, so when you unslop that example, find your own answer instead of rebuilding it.
|
||||
|
||||
| File | What it shows | Why it works |
|
||||
| --- | --- | --- |
|
||||
| [unslop-hero-after.webp](refs/unslop-hero-after.webp) | An invoicing hero with a paper plane drawn in digits and currency signs, small notes pinned to it, and the product rising from below | The visual is made of the product's own material, money, so it is the moment and the signature at once. Text sits on calm sky, the chrome is quiet, and the product view invites a scroll |
|
||||
| [unslop-dashboard-after.webp](refs/unslop-dashboard-after.webp) | A finance dashboard with two rendered payment cards and a revenue chart drawn as soft colored curves over a mirrored grey comparison | Quiet graphite chrome, and all the color lives in two moments, a real looking object and a chart where every color means a series |
|
||||
| [beacon-engraving.webp](refs/beacon-engraving.webp) | An incident monitoring hero drawn as one blue engraving of a lighthouse on a rocky coast, with a serif headline in the same ink | A print style working because the whole page is in it. One ink, one paper tone, and the headline sits in the calm sky the drawing leaves for it |
|
||||
| [meadow-3d.webp](refs/meadow-3d.webp) | A footer as a soft 3D meadow with a few matte crystal shapes and a huge wordmark standing behind the hills | Friendly stylized 3D with a restrained palette, type that becomes part of the scene, and quiet links on the open sky |
|
||||
| [story-dashboard-frames.webp](refs/story-dashboard-frames.webp) | Eight frames from the viral story video, a vibecoded dashboard unslopping itself in about 10 seconds | Everything morphs in place in a wave, red redlines measure the slop and turn white when fixed, copy rolls letter by letter, and the frame is square and cropped close on the sidebar and first cards |
|
||||
|
||||
What they share: one bright element per screen, quiet chrome around it, a visual that can be explained in one sentence, and headlines placed on space that was left empty for them.
|
||||
|
||||
---
|
||||
|
||||
Part of unslop by Damir Sokolovsky ([@heydamir](https://x.com/heydamir)), CC BY 4.0. Keep this credit in copies and adaptations.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 556 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 280 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 183 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 73 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
@@ -0,0 +1,103 @@
|
||||
# Signs of slop
|
||||
|
||||
Go through every group and note each sign you find with where it lives. Each line pairs the sign with its fix.
|
||||
|
||||
## Surfaces and color
|
||||
|
||||
- Purple to blue or violet to pink gradients on backgrounds, cards, buttons or the active nav item → one neutral surface, accent only where it means something
|
||||
- Gradient text in headings, numbers or the logo → solid primary text color
|
||||
- Colored glows, like `shadow-[0_0_20px_rgba(139,92,246,0.5)]`, `drop-shadow` on emoji or glowing pills → remove, depth from surface steps and hairlines
|
||||
- Radial color washes or blurred blobs behind content → remove
|
||||
- Neon or high contrast colored borders → hairline at 6 to 10% opacity
|
||||
- Many near identical tints of the same hue for text, like five lavenders → three text roles from one neutral ramp
|
||||
- Every element highlighted, so nothing stands out → one accent used once or twice per screen
|
||||
- Glass cards with a white inner highlight and a colored shadow → plain raised surface with a hairline
|
||||
|
||||
## Type
|
||||
|
||||
- Headings at weight 800 or 900 → 400 to 500 and size for hierarchy
|
||||
- Uppercase labels with wide tracking, like `NAVIGATION` or `PAID THIS MONTH` → sentence case at normal or slightly open tracking
|
||||
- Too many sizes and weights on one screen → a few sizes from the scale, weights 400 and 500
|
||||
- Numbers in mixed formats, like `$48,210` next to `$124,860.40`, or `07` → one format, tabular figures
|
||||
- Negative letter spacing on small text → zero or slightly positive
|
||||
- Italic used for emphasis → color or weight instead
|
||||
|
||||
## Icons
|
||||
|
||||
- Emoji as nav icons, label decoration or status, like 🏠 📥 🚀 💰 ⚠️ 📈 🔥 😬 → one line icon set or nothing
|
||||
- Emoji inside pills and counts, like `18 👤` → plain number
|
||||
- Icons in many sizes and colors → one size, one stroke, text role color
|
||||
- Icons nudged off the column → same icon box width in every row
|
||||
- A sparkle ✨ for anything AI → remove or use a plain icon only where it explains something
|
||||
|
||||
## Spacing
|
||||
|
||||
- Arbitrary values, like `mt-[13px]`, `ml-[7px]`, `h-[31px]` → steps on the 4px scale
|
||||
- Rows in one list with different heights or gaps → one height, one gap
|
||||
- Groups that sit closer to their neighbours than to their own items → smaller gaps inside groups, bigger between them
|
||||
- Uneven padding inside sibling cards → one padding
|
||||
- Big empty gaps above a section and cramped space below it → balanced rhythm
|
||||
|
||||
## Copy
|
||||
|
||||
- Greeting headlines, like `Welcome back, Damir! 👋` → the page or period as the title
|
||||
- Exclamation marks and `!!` → plain statement
|
||||
- Changes without a reference, like `+12%!!` or `3 less!!` → `+12% vs last month`, `3 fewer vs last month`
|
||||
- Wordy placeholders, like `Search anything...` → `Search`
|
||||
- Hype words, like supercharge, seamless, unlock, magic, revolutionize → what the thing actually does
|
||||
- Trailing periods on labels and buttons → none
|
||||
- Fake looking data, like `$123,456` or `[X]` → plausible numbers
|
||||
|
||||
## Components
|
||||
|
||||
- Glowing pills for counts, plans, shortcuts or status → quiet mono text or a keycap
|
||||
- `PRO`, `New ✨`, `Beta 🚀` badges everywhere → remove or one quiet text badge
|
||||
- Every element a full pill, from search to buttons to cards → radius by size, nested radius math
|
||||
- Charts with rainbow bars, glowing bars, pill shaped bars or uppercase day labels → one or two series, muted grid and mono labels, one highlight color
|
||||
- Every chart or stack in several shades of the accent, so nothing leads → neutral steps with the accent only on the one thing that matters
|
||||
- Sparklines, tallies or mini bars added to every stat card → the number, its label and its change
|
||||
- Equal cards side by side, each with the same chart or layout → one main chart, compact stats, varied widths
|
||||
- Logo as a gradient tile with a sparkle and a gradient wordmark → product name as text plus a simple flat mark
|
||||
- Logo mark as a glossy orb, a gradient ball or a glowing dot → a flat glyph in one solid color, or the wordmark alone
|
||||
|
||||
## Landing pages and heroes
|
||||
|
||||
- A pill badge above the headline, like `✨ Introducing X 2.0` → remove, or a quiet text link if it is real news
|
||||
- A headline that could fit any product, like `Supercharge your workflow with AI` → say what the product does for whom
|
||||
- Gradient or shimmer on the headline → solid text
|
||||
- Floating blobs, orbs, grids and glowing lines behind the hero → plain background or one real product visual
|
||||
- Everything stacked and centered, headline, text, buttons and a picture below → an asymmetric split or text over a large product view, see the Layout pass
|
||||
- Three or more buttons, or chips and avatars stacked under the buttons → one primary and at most one secondary action
|
||||
- `Trusted by 10,000+ teams`, `Loved by 10,000+`, rows of avatar dots or five stars without a source → ask if it is real, then make it quiet and specific or remove it
|
||||
- Parallax and elements floating on scroll → static layout, motion only with a purpose
|
||||
- Feature cards with an emoji, a bold title and a vague line each → fewer features, concrete copy, line icons or none
|
||||
|
||||
## Visuals
|
||||
|
||||
- Glossy 3D blobs, orbs, liquid chrome shapes or a purple nebula as the hero visual → the product itself or one concrete metaphor for what it does
|
||||
- Stock robots, glowing brains, circuit lines or holograms for anything AI → remove, or show what the AI actually produces
|
||||
- Text, fake buttons or a fake logo baked into an image → real text in the page, a clean image without text
|
||||
- An image that does not blend into the page, with a hard edge and a different black → background matched to the page and a soft mask at the edges
|
||||
- A visual on every section → one visual where it helps, usually only the hero
|
||||
|
||||
## Motion
|
||||
|
||||
- `animate-pulse`, `animate-ping` or breathing glows on things that are not live → static
|
||||
- `transition-all` and `hover:scale` on cards and buttons → color or surface step on hover, a tiny scale down on press
|
||||
- Bouncy springs and overshoot on simple UI → one ease-out curve
|
||||
- Everything flying in on load from different directions → one short fade and rise with a small stagger
|
||||
- Parallax, floating loops and elements that move on scroll → static layout
|
||||
- Animating width, height, top or left → transform and opacity
|
||||
|
||||
## Template look
|
||||
|
||||
These are not slop, but they are what a cleaned up screen falls back to when nobody made a decision. Watch for them after the passes.
|
||||
|
||||
- Pure grey #161616 page, Inter, white text and the old brand color on one button, on a product whose world is warm or bright → a ramp tinted toward the world and an accent from it
|
||||
- A headline that would fit another product if you swapped the name → say what this product does, with its own nouns
|
||||
- A hero visual that is an empty box or a generic picture → a product preview in code or an image from the world
|
||||
- Every product getting the same layout, type and palette → let the direction choose
|
||||
|
||||
---
|
||||
|
||||
Part of unslop by Damir Sokolovsky ([@heydamir](https://x.com/heydamir)), CC BY 4.0. Keep this credit in copies and adaptations.
|
||||
@@ -0,0 +1,135 @@
|
||||
# Tokens
|
||||
|
||||
Starting values for each pass. When the project already has a good system, keep its values and only fix how they are used. These are starting points, not a house style, so let the direction move them.
|
||||
|
||||
## Spacing scale
|
||||
|
||||
4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80, 96, 128
|
||||
|
||||
- Inside a control: 4 to 12
|
||||
- Between related items, like rows in a list: 2 to 8
|
||||
- Between groups, like a label and its list or two cards: 12 to 24
|
||||
- Between sections: 32 to 64, landing sections 80 to 128
|
||||
- Row heights: 32 compact, 36 default, 40 to 44 touch
|
||||
- Hero: 96 to 128 from the navigation to the headline on desktop, 48 to 64 on mobile
|
||||
|
||||
## App layout
|
||||
|
||||
- Sidebar 240 to 280 wide, content fluid with page padding 24 to 32, capped around 1440 to 1600.
|
||||
- Top bar or page header 56 to 64 tall.
|
||||
- Navigation rows 32 to 36, gap 2 to 4, section labels 24 above and 8 below.
|
||||
- Stat cards in a row share one height, usually 3 or 4 compact cards per row on desktop, numbers only.
|
||||
- The main chart spans at least two thirds of the content width and is 280 to 360 tall. A second data block next to it is a list or table, narrower, not another chart.
|
||||
|
||||
## Neutral ramps
|
||||
|
||||
For apps and dashboards use graphite, or a ramp just as grey, for every surface. For landing pages pick the ramp whose tint matches the world in the direction. Warm suits paper, wood, food, craft and anything human. Ink suits night, sky, water and anything technical. You can tint your own ramp the same way, keeping saturation very low, so a surface never reads as a color on its own.
|
||||
|
||||
### Dark
|
||||
|
||||
| Role | Graphite | Warm | Ink |
|
||||
| --- | --- | --- | --- |
|
||||
| Deepest surface, like a sidebar | #0f0f10 | #12110f | #07080d |
|
||||
| Page background | #141415 | #161513 | #0b0d14 |
|
||||
| Card | #19191b | #1c1a18 | #11141c |
|
||||
| Raised, like an active row or input | #232325 | #262421 | #1a1e28 |
|
||||
| Hairline | primary text at 8% | primary text at 8% | primary text at 8% |
|
||||
| Primary text | #ededed | #efe9de | #eef0f5 |
|
||||
| Secondary text | #a1a1a6 | #aaa398 | #a3a9b8 |
|
||||
| Muted text and icons | #85858b | #8a847a | #7a8296 |
|
||||
|
||||
### Light
|
||||
|
||||
| Role | Graphite | Warm | Ink |
|
||||
| --- | --- | --- | --- |
|
||||
| Page background | #fafafa | #f7f4ee | #f5f7fa |
|
||||
| Card | #ffffff | #fdfbf7 | #ffffff |
|
||||
| Raised, like an active row or input | #f4f4f5 | #efe9df | #eceff4 |
|
||||
| Hairline | primary text at 8% | primary text at 8% | primary text at 8% |
|
||||
| Primary text | #171717 | #1f1b16 | #111827 |
|
||||
| Secondary text | #525252 | #5e554a | #4b5565 |
|
||||
| Muted text and icons | #737373 | #7b7266 | #6b7487 |
|
||||
|
||||
Muted text is for meta and captions only and stays near 4.5 to 1 on its surface. Body text uses primary or secondary.
|
||||
|
||||
Semantic colors, toned to sit inside any ramp: positive #4cc38a on dark and #16a34a on light, negative #e5484d on dark and #dc2626 on light.
|
||||
|
||||
## Accent
|
||||
|
||||
- Keep a real brand color. A violet to blue or any other hue to hue glow gradient is slop, not a brand color, unless the user says otherwise.
|
||||
- Without a brand color, take it from the world. Warm worlds suit amber, coral or ember orange. Cool worlds suit cobalt, teal or a clear sky blue. Natural worlds suit moss or sage. Avoid the purple to blue family unless the brand really is purple.
|
||||
- Text on the accent reaches 4.5 to 1 contrast. Darken the accent or use dark text when it does not.
|
||||
- The accent covers a few percent of the screen, like the primary action, the selection and one key figure.
|
||||
|
||||
## Buttons
|
||||
|
||||
- Hero buttons 44 to 48 tall, navigation buttons 32 to 36, padding 16 to 20 on the sides.
|
||||
- Primary on dark: an off white from the ramp with dark text, or the accent. Primary on light: the primary text color as a fill with white text, or the accent.
|
||||
- Secondary: the raised surface with a hairline, primary text.
|
||||
- A soft top highlight like `inset 0 1px 0 rgba(255,255,255,0.12)` on dark buttons gives depth without a glow.
|
||||
|
||||
## Type families
|
||||
|
||||
Pick by the character in the direction. All of these are free, and a partner can come from any row.
|
||||
|
||||
| Character | Interface family | Optional partner |
|
||||
| --- | --- | --- |
|
||||
| Precise, technical, calm | Geist, Inter, IBM Plex Sans | Geist Mono, IBM Plex Mono, JetBrains Mono for figures and meta |
|
||||
| Warm, friendly, human | Instrument Sans, DM Sans, Figtree | Instrument Serif or Newsreader for the hero headline only |
|
||||
| Editorial, literary, crafted | Inter or Instrument Sans for the interface | Newsreader, Instrument Serif or Fraunces for headlines |
|
||||
| Playful, bold | Space Grotesk, Bricolage Grotesque | a mono for small labels |
|
||||
|
||||
A display serif belongs to large headlines only, never to labels or buttons.
|
||||
|
||||
## Type scale
|
||||
|
||||
| Use | Size | Line height | Weight |
|
||||
| --- | --- | --- | --- |
|
||||
| Small labels, meta, counts | 12 | 16 | 400, mono welcome |
|
||||
| Secondary text, captions | 13 to 14 | 20 | 400 |
|
||||
| Body, nav items | 14 to 15 | 20 to 22 | 400 to 500 |
|
||||
| Logo wordmark | 16 to 18 | 1 | 500 to 600 |
|
||||
| Hero subhead | 18 to 20 | 28 to 30 | 400, secondary color, at most 36rem wide |
|
||||
| Card figures | 28 to 32 | 1 | 500, tabular |
|
||||
| Page title | 28 to 36 | 1.15 | 400 to 500 |
|
||||
| Hero headline | 56 to 72 desktop, 36 to 44 mobile | 1.02 to 1.08 | 400 to 500 |
|
||||
|
||||
Letter spacing is 0 for anything under 16px, slightly positive like 0.01em is fine for small muted text. Display text from 40px can go to -0.02em, and from 56px to -0.03em.
|
||||
|
||||
## Radius
|
||||
|
||||
| Element | Radius |
|
||||
| --- | --- |
|
||||
| Keycap, tag, small button | 4 to 6 |
|
||||
| Row, input, button | 8 to 10 |
|
||||
| Card | 12 to 16 |
|
||||
| Panel or window | 14 to 20 |
|
||||
|
||||
Nested radius: inner = outer − padding, never below 4. A 16px card with 8px padding holds items with an 8px radius.
|
||||
|
||||
## Icons
|
||||
|
||||
lucide, 16 or 18px, stroke 1.5, color of the muted or secondary text role, 8 to 10px gap to the label.
|
||||
|
||||
## Depth
|
||||
|
||||
- Dark: surface steps plus a hairline. An active row can carry `inset 0 1px 0 rgba(255,255,255,0.12)` as a top highlight.
|
||||
- Light: `0 1px 2px rgba(0,0,0,0.05)` for small things, `0 8px 24px rgba(0,0,0,0.06)` for floating ones.
|
||||
|
||||
## Motion
|
||||
|
||||
One easing curve for everything, `cubic-bezier(0.22, 1, 0.36, 1)`.
|
||||
|
||||
| Use | Duration |
|
||||
| --- | --- |
|
||||
| Hover, press, toggles | 120 to 180ms |
|
||||
| Menus, popovers, tooltips | 160 to 220ms |
|
||||
| Entrances, sheets, layout changes | 300 to 500ms |
|
||||
| Stagger between items | 40 to 60ms, at most six items |
|
||||
|
||||
- Rise distance on enter: 6 to 12px. Press scale: 0.97 to 0.98.
|
||||
- Transition color, background, border, opacity and transform only.
|
||||
|
||||
---
|
||||
|
||||
Part of unslop by Damir Sokolovsky ([@heydamir](https://x.com/heydamir)), CC BY 4.0. Keep this credit in copies and adaptations.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Visuals
|
||||
|
||||
How to replace decorative filler with a visual that belongs to this product, and how to generate one when an image tool is available.
|
||||
|
||||
## Decide what the visual is
|
||||
|
||||
1. **Does the screen need one?** Dashboards, settings and most app screens do not. Landing heroes and empty states usually do.
|
||||
2. **Is there an image tool?** For a landing hero, generate a scene from the world as the background of the whole hero, with the subject on one side and calm space for the headline on the other. Then show the product rising from the bottom edge. This is the strongest hero.
|
||||
3. **Show the product.** Below the scene, or as the visual when there is no tool, build a preview in code of what the page already promises. Pick the one moment that proves the headline, not the whole app. A scheduling tool shows one week with a booked slot, a code review tool shows one diff with a comment, a habit app shows a streak on one card. Use the project's own components when they exist, otherwise careful HTML in the new palette, with plausible data and real looking names. Frame it like a window or a device only if that helps it read.
|
||||
4. **Write the scene as one sentence.** Use the world and character from the direction and write the subject as one plain sentence before generating anything. If you cannot say it in one sentence, it is not clear yet.
|
||||
|
||||
These examples only show the shape of a good sentence. Do not reuse them, derive yours from the product in front of you.
|
||||
|
||||
- A tool that protects backups → a small lighthouse on a dark rocky coast with its beam reaching into fog
|
||||
- A meditation app → a single cup of tea steaming on a windowsill at first light
|
||||
- A logistics API → one container ship seen from straight above on a calm grey sea
|
||||
- A language learning app → a stack of handwritten flashcards on a café table in afternoon sun
|
||||
|
||||
Avoid the AI defaults, unless the product is literally about them. No glossy blobs, orbs, nebulas, chrome liquid, floating cubes, neon circuits, holograms, glowing brains, robots, rockets or crystal shapes.
|
||||
|
||||
## Styles that work
|
||||
|
||||
Pick one that fits the character, and keep it for the whole page.
|
||||
|
||||
- **Painted background art.** A calm sky or landscape with one subject, painted like the backgrounds of modern Japanese animated films, crisp clouds and soft light, with open sky left for the text. Good for warm, hopeful, human products.
|
||||
- **Cinematic photo of one object.** A single real thing in simple dramatic light on a plain field, like a lighthouse in fog or a paper object in mid air. Good for calm, serious, trustworthy products.
|
||||
- **Soft stylized 3D.** One small scene or object with matte materials and gentle light, like a doorway standing in a meadow. Good for playful or friendly products.
|
||||
- **Product object render.** A real looking object from the product, like a payment card, a phone or a package, studio lit with true materials, used as a moment inside the interface. Good for anything that touches the physical world.
|
||||
- **Light and optics.** One physical phenomenon on a dark field, like light splitting through a prism. Good for technical or data products.
|
||||
- **Engraving or print.** A monochrome engraving or woodcut in one ink on a soft tinted page, with type and palette that belong to the same world. Good for products with heritage, craft or an editorial voice.
|
||||
- **A code treatment over an image.** Rendering a generated image as glyphs, dots or a duotone in the page palette, so it feels made for the page. See [effects.md](effects.md).
|
||||
|
||||
What tends to fail:
|
||||
|
||||
- Prompts that ask for film grain, paper grain or a mottled texture. Models bake a fine checker pattern into the image. Add texture in code if you need it.
|
||||
- A print style, like engraving, woodcut or risograph, on one image while the rest of the page is a plain modern interface. It reads as a filter. The same styles work well when the whole brand lives in them, with matching type and a paper palette, like a monochrome engraving on a soft tinted page.
|
||||
- Several subjects or a busy collage. The idea stops reading in a second.
|
||||
- Purple and blue glossy abstractions of any kind.
|
||||
|
||||
## Objects for moments
|
||||
|
||||
Besides whole scenes, generate single objects to place into the interface, like a payment card, a phone, a parcel, a coffee cup or a notebook from the product's world. They make a coded preview feel real.
|
||||
|
||||
- Ask for one object, studio lit, on a plain background in the exact page color, with soft contact shadow and nothing else in frame.
|
||||
- Ask for the angle you need in the layout, like "tilted 20 degrees, seen slightly from above".
|
||||
- If the tool can remove backgrounds, cut the object out. Otherwise blend it with a mask into the page color.
|
||||
- Place it overlapping the edge of a card or window, a little rotated, with a soft shadow in the ramp's darkest color, so it sits in the same space as the UI.
|
||||
- Background removal deletes dark areas that are close to the background color, like a switched off black screen. Ask for the screen in a contrasting flat color, or rebuild the alpha from the outer edge of the cutout.
|
||||
- To show real UI on a generated device, generate it straight on with no perspective, measure the screen rectangle, lay HTML over it and rotate both together. The text stays sharp and real.
|
||||
- Everything inside one visual tells the same story. If the list shows invoice INV-0142 for a client, the phone shows the same client, number and amount.
|
||||
|
||||
## Generate it
|
||||
|
||||
Look for an image generation tool among the tools you have, for example an MCP server for fal, Replicate, OpenAI images, Higgsfield or a design tool. If there is one:
|
||||
|
||||
- Show the user the prompt, the model and the size, and ask before running, since generation can cost money.
|
||||
- Run once. Judge the result against the one sentence and the palette before trying again, and change the prompt rather than rerolling the same one.
|
||||
- Save the result into the project's assets, not a temporary URL.
|
||||
|
||||
If there is no tool and the screen needs a scene or an object, say so before the Visuals pass and offer to connect one, like an MCP server for fal, Replicate, OpenAI images or Higgsfield, then go on once it is connected. If the user skips it, leave the slot empty and put the prompt in your report, not on the page. The empty slot is a plain surface step of the exact size with the radius of the layout and no text inside, so nothing unfinished can ship by accident. Tell the user which kind of service can generate it and the file name to save it under.
|
||||
|
||||
## Prompt recipe
|
||||
|
||||
Build the prompt from these parts, in this order:
|
||||
|
||||
- **Subject.** The one sentence, concrete and physical.
|
||||
- **Setting.** Where it is, kept simple.
|
||||
- **Light and palette.** Matched to the page. Name the page background color so the edges can blend into it, and the direction of the light.
|
||||
- **Style.** One clear direction that fits the character, like cinematic photo, soft 3D render, editorial illustration, risograph print or painted background. Pick what fits the product, not what is trending.
|
||||
- **Composition.** Where the subject sits so the text has room, like "subject on the right half, calm empty space on the left". For a scene behind the whole hero, ask for 16:9 or wider, keep the lower fifth calm too, since the product view rises over it, and fade the edges into the page color in code.
|
||||
- **Exclusions.** No text, no letters, no logos, no UI, no watermark.
|
||||
- **Format.** The aspect ratio of the slot at twice the display size.
|
||||
|
||||
## Put it on the page
|
||||
|
||||
- Export as WebP or AVIF at twice the display size, with a real `alt` that says what is shown.
|
||||
- Match the image edges to the page background. If they do not match, fade them with a mask, like `mask-image: radial-gradient(closest-side, #000 70%, transparent)`, or a gradient overlay in the page color. A hard rectangle on a page of a different color is a sign of slop.
|
||||
- Keep it static or give it one slow, subtle motion that fits the subject. No parallax.
|
||||
- A treatment in code, like a duotone into the page palette, can make a generated image feel native. Use one only when it fits the character, and never as new decoration.
|
||||
- Load the hero image with high priority and give it fixed dimensions so the layout does not jump.
|
||||
|
||||
---
|
||||
|
||||
Part of unslop by Damir Sokolovsky ([@heydamir](https://x.com/heydamir)), CC BY 4.0. Keep this credit in copies and adaptations.
|
||||
Reference in new issue
Block a user