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
+22 -7
View File
@@ -587,24 +587,39 @@ export interface DayScheduleProvider {
/** A colour the host paints into one day card in the Plan sidebar (and into that day's
* mobile chip) — so a trip split into legs shows its leg membership while you scroll
* the itinerary. Tone only, never a hex: the host picks the alpha per theme AND per
* region, so the tint stays subtle and readable in both light and dark.
* the itinerary.
*
* The card has three separately tintable regions. Set `tone` to colour all of them,
* or name regions individually for finer control — e.g. a bold badge marking the leg
* with the activity list left plain, so a dense day stays easy to read. */
* Pick a `tone` from the shared palette, or send your own `#rrggbb` when four tones
* cannot keep your legs apart — a twenty-stop trip needs twenty colours. Either way
* you choose the hue and the host chooses the weight: it sets the alpha per theme and
* per region and clamps a 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, never as a
* fill, and no colour you send can make a day unreadable. Anything that is not exactly
* six hex digits is ignored (`#RGB` shorthand and CSS names included).
*
* The card has three separately tintable regions. Set `tone` / `color` to paint all of
* them, or name regions individually for finer control — e.g. a bold badge marking the
* leg with the activity list left plain, so a dense day stays easy to read.
*
* Colour is decoration, not information: pair it with `label` (and with a
* dayScheduleProvider row where it matters) so the meaning survives for anyone who
* cannot tell your legs apart by hue. */
export interface DayTintContribution {
dayId: number; // must be a day of the requested trip
/** Shorthand: tints every region you do NOT name below. Omit it and only the
* regions you name are tinted. */
/** Shorthands: paint every region you do NOT name below. Omit both and only the
* regions you name are tinted. `color` wins over `tone` at the same level. */
tone?: ContributionTone;
color?: string; // your own colour, `#rrggbb` only
/** The day-number badge — the smallest, boldest mark. Also tints the mobile day chip. */
badgeTone?: ContributionTone;
badgeColor?: string;
/** The day header row (number, title, date, cost). Its hover state deepens the tint. */
headerTone?: ContributionTone;
headerColor?: string;
/** The expanded activity list — places, bookings and transport for the day. The
* largest surface and the one behind the densest text, so it is tinted faintest. */
activityTone?: ContributionTone;
activityColor?: string;
label?: string; // optional tooltip on the day (≤60 chars)
}
export interface DayTintProvider {