Let a dayTintProvider send its own colour

Four tones cannot keep a twenty-stop trip's legs apart, which is most of
the point of colour-coding one. A contribution can now carry `#rrggbb`
alongside `tone`, per region or as a shorthand.

The split of control is: the plugin picks the hue, the host picks the
weight. A colour rides the same per-theme, per-region alphas as a tone,
and its OKLCH lightness is clamped into a per-theme band first, so no
contribution can hand a user a day card they can't read. The band
brackets the built-in tones rather than tightening around them, so an
ordinary colour passes through untouched and a hex equal to a tone
renders exactly like that tone. Hue and chroma are never touched - it
bounds a colour, it doesn't boost one, so a near-white grey still lands
as a faint tint.

Only `#rrggbb` survives normalization, and that is a security boundary
rather than tidying: the value ends up inside a `color-mix()` in an
inline `background`, a shorthand that takes a layer list, so a string
closing the paren early could append a `url(...)` and beacon everyone
viewing the trip.

A region gets exactly one paint instruction - what it names beats the
shorthand, a colour beats a tone at the same level - so the client never
has a tie to break. A region naming a colour the host can't use degrades
to `default` like a bogus tone already did.
This commit is contained in:
Renzo Beux
2026-07-30 15:37:20 -03:00
committed by Maurice
parent c89d2bf3a1
commit f718240ffc
12 changed files with 314 additions and 57 deletions
+38 -9
View File
@@ -485,7 +485,8 @@ dayScheduleProvider: {
`hook:day-schedule-provider` can announce "Kanazawa begins" as a row, but it cannot make
day 12 *look* like it belongs to Kanazawa. `hook:day-tint-provider` does — it colours a
day card in the Plan sidebar (and that day's mobile chip) with one of the four tones:
day card in the Plan sidebar (and that day's mobile chip), with one of the four tones or
with a colour of your own:
```js
dayTintProvider: {
@@ -504,6 +505,35 @@ Return **one entry per day** — the bound here is the trip's day count, not a f
cap, so this stays correct on a six-month itinerary where the ≤60-item day-schedule hook
would tint only the first 60 days.
#### Bring your own colour
Four tones cannot keep a twenty-stop trip's legs apart. Send `color` instead — `#rrggbb`
only, and nothing else survives (`#abc`, `rgb(...)` and CSS names are all ignored,
because the value ends up inside a CSS colour the host builds):
```js
// One hue per leg, from your own palette.
return days.map((d) => ({ dayId: d.id, color: legColor(d), label: legName(d) }))
// Mix freely: your colour on the badge, a shared tone for the rest of the card.
return days.map((d) => ({ dayId: d.id, tone: 'default', badgeColor: legColor(d) }))
```
Each region takes `<region>Color` alongside `<region>Tone`. Within one region a colour
wins over a tone, and anything you name on a region beats the shorthand — so the second
example above tints the badge with your colour and the header and activity list with
`default`.
You choose the hue; the host still chooses the weight. It sets the alpha per theme and
per region and clamps the colour's lightness into a band that reads on both the light
and the dark sidebar, so the tint always lands as a wash behind the day rather than a
fill, and no colour you send can make a day unreadable. An ordinary mid-range colour
comes through as you sent it; only the extremes (near-white, near-black) are pulled in.
Colour is decoration, not information — pair it with `label` (and a day-schedule row
where the meaning matters) so it still works for anyone who can't tell your legs apart
by hue.
#### Tint one region instead of the whole card
A day card has three separately tintable regions, and `tone` above is just the shorthand
@@ -528,17 +558,16 @@ return days.map((d) => ({ dayId: d.id, headerTone: legTone(d), activityTone: ove
| Field | Region |
|---|---|
| `badgeTone` | the day-number badge — smallest and boldest; also the mobile day chip |
| `headerTone` | the day header row (number, title, date, cost); hovering deepens the tint |
| `activityTone` | the expanded activity list — largest, behind the densest text, tinted faintest |
| `badgeTone` / `badgeColor` | the day-number badge — smallest and boldest; also the mobile day chip |
| `headerTone` / `headerColor` | the day header row (number, title, date, cost); hovering deepens the tint |
| `activityTone` / `activityColor` | the expanded activity list — largest, behind the densest text, tinted faintest |
A region you never name is not tinted and renders exactly as it does without your plugin.
Give the host a tone, never a hex: it picks the alpha per theme *and* per region, so the
tint stays subtle in both light and dark. `label` becomes the day's tooltip. A day takes
at most one contribution and it is resolved **whole** — within your list the first entry
for a day wins, and if another plugin also tints that day the first granted provider
wins outright, so days never flicker and two plugins can't each own part of one card.
`label` becomes the day's tooltip. A day takes at most one contribution and it is
resolved **whole** — within your list the first entry for a day wins, and if another
plugin also tints that day the first granted provider wins outright, so days never
flicker and two plugins can't each own part of one card.
---