1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
# Effects: feel reviews
**The rule above all the others:** no effect may alter or cover a focus
indicator (the focused pane's tag colour, its grip, the cursor, the
selection) or reduce its contrast. The effects are sugar; the indicators are
how a person knows where they are. Every G stage is reviewed against this.
Lift, for one, falls only on pane bodies and rails, never on a tag, a grip or
a header, and its strength is capped so text and the selection keep min(their
contrast, 4.5) (tests in src/gui/gui.zig). The rule binds effects, not a
theme's structure: the rules between panes (2px, `rule_px`) are the theme's
borders, and the one over a tag band stays in the band's slack, never over
its text. A dim touches only the unfocused
panes, with the same floor, and the focused pane's text is never at less
contrast than theirs (tests in src/draw.zig).
**Density.** A pixel shell draws every size given in pixels in logical
pixels, `dp()` in gui.zig (SDL's window display scale, rounded, at least
one; `PARDES_TEST_SCALE` forces one in a harness, and a test window is 1x
otherwise): the rules (`rule_px`), the tag rule, the grip's ring and its
dirty gap and mode bars, the rail's gap to the text and its minimum, the
thumb's inset, the rim, a bar cursor's minimum width, underline and
strikethrough thickness, the rule-grab margin, and the Crt's scanline and
mask periods (`pardesScale`). Left as they are: what is measured in cells
or the tagline (the rail's width, `rail_px` scaled with the tagline; Lift's
blur and offsets; every motion, in cells), Shadertoy's iResolution,
fragCoord and iMouse (the display's pixels, as ghostty's), and the pet and
touch debug overlays.
Each effect of docs/render-pipeline.md §9 lands behind its own switch, off,
and is kept only after a feel review (§8.4): a frame series on the virtual
clock, a recording, and a verdict — keep, polish or drop — with one line of
why. The verdict is the user's.
| effect | switch | state | review material | verdict |
|---|---|---|---|---|
| Crt (bundled post pass) | `Crt 0..3` | kept, rewritten (no barrel) | fx-compare stills, live window | kept at the user's live look |
| Ripple, Glitch | — | removed | live window | dropped by the user after a live look |
| G1 lift | `Lift shadow\|rim\|auto` (off), `InactiveDim <percent>` | opt-in until the focus-lift default is decided | lift-shots-2 (off, shadow, rim, auto on acme, dusk, forge); frame series + mp4 per Motion flavour | shadow on acme: keep as is. Round 1 dropped glow and surface (surface lowered the focused text's contrast) and made rim a hairline just above the focused tag (light on a dark page, shade on a light one). auto on a dark page recedes the others (InactiveDim 30) |
| G3 cursor | follows `Motion`; `CursorBlink on\|off` (on) | glide on by default through the default flavour (smooth); blink on (decision 6) | .scratch/render/cursor/cursor-flavours-grid.mp4 (and quarter speed), raw/<flavour>/<motion>/ | pending |
| G4 bloom, vignette, grain | `Bloom`, `Vignette`, `Grain` `0..3` (off) | bundled post passes; none touches a tag, grip, notice or the cursor (pardesSpare); each capped per theme (pardesCap) | .scratch/render/{bloom,vignette,grain}/ (levels 0-3 on forge, dusk, acme; crops; mp4 stepping through the levels) | pending |
| G5 lapis | `Theme lapis` (its `decor`) | a native theme with ornament: tag plaques with hard shadows inside their bands (the focused one striped), dots, rail checker, title shadow | .scratch/render/lapis/ (whole window at 1x and 2x, crops, a slide mp4) | pending |
## Motion flavours
`Motion off|crisp|smooth|bouncy|playful` (default smooth, tuned so arrivals
land inside §8.1's 220 ms; bouncy and playful run longer, opt-in) is one parameter set
(animation.Motion) every fx animation reads, so a flavour is data, not a
branch in each effect. Input is never blocked, and a new target always
retargets from the current position and velocity.
| flavour | timing (ω, length) | follow-through (ζ) | anticipation | squash/stretch | exaggeration (gain) | secondary lag | arcs |
|---|---|---|---|---|---|---|---|
| off | instant | — | — | — | — | — | — |
| crisp | 60, 0.6× (leaves at full speed) | 1 (none) | — | — | 1 | in step | — |
| smooth | 34, 1.1× (slow in, slow out; ≤220 ms) | 1 (none) | — | — | 1 | 0.85 | 0.04 |
| bouncy | 24, 1.4× | 0.35 (overshoots ~30%, settles twice) | — | 0.15 | 1.25 | 0.8 | 0.08 |
| playful | 18, 1.7× | 0.28 (overshoots ~40%) | 0.15 of the move | 0.35 | 1.4 | 0.55 | 0.15 |
The flavours are distinct by design, not tuning: crisp leaves at full speed
and never passes its mark; smooth takes twice as long and eases in and out; bouncy clearly passes its mark and settles back once or twice;
playful winds up the other way first, flies well past, and stretches along
its path and squashes as it lands, its text with it (the user's choice); a
pointer inverts the same stretched box, and landed, a pane is exactly its
target. A motion only a few pixels big (Lift) is
exaggerated by the flavour's gain so its character shows. The flavours drive
the Lift spring (time-based), the notice drop and the pane moves (PanelSlide,
PanelZoom, PanelVertical opening or moving, whose length scales with the
flavour); a closing pane keeps its effect's own exit. Chart and side-by-side
video: .scratch/render/motion/compare/.
Which principles each motion uses:
- **Lift** (G1): timing, slow in / slow out (the spring), follow-through
(bouncy and playful lift past full and settle back), anticipation (playful
dips before it rises; below zero nothing is drawn, so it reads as a beat
before the lift), staging (only the focused pane lifts; nothing else moves
with a focus change), exaggeration (the flavour's gain). Squash, stretch,
arcs and secondary lag have nothing to act on in a lift.
- **Notice drop and pane moves**: timing and follow-through from the flavour's
spring over the move's length; anticipation (playful). A pane past its mark
never takes a neighbour's click, a rising one stays in its box, and a notice
past its row stays in its pane's body.
- **Cursor** (G3): timing (the flavour's spring, 2.4x its pace: it follows
input), follow-through (bouncy and playful pass the target), squash and
stretch and secondary action (the trailing corners lag by the flavour's
follow, so a jump smears and collapses), arcs (a jump over 8 cells bows by
the flavour's arc), anticipation (playful's wind-up). A step of a cell or
less, or any move in insert mode, lands at once, so typing never trails;
a scroll carries the cursor with the text. Only the focused cursor moves.
Notes on G1: the lift and the dim run on one focus spring per pane, at the
Motion flavour's pace, sampled at each frame's own time; while it moves the
GUI draws at the display's rate, and a grid snaps. A notice floats on a lift
of 1 while a shadow or rim is on. InactiveDim under `Lift auto` defaults to
30. The fade is in linear light, so on a dark page it is gentle: at 30
forge's text goes from 15.5:1 to 11.2:1 and dusk's from 6.4:1 to 4.8:1,
plainly quieter and still easy reading; at 10 forge barely moves (14.1:1).
On a light page the same percent bites far harder (acme: 7.0:1 at 10, the
4.5 floor at 30), which is why `auto` shades there instead of dimming.
Notes on G4: the three are post passes, so each is a frame of cost only
when it is in the chain. None moves (grain is still, by choice: moving
grain would redraw every frame for a texture), so none asks for frames.
The focus rule is kept by construction: the shell hands every pass the
rectangles of the tags (with their grips), the column and workspace tags,
the notices and the cursor, and a bundled pass leaves them as they are; its
strength is capped per theme so the text on the page and the selection's
text keep min(their contrast, 4.5) (Post.capsOf, tested over the native
themes). Bloom takes only what is brighter than the page, so a light page
does not bloom at all; its glow is a dual Kawase blur in float textures at
half size and down (3 to 5 steps by level), within §8.2's 5%. The vignette
is round, starts halfway to a corner and reaches 10, 18 and 28%. Grain is
1.5, 3 and 5 of 255 on page-coloured pixels only, a logical pixel big.
Found on the way: SDL GPU binds at most 4 KiB of a uniform block, so every
Shadertoy colour after the palette (iBackgroundColor and on) read zero; the
block is now cut in three with ghostty's names and order kept.
Notes on G5: lapis (src/themes/lapis.zig) is 0x4200.cafe's lapis.css in
pardes's chrome, drawn inside it (decision 1). The tags are the site's
trail and title bars: every tag a plaque in its own band, framed in gold
(2 px, the trail's border) with the trail's hard vermilion shadow (5 px)
down and right, the frame and the shadow both inside the band's rows and
columns, so no shadow reaches a body, a grip or the next pane; down, the
shadow gets what the band's slack under the text leaves (a pixel or three
at a tagline under the body's size, more at a larger one), to the right
its full offset. The focused pane's plaque is the code window's title bar,
gold and lapis stripes of 2 px, its words on plates of the focused ground
(the words' cells stay opaque, the blanks between let the stripes show),
so the focus indicator is plainer to see than any theme's tint, never
less; the unfocused ones are plain. Column and workspace tags take the
quieter object-label language: a 1 px frame and a 2 px shadow of vermilion
toward the page. The plaques are decor drawn under their cells (a group's
under range) with the cells blended premultiplied over them; blank tag
cells on the tag's ground are see-through there. They need an opaque
window: under WindowOpacity 100 the tags are plain. Also from the site:
the colours (lapis-deep page, vellum text, gold names and rules,
vermilion rubrics and tag rule, gold selection in lapis ink), the page's
gold dot grid (22 px, #e8c46a22) on page-coloured cells only, fixed to the
window; the dither frame's checker in the scroll track, anchored to its
rail (the thumb solid gold); the titles' vermilion offset shadow under a
file name's letters. Left out: Crimson Pro and Departure Mono, the
ornaments, a decor crossfade (it snaps while the colours fade). The tty
shows the colours alone: a tag row abuts its body and its neighbour, so
there is no cell for a shadow row or column.
|