# makeover-timing The time axis of the make-family design system. [`makeover`](https://makenot.work/git/max/makeover) resolves colour, [`makeover-geometry`](https://makenot.work/git/max/makeover-geometry) resolves distance, and [`makeover-layout`](https://makenot.work/git/max/makeover-layout) names what a thing is without resolving anything. This crate answers the remaining question of the same shape: how long. ## A duration is named by what it is waiting for | Intent | What it waits for | Resolves to | |---|---|---| | `revert` | a control returning to its resting label after confirming | 1500ms | | `clear` | a status line emptying itself | 2000ms | | `dismiss` | a transient notice's lifetime | 3000ms | | `debounce` | typing settling before the work starts | 150ms | Whether a toast should live 3000ms or 3500ms is unanswerable on its own. Whether a message is a toast or a banner is not. That is the whole argument for the layer, and it is the same one `gap-peer` makes about six pixels. ## One duration per intent, on every renderer An intent resolves to exactly one duration everywhere. Not one per renderer, not one per theme. Distance has a renderer axis because a fingertip is coarser than a cursor and a terminal cell is coarser than a pixel: the surface differs. Time does not differ that way. A second is a second in a browser, in egui and in a terminal, and the reader waiting it out is the same reader. A divergence between renderers is a bug report, not an axis. ## What is deliberately not a timing intent **A race is not an intent.** Waiting 300ms before navigating because the write "should" have landed, or 150ms before closing a dropdown so a mousedown can beat the blur, is a synchronisation bug wearing a duration's clothes. Naming those would launder them into design decisions and give every future one a token to hide behind. They get fixed per screen. **Severity is not an intent, and it is not a fifth number.** Two renderers already reached for one: MNW gives an error toast 6000ms against an ordinary one's 3000ms, and audiofiles' footer never expires an error at all. Read together those are one statement, not two durations. A message the user must not miss does not go away on its own, `makeover-layout` already has the word for that (such a message is not `Notice::transient`), and a banner has no lifetime. **A poll interval is not an intent.** A retry backoff, a health check, an update check: those are answerable from what they talk to, not from what a reader can follow. ## Motion is a separate axis `Intent` says how long a state lasts. `Motion` says how long a change takes. CSS itself draws that line, a `setTimeout` against a `transition-duration`, and folding the two together is what makes a "timing scale" unusable: 300ms of fade and 3000ms of toast are not two rungs of one ramp. `Motion` has one rung, `fade` at 300ms, because the tree has one measured transition. It grows when something is measured, not when a scale looks short. ## Using it Web surfaces bake the stylesheet in at build time. Nothing here changes at runtime, so there is no load-time JS step: ```rust use makeover_timing::timing_css; std::fs::write("static/timing.css", timing_css())?; ``` ```css :root { --timing-revert: 1500ms; --timing-clear: 2000ms; --timing-dismiss: 3000ms; --timing-debounce: 150ms; --motion-fade: 300ms; } ``` egui and ratatui surfaces resolve through `Duration` instead, which is the reason this is a crate rather than a stylesheet: ```rust use makeover_timing::{Intent, Motion, notice_lifetime}; let settle = Intent::Debounce.duration(); let gone = notice_lifetime(true).map(|life| life + Motion::Fade.duration()); ``` `notice_lifetime` is the seam the crate was built for. `makeover-layout` documents `Notice::Toast` as "transient, stacked, dismisses itself" and says nothing about when; this says when, on the renderer's side of the line. It takes the bool rather than the enum so this crate stays off `makeover-layout`'s dependency graph, and the bool is exactly what the description asserts. ## Where the numbers came from Every value is a count from the tree, not a preference: ```text revert 1500ms MNW: 6 hand-rolled sites, plus core/clipboard.ts's own default clear 2000ms MNW: 4 sites dismiss 3000ms MNW: the toast renderer's lifetime debounce 150ms audiofiles SEARCH_DEBOUNCE, MNW docs-search.js fade 300ms MNW: TOAST_FADE_MS, matching the .fade-out transition ``` The one contested value is the debounce, where MNW's two category typeaheads sit at 200ms against everything else's 150ms. 150 wins on the count and on the cross-renderer agreement, and the 200s conform. ## Licence MIT.