/* LUCID — THE TODAY ROOM'S STYLES (view-today: the calendar page).
 *
 * Moved out of app.css on 15 October 2026 by the room rebuild, loaded straight
 * after app.css (see the room-css:start marker in index.html). The comments that
 * explain a decision came with their rules. tests/test-room-today-unchanged.js
 * is the proof that nothing on screen changed. The mini-calendar grid (.mc-grid,
 * .mc-cell, .mc-nav, .mc-label) is also drawn by the Life and Timeline rooms and
 * stays in app.css.
 */

/* ======================================================================
     THE DAY, GIVEN THE SCREEN IT IS ON
     ----------------------------------------------------------------------
     "We can also shrink the width of the agenda (half is wasted space) and
      increase the size of the actual calendar."

     He is right, and it measures almost exactly as he described it:

       left rail          390px
       widest agenda line 203px      → 187px of it doing nothing
       day track          726px      at EVERY screen size
       dead space right   586px      on a 1920 display

     Two separate faults. The rail was sized for the mini-calendar rather than
     for the agenda beside it — 352px of month grid, seven columns at 50px,
     far more than a date needs. And `max-width:1220px` capped the whole view,
     so a third of a wide screen was black and the day track never grew.

     Now: the rail is sized to the agenda's real content, the month grid fits
     inside that, and everything else goes to the day itself. On his 1920
     display the track roughly doubles.

     The cap does not vanish — a 3440px ultrawide would give a single day a
     3-metre-wide hour track, which is its own kind of unusable. It moves to
     a width a day can actually fill.

     AND THEN IT WENT PAST THE MARK. "you have calendar marked done, but never
     shrunk agenda width and rebalanced the page with calendar" — 3 September.
     The shrink above happened; the REBALANCE never did, because 286px is a
     hard number and the day track is simply whatever is left, so the wider the
     display the further apart the two panes drift. Measured on a 1680 window
     with an ordinary five-item day on it:

       agenda rail   286px   four of the five titles wrapping to two lines
       day track    1232px   event blocks 1120px wide holding 25 characters

     Both of those are the same fault. The rail is starved of the width its own
     content needs while the track holds 800px it has no way to use, and on the
     1920 display he actually works on the track is wider still and the gap is
     worse. A day is a column of hours; it does not get better at 1400px than
     at 1150.

     So the rail becomes a SHARE of the page rather than a fixed pixel, bounded
     at both ends: never narrower than the 286px it is today — below that the
     month grid's seven columns stop working — and never wider than the
     330–390px band every other secondary rail in Lucid already uses
     (.gv-wrap, .gv-wrap.q-open) — 330px is .gv-wrap's rail exactly, and it
     is also what test-calendar-layout has asserted since the shrink: a rail
     under 340px. Four of the five titles that were wrapping now fit on one
     line, which makes the agenda SHORTER as well as better balanced, and the
     track gives up 44px it had no way to use.

     ROW 1923, AND HE HAS NOW ASKED FOR THIS THREE TIMES.

     "Are we ever going to shrink width of agenda and make the calendar a
      little wider?"   (Matt, 11 September.)

     Measured before touching anything, on the fixture day, at four widths:

       viewport   rail    rail as % of the split   day track
       1280       286px   25.2%                    746px
       1500       312px   23.0%                    940px
       1680       330px   21.5%                   1102px
       1920       330px   18.6%                   1342px

     THE FIRST THING MEASURED WAS THAT THE WIDTH IS NOT WHERE THE PROBLEM IS.
     Narrowing the rail on its own, to the 252px the month grid genuinely
     allows, made the agenda WORSE: four of the ten titles on that day began
     to wrap and the rail's total height went from 759px to 838px. Narrower
     and taller is not what he asked for. It is also exactly the trade the
     August attempt made, which is why this row keeps coming back.

     What was actually eating the rail was INSIDE the row. Two things, and
     both of them cost width on every row whether or not they were ever used:

       · the ⋯ action button — 28px of button plus the row's 10px gap taken
         off the title's width, permanently, as a flex child. It is absolute
         now and sits in the row's own corner; see .agenda-act below.
       · the meta line — "STS · 540 N Michigan Ave, Chicago · 2 attendees",
         which wrapped to four lines beside the "In person" pill. One line
         with the whole of it on the row's tooltip now; see .agenda-meta.

     With those 38px given back to the title, the rail can be narrower than it
     has ever been AND hold the same titles on one line. Measured on the same
     day, before → after:

       viewport   rail          day track       agenda rail, total height
       1280       286 → 276px    746 → 756px     816 → 728px
       1500       312 → 276px    940 → 976px     759 → 728px
       1680       330 → 276px   1102 → 1156px    747 → 728px
       1920       330 → 288px   1342 → 1384px    747 → 728px

     Exactly one title wraps at every one of those widths, which is what the
     WIDEST setting managed before. So the agenda is narrower at every size,
     shorter at every size, and no harder to read; and the day track is wider
     at every size. The floor is 276 rather than 252 because 252 is where the
     titles start wrapping again — the month grid would take 252 happily, but
     the month is not the thing this rail is for. */
  .today-grid{ display:grid; grid-template-columns:clamp(276px, 33%, 600px) minmax(0, 1fr); gap:var(--space-5);
               max-width:1800px; flex:1; min-height:0; }

/* The "This Week" panel is a sibling of .today-grid swapped in by inline
     display, so it needs the same fill-the-remaining-space rule or it would
     size to its content and leave the old dead band at the bottom. */
  #weekView{ flex:1; min-height:0; overflow-y:auto; }

@media(max-width:1100px){
.today-grid{ grid-template-columns:1fr; }
}

@media (max-width:780px){
.today-grid{ height:auto; }
}

@media (hover:none) and (pointer:coarse) and (max-height:600px){
.today-grid{ height:auto; }
}

.left-panel{
    display:flex;
    flex-direction:column;
    padding:var(--space-5);
    min-height:0;
    border-radius:var(--radius-card);
    background-image: linear-gradient(180deg, rgba(255,255,255,.045) 0%, rgba(255,255,255,.014) 38%, rgba(255,255,255,0) 100%), var(--glass-sheen);
    background-color: var(--glass-bg);
    box-shadow:var(--elev-raised);
  }

.agenda-scroll{ flex:1; overflow-y:auto; min-height:0; margin:0 -6px; padding:0 var(--space-2); }

@media(max-width:1100px){
.agenda-scroll{ padding-inline-end:calc(var(--space-2) + var(--orb-keepout-x)); }
}

/* 68 — "the agenda should be shorter." Padding 10/margin 7 down to 8/5: on
     a six-event day that alone gives back about 30px, which does not sound
     like much until it is the difference between the last event needing a
     scroll and not. The bigger cut is below — the row's OWN lines got
     tighter too, and between the two a day like the one in the reference
     screenshot loses close to a fifth of its height with nothing removed
     from it. */
  .agenda-item{
    display:flex;
    align-items:flex-start;
    gap:var(--space-3);
    padding:var(--space-2) var(--space-3);
    margin-bottom:var(--space-2);
    cursor:pointer;
    border-left:3px solid transparent;
    border-radius:var(--radius-card);
    position:relative;
  }

.agenda-item:hover{ background:rgba(120,120,140,.08); }

.agenda-item.selected{ background:rgba(120,120,140,.10); box-shadow:0 0 0 1.5px var(--ring), 0 0 22px var(--ring-soft); }

/* "1:22 AM — 2:22 AM" IN A 58px COLUMN WRAPPED TO THREE LINES, and pushed
     every agenda row to three times the height it needed — which is most of
     where "half is wasted space" came from. A time is one line.
     Tabular figures so a column of them does not jitter as the digits change,
     and the end time sits under the start rather than beside it. */
  /* Stacked above the title, so the title has the whole rail — see the note in
     renderAgenda. Both times on ONE line now that there is room for them. */
  .agenda-time{
    font-family:var(--font-mono);
    font-size:var(--type-label);
    font-variant-numeric:tabular-nums;
    letter-spacing:.02em;
    color:var(--text-faint);
    white-space:nowrap;
    margin-bottom:1px;
    padding-right:var(--space-8);
  }

.agenda-time .ag-to{ display:inline; opacity:.72; }

.agenda-time .ag-to::before{ content:' – '; opacity:.6; }

.agenda-title{ font-size:var(--type-body); font-weight:600; line-height:1.28; letter-spacing:-.005em; }

.agenda-meta{ font-size:var(--type-body); color:var(--text-dim); margin-top:var(--space-hair); display:flex; align-items:center; gap:var(--space-2); }

/* ROW 1923 — THE META LINE IS ONE LINE.
     On the fixture day the site walk's meta read "STS · 540 N Michigan Ave,
     Chicago · 2 attendees" and, beside the "In person" pill in a 252px rail,
     took FOUR lines — one row as tall as the three above it put together, in
     the column he says is half wasted space. A row of a scanning list should
     be a fixed height; if it is not, the list has a ragged right edge AND a
     ragged left one.
     The pill keeps its natural width (flex:none) and the words take whatever
     is left and stop, which is why min-width:0 is here: a flex child will not
     shrink below its content without it, and an ellipsis that never triggers
     is just an overflow. Nothing is lost — renderAgenda puts the whole string
     on this element's own title, so the address is a hover away rather than
     three lines away. */
  .agenda-meta .agenda-meta-txt{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.agenda-meta .where-tag, .agenda-meta .kind-dot{ flex:none; }

.agenda-meta .where-tag{ margin-right:0; }

/* 68 — the row of "View contact / Join meeting / Remind me" links. Its own
     class rather than the inline style it used to carry, so the tighter
     margin lives with the rest of the row's rhythm instead of fighting it
     from a template string. renderAgenda only writes this element when at
     least one link is real — an empty one with a top margin was a blank line
     under every row that had nothing to offer, which is the opposite of
     shorter. */
  .agenda-links{ display:flex; gap:var(--space-3); margin-top:var(--space-1); flex-wrap:wrap; }

/* .mini-link carries its own margin-top:4px for the places it sits directly
     under a paragraph with nothing else managing the gap — redundant here,
     where .agenda-links already set one, and redundant margins are exactly
     how a "shorter" pass grows back the height it just gave up. */
  .agenda-links .mini-link{ margin-top:0; }

/* 68, THE LAST 12px — AN EMOJI WAS SETTING THE HEIGHT OF THE ROW.
     Every other part of this item landed and the rail was still 93.625px a
     row against a target of 86. The excess is not padding and not margin:
     it is `line-height:normal` resolved against the colour-emoji face. At
     11px the label face gives a 12px line box; the same 11px asked of the
     emoji face gives 18px. “🎥 Video” therefore stood the pill up at
     22px inside a row whose own text lines are 15px, and “🔔 Remind me”
     made the link row 18px instead of 12 — twelve pixels of a 93.6px row,
     spent on two glyphs.
     Pinning the line box to the LABEL’s own leading is the fix, not taking
     the glyphs away: the glyph is a deliberate channel on this row (see the
     note above .where-tag, and 265 for when a glyph is NOT worth keeping).
     1.1 unitless rather than 12px so it still tracks the font-scale slider,
     and the emoji’s painted box measures 12.0px against a 12.1px line box
     — it sits inside the pill, it is not clipped by it.
     Measured on the same fixture: 93.625px → 81.8125px a row. */
  .agenda-meta .where-tag,
  .agenda-links .mini-link{ line-height:1.1; }

/* THE DAY SO FAR, SETTLED BACK. See calIsPast() — by end time, today only,
     repainted on the minute. It recedes rather than greys out: what happened is
     still worth being able to read, it has simply stopped being what is next.
     0.52 rather than something fainter because the readability harness measures
     every painted glyph and a dimmed block still has to clear 4.5:1. */
  .cal-event.cal-past{ opacity:.52; filter:saturate(.55); }

.cal-event.cal-past:hover{ opacity:.95; filter:none; }

.cal-event.cal-past.selected{ opacity:1; filter:none; }

.kind-dot{ width:6px; height:6px; border-radius:var(--radius-round); flex:none; }

.kind-event{ background:var(--green); box-shadow:0 0 6px var(--green-glow); }

.kind-task{ background:var(--violet); box-shadow:0 0 6px var(--violet-glow); }

.kind-reminder{ background:var(--blue); box-shadow:0 0 6px var(--blue-glow); }

/* VIDEO OR IN PERSON. A glyph and a word, deliberately not a colour — colour
     already means "which calendar" on these rows, and two meanings on one
     channel makes both unreadable. text-dim rather than text-faint because the
     readability harness measures every painted glyph against 4.5:1. */
  .where-tag{ display:inline-flex; align-items:center; gap:var(--space-1); margin-right:var(--space-2);
    padding:1px var(--space-2); border-radius:var(--radius-pill); font-size:var(--type-label); font-weight:600;
    color:var(--text-dim); border:1px solid var(--glass-border-hi); white-space:nowrap; }

.where-tag.where-video{ color:var(--green); border-color:color-mix(in srgb, var(--green) 40%, transparent); }

.where-tag.where-place{ color:var(--text-dim); }

.cal-where{ margin-left:var(--space-2); font-size:var(--type-label); opacity:.9; }

/* THE ACTION BUTTON ON AN AGENDA ROW.
     Present at all times rather than on hover: hover does not exist on the
     phone, and a control that only appears to a mouse is a control half his
     devices do not have. It is quiet instead — dim until the row is under the
     cursor, and then legible. Colour is deliberately text-dim rather than
     text-faint at rest, because the readability harness measures every painted
     glyph and faint-on-glass does not clear 4.5:1. */
  .agenda-act{
    flex:none;
    align-self:auto;
    width:28px;
    height:28px;
    padding:0;
    display:flex;
    align-items:center;
    justify-content:center;
    background:transparent;
    border:none;
    border-radius:var(--radius-control);
    cursor:pointer;
    font-size:var(--type-lead);
    line-height:1;
    color:var(--text-dim);
    position:absolute;
    top:5px;
    right:6px;
  }

.agenda-item:hover .agenda-act{ color:var(--text); background:rgba(120,120,140,.12); }

.agenda-item .agenda-act:hover{ color:var(--text); background:rgba(120,120,140,.20); }

.agenda-act:focus-visible{ outline:2px solid var(--ring); outline-offset:1px; }

/* ROW 1923 — AND IT COMES OUT OF THE ROW'S FLOW.
     This button was a flex CHILD: 28px of it plus the row's 10px gap, taken
     off the title's width on every row, forever, whether or not anything was
     ever pressed. In a 330px rail that left the title 232px; the moment the
     rail was narrowed to the 252px the month grid actually allows, four of the
     ten titles on the fixture day wrapped and the agenda got TALLER — the
     exact trade the first shrink made and the reason this row has been open
     since August.
     Lifted to the corner, the title and the meta get the whole inner width
     back, so the rail can be narrower than it has ever been and still hold the
     same titles on one line. The time line above it is the only thing the
     button can reach, and it is a short string — 30px of padding keeps them
     apart, and the button sits on the row's own padding rather than over any
     word. Absolute needs a positioned ancestor, hence .agenda-item below. */



/* MINI CALENDAR */
  .mini-cal{ padding-bottom:var(--space-4); margin-bottom:var(--space-2); border-bottom:1px solid var(--glass-border); flex:none; }

:root[data-mode="light"] .mc-add{ background:linear-gradient(135deg,var(--accent),color-mix(in srgb, var(--accent) 82%, var(--neutral-2))); color:var(--on-accent); }

.mc-add{ width:22px; height:22px; border-radius:var(--radius-control); background:linear-gradient(135deg,var(--accent),var(--accent-2)); color:var(--ink); font-weight:800; font-size:var(--type-lead); display:flex; align-items:center; justify-content:center; cursor:pointer; box-shadow:0 0 12px var(--accent-glow); }

.mc-legend{ display:flex; align-items:center; gap:var(--space-2); margin-top:var(--space-2); font-size:var(--type-label);
      color:var(--text-faint); font-weight:600; letter-spacing:.03em; }

.mc-legend i{ width:11px; height:11px; border-radius:var(--radius-tight); display:block; font-style:normal;
      background:color-mix(in srgb, var(--accent) 9%, transparent); }

.mc-legend i.l2{ background:color-mix(in srgb, var(--accent) 17%, transparent); }

.mc-legend i.l3{ background:color-mix(in srgb, var(--accent) 27%, transparent); }

.mc-legend i.l4{ background:color-mix(in srgb, var(--accent) 38%, transparent); }

/* ONE DOT PER THING, in the colour of the calendar it is on. See mcDots().
     A row rather than a single mark, centred under the number, capped at four
     — the fifth would not fit in a 30px square and "more than four" is not a
     number anybody reads off a dot anyway, so it becomes a dash. */
  .mc-dots{ position:absolute; bottom:2px; left:0; right:0; display:flex; justify-content:center;
      align-items:center; gap:var(--space-hair); pointer-events:none; line-height:0; }

.mc-dots i{ width:3px; height:3px; border-radius:var(--radius-round); display:block; font-style:normal; }

.mc-dots i.mc-more{ width:6px; border-radius:var(--radius-tight); background:var(--text-faint); }

/* On the selected day the background is a solid green gradient and an
     account colour on top of it is a smudge, so every dot goes to the ink
     colour — the row still says how many, which is the part that survives. */
  .mc-selected .mc-dots i{ background:var(--ink) !important; }

:root[data-mode="light"] .mc-selected .mc-dots i{ background:var(--neutral-ink) !important; }

/* The week's day header is a row now — a label on the left and the forecast
     pushed to the right — where before it was one string. */
  .week-day-head{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:var(--space-2);
    font-family:var(--font-display);
    font-size:var(--type-lead);
    font-weight:600;
    margin-bottom:var(--space-2);
    padding-bottom:var(--space-2);
    border-bottom:1px solid var(--glass-border);
  }

.week-day-head .wx-day{ margin-left:0; }

/* ---------------------------------------------------------------------
     THE CALENDAR TOOLBAR — search on the left, the calendar filter on the
     right, both permanent. Filters in one row above the views, which is where
     a filter belongs: over the thing it filters, not three feet away at the
     top of the window where the account switcher lives.
     --------------------------------------------------------------------- */
  .cal-toolbar{ display:flex; align-items:center; gap:var(--space-3); margin:0 0 var(--space-4); flex-wrap:wrap; }

.cal-search{ position:relative; display:flex; align-items:center; flex:1; min-width:220px; max-width:520px; }

.cal-search-ico{ position:absolute; left:11px; width:15px; height:15px; color:var(--text-faint);
      pointer-events:none; }

#calSearchInput{ width:100%; background:var(--glass-bg); border:1px solid var(--glass-border);
      border-radius:var(--radius-card); color:var(--text); font:inherit; font-size:var(--type-body);
      padding:var(--space-2) var(--space-8) var(--space-2) var(--space-8); transition:border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out); }

#calSearchInput::placeholder{ color:var(--text-faint); }

#calSearchInput:focus{ outline:none; border-color:color-mix(in srgb, var(--accent) 55%, transparent);
      background:color-mix(in srgb, var(--text) 5%, var(--glass-bg)); }

/* Safari draws its own X inside a type=search and it does not match anything
     else in the app, so it is removed and replaced with one that does. */
  #calSearchInput::-webkit-search-cancel-button{ -webkit-appearance:none; appearance:none; }

.cal-search-x{ position:absolute; right:8px; background:none; border:none; color:var(--text-faint);
      font-size:var(--type-title); line-height:1; cursor:pointer; padding:0 var(--space-1); }

.cal-search-x:hover{ color:var(--text); }

.cal-filter{ display:flex; gap:var(--space-2); flex-wrap:wrap; }

/* WHY THESE ARE WRITTEN AS .cal-filter .cal-chip AND NOT .cal-chip.
     The row is folded into a chevron by foldChipRow(), and that mechanism
     finds the things it can pick by `.chip` — so these buttons carry BOTH
     classes now. `.chip` is declared some 1300 lines further down this file,
     and a bare `.cal-chip` would lose every shared property to it on source
     order alone: the pill would come back as a 20px-radius settings chip and
     .chip.active would paint over the accent tint that says which mailbox you
     are looking at. One extra ancestor is all it takes to keep the calendar's
     own pill, and the row is always inside .cal-filter — it IS .cal-filter. */
  .cal-filter .cal-chip{ display:inline-flex; align-items:center; gap:var(--space-2); font:inherit; font-size:var(--type-body);
      padding:var(--space-2) var(--space-3); border-radius:var(--radius-pill); cursor:pointer; color:var(--text-dim);
      background:var(--glass-bg); border:1px solid var(--glass-border);
      transition:background var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }

.cal-filter .cal-chip i{ width:7px; height:7px; border-radius:var(--radius-round); display:inline-block; }

.cal-filter .cal-chip:hover{ color:var(--text); }

.cal-filter .cal-chip.active{ color:var(--text); background:color-mix(in srgb, var(--accent) 18%, transparent);
      border-color:color-mix(in srgb, var(--accent) 50%, transparent); font-weight:600; }

/* THE PILLS, ROLLED UP. "Pills on top of agenda (all calendars, STS, etc)
     all need to roll up in a chevron with all calendars the default."
     — 3 September.

     This is the same fold Settings and the kitchen's recipe picker use, so
     there is one dropdown in Lucid rather than two that look alike; all this
     adds is the three things a toolbar needs that a settings pane does not.
     .st-drop is built for a full-width column control — width:100% button,
     14px of margin under it — and here it is one item in a flex row beside a
     search field, so it sizes to itself and drops its margin. The menu keeps
     .st-drop-menu's absolute positioning, which is why it can be wider than
     the button without pushing the day grid down the page when it opens. */
  .cal-toolbar .st-drop{ margin-bottom:0; flex:none; }

.cal-toolbar .st-drop-btn{ width:auto; min-width:150px; font-size:var(--type-body); padding:var(--space-2) var(--space-3); }

.cal-toolbar .st-drop-menu{ left:auto; right:0; min-width:210px; padding:var(--space-2); }

/* One calendar per line inside the menu. The row is a wrapping flex row when
     it is out in the open and must not stay one in a 210px dropdown. */
  .cal-toolbar .st-drop-menu .cal-filter{ flex-direction:column; flex-wrap:nowrap; gap:var(--space-1); }

.cal-toolbar .st-drop-menu .cal-chip{ width:100%; justify-content:flex-start; }

.cal-results{ padding:var(--space-3) var(--space-4); margin:0 0 var(--space-4); max-height:min(48vh, 420px); overflow-y:auto; }

.cal-res-head{ display:flex; align-items:center; gap:var(--space-3); flex-wrap:wrap; padding-bottom:var(--space-2);
      margin-bottom:var(--space-2); border-bottom:1px solid var(--glass-border); font-size:var(--type-body); font-weight:700; }

.cal-res-note{ font-size:var(--type-body); font-weight:400; color:var(--text-faint); }

.cal-res-bad{ color:var(--orange); }

.cal-deeper{ margin-left:auto; background:none; border:1px solid var(--glass-border); border-radius:var(--radius-control);
      color:var(--text-dim); font:inherit; font-size:var(--type-body); padding:var(--space-2) var(--space-3); cursor:pointer; }

.cal-deeper:hover{ color:var(--text); border-color:color-mix(in srgb, var(--accent) 55%, transparent); }

.cal-res{ display:flex; align-items:center; gap:var(--space-3); padding:var(--space-2) var(--space-1); font-size:var(--type-body);
      cursor:pointer; border-radius:var(--radius-control); }

.cal-res:hover{ background:color-mix(in srgb, var(--text) 6%, transparent); }

.cal-res-dot{ width:8px; height:8px; border-radius:var(--radius-round); flex:none; }

.cal-res-title{ flex:none; max-width:46%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.cal-res-where{ flex:1; min-width:0; color:var(--text-faint); font-size:var(--type-body);
      overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.cal-res-when{ flex:none; color:var(--text-dim); font-size:var(--type-body); white-space:nowrap; }

.cal-res-empty{ padding:var(--space-4) var(--space-1); color:var(--text-faint); font-size:var(--type-body); }

@media (max-width: 640px){
.cal-res-where{ display:none; }
.cal-res-title{ max-width:none; flex:1; }
}

/* TIMELINE PANEL */
  .timeline-panel{
    padding:var(--space-5) var(--space-5) var(--space-4);
    display:flex;
    flex-direction:column;
    min-height:0;
    position:relative;
    border-radius:var(--radius-card);
    /* --glass-bg is a COLOUR, and a colour is not a valid background-image — naming it here dropped the entire declaration and the panel computed to `none`. It goes in background-color, where it belongs. */
    background-image: linear-gradient(180deg, rgba(255,255,255,.055) 0%, rgba(255,255,255,.018) 34%, rgba(255,255,255,0) 100%), var(--glass-sheen);
    background-color: var(--glass-bg);
    box-shadow:var(--elev-raised);
    overflow:hidden;
  }

.cal-head{ display:flex; justify-content:space-between; align-items:baseline; margin-bottom:var(--space-3); flex:none; }

.cal-title{ font-family:var(--font-display); font-size:var(--type-title); font-weight:600; }

.cal-date{ font-size:var(--type-body); color:var(--text-dim); }

/* THE LINE THAT SAYS WHAT THE HOUR TRACK CANNOT SHOW. See calTaskNoteHtml()
     in app.js for the whole diagnosis; this is only its shape.
     flex:none on the HOLDER, which is the flex item — .timeline-panel is a
     column, and a default flex item shrinks, so two lines of explanation on a
     short window would have been squeezed to one and a half. And display:none
     when empty, because on most days it renders nothing at all and a sentence
     that is not there must cost no space.
     Coloured from the accent rather than a warning red: a task with no time is
     not an error, it is a fact about the task, and painting it as a fault would
     put a red bar on his calendar every day he keeps a to-do list. The failed
     case IS a fault and takes --orange, which is the app's "something did not
     work, try again" colour rather than its "this is broken" one. */
  #calTaskNote{ flex:none; }

#calTaskNote:empty{ display:none; }

/* ROW 2230's banner, above the day/week switch rather than inside the day
     pane. Same rule and the same reason: on a page where nothing went wrong it
     must cost no space at all. */
  #calReadNote:empty{ display:none; }

.cal-note{ font-size:var(--type-body); line-height:1.45; color:var(--text-dim);
    background:color-mix(in srgb, var(--accent) 9%, transparent);
    border:1px solid color-mix(in srgb, var(--accent) 26%, transparent);
    border-radius:var(--radius-card); padding:var(--space-2) var(--space-3); margin:0 0 var(--space-3); }

.cal-note-bad{ color:var(--text);
    background:color-mix(in srgb, var(--orange) 11%, transparent);
    border-color:color-mix(in srgb, var(--orange) 34%, transparent); }

.cal-note-act{ font:inherit; font-weight:600; margin-left:var(--space-2); cursor:pointer;
    color:var(--text); background:transparent; padding:var(--space-hair) var(--space-2); border-radius:var(--radius-control);
    border:1px solid color-mix(in srgb, var(--orange) 44%, transparent); }

.cal-note-act:hover{ background:color-mix(in srgb, var(--orange) 16%, transparent); }

.cal-body{ display:grid; grid-template-columns:44px 1fr; flex:1; overflow-y:auto; min-height:0; }

.cal-hours{ display:flex; flex-direction:column; }

.cal-hour{ font-family:var(--font-mono); font-size:var(--type-label); color:var(--text-dim); position:relative; top:-6px; letter-spacing:.06em; font-variant-numeric:tabular-nums; }

.cal-track{ position:relative; border-left:1px solid var(--glass-border); margin-left:var(--space-1); border-left-color:color-mix(in srgb, var(--glass-border) 60%, transparent); }

.cal-line{ position:absolute; left:0; right:0; height:1px; background:color-mix(in srgb, var(--glass-border) 46%, transparent); }

.cal-event{
    position:absolute;
    border-radius:var(--radius-control);
    padding:var(--space-2) var(--space-3);
    font-size:var(--type-body);
    font-weight:700;
    overflow:hidden;
    cursor:pointer;
    line-height:1.3;
    transition:all var(--dur-fast) var(--ease-out);
    display:flex;
    align-items:baseline;
    gap:var(--space-2);
    white-space:nowrap;
    background: linear-gradient(160deg, color-mix(in srgb, var(--row-color, var(--green)) 30%, transparent) 0%, color-mix(in srgb, var(--row-color, var(--green)) 15%, transparent) 58%, color-mix(in srgb, var(--row-color, var(--green)) 11%, transparent) 100%);
    border:1px solid color-mix(in srgb, var(--row-color, var(--green)) 48%, transparent);
    color:var(--text);
    box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 26%, transparent), inset 0 -1px 0 color-mix(in srgb, var(--row-color, var(--green)) 26%, transparent), 0 6px 16px -10px rgba(0,0,0,.6);
    backdrop-filter:none;
  }

.cal-ev-title{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; }

.cal-event:hover{ filter:brightness(1.08); transform:translateY(-1px); }

/* A TASK IS NOT A MEETING, AND THE DAY SHOULD SAY SO WITHOUT A LEGEND.
   *
   * Tasks appear on the day grid now (see tasksForDate). They sit at their own
   * time like everything else, but nobody else booked them and nobody else is
   * blocked by them — so: dashed, unfilled, no account colour, no glow. The
   * difference is carried by the border style rather than by hue alone, which
   * is the same rule the goal dots follow and the reason it survives every
   * theme and colour-blindness. */
  .cal-event.cal-task{ background:transparent; border-style:dashed; font-weight:600; }

:root:not([data-mode="light"]) .cal-event.cal-task{ box-shadow:none; }

.cal-event.cal-task .cal-ev-time{ opacity:.9; }

/* A reminder is a solid block like a meeting, because it IS a moment in the
     day rather than something to fit in around it — it just wears its own
     colour instead of the account's. The dashed edge stays exclusive to
     tasks, which is the thing it means: nobody booked this. */
  .cal-event.cal-reminder{ font-weight:650; }

.agenda-item.agenda-task{ border-left-style:dashed; }

/* LATE, AND IT SHOULD LOOK LATE.
     1 September: a task due last Thursday stopped appearing on Today at all,
     because the day view matched due_date EXACTLY. It is back, and a row that
     was supposed to have happened already has to be distinguishable from one
     that is merely on the list — a solid edge rather than the dashed one every
     other task wears, and the time column says "Late" rather than a clock. */
  /* AND IT HAS TO CATCH THE EYE, not merely differ from its neighbours.
     Matt, on seeing the first version: "I see the overdue, can we do something
     to bring more attention to it? (glow, blink, i dont know)".
     A slow breath rather than a blink. A blink is an alarm and it is read as
     one for about a day, after which it is read as nothing; a two-second swell
     is noticed on the way past and does not have to be dismissed. Two of them
     on a screen still read as two things, which a row of flashing edges does
     not. */
  .agenda-item.is-late{ border-left-style:solid; border-left-width:3px;
    border-left-color:var(--pink) !important;
    background:color-mix(in srgb, var(--pink) 7%, transparent);
    animation:lateBreath 2.4s var(--ease-in-out) infinite; }

@media (prefers-reduced-motion: reduce){
.agenda-item.is-late{ animation:none;
      box-shadow:0 0 12px -3px color-mix(in srgb, var(--pink) 45%, transparent); }
}

/* THE WORD "LATE" ITSELF, WHICH MEASURED 1.62:1 — the worst reading anywhere
     in the app, on the label added this morning to draw his eye to exactly this
     row. --pink-2 is the PALE step of the ramp: on a dark ground that is the
     readable one, and on linen it is very nearly the paper. --pink is stepped
     to be legible on both. `opacity:1` because .agenda-time is deliberately
     quiet — a time is not the point of a row, and "Late" is. */
  /* AND A SIZE, not only a colour. At 10.5px "Late" still measured 4.48:1 in the
     light theme — a hair under the floor and for the reason this codebase keeps
     rediscovering: bold mono at ten and a half pixels has strokes about one
     device pixel wide, so every pixel of the glyph is a blend with the tinted
     panel under it and no amount of stepping the pink moves it. Half a pixel of
     type is worth more here than another shade. */
  .agenda-item.is-late .agenda-time{ color:var(--pink); opacity:1; font-weight:700;
    font-size:var(--type-body); letter-spacing:.04em; }

/* THE COUNT, ABOVE THE DAY. Three late tasks scattered down a list of twelve
     rows is three separate small surprises; one line at the top is a fact about
     the day, and it is the thing he would want to know before reading any of
     it. */
  /* 13px/700 rather than 12px/600, same argument as the "Late" label above: the
     sentence sits on a panel tinted with its own hue, which caps how far apart
     ink and paper can ever be, so what is left to spend is weight and size. It
     measured 4.30:1 at 12px. */
  .agenda-late-bar{ display:flex; align-items:center; gap:var(--space-2); margin:0 0 var(--space-2);
    padding:var(--space-2) var(--space-3); border-radius:var(--radius-card); font-size:var(--type-body); font-weight:700;
    color:var(--pink);
    border:1px solid color-mix(in srgb, var(--pink) 35%, transparent);
    background:color-mix(in srgb, var(--pink) 10%, transparent); }

.agenda-late-bar .alb-dot{ width:7px; height:7px; border-radius:var(--radius-round); flex:none;
    background:var(--pink); box-shadow:0 0 8px var(--pink); }

/* THE THIRD ROUND ON THIS BAR, AND THE ONE THAT ADDRESSES THE CAUSE.
     -------------------------------------------------------------------------
     The two comments above describe stepping the pink, then adding weight and
     size, and each bought about a fifth of a point. On the paper themes it was
     still 4.28–4.34:1 — measured 3 September, by tests/test-readability.js,
     which has been reporting it every run.
     Both previous rounds tried to move the INK. The comment above already names
     why that has a ceiling: "the sentence sits on a panel tinted with its own
     hue, which caps how far apart ink and paper can ever be." A 10% wash of the
     same colour the text is drawn in raises the paper toward the ink by exactly
     as much as any step of the ink lowers it.
     So on paper the ink stops being --pink and becomes --pink mixed toward
     black. The panel keeps its tint — the bar still reads as a red thing, which
     is the whole point of it — and the lettering stops sharing a hue with its
     own background at the same lightness. Worst light family goes from 4.34 to
     5.48. The dark themes are untouched: they measure 8:1 and above, because a
     10% wash on a near-black ground barely moves it. */
  :root[data-mode="light"] .agenda-late-bar,
  :root[data-mode="light"] .agenda-item.is-late .agenda-time{
    color:color-mix(in srgb, var(--pink) 70%, #000); }

.cal-event.selected{ filter:brightness(1.2) saturate(1.25); box-shadow:0 0 0 2px var(--ring), 0 0 26px var(--ring-soft); transform:scale(1.02); z-index:6; }

.cal-ev-time{ font-size:var(--type-label); font-weight:500; opacity:.8; flex:none; }

.cal-now-line{ position:absolute; left:0; right:0; height:2px; background:var(--blue); box-shadow:0 0 10px var(--blue-glow); z-index:5; }

.cal-now-dot{ position:absolute; left:-4px; top:-4px; width:9px; height:9px; border-radius:var(--radius-round); background:var(--blue); box-shadow:0 0 10px var(--blue-glow); animation:breathe 2s infinite; }

/* Date, the Day/This Week toggle and the weather chip now share ONE row
     sitting directly on top of the calendar, instead of stacking into three
     separate bands. Saves ~60px of vertical space on the busiest view. */
  .today-title-row{ display:flex; justify-content:flex-start; align-items:center; gap:var(--space-4); margin-bottom:var(--space-3); flex-wrap:wrap; }

.today-title-row .page-title{ margin-bottom:var(--space-1); }

.today-title-row .page-sub{ margin-bottom:0; }

/* BACK TO TODAY — item 1755. It is only on screen while the calendar is
     showing some other day, so it is styled as an ANSWER to the date beside it
     rather than as a third control competing with the Day/Week toggle: an
     outline pill, the accent for the word, no fill until the pointer is on it.
     Every colour is a token. The accent is a different colour in every theme
     and a literal here would be right in exactly one of them.
     --accent-2 is the light-on-dark variant, which is what makes the word
     readable against a near-black header; painted on cream it is barely there,
     so light mode takes the solid one — the same swap the weather chip above
     makes, for the same reason.
     The arrow is drawn by the pseudo-element rather than typed into the markup,
     so a screen reader reads "Today" and not "left arrow Today". */
  .cal-today-btn{ flex:none; display:inline-flex; align-items:center; gap:var(--space-2);
    padding:var(--space-2) var(--space-4); border-radius:var(--radius-pill); font-size:var(--type-body); font-weight:600; cursor:pointer;
    background:transparent; color:var(--accent-2); border:1px solid var(--glass-border-hi);
    transition:background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out); }

:root[data-mode="light"] .cal-today-btn{ color:var(--accent); }

.cal-today-btn::before{ content:"\2190"; font-size:var(--type-lead); line-height:1; opacity:.75; }

/* MEASURED RATHER THAN EYEBALLED, and the measuring is the part worth
     writing down. The light-mode hover appeared to do nothing, and it very
     nearly got redesigned for a problem it did not have: the screenshot script
     had left the pointer parked on the button from the previous theme, so the
     "resting" picture was a hovered one and the pair was identical. Counted
     properly — see the note in tests/shots-comes-back.js — this fill and border
     move 6,042 pixels in midnight, 6,066 in light and 6,062 in linen, with the
     largest single-channel jump in the two LIGHT themes rather than the dark
     one. It reads everywhere.
     AND THE TEXT COLOUR IS DELIBERATELY LEFT ALONE. `color:var(--text)` was
     here and was a dead rule in half the app: the light-mode line above it is
     `:root[data-mode="light"] .cal-today-btn`, which outranks a bare `:hover`,
     so the word changed colour in dark themes and did not in light ones. A rule
     that quietly does nothing in half the themes is worse than no rule; the
     fill and the border say "pressable" in both. */
  .cal-today-btn:hover{ background:color-mix(in srgb, var(--accent) 22%, transparent);
    border-color:var(--accent); }

.cal-today-btn:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }

.week-strip{ display:grid; grid-template-columns:repeat(7,1fr); gap:var(--space-2); margin-bottom:var(--space-6); }

.week-cell{ padding:var(--space-3) var(--space-2); border-radius:var(--radius-card); text-align:center; cursor:pointer; border:1px solid var(--glass-border); transition:all var(--dur-fast) var(--ease-out); }

.week-cell:hover{ border-color:var(--glass-border-hi); }

.week-cell.today{ background:color-mix(in srgb, var(--blue) 16%, transparent); border-color:transparent; box-shadow:0 0 0 1px color-mix(in srgb, var(--blue) 40%, transparent); }

.week-dow{ font-size:var(--type-label); text-transform:uppercase; color:var(--text-faint); margin-bottom:var(--space-1); letter-spacing:.05em; }

.week-daynum{ font-size:var(--type-lead); font-weight:700; }

.week-dot{ width:4px; height:4px; border-radius:var(--radius-round); background:var(--green); margin:var(--space-2) auto 0; }

/* Seven real columns, aligned under the seven headers above. It used to be a
     vertical list under a horizontal strip, which is why the header row and the
     content underneath looked like two unrelated widgets. */
  #weekDays{ display:grid; grid-template-columns:repeat(7,1fr); gap:var(--space-2); align-items:start; }

.week-day-section{ min-width:0; padding:var(--space-3) var(--space-2) var(--space-3); border-radius:var(--radius-card);
    background:rgba(120,120,140,.05); border:1px solid var(--glass-border); }

.week-day-section.is-today{ border-color:color-mix(in srgb, var(--accent) 45%, transparent);
    background:color-mix(in srgb, var(--accent) 7%, transparent); }

.week-day-section .week-event-row{ display:block; padding:var(--space-2) var(--space-2); }

.week-day-section .week-event-time{ display:block; width:auto; margin-bottom:1px; }

.week-day-section .kind-dot{ display:inline-block; margin-right:var(--space-2); }

@media(max-width:1100px){
#weekDays{ grid-template-columns:repeat(2,1fr); }
}

@media(max-width:700px){
#weekDays{ grid-template-columns:1fr; }
}


/* The account's edge. 3px and transparent by default, so a row with no
     mailbox behind it — a task, a subscribed fixture — keeps the same indent
     as the rows beside it instead of stepping left and breaking the column. */
  .week-event-row{ display:flex; align-items:center; gap:var(--space-3); padding:var(--space-2) var(--space-1) var(--space-2) var(--space-2); font-size:var(--type-body); cursor:pointer; border-radius:var(--radius-control);
    border-left:3px solid transparent; }

.week-event-row:hover{ background:rgba(120,120,140,.07); }

.week-event-time{ font-size:var(--type-body); color:var(--text-faint); width:64px; flex:none; }

/* THE DAY, AS A SURFACE RATHER THAN A VOID.
     ------------------------------------------------------------------------
     An hour rule every 64px with nothing between them is why an empty
     afternoon read as a rendering failure rather than as a free afternoon.
     Two additions, both quiet: a band every other hour so the eye has ground
     to stand on, and a line saying where we are in the day — the single most
     useful mark on a day view, and it was simply absent. */
  .cal-band{ position:absolute; left:0; right:0; pointer-events:none; background:rgba(255,255,255,.012); }

:root[data-mode="light"] .cal-band{ background:rgba(20,24,44,.016); }

/* NOW. Drawn only on today — a marker across next Tuesday means nothing. */
  .cal-now{ position:absolute; left:0; right:0; height:0; pointer-events:none; z-index:4; border-top:1.5px solid var(--pink); box-shadow:0 0 10px 1px var(--pink-glow); }

.cal-now > i{ position:absolute; left:-4px; top:-4.5px; width:8px; height:8px; border-radius:var(--radius-round); background:var(--pink); box-shadow:0 0 8px 1px var(--pink-glow); }

@media (prefers-reduced-motion: reduce){
.cal-now{ box-shadow:none; }
}


/* ==========================================================================
     THE CALENDAR, IN GLASS
     --------------------------------------------------------------------------
     "Need elegance for the calendar." Then, after the first attempt:
     "Asked for glass and got nothing changed."
     
     He was right, and it is worth naming what I did wrong rather than quietly
     fixing it. He sent two references and asked for a MATERIAL; I measured the
     layout, moved 630 pixels of width from a rail to a day track, and shipped
     geometry. Geometry was also asked for and was also wrong — but a day that
     is the right width and still looks like a spreadsheet has not been made
     elegant, it has been made bigger.
     
     So this is the material, and it is built from four things rather than one
     blur:
     
       1. THE PANEL IS A SURFACE, not a rectangle. A real backdrop blur with
          saturation, so whatever is behind it shows through as colour rather
          than as detail — that is the difference between frosted glass and a
          grey box with rounded corners.
       2. THE TOP EDGE CATCHES LIGHT. One-pixel inset highlight along the top,
          which is what makes a pane read as having thickness. Without it the
          blur just looks like a smudge.
       3. THE BLOCKS ARE PANES, not fills. Each event gets a gradient from its
          own hue, an inset highlight, and a shadow that lifts it off the
          grid — but NO backdrop-filter, deliberately: one blurred surface is
          elegant and forty of them is a slideshow on a laptop.
       4. THE GRID RECEDES. Hour lines at a third of their old weight, the
          hour numbers lighter and tracked out. Structure you can find when you
          look for it and cannot see when you are not.
     ========================================================================== */
  /* NO backdrop-filter HERE, AND THAT IS THE WHOLE POINT.
     
     I nearly shipped one, and it would have been the worst kind of regression:
     the panel ALREADY carries `.glass`, which applies blur and saturation
     through --glass-blur/--glass-sat — tokens the app turns OFF on its own when
     it measures a frame rate it cannot hold. That mechanism exists because of
     a complaint from this same person: "the whole UI is all herky jerky, like
     it is shorting out", traced to thirty-six live backdrop-filters.
     
     A hardcoded blur here would be invisible to data-fx="lite" and would put
     the stutter back on his machine while I was busy answering "asked for
     glass". So the material below is built entirely from things that cost the
     compositor nothing: a gradient sheen, a lit top edge, and a layered
     shadow. That is what actually reads as glass; the blur was never the part
     he could see. */

/* The pane's own reflection: a soft sheen falling across the top-left, fixed
     to the panel rather than scrolling with the day. */
  .timeline-panel::before{ content:''; position:absolute; inset:0; pointer-events:none; z-index:0; background:radial-gradient(120% 68% at 6% -8%, rgba(255,255,255,.075), transparent 62%); }

.timeline-panel > *{ position:relative; z-index:1; }

:root[data-mode="light"] .timeline-panel{
    background-image:
      linear-gradient(180deg, rgba(255,255,255,.72) 0%, rgba(255,255,255,.40) 40%, rgba(255,255,255,.22) 100%),
      var(--glass-sheen);
    background-color: var(--glass-bg);
  }

:root[data-mode="light"] .timeline-panel::before{ background:radial-gradient(120% 68% at 6% -8%, rgba(255,255,255,.6), transparent 62%); }

/* The rail gets the same material, so the two halves of the day are made of
     the same thing. */
  /* Same rule, same reason — the rail is already `.glass` in the markup. */

:root[data-mode="light"] .left-panel{
    background-image:
      linear-gradient(180deg, rgba(255,255,255,.66) 0%, rgba(255,255,255,.34) 44%, rgba(255,255,255,.18) 100%),
      var(--glass-sheen);
    background-color: var(--glass-bg);
  }

/* THE GRID RECEDES. */


/* NO OPACITY HERE. The first version of this "recede the grid" pass set
     opacity:.72 on the hour numbers and dropped them to 3.08:1 against the
     panel — below the 4.5:1 floor, on the one column of text a day view exists
     to be read against. test-readability.js caught it, which is what it is for.
     
     Tracking and tabular figures are the parts that actually made the column
     quieter; the opacity was me reaching for the easiest dimmer switch and
     taking legibility with it. --text-faint is already the receded token and
     is already the value that passes. */

/* THE BLOCKS ARE PANES.
     No backdrop-filter here on purpose — see note 3 above. The gradient plus
     the inset highlight is what does the work, and it costs nothing. */

:root[data-mode="light"] .cal-event{
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.7),
      inset 0 -1px 0 color-mix(in srgb, var(--row-color, var(--green)) 22%, transparent),
      0 6px 14px -11px rgba(20,24,44,.45);
  }

/* The dark-mode bloom already applied to every block was doubling up with the
     new shadow and turning the day into fog. Softened rather than removed —
     the glow is what makes the blocks feel lit from within. */
  :root:not([data-mode="light"]) .cal-event{
    box-shadow:
      inset 0 1px 0 color-mix(in srgb, #fff 26%, transparent),
      inset 0 -1px 0 color-mix(in srgb, var(--row-color, var(--green)) 26%, transparent),
      0 6px 16px -10px rgba(0,0,0,.6),
      0 0 14px 0 var(--row-glow, var(--green-glow));
  }


/* A band on glass wants to be lighter than a band on a flat panel. */


/* THE NARROWER RAIL, EARNED.
     286px rather than 316px. "Agenda still too wide" — after it had already
     come down from 390px, so this is the second cut, and at 286 a real title
     ("David Klask — discovery call") began to wrap. test-calendar-layout.js
     caught it, which is what it is there for.
     
     The answer is NOT to let it wrap: a wrapped title makes the row twice as
     tall, which is most of where "half is wasted space" came from in the first
     place, so narrowing the rail that way would trade one kind of waste for
     another. It fits instead — a half-point smaller and slightly tighter,
     which at 13px is invisible and buys the twenty pixels needed. */


/* ==========================================================
     A TASK'S PRIORITY, AS A GLOW BEHIND ITS COLOUR.

     "Tasks should be a color, and then glow whatever priority
     they are…"   (Matt, 1 September.)

     Two channels rather than one. The LEFT EDGE stays the kind
     colour — violet for a task, blue for a reminder — because
     that is the question "what is this?", and it is the one a
     person answers at a glance down a column. The GLOW is the
     second question, how much it matters, and it is a halo on
     the same edge rather than a different colour on it.

     Colouring the bar itself by priority was the obvious build
     and the wrong one: a red bar beside a red Cubs fixture tells
     you nothing about which is which, and the kind colour is the
     thing that makes a task legible among meetings at all.

     Nothing pulses. A calendar that breathes at you is a
     calendar people stop looking at, and high priority means
     "today", not "emergency".
     ========================================================== */
  .agenda-item.has-priority,
  .week-event-row.has-priority{ box-shadow: -3px 0 10px -2px color-mix(in srgb, var(--prio, transparent) 60%, transparent); }

.agenda-item.has-priority{ border-left-width:3px; }

/* Selected already carries its own emphasis; two at once is neither. */
  .agenda-item.has-priority.selected{ box-shadow:none; }

/* BAND 3 WAS THE ACCENT MIXED WITH --orange AND IT CAME OUT GREY.
     Blue and orange are opposite each other, so a 50/50 srgb mix of them is
     mud — on Midnight the "IMPORTANT" pill on a band-3 row rendered a
     desaturated mauve and the time above it nearly white, which is the exact
     opposite of "hotter than band 2". Seen in a screenshot, not reasoned about.
     The fix is to stop crossing the wheel: bands 3 and 4 both live in the warm
     end, so the ramp goes quiet ink → the theme's accent → amber → the theme's
     late red, and every step is warmer than the one below it. Three of the five
     bands carry no chroma worth the name, which is why a day of this reads as
     one or two warm rows in a calm list rather than as a row of coloured
     labels. */
  .agenda-item[data-heat="0"]{ --ag-heat: var(--ag-heat-0); --ag-wash: 0%; }

.agenda-item[data-heat="1"]{ --ag-heat: var(--ag-heat-1); --ag-wash: 3%; }

.agenda-item[data-heat="2"]{ --ag-heat: var(--ag-heat-2); --ag-wash: 5%; }

.agenda-item[data-heat="3"]{ --ag-heat: var(--ag-heat-3); --ag-wash: 8%; }

.agenda-item[data-heat="4"]{ --ag-heat: var(--ag-heat-4); --ag-wash: 12%; }

/* AND ON PAPER THOSE FIVE NUMBERS ARE THE WRONG ONES, which is what looking
     at all ten themes instead of two actually found.
     -----------------------------------------------------------------------
     A wash is a percentage of a colour laid over the row's own ground, and the
     two kinds of ground in this app do completely different things with it. On
     Midnight the ground is near-black, so twelve percent of #FF5A47 ADDS light
     and reads as a warm glow at the left edge. On Alabaster the ground is
     paper, the theme's own --pink is #9B4E5C rather than #FF5A47 — the same
     idea at half the chroma, because a bright red on cream is a shout — and
     twelve percent of it over white lands on rgb(243,234,236). That is two
     luminance points. It is not a subtle temperature, it is nothing, and in
     the first set of pictures the band-3 row and the band-1 row two inches
     below it had visibly the same paper.
     So the light half of the ramp is roughly double, and it is still a fade
     rather than a fill. Twenty-four percent of that same pink lands on
     rgb(229,209,213), which is a blush you can see from across the room and
     still not a highlighted row. This is the same shape of honesty tokens.css
     writes down about elevation: on dark grounds the temperature is light, and
     on paper it is a stain. */
  :root[data-mode="light"] .agenda-item[data-heat="1"]{ --ag-wash: 6%; }

:root[data-mode="light"] .agenda-item[data-heat="2"]{ --ag-wash: 10%; }

:root[data-mode="light"] .agenda-item[data-heat="3"]{ --ag-wash: 16%; }

:root[data-mode="light"] .agenda-item[data-heat="4"]{ --ag-wash: 24%; }

/* THE WASH LEANS ON THE EDGE THE COLOUR IS ALREADY ON and is
     gone by two thirds across, so a column of them reads as a
     temperature at the left margin rather than as five
     differently-tinted cards. A flat fill at twelve percent is
     a highlighted row; a fade is a warm edge. */
  .agenda-item[data-heat]{
    background-image:linear-gradient(90deg,
      color-mix(in srgb, var(--ag-heat) var(--ag-wash, 0%), transparent), transparent 64%);
  }

/* Hover and selection are still the louder state. Both paint
     background-color, which sits UNDER background-image, so the
     wash would otherwise hide them — hence the explicit blend
     rather than hoping the cascade does the right thing. */
  .agenda-item[data-heat]:hover,
  .agenda-item[data-heat].selected{ background-image:none; }

/* THE TIME IS THE DUE-TIME CHANNEL, so the temperature is
     spent there first. Below band 2 it keeps --text-faint: a
     quiet row should look like nothing was decided about it. */
  .agenda-item[data-heat="2"] .agenda-time,
  .agenda-item[data-heat="3"] .agenda-time,
  .agenda-item[data-heat="4"] .agenda-time{ color:var(--ag-heat); opacity:1; font-weight:600; }

.agenda-item[data-heat="4"] .agenda-time{ font-weight:700; letter-spacing:.035em; }

/* AND CONTRAST DOES THE REST, which is the half that survives
     a monochrome screen and colour-blindness. Cold rows step
     back; hot ones come forward to full ink and a heavier face.
     Nothing here is colour alone.

     THE INK IS THREE STEPS AND NOT TWO, corrected after looking.
     Bands 0 and 1 were both --text-dim separated only by 560
     against 600, and in the pictures they were one band wearing
     two names — which is the rainbow's opposite number and just
     as bad: a ramp with an invisible step is a ramp a person
     learns to read as four. Band 0 drops to --text-faint, which
     every theme already states and which is a real step below
     --text-dim on all ten that were photographed.

     AND THE WEIGHTS ARE THE FOUR THE APP DOWNLOADS. 560 was not
     one of them. index.html asks Google for Public Sans at 400,
     500, 600 and 700, so `font-weight:560` was a request for a
     face that is not in the file the browser fetched and the
     browser quietly rounded it to whichever of 500 and 600 it
     felt like — which means the quietest step of this ramp was
     never a decision anybody made. tokens.css counts fifty such
     declarations across app.css and names the four that are
     real; these are three of them. */
  .agenda-item[data-heat="0"] .agenda-title{ color:var(--text-faint); font-weight:var(--weight-medium); }

.agenda-item[data-heat="1"] .agenda-title{ color:var(--text-dim); font-weight:var(--weight-strong); }

.agenda-item[data-heat="3"] .agenda-title,
  .agenda-item[data-heat="4"] .agenda-title{ color:var(--text); font-weight:var(--weight-loud); }

/* WHAT USED TO BE HERE WAS HALF A PIXEL, AND HALF A PIXEL IS
     NOT A STEP. Band 4 set font-size:14px against a .agenda-title
     of 13.5px, on the argument the "Late" label won in September.
     That argument was about CONTRAST — a bold mono glyph one
     device pixel wide cannot be made legible by restating its
     colour — and it does not transfer to a 600-weight title that
     already measures well past the floor. Photographed side by
     side at 2x the two rows were indistinguishable in size, which
     is the exact complaint tokens.css opens with: 11.5 against
     12 communicates nothing, so it is not a decision, it is a
     number somebody typed. The weight, the ink, the hotter time
     above it and twice the wash are doing this. Letter-spacing
     stays, because at 700 the title genuinely does want a hair
     of negative tracking to stop it looking swollen. */
  .agenda-item[data-heat="4"] .agenda-title{ letter-spacing:-.006em; }

/* THE "IMPORTANT" CHIP AND ITS FOUR RULES CAME OUT ON 22 SEPTEMBER.
     The row's second line is 203px wide and what was already on it needed
     217px, so two rows were cutting their own words — a location down to
     "STS · Ulele, T…" and an overdue task losing the word "late". This chip
     was about 90px of that line, spent saying in a word what the row's warmth
     and its heavier title already say. The blue "In person" chip stays: where
     a meeting is, is not said anywhere else on the row. */
  :root[data-mode="light"] .agenda-item[data-heat="2"] .agenda-time,
  :root[data-mode="light"] .agenda-item[data-heat="3"] .agenda-time,
  :root[data-mode="light"] .agenda-item[data-heat="4"] .agenda-time{
    color:color-mix(in srgb, var(--ag-heat) 76%, var(--text)); }

@media (hover: none) and (pointer: coarse), (max-width: 480px){
/* New event, and it does get visibly bigger. It is a filled accent square
     and there is no way to widen it invisibly: its neighbours in the
     mini-calendar head are 8px away, so an invisible reach of 44 would sit on
     top of the arrows next to it and the wrong thing would open. Bigger and
     correct beats invisible and wrong. */
  .mc-add{ min-width:44px; min-height:44px; }
  /* The Try again / Load it inside a calendar note, and inside the meeting
     card's "could not read your mail" note. 32 tall on a phone was under the
     44 a thumb needs; it sits alone at the end of its sentence, so it can
     simply grow. */
  .cal-note-act{ min-width:44px; min-height:44px; }
}

@media (min-width:1101px){
/* GRID ROWS RATHER THAN FLEX GROW - AND THIS IS THE SECOND ATTEMPT.
     flex:6 and flex:4 from a zero basis measured 38.5 / 61.5, not 40 / 60.
     The rail is a column holding three children, and the mini-cal's own
     margin-bottom and padding ride INSIDE the share it is handed, so the two
     panes came out 279px and 175px of a 454px rail where 272 and 182 was the
     arithmetic. Adding min-height:0 to the cells did not move it either,
     which ruled out the month grid flooring on its own numerals.
     Explicit fr ROWS do not have the problem. The "Agenda" label takes its
     own auto row, whatever is left is divided 6:4 by construction, and the
     two panes then measure exactly 40 / 60 of each other. The mini-cal's
     margin goes to 0 because in a grid a margin eats the row it sits in; the
     border-bottom is what separates the month from the list and it stays. */
  .left-panel{ display:grid; grid-template-rows:6fr auto 4fr; }
.left-panel > .mini-cal{ min-height:0; margin-bottom:0; display:flex; flex-direction:column; }
/* The grid is the part that should grow - the head, the weekday letters and
     the legend keep their own heights. minmax(0,1fr) lets the six week rows
     share whatever is left in equal parts without flooring on their content. */
  .left-panel > .mini-cal > .mc-grid{ flex:1 1 auto; min-height:0; grid-auto-rows:minmax(0, 1fr); }
/* Every child, not just .mc-cell: the padding days out of the neighbouring
     month are drawn by the same grid and would stay 34px tall and break the
     week rows if only the in-month cells were released. */
  .left-panel > .mini-cal > .mc-grid > *{ aspect-ratio:auto; max-height:none; min-height:0; }
.left-panel > .agenda-scroll{ min-height:0; }
}

@media (min-width:1101px){
/* THE FOUR MARGINS. 16 + 8 + 4 + 8 becomes 8 + 4 + 2 + 4. */
  .left-panel > .mini-cal{ padding-bottom:var(--space-2); }
.left-panel > .mini-cal > .mini-cal-head{ margin-bottom:var(--space-1); }
.left-panel > .mini-cal > .mc-weekdays{ margin-bottom:var(--space-hair); }
.left-panel > .mini-cal > .mc-legend{ margin-top:var(--space-1); }
/* ONE PITCH FOR BOTH ROWS, so a letter sits over its column. */
  .left-panel > .mini-cal > .mc-grid,
  .left-panel > .mini-cal > .mc-weekdays{ grid-template-columns:repeat(7, minmax(0, 1fr)); column-gap:var(--space-hair); }
.left-panel > .mini-cal > .mc-grid{ row-gap:var(--space-hair); }
/* THE NUMERAL, AT LAST. Both this month's days and the neighbouring
     month's, so the week rows keep one pitch -- weight is what tells those
     apart and that is section 1's rule, which this does not touch. */
  .left-panel > .mini-cal > .mc-grid > *{
    font-size:var(--type-lead);
    line-height:1;
    /* The numeral centres in the room ABOVE the dots rather than in the
       whole cell, because .mc-dots is absolutely positioned and does not
       push it down. At 11px there was slack enough that nobody noticed; at
       16px in a 37.6px cell the two would have met. */
    padding-bottom:var(--space-2);
  }
/* ROOM TO BREATHE UNDER EACH DAY. 3px square was at the edge of being a
     mark at all, and the legend swatches beside them are 11px. */
  .left-panel > .mini-cal > .mc-grid .mc-dots{ bottom:5px; gap:3px; }
.left-panel > .mini-cal > .mc-grid .mc-dots i{ width:5px; height:5px; }
.left-panel > .mini-cal > .mc-grid .mc-dots i.mc-more{ width:10px; }
.left-panel > .mini-cal > .mc-grid .mc-dot{ bottom:5px; width:5px; height:5px; }
}

/* ---------------------------------------------------------------------------
   AND THE COST OF ALL THAT, PAID RATHER THAN IGNORED.
   ---------------------------------------------------------------------------
   Nine per cent of the width came off the day timeline, and its event titles
   were ALREADY being cut at the old width -- his screenshot says "HUBSPOT
   REPOR..." and "Walkabout With T...". So this is measured, not hoped.

   WHAT WAS REALLY CAUSING IT, at 1481 on master: three events share nine
   o'clock, layoutOverlappingItems() hands each of them a third of the track,
   and each block comes out 275px wide and 64px TALL with its title on ONE
   line:

     "HUBSPOT REPORTING DASHBOARD REBUILD"    needs 289px, has 167px, 1 line
     "Walkabout With Terry and the site team" needs 244px, has 167px, 1 line
     "Fenwick Heating - boiler quote follow-up" needs 248px, has 167px, 1 line

   167 twice over is 334, and 334 is more than any of the three. The room was
   already inside the block -- 34px of empty block under a cut title -- and
   .cal-event carried white-space:nowrap, so nothing could ever reach it. A
   block tall enough for a second line gets one now. How many lines it has
   room for is counted in app.js from the block's own pixel height, because a
   stylesheet cannot know a height that arrives as an inline style.

   AND EVERY BLOCK NOW CARRIES ITS FULL TITLE AS A TOOLTIP. That half was not
   a proportion, it was a plain fault: on master a cut title had no title
   attribute, no aria-label and no other way of being read. "HUBSPOT REPOR..."
   was the whole of what the app would ever tell him. A shortened title is
   fine; a shortened title with no way to see the rest is the thing rule 211
   is about. */
/* AND THE BLOCK STOPS BEING A FLEX ROW, which is the half of this I had
   wrong the first time and only saw in the picture.
   Wrapping the TITLE alone inside the flex row left the time sitting in its
   own column, so the title had 137px to wrap in rather than the block's
   211px, and it broke at the first word boundary that did not fit:

     line 1  "HUBSPOT"      75px used of 137
     line 2  "REPORTING..."  85px used of 137

   160px of a 289px title on two lines, when ONE line at the old width had
   shown 167px. The wrap had bought nothing. The block is laid out as text
   here instead -- the time is the first word of the paragraph and the title
   flows round it and under it, so line one has 155px and every line after
   has the full 211. 155 + 211 is 366 against a 289px title, and the whole of
   it now fits in the two lines the block has room for.

   -webkit-box with box-orient:vertical is the line-clamp idiom: it keeps the
   children flowing as inline text and still gives the last visible line an
   ellipsis, which a plain overflow:hidden on the block would not. */
.cal-event.cal-ev-wrap{
  display:-webkit-box; -webkit-box-orient:vertical;
  /* --ev-lines is set inline beside this class, so it is never missing when
     the class is present. If it ever were, this declaration would die and
     the block's own overflow:hidden would still cut at a line. */
  -webkit-line-clamp:var(--ev-lines, 2);
  white-space:normal;
}

/* No flex, so no gap: the space after the time has to be its own margin. */
.cal-event.cal-ev-wrap .cal-ev-time{ margin-right:var(--space-2); line-height:1.3; }

.cal-event.cal-ev-wrap .cal-ev-title{
  display:inline; white-space:normal; overflow:visible; text-overflow:clip;
}

/* THE FAMILY PLAN — 14 October. Only ever drawn for somebody in a family (or,
   in Settings › Plan, the one line that starts one). The Family switch sits in
   the calendar toolbar beside the filter and borrows its pill exactly. */
.cal-toolbar .cal-family{ display:flex; gap:var(--space-2); flex:none; }

@media (max-width: 780px), (hover: none) and (pointer: coarse) and (max-height: 500px){
/* CALENDAR'S DAY / THIS WEEK AND THE WEATHER CHIP sat with their top ten
     pixels inside the Lucid thread's own reach (.lth-line answers a finger
     from 50 to 104px down on a phone, and that reach is the thread's, not
     this file's to move). A finger on the top of "Day" opened Lucid. They come
     down a space-3, which clears it; the thread is untouched. */
  .today-title-row .view-toggle, .today-title-row .weather-chip{ margin-top:var(--space-3); }
}

@media (max-width: 380px){
.today-grid .left-panel{ padding-inline:var(--space-3); }
}

@keyframes lateBreath{
    0%, 100%{ box-shadow:0 0 0 0 color-mix(in srgb, var(--pink) 0%, transparent); }
    50%     { box-shadow:0 0 14px -2px color-mix(in srgb, var(--pink) 55%, transparent); }
  }

/* A MEETING AGREED IN AN EMAIL — the card (rooms/today.js meetCardHtml), on
   Today above the day and on the open email. The card itself is the kit's
   k-card; these are only its quote and its status lines. Orange for "could
   not check" — the app's "look again" colour, not its "broken" one. "It is
   on your calendar" is plain text with a tick: not a warning, so not a
   colour some themes paint orange. */
#meetFromMail:empty, #rpMeetingCard:empty{ display:none; }
#meetFromMail{ display:grid; grid-template-columns:minmax(0, 1fr); gap:var(--space-3); margin:var(--space-2) 0 var(--space-3); }
#rpMeetingCard{ margin:0 0 var(--space-3); }
.mfm-quote{ margin:var(--space-2) 0 0; color:var(--text); font-style:italic; line-height:1.45; }
.mfm-by{ font-style:normal; color:var(--text-dim); }
.mfm-note{ margin:var(--space-2) 0 0; font-size:var(--type-label); color:var(--text-dim); line-height:1.45; }
.mfm-note.is-ok{ color:var(--text); }
.mfm-note.is-ok::before{ content:'✓ '; }
.mfm-note.is-warn{ color:var(--orange); }

/* THE DAILY FIVE — five quick questions (rooms/today.js dfivePaint), atop
   today's agenda. The card is the kit's k-card and the buttons are kit
   buttons; these are only the list inside it. An answered question stays,
   quieter, with what was done in plain words (the meeting card's tick). */
#dailyFive:empty{ display:none; }
#dailyFive{ margin:var(--space-2) 0 var(--space-3); }
.d5-list{ list-style:none; margin:var(--space-3) 0 0; padding:0; display:grid; gap:0; }
.d5-item{ padding:var(--space-3) 0; border-top:var(--space-hair) solid var(--glass-border); }
.d5-item:first-child{ border-top:0; padding-top:var(--space-1); }
.d5-q{ margin:0; color:var(--text); line-height:1.4; font-weight:var(--weight-strong); }
.d5-item.is-done .d5-q{ color:var(--text-dim); font-weight:inherit; }
.d5-why{ margin:var(--space-1) 0 0; font-size:var(--type-label); color:var(--text-faint); }
.d5-acts{ display:flex; flex-wrap:wrap; gap:var(--space-2); margin-top:var(--space-2); }
.d5-item .k-field{ margin-top:var(--space-2); }
/* "I think…" (rooms/today.js iThinkDraw): the kit card, its three lines. */
#iThinkCard{ margin:0 0 var(--space-4); }
.ithink-row + .ithink-row{ margin-top:var(--space-4); padding-top:var(--space-4); border-top:1px solid var(--glass-border); }
.ithink-say{ margin:0; font-weight:var(--weight-strong); line-height:1.45; }
.ithink-why{ margin:var(--space-1) 0 0; color:var(--text-dim); font-size:var(--type-label); line-height:1.45; }
.ithink-acts{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--space-2); margin-top:var(--space-2); }

/* The prep card (rooms/today.js prepPaint): the kit card, its lines and the rating row. */
#prepCards:empty{ display:none; }
#prepCards{ margin:var(--space-2) 0 var(--space-3); display:grid; gap:var(--space-3); }
.pc-lines{ list-style:none; margin:var(--space-2) 0 0; padding:0; display:grid; gap:var(--space-1); }
.pc-line, .pc-ask{ margin:0; line-height:1.4; }
.pc-ask{ margin-top:var(--space-2); font-weight:var(--weight-strong); }
.pc-tag{ color:var(--text-faint); font-size:var(--type-label); margin-right:var(--space-1); }
.pc-rate{ display:flex; flex-wrap:wrap; gap:var(--space-2); margin-top:var(--space-3); }
